Merge "atrace: New --prefer_sdk flag" into main am: 5e1cbe0668 am: cb30dd6bdf

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

Change-Id: I0f5afa67f8abc36d8d1f2aa4f6622bc038f3ec1d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/include/input/Input.h b/include/input/Input.h
index ec08cdd..456977b 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -25,6 +25,7 @@
 #include <android/input.h>
 #ifdef __linux__
 #include <android/os/IInputConstants.h>
+#include <android/os/MotionEventFlag.h>
 #endif
 #include <android/os/PointerIconType.h>
 #include <math.h>
@@ -69,15 +70,17 @@
      * actual intent.
      */
     AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+            static_cast<int32_t>(android::os::MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED),
+
     AMOTION_EVENT_FLAG_HOVER_EXIT_PENDING =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING,
+            static_cast<int32_t>(android::os::MotionEventFlag::HOVER_EXIT_PENDING),
+
     /**
      * This flag indicates that the event has been generated by a gesture generator. It
      * provides a hint to the GestureDetector to not apply any touch slop.
      */
     AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE,
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_GENERATED_GESTURE),
 
     /**
      * This flag indicates that the event will not cause a focus change if it is directed to an
@@ -86,27 +89,27 @@
      * into focus.
      */
     AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
+            static_cast<int32_t>(android::os::MotionEventFlag::NO_FOCUS_CHANGE),
 
     /**
      * This event was generated or modified by accessibility service.
      */
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_ACCESSIBILITY_EVENT),
 
     AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS,
+            static_cast<int32_t>(android::os::MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS),
 
     /* Motion event is inconsistent with previously sent motion events. */
-    AMOTION_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
+    AMOTION_EVENT_FLAG_TAINTED = static_cast<int32_t>(android::os::MotionEventFlag::TAINTED),
 
     /** Private flag, not used in Java. */
     AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION =
-            android::os::IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION,
+            static_cast<int32_t>(android::os::MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION),
 
     /** Private flag, not used in Java. */
-    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = android::os::IInputConstants::
-            MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION,
+    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = static_cast<int32_t>(
+            android::os::MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION),
 
     /** Mask for all private flags that are not used in Java. */
     AMOTION_EVENT_PRIVATE_FLAG_MASK = AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h
index 222dac8..9bbaa0c 100644
--- a/include/input/VirtualInputDevice.h
+++ b/include/input/VirtualInputDevice.h
@@ -122,4 +122,11 @@
     bool handleStylusUp(uint16_t tool, std::chrono::nanoseconds eventTime);
 };
 
+class VirtualRotaryEncoder : public VirtualInputDevice {
+public:
+    VirtualRotaryEncoder(android::base::unique_fd fd);
+    virtual ~VirtualRotaryEncoder() override;
+    bool writeScrollEvent(float scrollAmount, std::chrono::nanoseconds eventTime);
+};
+
 } // namespace android
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index ff6b558..2699368 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -62,7 +62,7 @@
 
     status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp,
             const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
             const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index af91bb3..5db5394 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1059,7 +1059,8 @@
     uncacheBuffer.token = BufferCache::getInstance().getToken();
     uncacheBuffer.id = cacheId;
     Vector<ComposerState> composerStates;
-    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
+    Vector<DisplayState> displayStates;
+    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, displayStates,
                                               ISurfaceComposer::eOneWay,
                                               Transaction::getDefaultApplyToken(), {}, systemTime(),
                                               true, {uncacheBuffer}, false, {}, generateId(), {});
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index eb4a802..1ecc216 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -112,7 +112,7 @@
     /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */
     virtual status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index 9cf62bc..7ee291d 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -74,6 +74,7 @@
 } // namespace android::gui
 
 using android::gui::METADATA_ACCESSIBILITY_ID;
+using android::gui::METADATA_CALLING_UID;
 using android::gui::METADATA_DEQUEUE_TIME;
 using android::gui::METADATA_GAME_MODE;
 using android::gui::METADATA_MOUSE_CURSOR;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 43cd0f8..5e91088 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -636,7 +636,7 @@
 
     status_t setTransactionState(
             const FrameTimelineInfo& /*frameTimelineInfo*/, Vector<ComposerState>& /*state*/,
-            const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
+            Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
             const sp<IBinder>& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/,
             int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
             const std::vector<client_cache_t>& /*cachedBuffer*/, bool /*hasListenerCallbacks*/,
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index d782f42..c2a7ebb 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,6 +30,7 @@
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
         "android/os/InputConfig.aidl",
+        "android/os/MotionEventFlag.aidl",
         "android/os/PointerIconType.aidl",
     ],
 }
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index eea06f1..b73ee65 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -509,4 +509,15 @@
     return true;
 }
 
+// --- VirtualRotaryEncoder ---
+VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+
+VirtualRotaryEncoder::~VirtualRotaryEncoder() {}
+
+bool VirtualRotaryEncoder::writeScrollEvent(float scrollAmount,
+                                            std::chrono::nanoseconds eventTime) {
+    return writeInputEvent(EV_REL, REL_WHEEL, static_cast<int32_t>(scrollAmount), eventTime) &&
+            writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
+}
+
 } // namespace android
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index a77dfa5..e23fc94 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -49,130 +49,24 @@
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
     /**
-     * This flag indicates that the window that received this motion event is partly
-     * or wholly obscured by another visible window above it and the event directly passed through
-     * the obscured area.
-     *
-     * A security sensitive application can check this flag to identify situations in which
-     * a malicious application may have covered up part of its content for the purpose
-     * of misleading the user or hijacking touches.  An appropriate response might be
-     * to drop the suspect touches or to take additional precautions to confirm the user's
-     * actual intent.
-     */
-    const int MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1;
-
-    /**
-     * This flag indicates that the window that received this motion event is partly
-     * or wholly obscured by another visible window above it and the event did not directly pass
-     * through the obscured area.
-     *
-     * A security sensitive application can check this flag to identify situations in which
-     * a malicious application may have covered up part of its content for the purpose
-     * of misleading the user or hijacking touches.  An appropriate response might be
-     * to drop the suspect touches or to take additional precautions to confirm the user's
-     * actual intent.
-     *
-     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
-     * obstructed in areas other than the touched location.
-     */
-    const int MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
-
-    /**
-     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
-     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
-     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_HOVER_EXIT_PENDING = 0x4;
-
-    /**
-     * This flag indicates that the event has been generated by a gesture generator. It
-     * provides a hint to the GestureDetector to not apply any touch slop.
-     *
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8;
-
-    /**
-     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
-     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
-     * is set, the typical actions that occur in response for a pointer going up (such as click
-     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
-     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
-     * screen.
-     *
-     * @see #ACTION_POINTER_UP
-     * @see #ACTION_CANCEL
+     * Common input event flag used for both motion and key events for a gesture or pointer being
+     * canceled.
      */
     const int INPUT_EVENT_FLAG_CANCELED = 0x20;
 
     /**
-     * This flag indicates that the event will not cause a focus change if it is directed to an
-     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
-     * gestures to allow the user to direct gestures to an unfocused window without bringing the
-     * window into focus.
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40;
-
-    /**
-     * This flag indicates that the event has a valid value for AXIS_ORIENTATION.
-     *
-     * This is a private flag that is not used in Java.
-     * @hide
-     */
-    const int MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION = 0x80;
-
-    /**
-     * This flag indicates that the pointers' AXIS_ORIENTATION can be used to precisely determine
-     * the direction in which the tool is pointing. The value of the orientation axis will be in
-     * the range [-pi, pi], which represents a full circle. This is usually supported by devices
-     * like styluses.
-     *
-     * Conversely, AXIS_ORIENTATION cannot be used to tell which direction the tool is pointing
-     * when this flag is not set. In this case, the axis value will have a range of [-pi/2, pi/2],
-     * which represents half a circle. This is usually the case for devices like touchscreens and
-     * touchpads, for which it is difficult to tell which direction along the major axis of the
-     * touch ellipse the finger is pointing.
-     *
-     * This is a private flag that is not used in Java.
-     * @hide
-     */
-    const int MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100;
-
-    /**
-     * The input event was generated or modified by accessibility service.
-     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
-     * set of flags, including in input/Input.h and in android/input.h.
+     * Common input event flag used for both motion and key events, indicating that the event
+     * was generated or modified by accessibility service.
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
 
     /**
-     * Private flag that indicates when the system has detected that this motion event
-     * may be inconsistent with respect to the sequence of previously delivered motion events,
-     * such as when a pointer move event is sent but the pointer is not down.
-     *
-     * @hide
-     * @see #isTainted
-     * @see #setTainted
+     * Common input event flag used for both motion and key events, indicating that the system has
+     * detected this event may be inconsistent with the current event sequence or gesture, such as
+     * when a pointer move event is sent but the pointer is not down.
      */
     const int INPUT_EVENT_FLAG_TAINTED = 0x80000000;
 
-    /**
-     * Private flag indicating that this event was synthesized by the system and should be delivered
-     * to the accessibility focused view first. When being dispatched such an event is not handled
-     * by predecessors of the accessibility focused view and after the event reaches that view the
-     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
-     * click on any view that has accessibility focus which is semantically equivalent to asking the
-     * view to perform a click accessibility action but more generic as views not implementing click
-     * action correctly can still be activated.
-     *
-     * @hide
-     * @see #isTargetAccessibilityFocus()
-     * @see #setTargetAccessibilityFocus(boolean)
-     */
-    const int MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
-
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 
diff --git a/libs/input/android/os/MotionEventFlag.aidl b/libs/input/android/os/MotionEventFlag.aidl
new file mode 100644
index 0000000..2093b06
--- /dev/null
+++ b/libs/input/android/os/MotionEventFlag.aidl
@@ -0,0 +1,152 @@
+/**
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.IInputConstants;
+
+/**
+ * Flag definitions for MotionEvents.
+ * @hide
+ */
+@Backing(type="int")
+enum MotionEventFlag {
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event directly passed through
+     * the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    WINDOW_IS_OBSCURED = 0x1,
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event did not directly pass
+     * through the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     *
+     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
+     * obstructed in areas other than the touched location.
+     */
+    WINDOW_IS_PARTIALLY_OBSCURED = 0x2,
+
+    /**
+     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+     * @hide
+     */
+    HOVER_EXIT_PENDING = 0x4,
+
+    /**
+     * This flag indicates that the event has been generated by a gesture generator. It
+     * provides a hint to the GestureDetector to not apply any touch slop.
+     *
+     * @hide
+     */
+    IS_GENERATED_GESTURE = 0x8,
+
+    /**
+     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
+     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
+     * is set, the typical actions that occur in response for a pointer going up (such as click
+     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
+     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
+     * screen.
+     *
+     * @see #ACTION_POINTER_UP
+     * @see #ACTION_CANCEL
+     */
+    CANCELED = IInputConstants.INPUT_EVENT_FLAG_CANCELED,
+
+    /**
+     * This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
+     * gestures to allow the user to direct gestures to an unfocused window without bringing the
+     * window into focus.
+     * @hide
+     */
+    NO_FOCUS_CHANGE = 0x40,
+
+    /**
+     * This flag indicates that the event has a valid value for AXIS_ORIENTATION.
+     *
+     * This is a private flag that is not used in Java.
+     * @hide
+     */
+    PRIVATE_FLAG_SUPPORTS_ORIENTATION = 0x80,
+
+    /**
+     * This flag indicates that the pointers' AXIS_ORIENTATION can be used to precisely determine
+     * the direction in which the tool is pointing. The value of the orientation axis will be in
+     * the range [-pi, pi], which represents a full circle. This is usually supported by devices
+     * like styluses.
+     *
+     * Conversely, AXIS_ORIENTATION cannot be used to tell which direction the tool is pointing
+     * when this flag is not set. In this case, the axis value will have a range of [-pi/2, pi/2],
+     * which represents half a circle. This is usually the case for devices like touchscreens and
+     * touchpads, for which it is difficult to tell which direction along the major axis of the
+     * touch ellipse the finger is pointing.
+     *
+     * This is a private flag that is not used in Java.
+     * @hide
+     */
+    PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100,
+
+    /**
+     * The input event was generated or modified by accessibility service.
+     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
+     * set of flags, including in input/Input.h and in android/input.h.
+     */
+    IS_ACCESSIBILITY_EVENT = IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+
+    /**
+     * Private flag that indicates when the system has detected that this motion event
+     * may be inconsistent with respect to the sequence of previously delivered motion events,
+     * such as when a pointer move event is sent but the pointer is not down.
+     *
+     * @hide
+     * @see #isTainted
+     * @see #setTainted
+     */
+    TAINTED = IInputConstants.INPUT_EVENT_FLAG_TAINTED,
+
+    /**
+     * Private flag indicating that this event was synthesized by the system and should be delivered
+     * to the accessibility focused view first. When being dispatched such an event is not handled
+     * by predecessors of the accessibility focused view and after the event reaches that view the
+     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
+     * click on any view that has accessibility focus which is semantically equivalent to asking the
+     * view to perform a click accessibility action but more generic as views not implementing click
+     * action correctly can still be activated.
+     *
+     * @hide
+     * @see #isTargetAccessibilityFocus()
+     * @see #setTargetAccessibilityFocus(boolean)
+     */
+    TARGET_ACCESSIBILITY_FOCUS = 0x40000000,
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 564d94d..c46b7bb 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -19,6 +19,7 @@
 use crate::ffi::RustInputDeviceIdentifier;
 use bitflags::bitflags;
 use inputconstants::aidl::android::os::IInputConstants;
+use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag;
 use std::fmt;
 
 /// The InputDevice ID.
@@ -193,31 +194,34 @@
 
 bitflags! {
     /// MotionEvent flags.
+    /// The source of truth for the flag definitions are the MotionEventFlag AIDL enum.
+    /// The flag values are redefined here as a bitflags API.
     #[derive(Debug)]
     pub struct MotionFlags: u32 {
         /// FLAG_WINDOW_IS_OBSCURED
-        const WINDOW_IS_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32;
+        const WINDOW_IS_OBSCURED = MotionEventFlag::WINDOW_IS_OBSCURED.0 as u32;
         /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-        const WINDOW_IS_PARTIALLY_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32;
+        const WINDOW_IS_PARTIALLY_OBSCURED = MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED.0 as u32;
         /// FLAG_HOVER_EXIT_PENDING
-        const HOVER_EXIT_PENDING = IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32;
+        const HOVER_EXIT_PENDING = MotionEventFlag::HOVER_EXIT_PENDING.0 as u32;
         /// FLAG_IS_GENERATED_GESTURE
-        const IS_GENERATED_GESTURE = IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32;
+        const IS_GENERATED_GESTURE = MotionEventFlag::IS_GENERATED_GESTURE.0 as u32;
         /// FLAG_CANCELED
-        const CANCELED = IInputConstants::INPUT_EVENT_FLAG_CANCELED as u32;
+        const CANCELED = MotionEventFlag::CANCELED.0 as u32;
         /// FLAG_NO_FOCUS_CHANGE
-        const NO_FOCUS_CHANGE = IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32;
+        const NO_FOCUS_CHANGE = MotionEventFlag::NO_FOCUS_CHANGE.0 as u32;
         /// PRIVATE_FLAG_SUPPORTS_ORIENTATION
-        const PRIVATE_SUPPORTS_ORIENTATION = IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION as u32;
+        const PRIVATE_FLAG_SUPPORTS_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION.0 as u32;
         /// PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION
-        const PRIVATE_SUPPORTS_DIRECTIONAL_ORIENTATION =
-                IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION as u32;
+        const PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION.0 as u32;
         /// FLAG_IS_ACCESSIBILITY_EVENT
-        const IS_ACCESSIBILITY_EVENT = IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32;
+        const IS_ACCESSIBILITY_EVENT = MotionEventFlag::IS_ACCESSIBILITY_EVENT.0 as u32;
         /// FLAG_TAINTED
-        const TAINTED = IInputConstants::INPUT_EVENT_FLAG_TAINTED as u32;
+        const TAINTED = MotionEventFlag::TAINTED.0 as u32;
         /// FLAG_TARGET_ACCESSIBILITY_FOCUS
-        const TARGET_ACCESSIBILITY_FOCUS = IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32;
+        const TARGET_ACCESSIBILITY_FOCUS = MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS.0 as u32;
     }
 }
 
@@ -333,10 +337,24 @@
 #[cfg(test)]
 mod tests {
     use crate::input::SourceClass;
+    use crate::MotionFlags;
     use crate::Source;
+    use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag;
+
     #[test]
     fn convert_source_class_pointer() {
         let source = Source::from_bits(input_bindgen::AINPUT_SOURCE_CLASS_POINTER).unwrap();
         assert!(source.is_from_class(SourceClass::Pointer));
     }
+
+    /// Ensure all MotionEventFlag constants are re-defined in rust.
+    #[test]
+    fn all_motion_event_flags_defined() {
+        for flag in MotionEventFlag::enum_values() {
+            assert!(
+                MotionFlags::from_bits(flag.0 as u32).is_some(),
+                "MotionEventFlag value {flag:?} is not redefined as a rust MotionFlags"
+            );
+        }
+    }
 }
diff --git a/libs/input/rust/keyboard_classification_config.rs b/libs/input/rust/keyboard_classification_config.rs
new file mode 100644
index 0000000..ab74efb
--- /dev/null
+++ b/libs/input/rust/keyboard_classification_config.rs
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::input::KeyboardType;
+
+// TODO(b/263559234): Categorize some of these to KeyboardType::None based on ability to produce
+//  key events at all. (Requires setup allowing InputDevice to dynamically add/remove
+//  KeyboardInputMapper based on blocklist and KeyEvents in case a KeyboardType::None device ends
+//  up producing a key event)
+pub static CLASSIFIED_DEVICES: &[(
+    /* vendorId */ u16,
+    /* productId */ u16,
+    KeyboardType,
+    /* is_finalized */ bool,
+)] = &[
+    // HP X4000 Wireless Mouse
+    (0x03f0, 0xa407, KeyboardType::NonAlphabetic, true),
+    // Microsoft Wireless Mobile Mouse 6000
+    (0x045e, 0x0745, KeyboardType::NonAlphabetic, true),
+    // Microsoft Surface Precision Mouse
+    (0x045e, 0x0821, KeyboardType::NonAlphabetic, true),
+    // Microsoft Pro IntelliMouse
+    (0x045e, 0x082a, KeyboardType::NonAlphabetic, true),
+    // Microsoft Bluetooth Mouse
+    (0x045e, 0x082f, KeyboardType::NonAlphabetic, true),
+    // Xbox One Elite Series 2 gamepad
+    (0x045e, 0x0b05, KeyboardType::NonAlphabetic, true),
+    // Logitech T400
+    (0x046d, 0x4026, KeyboardType::NonAlphabetic, true),
+    // Logitech M720 Triathlon (Unifying)
+    (0x046d, 0x405e, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 2S (Unifying)
+    (0x046d, 0x4069, KeyboardType::NonAlphabetic, true),
+    // Logitech M585 (Unifying)
+    (0x046d, 0x406b, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Unifying)
+    (0x046d, 0x4072, KeyboardType::NonAlphabetic, true),
+    // Logitech Pebble M350
+    (0x046d, 0x4080, KeyboardType::NonAlphabetic, true),
+    // Logitech T630 Ultrathin
+    (0x046d, 0xb00d, KeyboardType::NonAlphabetic, true),
+    // Logitech M558
+    (0x046d, 0xb011, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master (Bluetooth)
+    (0x046d, 0xb012, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Bluetooth)
+    (0x046d, 0xb013, KeyboardType::NonAlphabetic, true),
+    // Logitech M720 Triathlon (Bluetooth)
+    (0x046d, 0xb015, KeyboardType::NonAlphabetic, true),
+    // Logitech M535
+    (0x046d, 0xb016, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master / Anywhere 2 (Bluetooth)
+    (0x046d, 0xb017, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 2S (Bluetooth)
+    (0x046d, 0xb019, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2S (Bluetooth)
+    (0x046d, 0xb01a, KeyboardType::NonAlphabetic, true),
+    // Logitech M585/M590 (Bluetooth)
+    (0x046d, 0xb01b, KeyboardType::NonAlphabetic, true),
+    // Logitech G603 Lightspeed Gaming Mouse (Bluetooth)
+    (0x046d, 0xb01c, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master (Bluetooth)
+    (0x046d, 0xb01e, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Bluetooth)
+    (0x046d, 0xb01f, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 3 (Bluetooth)
+    (0x046d, 0xb023, KeyboardType::NonAlphabetic, true),
+    // Logitech G604 Lightspeed Gaming Mouse (Bluetooth)
+    (0x046d, 0xb024, KeyboardType::NonAlphabetic, true),
+    // Logitech Spotlight Presentation Remote (Bluetooth)
+    (0x046d, 0xb503, KeyboardType::NonAlphabetic, true),
+    // Logitech R500 (Bluetooth)
+    (0x046d, 0xb505, KeyboardType::NonAlphabetic, true),
+    // Logitech M500s
+    (0x046d, 0xc093, KeyboardType::NonAlphabetic, true),
+    // Logitech Spotlight Presentation Remote (USB dongle)
+    (0x046d, 0xc53e, KeyboardType::NonAlphabetic, true),
+    // Elecom Enelo IR LED Mouse 350
+    (0x056e, 0x0134, KeyboardType::NonAlphabetic, true),
+    // Elecom EPRIM Blue LED 5 button mouse 228
+    (0x056e, 0x0141, KeyboardType::NonAlphabetic, true),
+    // Elecom Blue LED Mouse 203
+    (0x056e, 0x0159, KeyboardType::NonAlphabetic, true),
+    // Zebra LS2208 barcode scanner
+    (0x05e0, 0x1200, KeyboardType::NonAlphabetic, true),
+    // RDing FootSwitch1F1
+    (0x0c45, 0x7403, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Sensei RAW Frost Blue
+    (0x1038, 0x1369, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Rival 3 Wired
+    (0x1038, 0x1824, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Rival 3 Wireless (USB dongle)
+    (0x1038, 0x1830, KeyboardType::NonAlphabetic, true),
+    // Yubico.com Yubikey
+    (0x1050, 0x0010, KeyboardType::NonAlphabetic, true),
+    // Yubico.com Yubikey 4 OTP+U2F+CCID
+    (0x1050, 0x0407, KeyboardType::NonAlphabetic, true),
+    // Lenovo USB-C Wired Compact Mouse
+    (0x17ef, 0x6123, KeyboardType::NonAlphabetic, true),
+    // Corsair Katar Pro Wireless (USB dongle)
+    (0x1b1c, 0x1b94, KeyboardType::NonAlphabetic, true),
+    // Corsair Katar Pro Wireless (Bluetooth)
+    (0x1bae, 0x1b1c, KeyboardType::NonAlphabetic, true),
+    // Kensington Pro Fit Full-size
+    (0x1bcf, 0x08a0, KeyboardType::NonAlphabetic, true),
+    // Huion HS64
+    (0x256c, 0x006d, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Star G640
+    (0x28bd, 0x0914, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Artist 12 Pro
+    (0x28bd, 0x091f, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Deco mini7W
+    (0x28bd, 0x0928, KeyboardType::NonAlphabetic, true),
+];
diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs
index 1063fac..8721ef7 100644
--- a/libs/input/rust/keyboard_classifier.rs
+++ b/libs/input/rust/keyboard_classifier.rs
@@ -35,6 +35,7 @@
 //! TODO(b/263559234): Data store implementation to store information about past classification
 
 use crate::input::{DeviceId, InputDevice, KeyboardType};
+use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
 use crate::{DeviceClass, ModifierState};
 use std::collections::HashMap;
 
@@ -126,6 +127,14 @@
                 (KeyboardType::NonAlphabetic, true)
             };
         }
+
+        // Check in known device list for classification
+        for data in CLASSIFIED_DEVICES.iter() {
+            if device.identifier.vendor == data.0 && device.identifier.product == data.1 {
+                return (data.2, data.3);
+            }
+        }
+
         // Any composite device with multiple device classes should be categorized as non-alphabetic
         // keyboard initially
         if device.classes.contains(DeviceClass::Touch)
@@ -169,6 +178,7 @@
 #[cfg(test)]
 mod tests {
     use crate::input::{DeviceId, InputDevice, KeyboardType};
+    use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
     use crate::keyboard_classifier::KeyboardClassifier;
     use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier};
 
@@ -326,6 +336,17 @@
         assert!(!classifier.is_finalized(DEVICE_ID));
     }
 
+    #[test]
+    fn classify_known_devices() {
+        let mut classifier = KeyboardClassifier::new();
+        for device in CLASSIFIED_DEVICES.iter() {
+            classifier
+                .notify_keyboard_changed(create_device_with_vendor_product_ids(device.0, device.1));
+            assert_eq!(classifier.get_keyboard_type(DEVICE_ID), device.2);
+            assert_eq!(classifier.is_finalized(DEVICE_ID), device.3);
+        }
+    }
+
     fn create_device(classes: DeviceClass) -> InputDevice {
         InputDevice {
             device_id: DEVICE_ID,
@@ -342,4 +363,21 @@
             classes,
         }
     }
+
+    fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> InputDevice {
+        InputDevice {
+            device_id: DEVICE_ID,
+            identifier: RustInputDeviceIdentifier {
+                name: "test_device".to_string(),
+                location: "location".to_string(),
+                unique_id: "unique_id".to_string(),
+                bus: 123,
+                vendor,
+                product,
+                version: 567,
+                descriptor: "descriptor".to_string(),
+            },
+            classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
+        }
+    }
 }
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 5010475..af8f889 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -18,6 +18,7 @@
 
 mod input;
 mod input_verifier;
+mod keyboard_classification_config;
 mod keyboard_classifier;
 
 pub use input::{
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index e62640e..5f37125 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -514,7 +514,7 @@
     auto shader = parameters.shader;
     if (stretchEffect.hasEffect()) {
         const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
-        if (graphicBuffer && parameters.shader) {
+        if (graphicBuffer && shader) {
             shader = mStretchShaderFactory.createSkShader(shader, stretchEffect);
         }
     }
@@ -525,21 +525,26 @@
                           static_cast<ui::PixelFormat>(targetBuffer->getPixelFormat()))
                 : std::nullopt;
 
-        if (parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local) {
-            // TODO: Handle color matrix transforms in linear space.
-            SkImage* image = parameters.shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
-            if (image) {
-                static MouriMap kMapper;
-                const float ratio = getHdrRenderType(parameters.layer.sourceDataspace, format) ==
-                                HdrRenderType::GENERIC_HDR
-                        ? 1.0f
-                        : parameters.layerDimmingRatio;
-                return kMapper.mouriMap(getActiveContext(), parameters.shader, ratio);
-            }
+        const auto hdrType = getHdrRenderType(parameters.layer.sourceDataspace, format,
+                                              parameters.layerDimmingRatio);
+
+        const auto usingLocalTonemap =
+                parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local &&
+                hdrType != HdrRenderType::SDR &&
+                shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
+
+        if (usingLocalTonemap) {
+            static MouriMap kMapper;
+            const float ratio =
+                    hdrType == HdrRenderType::GENERIC_HDR ? 1.0f : parameters.layerDimmingRatio;
+            shader = kMapper.mouriMap(getActiveContext(), shader, ratio);
         }
 
+        // disable tonemapping if we already locally tonemapped
+        auto inputDataspace =
+                usingLocalTonemap ? parameters.outputDataSpace : parameters.layer.sourceDataspace;
         auto effect =
-                shaders::LinearEffect{.inputDataspace = parameters.layer.sourceDataspace,
+                shaders::LinearEffect{.inputDataspace = inputDataspace,
                                       .outputDataspace = parameters.outputDataSpace,
                                       .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha,
                                       .fakeOutputDataspace = parameters.fakeOutputDataspace};
@@ -555,20 +560,22 @@
 
         mat4 colorTransform = parameters.layer.colorTransform;
 
-        colorTransform *=
-                mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
-                                 parameters.layerDimmingRatio, 1.f));
+        if (!usingLocalTonemap) {
+            colorTransform *=
+                    mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
+                                     parameters.layerDimmingRatio, 1.f));
+        }
 
         const auto targetBuffer = parameters.layer.source.buffer.buffer;
         const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
         const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr;
-        return createLinearEffectShader(parameters.shader, effect, runtimeEffect,
-                                        std::move(colorTransform), parameters.display.maxLuminance,
+        return createLinearEffectShader(shader, effect, runtimeEffect, std::move(colorTransform),
+                                        parameters.display.maxLuminance,
                                         parameters.display.currentLuminanceNits,
                                         parameters.layer.source.buffer.maxLuminanceNits,
                                         hardwareBuffer, parameters.display.renderIntent);
     }
-    return parameters.shader;
+    return shader;
 }
 
 void SkiaRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index bd50107..a1f917d 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -29,7 +29,6 @@
 #include <GrDirectContext.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
-#include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
 
 #include <android-base/stringprintf.h>
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index 0a2f9b2..d2bb3d5 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -17,8 +17,6 @@
 #ifndef SF_SKIAVKRENDERENGINE_H_
 #define SF_SKIAVKRENDERENGINE_H_
 
-#include <vk/GrVkBackendContext.h>
-
 #include "SkiaRenderEngine.h"
 #include "VulkanInterface.h"
 #include "compat/SkiaGpuContext.h"
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index 5e756b0..37b69f6 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -32,21 +32,8 @@
 namespace renderengine {
 namespace skia {
 
-GrVkBackendContext VulkanInterface::getGaneshBackendContext() {
-    GrVkBackendContext backendContext;
-    backendContext.fInstance = mInstance;
-    backendContext.fPhysicalDevice = mPhysicalDevice;
-    backendContext.fDevice = mDevice;
-    backendContext.fQueue = mQueue;
-    backendContext.fGraphicsQueueIndex = mQueueIndex;
-    backendContext.fMaxAPIVersion = mApiVersion;
-    backendContext.fVkExtensions = &mGrExtensions;
-    backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
-    backendContext.fGetProc = mGrGetProc;
-    backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
-    backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
-    backendContext.fDeviceLostProc = onVkDeviceFault;
-    return backendContext;
+VulkanBackendContext VulkanInterface::getGaneshBackendContext() {
+    return this->getGraphiteBackendContext();
 };
 
 VulkanBackendContext VulkanInterface::getGraphiteBackendContext() {
@@ -57,7 +44,7 @@
     backendContext.fQueue = mQueue;
     backendContext.fGraphicsQueueIndex = mQueueIndex;
     backendContext.fMaxAPIVersion = mApiVersion;
-    backendContext.fVkExtensions = &mGrExtensions;
+    backendContext.fVkExtensions = &mVulkanExtensions;
     backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
     backendContext.fGetProc = mGrGetProc;
     backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
@@ -429,11 +416,11 @@
         mDeviceExtensionNames.push_back(devExt.extensionName);
     }
 
-    mGrExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
-                       enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
-                       enabledDeviceExtensionNames.data());
+    mVulkanExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
+                           enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
+                           enabledDeviceExtensionNames.data());
 
-    if (!mGrExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
+    if (!mVulkanExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
         BAIL("Vulkan driver doesn't support external semaphore fd");
     }
 
@@ -458,7 +445,7 @@
         tailPnext = &mProtectedMemoryFeatures->pNext;
     }
 
-    if (mGrExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+    if (mVulkanExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
         mDeviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
         mDeviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
         mDeviceFaultFeatures->pNext = nullptr;
@@ -484,7 +471,7 @@
             queuePriority,
     };
 
-    if (mGrExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+    if (mVulkanExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
         queueNextPtr = &queuePriorityCreateInfo;
     }
 
@@ -606,7 +593,7 @@
     mQueue = VK_NULL_HANDLE;          // Implicitly destroyed by destroying mDevice.
     mQueueIndex = 0;
     mApiVersion = 0;
-    mGrExtensions = skgpu::VulkanExtensions();
+    mVulkanExtensions = skgpu::VulkanExtensions();
     mGrGetProc = nullptr;
     mIsProtected = false;
     mIsRealtimePriority = false;
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
index f20b002..d0fe4d1 100644
--- a/libs/renderengine/skia/VulkanInterface.h
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
 #include <include/gpu/vk/VulkanExtensions.h>
 #include <include/gpu/vk/VulkanTypes.h>
 
@@ -24,10 +24,6 @@
 
 using namespace skgpu;
 
-namespace skgpu {
-struct VulkanBackendContext;
-} // namespace skgpu
-
 namespace android {
 namespace renderengine {
 namespace skia {
@@ -48,7 +44,8 @@
     bool takeOwnership();
     void teardown();
 
-    GrVkBackendContext getGaneshBackendContext();
+    // TODO(b/309785258) Combine these into one now that they are the same implementation.
+    VulkanBackendContext getGaneshBackendContext();
     VulkanBackendContext getGraphiteBackendContext();
     VkSemaphore createExportableSemaphore();
     VkSemaphore importSemaphoreFromSyncFd(int syncFd);
@@ -86,7 +83,7 @@
     VkQueue mQueue = VK_NULL_HANDLE;
     int mQueueIndex = 0;
     uint32_t mApiVersion = 0;
-    skgpu::VulkanExtensions mGrExtensions;
+    skgpu::VulkanExtensions mVulkanExtensions;
     VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr;
     VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr;
     VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr;
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
index b2eae00..b121fe8 100644
--- a/libs/renderengine/skia/compat/GaneshGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -25,7 +25,7 @@
 #include <include/gpu/ganesh/gl/GrGLDirectContext.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <include/gpu/gl/GrGLInterface.h>
-#include <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
 
 #include "../AutoBackendTexture.h"
 #include "GaneshBackendTexture.h"
@@ -56,10 +56,10 @@
 }
 
 std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Ganesh(
-        const GrVkBackendContext& grVkBackendContext,
+        const skgpu::VulkanBackendContext& vkBackendContext,
         GrContextOptions::PersistentCache& skSLCacheMonitor) {
     return std::make_unique<GaneshGpuContext>(
-            GrDirectContexts::MakeVulkan(grVkBackendContext, ganeshOptions(skSLCacheMonitor)));
+            GrDirectContexts::MakeVulkan(vkBackendContext, ganeshOptions(skSLCacheMonitor)));
 }
 
 GaneshGpuContext::GaneshGpuContext(sk_sp<GrDirectContext> grContext) : mGrContext(grContext) {
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
index 282dfe7..9fa6fb8 100644
--- a/libs/renderengine/skia/compat/SkiaGpuContext.h
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -23,7 +23,6 @@
 #include <include/gpu/GrDirectContext.h>
 #include <include/gpu/gl/GrGLInterface.h>
 #include <include/gpu/graphite/Context.h>
-#include <include/gpu/vk/GrVkBackendContext.h>
 #include "include/gpu/vk/VulkanBackendContext.h"
 
 #include "SkiaBackendTexture.h"
@@ -52,10 +51,10 @@
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     /**
-     * grVkBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     * vkBackendContext must remain valid until after SkiaGpuContext is destroyed.
      */
     static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
-            const GrVkBackendContext& grVkBackendContext,
+            const skgpu::VulkanBackendContext& vkBackendContext,
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite.
diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp
index 7d8b8a5..cc25fcc 100644
--- a/libs/renderengine/skia/filters/MouriMap.cpp
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -84,13 +84,13 @@
         float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio;
 
         if (localMax <= 1.0) {
-            return float4(fromLinearSrgb(linear), 1.0);
+            return float4(fromLinearSrgb(linear), rgba.a);
         }
 
         float maxRGB = max(linear.r, max(linear.g, linear.b));
         localMax = max(localMax, maxRGB);
         float gain = (1 + maxRGB / (localMax * localMax)) / (1 + maxRGB);
-        return float4(fromLinearSrgb(linear * gain), 1.0);
+        return float4(fromLinearSrgb(linear * gain), rgba.a);
     }
 )");
 
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index 7fa47b4..659666d 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -63,6 +63,8 @@
         "libhardware",
         "libpermission",
         "android.companion.virtual.virtualdevice_aidl-cpp",
+        "libaconfig_storage_read_api_cc",
+        "server_configurable_flags",
     ],
 
     static_libs: [
diff --git a/libs/sensor/SensorEventQueue.cpp b/libs/sensor/SensorEventQueue.cpp
index 4438d45..84852ea 100644
--- a/libs/sensor/SensorEventQueue.cpp
+++ b/libs/sensor/SensorEventQueue.cpp
@@ -15,31 +15,40 @@
  */
 
 #define LOG_TAG "Sensors"
-
-#include <sensor/SensorEventQueue.h>
-
-#include <algorithm>
-#include <sys/socket.h>
-
-#include <utils/RefBase.h>
-#include <utils/Looper.h>
-
-#include <sensor/Sensor.h>
-#include <sensor/BitTube.h>
-#include <sensor/ISensorEventConnection.h>
+#define ATRACE_TAG ATRACE_TAG_SYSTEM_SERVER
 
 #include <android/sensor.h>
+#include <com_android_hardware_libsensor_flags.h>
+#include <cutils/trace.h>
 #include <hardware/sensors-base.h>
+#include <sensor/BitTube.h>
+#include <sensor/ISensorEventConnection.h>
+#include <sensor/Sensor.h>
+#include <sensor/SensorEventQueue.h>
+#include <sensor/SensorManager.h>
+#include <sys/socket.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+
+#include <algorithm>
+#include <cinttypes>
+#include <string>
 
 using std::min;
+namespace libsensor_flags = com::android::hardware::libsensor::flags;
 
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
 
-SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection)
-    : mSensorEventConnection(connection), mRecBuffer(nullptr), mAvailable(0), mConsumed(0),
-      mNumAcksToSend(0) {
+SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection,
+                                   SensorManager& sensorManager)
+      : mSensorEventConnection(connection),
+        mRecBuffer(nullptr),
+        mSensorManager(sensorManager),
+        mAvailable(0),
+        mConsumed(0),
+        mNumAcksToSend(0) {
     mRecBuffer = new ASensorEvent[MAX_RECEIVE_BUFFER_EVENT_COUNT];
 }
 
@@ -65,8 +74,8 @@
 
 ssize_t SensorEventQueue::read(ASensorEvent* events, size_t numEvents) {
     if (mAvailable == 0) {
-        ssize_t err = BitTube::recvObjects(mSensorChannel,
-                mRecBuffer, MAX_RECEIVE_BUFFER_EVENT_COUNT);
+        ssize_t err =
+                BitTube::recvObjects(mSensorChannel, mRecBuffer, MAX_RECEIVE_BUFFER_EVENT_COUNT);
         if (err < 0) {
             return err;
         }
@@ -75,6 +84,20 @@
     }
     size_t count = min(numEvents, mAvailable);
     memcpy(events, mRecBuffer + mConsumed, count * sizeof(ASensorEvent));
+
+    if (CC_UNLIKELY(ATRACE_ENABLED()) &&
+        libsensor_flags::sensor_event_queue_report_sensor_usage_in_tracing()) {
+        for (size_t i = 0; i < count; i++) {
+            std::optional<std::string_view> sensorName =
+                    mSensorManager.getSensorNameByHandle(events->sensor);
+            if (sensorName.has_value()) {
+                char buffer[UINT8_MAX];
+                std::snprintf(buffer, sizeof(buffer), "Sensor event from %s",
+                              sensorName.value().data());
+                ATRACE_INSTANT_FOR_TRACK(LOG_TAG, buffer);
+            }
+        }
+    }
     mAvailable -= count;
     mConsumed += count;
     return static_cast<ssize_t>(count);
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 9411e20..3ca6f0f 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -38,6 +38,7 @@
 #include <sensor/SensorEventQueue.h>
 
 #include <com_android_hardware_libsensor_flags.h>
+namespace libsensor_flags = com::android::hardware::libsensor::flags;
 
 // ----------------------------------------------------------------------------
 namespace android {
@@ -78,6 +79,21 @@
     return DEVICE_ID_DEFAULT;
 }
 
+bool findSensorNameInList(int32_t handle, const Vector<Sensor>& sensorList,
+                          std::string* outString) {
+    for (auto& sensor : sensorList) {
+        if (sensor.getHandle() == handle) {
+            std::ostringstream oss;
+            oss << sensor.getStringType() << ":" << sensor.getName();
+            if (outString) {
+                *outString = std::move(oss.str());
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
 }  // namespace
 
 Mutex SensorManager::sLock;
@@ -355,6 +371,25 @@
     return nullptr;
 }
 
+std::optional<std::string_view> SensorManager::getSensorNameByHandle(int32_t handle) {
+    std::lock_guard<std::mutex> lock(mSensorHandleToNameMutex);
+    auto iterator = mSensorHandleToName.find(handle);
+    if (iterator != mSensorHandleToName.end()) {
+        return iterator->second;
+    }
+
+    std::string sensorName;
+    if (!findSensorNameInList(handle, mSensors, &sensorName) &&
+        !findSensorNameInList(handle, mDynamicSensors, &sensorName)) {
+        ALOGW("Cannot find sensor with handle %d", handle);
+        return std::nullopt;
+    }
+
+    mSensorHandleToName[handle] = std::move(sensorName);
+
+    return mSensorHandleToName[handle];
+}
+
 sp<SensorEventQueue> SensorManager::createEventQueue(
     String8 packageName, int mode, String16 attributionTag) {
     sp<SensorEventQueue> queue;
@@ -368,7 +403,7 @@
             ALOGE("createEventQueue: connection is NULL.");
             return nullptr;
         }
-        queue = new SensorEventQueue(connection);
+        queue = new SensorEventQueue(connection, *this);
         break;
     }
     return queue;
diff --git a/libs/sensor/include/sensor/SensorEventQueue.h b/libs/sensor/include/sensor/SensorEventQueue.h
index 8c3fde0..0bcaadc 100644
--- a/libs/sensor/include/sensor/SensorEventQueue.h
+++ b/libs/sensor/include/sensor/SensorEventQueue.h
@@ -42,6 +42,7 @@
 // ----------------------------------------------------------------------------
 
 class ISensorEventConnection;
+class SensorManager;
 class Sensor;
 class Looper;
 
@@ -65,7 +66,8 @@
     // Default sensor sample period
     static constexpr int32_t SENSOR_DELAY_NORMAL = 200000;
 
-    explicit SensorEventQueue(const sp<ISensorEventConnection>& connection);
+    explicit SensorEventQueue(const sp<ISensorEventConnection>& connection,
+                              SensorManager& sensorManager);
     virtual ~SensorEventQueue();
     virtual void onFirstRef();
 
@@ -107,6 +109,7 @@
     mutable Mutex mLock;
     mutable sp<Looper> mLooper;
     ASensorEvent* mRecBuffer;
+    SensorManager& mSensorManager;
     size_t mAvailable;
     size_t mConsumed;
     uint32_t mNumAcksToSend;
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 49f050a..8d7237d 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -17,22 +17,20 @@
 #ifndef ANDROID_GUI_SENSOR_MANAGER_H
 #define ANDROID_GUI_SENSOR_MANAGER_H
 
-#include <map>
-#include <unordered_map>
-
-#include <stdint.h>
-#include <sys/types.h>
-
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-
+#include <sensor/SensorEventQueue.h>
+#include <stdint.h>
+#include <sys/types.h>
 #include <utils/Errors.h>
+#include <utils/String8.h>
 #include <utils/StrongPointer.h>
 #include <utils/Vector.h>
-#include <utils/String8.h>
 
-#include <sensor/SensorEventQueue.h>
+#include <map>
+#include <string>
+#include <unordered_map>
 
 // ----------------------------------------------------------------------------
 // Concrete types for the NDK
@@ -66,6 +64,7 @@
     sp<SensorEventQueue> createEventQueue(
         String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
     bool isDataInjectionEnabled();
+    std::optional<std::string_view> getSensorNameByHandle(int32_t handle);
     bool isReplayDataInjectionEnabled();
     bool isHalBypassReplayDataInjectionEnabled();
     int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData);
@@ -97,6 +96,9 @@
     const String16 mOpPackageName;
     const int mDeviceId;
     std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
+
+    std::mutex mSensorHandleToNameMutex;
+    std::unordered_map<int32_t, std::string> mSensorHandleToName;
     int32_t mDirectConnectionHandle;
 };
 
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index e76b648..b2f15b4 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -78,6 +78,7 @@
     name: "libinputreader_defaults",
     srcs: [":libinputreader_sources"],
     shared_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc-host",
         "libbase",
         "libcap",
         "libcrypto",
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index fe70a51..643499c 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -33,6 +33,8 @@
 #include <sys/sysmacros.h>
 #include <unistd.h>
 
+#include <android_companion_virtualdevice_flags.h>
+
 #define LOG_TAG "EventHub"
 
 // #define LOG_NDEBUG 0
@@ -68,6 +70,8 @@
 
 namespace android {
 
+namespace vd_flags = android::companion::virtualdevice::flags;
+
 using namespace ftl::flag_operators;
 
 static const char* DEVICE_INPUT_PATH = "/dev/input";
@@ -998,26 +1002,23 @@
     return *device->configuration;
 }
 
-status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                       RawAbsoluteAxisInfo* outAxisInfo) const {
-    outAxisInfo->clear();
+std::optional<RawAbsoluteAxisInfo> EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis) const {
     if (axis < 0 || axis > ABS_MAX) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     // We can read the RawAbsoluteAxisInfo even if the device is disabled and doesn't have a valid
     // fd, because the info is populated once when the device is first opened, and it doesn't change
     // throughout the device lifecycle.
     auto it = device->absState.find(axis);
     if (it == device->absState.end()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
-    *outAxisInfo = it->second.info;
-    return OK;
+    return it->second.info;
 }
 
 bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -1130,22 +1131,20 @@
     return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
 }
 
-status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
-    *outValue = 0;
+std::optional<int32_t> EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const {
     if (axis < 0 || axis > ABS_MAX) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr || !device->hasValidFd()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     const auto it = device->absState.find(axis);
     if (it == device->absState.end()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
-    *outValue = it->second.value;
-    return OK;
+    return it->second.value;
 }
 
 base::Result<std::vector<int32_t>> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis,
@@ -2503,6 +2502,12 @@
         }
     }
 
+    // See if the device is a rotary encoder with a single scroll axis and nothing else.
+    if (vd_flags::virtual_rotary() && device->classes == ftl::Flags<InputDeviceClass>(0) &&
+        device->relBitmask.test(REL_WHEEL) && !device->relBitmask.test(REL_HWHEEL)) {
+        device->classes |= InputDeviceClass::ROTARY_ENCODER;
+    }
+
     // If the device isn't recognized as something we handle, don't monitor it.
     if (device->classes == ftl::Flags<InputDeviceClass>(0)) {
         ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath.c_str(),
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 7cf584d..2a43466 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -21,6 +21,7 @@
 #include <filesystem>
 #include <functional>
 #include <map>
+#include <optional>
 #include <ostream>
 #include <string>
 #include <unordered_map>
@@ -278,8 +279,8 @@
      */
     virtual std::optional<PropertyMap> getConfiguration(int32_t deviceId) const = 0;
 
-    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                         RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+    virtual std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                                   int axis) const = 0;
 
     virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
 
@@ -339,8 +340,7 @@
     virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
     virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
     virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
-    virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                          int32_t* outValue) const = 0;
+    virtual std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const = 0;
     /* Query Multi-Touch slot values for an axis. Returns error or an 1 indexed array of size
      * (slotCount + 1). The value at the 0 index is set to queried axis. */
     virtual base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
@@ -511,8 +511,8 @@
 
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override final;
 
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override final;
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override final;
 
     bool hasRelativeAxis(int32_t deviceId, int axis) const override final;
 
@@ -559,8 +559,8 @@
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override final;
     int32_t getKeyCodeForKeyLocation(int32_t deviceId,
                                      int32_t locationKeyCode) const override final;
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override final;
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId,
+                                                int32_t axis) const override final;
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override final;
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 2a7e262..086c26f 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -306,9 +306,12 @@
         return mEventHub->getDeviceControllerNumber(mId);
     }
     inline status_t getAbsoluteAxisInfo(int32_t code, RawAbsoluteAxisInfo* axisInfo) const {
-        if (const auto status = mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo); status != OK) {
-            return status;
+        std::optional<RawAbsoluteAxisInfo> info = mEventHub->getAbsoluteAxisInfo(mId, code);
+        if (!info.has_value()) {
+            axisInfo->clear();
+            return NAME_NOT_FOUND;
         }
+        *axisInfo = *info;
 
         // Validate axis info for InputDevice.
         if (axisInfo->valid && axisInfo->minValue == axisInfo->maxValue) {
@@ -379,8 +382,8 @@
         return mEventHub->getKeyCodeForKeyLocation(mId, locationKeyCode);
     }
     inline int32_t getSwitchState(int32_t sw) const { return mEventHub->getSwitchState(mId, sw); }
-    inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const {
-        return mEventHub->getAbsoluteAxisValue(mId, code, outValue);
+    inline std::optional<int32_t> getAbsoluteAxisValue(int32_t code) const {
+        return mEventHub->getAbsoluteAxisValue(mId, code);
     }
     inline base::Result<std::vector<int32_t>> getMtSlotValues(int32_t axis,
                                                               size_t slotCount) const {
@@ -432,9 +435,8 @@
     }
 
     inline bool hasAbsoluteAxis(int32_t code) const {
-        RawAbsoluteAxisInfo info;
-        mEventHub->getAbsoluteAxisInfo(mId, code, &info);
-        return info.valid;
+        std::optional<RawAbsoluteAxisInfo> info = mEventHub->getAbsoluteAxisInfo(mId, code);
+        return info.has_value() && info->valid;
     }
     inline bool isKeyPressed(int32_t scanCode) const {
         return mEventHub->getScanCodeState(mId, scanCode) == AKEY_STATE_DOWN;
@@ -442,11 +444,6 @@
     inline bool isKeyCodePressed(int32_t keyCode) const {
         return mEventHub->getKeyCodeState(mId, keyCode) == AKEY_STATE_DOWN;
     }
-    inline int32_t getAbsoluteAxisValue(int32_t code) const {
-        int32_t value;
-        mEventHub->getAbsoluteAxisValue(mId, code, &value);
-        return value;
-    }
     inline bool isDeviceEnabled() { return mEventHub->isDeviceEnabled(mId); }
     inline status_t enableDevice() { return mEventHub->enableDevice(mId); }
     inline status_t disableDevice() { return mEventHub->disableDevice(mId); }
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 27ff52f..20fd359 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -84,12 +84,18 @@
         }
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
-        std::optional<DisplayViewport> internalViewport =
-                config.getDisplayViewportByType(ViewportType::INTERNAL);
-        if (internalViewport) {
-            mOrientation = internalViewport->orientation;
+        if (getDeviceContext().getAssociatedViewport()) {
+            mDisplayId = getDeviceContext().getAssociatedViewport()->displayId;
+            mOrientation = getDeviceContext().getAssociatedViewport()->orientation;
         } else {
-            mOrientation = ui::ROTATION_0;
+            mDisplayId = ui::LogicalDisplayId::INVALID;
+            std::optional<DisplayViewport> internalViewport =
+                    config.getDisplayViewportByType(ViewportType::INTERNAL);
+            if (internalViewport) {
+                mOrientation = internalViewport->orientation;
+            } else {
+                mOrientation = ui::ROTATION_0;
+            }
         }
     }
     return out;
@@ -124,8 +130,6 @@
     // Send motion event.
     if (scrolled) {
         int32_t metaState = getContext()->getGlobalMetaState();
-        // This is not a pointer, so it's not associated with a display.
-        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
 
         if (mOrientation == ui::ROTATION_180) {
             scroll = -scroll;
@@ -147,7 +151,7 @@
 
         out.push_back(
                 NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
-                                 displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
+                                 mDisplayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
                                  metaState, /*buttonState=*/0, MotionClassification::NONE,
                                  AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                  &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index 14c540b..7e80415 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -47,6 +47,7 @@
     int32_t mSource;
     float mScalingFactor;
     ui::Rotation mOrientation;
+    ui::LogicalDisplayId mDisplayId = ui::LogicalDisplayId::INVALID;
     std::unique_ptr<SlopController> mSlopController;
 
     explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index a383490..81ec24e 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -931,7 +931,7 @@
             mSource |= AINPUT_SOURCE_STYLUS;
         }
     } else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) {
-        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD;
         mDeviceMode = DeviceMode::NAVIGATION;
     } else {
         ALOGW("Touch device '%s' has invalid parameters or configuration.  The device will be "
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index 4919068..8dc6e4d 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -139,13 +139,11 @@
     if (!mUsingSlotsProtocol) {
         return;
     }
-    int32_t initialSlot;
-    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-        status == OK) {
-        mCurrentSlot = initialSlot;
+    if (const std::optional<int32_t> initialSlot = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT);
+        initialSlot.has_value()) {
+        mCurrentSlot = initialSlot.value();
     } else {
-        ALOGE("Could not retrieve current multi-touch slot index. status=%s",
-              statusToString(status).c_str());
+        ALOGE("Could not retrieve current multi-touch slot index");
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
index 2b82ddf..4cf9243 100644
--- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
@@ -26,13 +26,13 @@
 }
 
 void SingleTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) {
-    mAbsX = deviceContext.getAbsoluteAxisValue(ABS_X);
-    mAbsY = deviceContext.getAbsoluteAxisValue(ABS_Y);
-    mAbsPressure = deviceContext.getAbsoluteAxisValue(ABS_PRESSURE);
-    mAbsToolWidth = deviceContext.getAbsoluteAxisValue(ABS_TOOL_WIDTH);
-    mAbsDistance = deviceContext.getAbsoluteAxisValue(ABS_DISTANCE);
-    mAbsTiltX = deviceContext.getAbsoluteAxisValue(ABS_TILT_X);
-    mAbsTiltY = deviceContext.getAbsoluteAxisValue(ABS_TILT_Y);
+    mAbsX = deviceContext.getAbsoluteAxisValue(ABS_X).value_or(0);
+    mAbsY = deviceContext.getAbsoluteAxisValue(ABS_Y).value_or(0);
+    mAbsPressure = deviceContext.getAbsoluteAxisValue(ABS_PRESSURE).value_or(0);
+    mAbsToolWidth = deviceContext.getAbsoluteAxisValue(ABS_TOOL_WIDTH).value_or(0);
+    mAbsDistance = deviceContext.getAbsoluteAxisValue(ABS_DISTANCE).value_or(0);
+    mAbsTiltX = deviceContext.getAbsoluteAxisValue(ABS_TILT_X).value_or(0);
+    mAbsTiltY = deviceContext.getAbsoluteAxisValue(ABS_TILT_Y).value_or(0);
 }
 
 void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index cf0d46a..bddf43e 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -76,6 +76,7 @@
         "PointerChoreographer_test.cpp",
         "PreferStylusOverTouch_test.cpp",
         "PropertyProvider_test.cpp",
+        "RotaryEncoderInputMapper_test.cpp",
         "SlopController_test.cpp",
         "SyncQueue_test.cpp",
         "TimerProvider_test.cpp",
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index daa000f..12736c8 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -16,6 +16,8 @@
 
 #include "FakeEventHub.h"
 
+#include <optional>
+
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
@@ -263,18 +265,16 @@
     return device->configuration;
 }
 
-status_t FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                           RawAbsoluteAxisInfo* outAxisInfo) const {
+std::optional<RawAbsoluteAxisInfo> FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId,
+                                                                     int axis) const {
     Device* device = getDevice(deviceId);
     if (device) {
         ssize_t index = device->absoluteAxes.indexOfKey(axis);
         if (index >= 0) {
-            *outAxisInfo = device->absoluteAxes.valueAt(index);
-            return OK;
+            return device->absoluteAxes.valueAt(index);
         }
     }
-    outAxisInfo->clear();
-    return -1;
+    return std::nullopt;
 }
 
 bool FakeEventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -417,18 +417,15 @@
     return AKEY_STATE_UNKNOWN;
 }
 
-status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                            int32_t* outValue) const {
+std::optional<int32_t> FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const {
     Device* device = getDevice(deviceId);
     if (device) {
         ssize_t index = device->absoluteAxisValue.indexOfKey(axis);
         if (index >= 0) {
-            *outValue = device->absoluteAxisValue.valueAt(index);
-            return OK;
+            return device->absoluteAxisValue.valueAt(index);
         }
     }
-    *outValue = 0;
-    return -1;
+    return std::nullopt;
 }
 
 void FakeEventHub::setMtSlotValues(int32_t deviceId, int32_t axis,
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index f07b344..c2c875f 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -168,8 +168,8 @@
     InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override;
     int32_t getDeviceControllerNumber(int32_t) const override;
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override;
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override;
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override;
     bool hasRelativeAxis(int32_t deviceId, int axis) const override;
     bool hasInputProperty(int32_t, int) const override;
     bool hasMscEvent(int32_t deviceId, int mscEvent) const override final;
@@ -187,7 +187,7 @@
     std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override;
     int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override;
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override;
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override;
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const override;
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
 
     // Return true if the device has non-empty key layout.
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index d2cb0ac..6099c91 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -16,6 +16,7 @@
 
 #include "FakeInputReaderPolicy.h"
 
+#include <android-base/properties.h>
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 
@@ -24,6 +25,12 @@
 
 namespace android {
 
+namespace {
+
+static const int HW_TIMEOUT_MULTIPLIER = base::GetIntProperty("ro.hw_timeout_multiplier", 1);
+
+} // namespace
+
 void FakeInputReaderPolicy::assertInputDevicesChanged() {
     waitForInputDevices([](bool devicesChanged) {
         if (!devicesChanged) {
@@ -241,9 +248,11 @@
     base::ScopedLockAssertion assumeLocked(mLock);
 
     const bool devicesChanged =
-            mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
-                return mInputDevicesChanged;
-            });
+            mDevicesChangedCondition.wait_for(lock,
+                                              ADD_INPUT_DEVICE_TIMEOUT * HW_TIMEOUT_MULTIPLIER,
+                                              [this]() REQUIRES(mLock) {
+                                                  return mInputDevicesChanged;
+                                              });
     ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
     mInputDevicesChanged = false;
 }
diff --git a/services/inputflinger/tests/HardwareProperties_test.cpp b/services/inputflinger/tests/HardwareProperties_test.cpp
index 8dfa8c8..643fab6 100644
--- a/services/inputflinger/tests/HardwareProperties_test.cpp
+++ b/services/inputflinger/tests/HardwareProperties_test.cpp
@@ -48,24 +48,20 @@
     static constexpr int32_t EVENTHUB_ID = 1;
 
     void setupValidAxis(int axis, int32_t min, int32_t max, int32_t resolution) {
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
-                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                    outAxisInfo->valid = true;
-                    outAxisInfo->minValue = min;
-                    outAxisInfo->maxValue = max;
-                    outAxisInfo->flat = 0;
-                    outAxisInfo->fuzz = 0;
-                    outAxisInfo->resolution = resolution;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+                .WillRepeatedly(Return(std::optional<RawAbsoluteAxisInfo>{{
+                        .valid = true,
+                        .minValue = min,
+                        .maxValue = max,
+                        .flat = 0,
+                        .fuzz = 0,
+                        .resolution = resolution,
+                }}));
     }
 
     void setupInvalidAxis(int axis) {
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
-                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                    outAxisInfo->valid = false;
-                    return -1;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+                .WillRepeatedly(Return(std::nullopt));
     }
 
     void setProperty(int property, bool value) {
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index b5c9232..19bc5be 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -57,16 +57,16 @@
 
 void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
                                     int32_t resolution) {
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, _))
-            .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                outAxisInfo->valid = valid;
-                outAxisInfo->minValue = min;
-                outAxisInfo->maxValue = max;
-                outAxisInfo->flat = 0;
-                outAxisInfo->fuzz = 0;
-                outAxisInfo->resolution = resolution;
-                return valid ? OK : -1;
-            });
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+            .WillRepeatedly(Return(valid ? std::optional<RawAbsoluteAxisInfo>{{
+                                                   .valid = true,
+                                                   .minValue = min,
+                                                   .maxValue = max,
+                                                   .flat = 0,
+                                                   .fuzz = 0,
+                                                   .resolution = resolution,
+                                           }}
+                                         : std::nullopt));
 }
 
 void InputMapperUnitTest::expectScanCodes(bool present, std::set<int> scanCodes) {
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index fe238f3..fa9d263 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -6245,7 +6245,7 @@
     SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) {
@@ -6268,7 +6268,7 @@
                                InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/);
 
     // Check whether device type update was successful.
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mDevice->getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) {
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 16d3193..bacc6d4 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -91,8 +91,8 @@
     MOCK_METHOD(InputDeviceIdentifier, getDeviceIdentifier, (int32_t deviceId), (const));
     MOCK_METHOD(int32_t, getDeviceControllerNumber, (int32_t deviceId), (const));
     MOCK_METHOD(std::optional<PropertyMap>, getConfiguration, (int32_t deviceId), (const));
-    MOCK_METHOD(status_t, getAbsoluteAxisInfo,
-                (int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo), (const));
+    MOCK_METHOD(std::optional<RawAbsoluteAxisInfo>, getAbsoluteAxisInfo,
+                (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasRelativeAxis, (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasInputProperty, (int32_t deviceId, int property), (const));
     MOCK_METHOD(bool, hasMscEvent, (int32_t deviceId, int mscEvent), (const));
@@ -131,7 +131,7 @@
     MOCK_METHOD(int32_t, getKeyCodeState, (int32_t deviceId, int32_t keyCode), (const, override));
     MOCK_METHOD(int32_t, getSwitchState, (int32_t deviceId, int32_t sw), (const, override));
 
-    MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue),
+    MOCK_METHOD(std::optional<int32_t>, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis),
                 (const, override));
     MOCK_METHOD(base::Result<std::vector<int32_t>>, getMtSlotValues,
                 (int32_t deviceId, int32_t axis, size_t slotCount), (const, override));
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
index b5f8971..d4d3c38 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -99,11 +99,8 @@
         setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
         // reset current slot at the beginning
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
-                .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) {
-                    *outValue = 0;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+                .WillRepeatedly(Return(0));
 
         // mark all slots not in use
         mockSlotValues({});
@@ -211,11 +208,8 @@
     const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
 
     // On buffer overflow mapper will be reset and MT slots data will be repopulated
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
-            .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) {
-                *outValue = 1;
-                return OK;
-            });
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+            .WillRepeatedly(Return(1));
 
     mockSlotValues(
             {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
new file mode 100644
index 0000000..94cfc32
--- /dev/null
+++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
@@ -0,0 +1,169 @@
+/*
+ * 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 "RotaryEncoderInputMapper.h"
+
+#include <list>
+#include <string>
+#include <tuple>
+#include <variant>
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/DisplayViewport.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <utils/Timers.h>
+
+#include "InputMapperTest.h"
+#include "InputReaderBase.h"
+#include "InterfaceMocks.h"
+#include "NotifyArgs.h"
+#include "TestEventMatchers.h"
+#include "ui/Rotation.h"
+
+#define TAG "RotaryEncoderInputMapper_test"
+
+namespace android {
+
+using testing::AllOf;
+using testing::Return;
+using testing::VariantWith;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+
+namespace {
+
+DisplayViewport createViewport() {
+    DisplayViewport v;
+    v.orientation = ui::Rotation::Rotation0;
+    v.logicalRight = DISPLAY_HEIGHT;
+    v.logicalBottom = DISPLAY_WIDTH;
+    v.physicalRight = DISPLAY_HEIGHT;
+    v.physicalBottom = DISPLAY_WIDTH;
+    v.deviceWidth = DISPLAY_HEIGHT;
+    v.deviceHeight = DISPLAY_WIDTH;
+    v.isActive = true;
+    return v;
+}
+
+DisplayViewport createPrimaryViewport() {
+    DisplayViewport v = createViewport();
+    v.displayId = DISPLAY_ID;
+    v.uniqueId = "local:1";
+    return v;
+}
+
+DisplayViewport createSecondaryViewport() {
+    DisplayViewport v = createViewport();
+    v.displayId = SECONDARY_DISPLAY_ID;
+    v.uniqueId = "local:2";
+    v.type = ViewportType::EXTERNAL;
+    return v;
+}
+
+/**
+ * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper.
+ *
+ * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates
+ * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input
+ * device doesn't set its associated viewport when it's configured.
+ *
+ * TODO(b/319217713): work out a way to avoid this fake.
+ */
+class ViewportFakingInputDeviceContext : public InputDeviceContext {
+public:
+    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
+                                     std::optional<DisplayViewport> viewport)
+          : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {}
+
+    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId)
+          : ViewportFakingInputDeviceContext(device, eventHubId, createPrimaryViewport()) {}
+
+    std::optional<DisplayViewport> getAssociatedViewport() const override {
+        return mAssociatedViewport;
+    }
+
+    void setViewport(const std::optional<DisplayViewport>& viewport) {
+        mAssociatedViewport = viewport;
+    }
+
+private:
+    std::optional<DisplayViewport> mAssociatedViewport;
+};
+
+} // namespace
+
+/**
+ * Unit tests for RotaryEncoderInputMapper.
+ */
+class RotaryEncoderInputMapperTest : public InputMapperUnitTest {
+protected:
+    void SetUp() override { SetUpWithBus(BUS_USB); }
+    void SetUpWithBus(int bus) override {
+        InputMapperUnitTest::SetUpWithBus(bus);
+
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL))
+                .WillRepeatedly(Return(true));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
+                .WillRepeatedly(Return(false));
+    }
+};
+
+TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdWithAssociatedViewport) {
+    DisplayViewport primaryViewport = createPrimaryViewport();
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+
+    // Set up the secondary display as the associated viewport of the mapper.
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(deviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+    // Ensure input events are generated for the secondary display.
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                              WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithDisplayId(SECONDARY_DISPLAY_ID)))));
+}
+
+TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdNoAssociatedViewport) {
+    // Set up the default display.
+    mFakePolicy->clearViewports();
+    mFakePolicy->addDisplayViewport(createPrimaryViewport());
+
+    // Set up the mapper with no associated viewport.
+    createDevice();
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
+
+    // Ensure input events are generated without display ID
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                              WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithDisplayId(ui::LogicalDisplayId::INVALID)))));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h
index ad48b0f..082bbb8 100644
--- a/services/inputflinger/tests/TestConstants.h
+++ b/services/inputflinger/tests/TestConstants.h
@@ -24,6 +24,9 @@
 
 using std::chrono_literals::operator""ms;
 
+// Timeout for waiting for an input device to be added and processed
+static constexpr std::chrono::duration ADD_INPUT_DEVICE_TIMEOUT = 500ms;
+
 // Timeout for waiting for an expected event
 static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
 
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 2b62dd1..1afb4f0 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -103,11 +103,8 @@
         setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
         setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_))
-                .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) {
-                    *outValue = 0;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+                .WillRepeatedly(Return(0));
         EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, testing::_, testing::_))
                 .WillRepeatedly([]() -> base::Result<std::vector<int32_t>> {
                     return base::ResultError("Axis not supported", NAME_NOT_FOUND);
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index ff425dd..6dea540 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -17,6 +17,7 @@
 
 #include <map>
 #include <memory>
+#include <optional>
 
 #include <EventHub.h>
 #include <InputDevice.h>
@@ -119,16 +120,26 @@
     void setAbsoluteAxisInfo(int32_t deviceId, int axis, const RawAbsoluteAxisInfo& axisInfo) {
         mAxes[deviceId][axis] = axisInfo;
     }
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override {
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override {
         if (auto deviceAxesIt = mAxes.find(deviceId); deviceAxesIt != mAxes.end()) {
             const std::map<int, RawAbsoluteAxisInfo>& deviceAxes = deviceAxesIt->second;
             if (auto axisInfoIt = deviceAxes.find(axis); axisInfoIt != deviceAxes.end()) {
-                *outAxisInfo = axisInfoIt->second;
-                return OK;
+                return axisInfoIt->second;
             }
         }
-        return mFdp->ConsumeIntegral<status_t>();
+        if (mFdp->ConsumeBool()) {
+            return std::optional<RawAbsoluteAxisInfo>({
+                    .valid = mFdp->ConsumeBool(),
+                    .minValue = mFdp->ConsumeIntegral<int32_t>(),
+                    .maxValue = mFdp->ConsumeIntegral<int32_t>(),
+                    .flat = mFdp->ConsumeIntegral<int32_t>(),
+                    .fuzz = mFdp->ConsumeIntegral<int32_t>(),
+                    .resolution = mFdp->ConsumeIntegral<int32_t>(),
+            });
+        } else {
+            return std::nullopt;
+        }
     }
     bool hasRelativeAxis(int32_t deviceId, int axis) const override { return mFdp->ConsumeBool(); }
     bool hasInputProperty(int32_t deviceId, int property) const override {
@@ -197,9 +208,12 @@
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override {
         return mFdp->ConsumeIntegral<int32_t>();
     }
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override {
-        return mFdp->ConsumeIntegral<status_t>();
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const override {
+        if (mFdp->ConsumeBool()) {
+            return mFdp->ConsumeIntegral<int32_t>();
+        } else {
+            return std::nullopt;
+        }
     }
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override {
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index f6f104e..b2dc89b 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -26,6 +26,11 @@
         "libfakeservicemanager",
         "libcutils",
         "liblog",
+        "libsensor_flags_c_lib",
+    ],
+    shared_libs: [
+        "libaconfig_storage_read_api_cc",
+        "server_configurable_flags",
     ],
     srcs: [
         "fuzzer.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 5b9a102..b40aea4 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1102,7 +1102,7 @@
 }
 
 ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync(bool flushEvenWhenDisabled) {
-    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([&]() {
+    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([this, flushEvenWhenDisabled]() {
                presentFrameAndReleaseLayers(flushEvenWhenDisabled);
                return true;
            })))
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ca53a0d..a4ffd51 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -317,6 +317,21 @@
     }
 }
 
+void updateMetadataAndGameMode(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+                               const LayerSnapshotBuilder::Args& args,
+                               const LayerSnapshot& parentSnapshot) {
+    if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
+        snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
+                ? requested.gameMode
+                : parentSnapshot.gameMode;
+    }
+    updateMetadata(snapshot, requested, args);
+    if (args.includeMetadata) {
+        snapshot.layerMetadata = parentSnapshot.layerMetadata;
+        snapshot.layerMetadata.merge(requested.metadata);
+    }
+}
+
 void clearChanges(LayerSnapshot& snapshot) {
     snapshot.changes.clear();
     snapshot.clientChanges = 0;
@@ -762,6 +777,11 @@
                                  RequestedLayerState::Changes::Input)) {
             updateInput(snapshot, requested, parentSnapshot, path, args);
         }
+        if (forceUpdate ||
+            (args.includeMetadata &&
+             snapshot.changes.test(RequestedLayerState::Changes::Metadata))) {
+            updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot);
+        }
         return;
     }
 
@@ -801,15 +821,8 @@
         }
     }
 
-    if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
-        snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
-                ? requested.gameMode
-                : parentSnapshot.gameMode;
-        updateMetadata(snapshot, requested, args);
-        if (args.includeMetadata) {
-            snapshot.layerMetadata = parentSnapshot.layerMetadata;
-            snapshot.layerMetadata.merge(requested.metadata);
-        }
+    if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::Metadata)) {
+        updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot);
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eFixedTransformHintChanged ||
@@ -1178,6 +1191,15 @@
     }
 }
 
+void LayerSnapshotBuilder::forEachSnapshot(const Visitor& visitor,
+                                           const ConstPredicate& predicate) {
+    for (int i = 0; i < mNumInterestingSnapshots; i++) {
+        std::unique_ptr<LayerSnapshot>& snapshot = mSnapshots.at((size_t)i);
+        if (!predicate(*snapshot)) continue;
+        visitor(snapshot);
+    }
+}
+
 void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const {
     for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) {
         LayerSnapshot& snapshot = *mSnapshots[(size_t)i];
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index 1cec018..dbbad76 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -86,6 +86,11 @@
     // Visit each visible snapshot in z-order and move the snapshot if needed
     void forEachVisibleSnapshot(const Visitor& visitor);
 
+    typedef std::function<bool(const LayerSnapshot& snapshot)> ConstPredicate;
+    // Visit each snapshot that satisfies the predicate and move the snapshot if needed with visible
+    // snapshots in z-order
+    void forEachSnapshot(const Visitor& visitor, const ConstPredicate& predicate);
+
     // Visit each snapshot interesting to input reverse z-order
     void forEachInputSnapshot(const ConstVisitor& visitor) const;
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 3e8d740..c3c2999 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -328,6 +328,7 @@
                 changes |= RequestedLayerState::Changes::GameMode;
             }
         }
+        changes |= RequestedLayerState::Changes::Metadata;
     }
     if (clientState.what & layer_state_t::eFrameRateChanged) {
         const auto compatibility =
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 846727b..be5ffbc 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -1641,7 +1641,7 @@
         case FrameRateCategory::Normal:
             return FpsRange{60_Hz, 120_Hz};
         case FrameRateCategory::Low:
-            return FpsRange{30_Hz, 120_Hz};
+            return FpsRange{48_Hz, 120_Hz};
         case FrameRateCategory::HighHint:
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 60681a2..e669261 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -123,19 +123,22 @@
     promotePacesetterDisplay(pacesetterIdOpt);
 }
 
-void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+                                PhysicalDisplayId activeDisplayId) {
     auto schedulePtr =
             std::make_shared<VsyncSchedule>(selectorPtr->getActiveMode().modePtr, mFeatures,
                                             [this](PhysicalDisplayId id, bool enable) {
                                                 onHardwareVsyncRequest(id, enable);
                                             });
 
-    registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
+    registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr),
+                            activeDisplayId);
 }
 
 void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId,
                                         RefreshRateSelectorPtr selectorPtr,
-                                        VsyncSchedulePtr schedulePtr) {
+                                        VsyncSchedulePtr schedulePtr,
+                                        PhysicalDisplayId activeDisplayId) {
     demotePacesetterDisplay();
 
     auto [pacesetterVsyncSchedule, isNew] = [&]() FTL_FAKE_GUARD(kMainThreadContext) {
@@ -145,7 +148,7 @@
                                                        std::move(schedulePtr), mFeatures)
                                    .second;
 
-        return std::make_pair(promotePacesetterDisplayLocked(), isNew);
+        return std::make_pair(promotePacesetterDisplayLocked(activeDisplayId), isNew);
     }();
 
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
@@ -158,7 +161,9 @@
     dispatchHotplug(displayId, Hotplug::Connected);
 }
 
-void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+void Scheduler::unregisterDisplay(PhysicalDisplayId displayId, PhysicalDisplayId activeDisplayId) {
+    LOG_ALWAYS_FATAL_IF(displayId == activeDisplayId, "Cannot unregister the active display!");
+
     dispatchHotplug(displayId, Hotplug::Disconnected);
 
     demotePacesetterDisplay();
@@ -173,7 +178,7 @@
         // headless virtual display.)
         LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!");
 
-        pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
+        pacesetterVsyncSchedule = promotePacesetterDisplayLocked(activeDisplayId);
     }
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
 }
@@ -1113,8 +1118,10 @@
                                                 .emitEvent = !choice.consideredSignals.idle});
         }
 
-        frameRateOverridesChanged = updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
-
+        if (!FlagManager::getInstance().vrr_bugfix_dropped_frame()) {
+            frameRateOverridesChanged =
+                    updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
+        }
         if (mPolicy.modeOpt != modeOpt) {
             mPolicy.modeOpt = modeOpt;
             refreshRateChanged = true;
@@ -1129,6 +1136,12 @@
     if (refreshRateChanged) {
         mSchedulerCallback.requestDisplayModes(std::move(modeRequests));
     }
+
+    if (FlagManager::getInstance().vrr_bugfix_dropped_frame()) {
+        std::scoped_lock lock(mPolicyLock);
+        frameRateOverridesChanged =
+                updateFrameRateOverridesLocked(consideredSignals, mPolicy.modeOpt->fps);
+    }
     if (frameRateOverridesChanged) {
         mSchedulerCallback.triggerOnFrameRateOverridesChanged();
     }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ccaa05f..1a4aa79 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -101,9 +101,16 @@
     using ConstVsyncSchedulePtr = std::shared_ptr<const VsyncSchedule>;
     using VsyncSchedulePtr = std::shared_ptr<VsyncSchedule>;
 
-    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext)
+    // After registration/unregistration, `activeDisplayId` is promoted to pacesetter. Note that the
+    // active display is never unregistered, since hotplug disconnect never happens for activatable
+    // displays, i.e. a foldable's internal displays or otherwise the (internal or external) primary
+    // display.
+    // TODO: b/255635821 - Remove active display parameters.
+    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr,
+                         PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext)
             EXCLUDES(mDisplayLock);
-    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void unregisterDisplay(PhysicalDisplayId, PhysicalDisplayId activeDisplayId)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     void run();
 
@@ -390,8 +397,9 @@
     // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
     void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock);
 
-    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr)
-            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr,
+                                 PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
 
     struct Policy;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index dd3c4b0..0644aca 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -360,7 +360,11 @@
     purgeTimelines(now);
 
     for (auto& timeline : mTimelines) {
-        if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) {
+        const bool isVsyncValid = FlagManager::getInstance().vrr_bugfix_24q4()
+                ? timeline.isWithin(TimePoint::fromNs(vsync)) ==
+                        VsyncTimeline::VsyncOnTimeline::Unique
+                : timeline.validUntil() && timeline.validUntil()->ns() > vsync;
+        if (isVsyncValid) {
             return timeline.isVSyncInPhase(model, vsync, frameRate);
         }
     }
@@ -395,8 +399,14 @@
         mLastCommittedVsync = TimePoint::fromNs(0);
 
     } else {
-        mTimelines.back().freeze(
-                TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+        if (FlagManager::getInstance().vrr_bugfix_24q4()) {
+            // We need to freeze the timeline at the committed vsync so that we don't
+            // overshoot the deadline.
+            mTimelines.back().freeze(mLastCommittedVsync);
+        } else {
+            mTimelines.back().freeze(
+                    TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+        }
     }
     mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate);
     purgeTimelines(TimePoint::fromNs(mClock->now()));
@@ -611,7 +621,10 @@
 
     while (mTimelines.size() > 1) {
         const auto validUntilOpt = mTimelines.front().validUntil();
-        if (validUntilOpt && *validUntilOpt < now) {
+        const bool isTimelineOutDated = FlagManager::getInstance().vrr_bugfix_24q4()
+                ? mTimelines.front().isWithin(now) == VsyncTimeline::VsyncOnTimeline::Outside
+                : validUntilOpt && *validUntilOpt < now;
+        if (isTimelineOutDated) {
             mTimelines.pop_front();
         } else {
             break;
@@ -660,9 +673,12 @@
             vsyncTime += missedVsync.fixup.ns();
             ATRACE_FORMAT_INSTANT("lastFrameMissed");
         } else if (mightBackpressure && lastVsyncOpt) {
-            // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
-            // first before trying to use it.
-            lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+            if (!FlagManager::getInstance().vrr_bugfix_24q4()) {
+                // lastVsyncOpt does not need to be corrected with the new rate, and
+                // it should be used as is to avoid skipping a frame when changing rates are
+                // aligned at vsync time.
+                lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+            }
             const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
             if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
                 // avoid a duplicate vsync
@@ -681,7 +697,10 @@
     }
 
     ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
-    if (mValidUntil && vsyncTime > mValidUntil->ns()) {
+    const bool isVsyncInvalid = FlagManager::getInstance().vrr_bugfix_24q4()
+            ? isWithin(TimePoint::fromNs(vsyncTime)) == VsyncOnTimeline::Outside
+            : mValidUntil && vsyncTime > mValidUntil->ns();
+    if (isVsyncInvalid) {
         ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
                               static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
         return std::nullopt;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 8ce61d8..66a7d71 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -106,6 +106,24 @@
         void shiftVsyncSequence(Duration phase);
         void setRenderRate(std::optional<Fps> renderRateOpt) { mRenderRateOpt = renderRateOpt; }
 
+        enum class VsyncOnTimeline {
+            Unique,  // Within timeline, not shared with next timeline.
+            Shared,  // Within timeline, shared with next timeline.
+            Outside, // Outside of the timeline.
+        };
+        VsyncOnTimeline isWithin(TimePoint vsync) {
+            const auto threshold = mIdealPeriod.ns() / 2;
+            if (!mValidUntil || vsync.ns() < mValidUntil->ns() - threshold) {
+                // if mValidUntil is absent then timeline is not frozen and
+                // vsync should be unique to that timeline.
+                return VsyncOnTimeline::Unique;
+            }
+            if (vsync.ns() > mValidUntil->ns() + threshold) {
+                return VsyncOnTimeline::Outside;
+            }
+            return VsyncOnTimeline::Shared;
+        }
+
     private:
         nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync);
         VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 596ec12..fd4bd11 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -3855,7 +3855,8 @@
         ftl::FakeGuard guard(kMainThreadContext);
 
         // For hotplug reconnect, renew the registration since display modes have been reloaded.
-        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
+                                    mActiveDisplayId);
     }
 
     if (display->isVirtual()) {
@@ -3894,7 +3895,7 @@
         if (display->isVirtual()) {
             releaseVirtualDisplay(display->getVirtualId());
         } else {
-            mScheduler->unregisterDisplay(display->getPhysicalId());
+            mScheduler->unregisterDisplay(display->getPhysicalId(), mActiveDisplayId);
         }
     }
 
@@ -4506,7 +4507,8 @@
                                              getFactory(), activeRefreshRate, *mTimeStats);
 
     // The pacesetter must be registered before EventThread creation below.
-    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
+                                mActiveDisplayId);
     if (FlagManager::getInstance().vrr_config()) {
         mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps,
                                   /*applyImmediately*/ true);
@@ -5167,7 +5169,7 @@
 
 status_t SurfaceFlinger::setTransactionState(
         const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-        const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+        Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
         InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp,
         const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
         const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
@@ -5182,7 +5184,7 @@
         composerState.state.sanitize(permissions);
     }
 
-    for (DisplayState display : displays) {
+    for (DisplayState& display : displays) {
         display.sanitize(permissions);
     }
 
@@ -6389,15 +6391,23 @@
         return NO_ERROR;
     }
 
-    // Traversal of drawing state must happen on the main thread.
-    // Otherwise, SortedVector may have shared ownership during concurrent
-    // traversals, which can result in use-after-frees.
+    // Collect debug data from main thread
     std::string compositionLayers;
     mScheduler
             ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
                 dumpVisibleFrontEnd(compositionLayers);
             })
             .get();
+    // get window info listener data without the state lock
+    auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
+    compositionLayers.append("Window Infos:\n");
+    StringAppendF(&compositionLayers, "  max send vsync id: %" PRId64 "\n",
+                  ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
+    StringAppendF(&compositionLayers, "  max send delay (ns): %" PRId64 " ns\n",
+                  windowInfosDebug.maxSendDelayDuration);
+    StringAppendF(&compositionLayers, "  unsent messages: %zu\n",
+                  windowInfosDebug.pendingMessageCount);
+    compositionLayers.append("\n");
     dumpAll(args, compositionLayers, result);
     write(fd, result.c_str(), result.size());
     return NO_ERROR;
@@ -6980,15 +6990,6 @@
 
     result.append(mTimeStats->miniDump());
     result.append("\n");
-
-    result.append("Window Infos:\n");
-    auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
-    StringAppendF(&result, "  max send vsync id: %" PRId64 "\n",
-                  ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
-    StringAppendF(&result, "  max send delay (ns): %" PRId64 " ns\n",
-                  windowInfosDebug.maxSendDelayDuration);
-    StringAppendF(&result, "  unsent messages: %zu\n", windowInfosDebug.pendingMessageCount);
-    result.append("\n");
 }
 
 mat4 SurfaceFlinger::calculateColorMatrix(float saturation) {
@@ -9335,7 +9336,9 @@
     std::vector<std::pair<Layer*, LayerFE*>> layers;
     if (mLayerLifecycleManagerEnabled) {
         nsecs_t currentTime = systemTime();
-        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+        const bool needsMetadata = mCompositionEngine->getFeatureFlags().test(
+                compositionengine::Feature::kSnapshotLayerMetadata);
+        mLayerSnapshotBuilder.forEachSnapshot(
                 [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
                         kMainThreadContext) {
                     if (cursorOnly &&
@@ -9358,6 +9361,12 @@
                     layerFE->mSnapshot = std::move(snapshot);
                     refreshArgs.layers.push_back(layerFE);
                     layers.emplace_back(legacyLayer.get(), layerFE.get());
+                },
+                [needsMetadata](const frontend::LayerSnapshot& snapshot) {
+                    return snapshot.isVisible ||
+                            (needsMetadata &&
+                             snapshot.changes.test(
+                                     frontend::RequestedLayerState::Changes::Metadata));
                 });
     }
     if (!mLayerLifecycleManagerEnabled) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index ee541c4..7762bbe 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -559,7 +559,7 @@
     sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
     status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 2e3273c..a56bb51 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -136,6 +136,7 @@
     DUMP_READ_ONLY_FLAG(vulkan_renderengine);
     DUMP_READ_ONLY_FLAG(renderable_buffer_usage);
     DUMP_READ_ONLY_FLAG(vrr_bugfix_24q4);
+    DUMP_READ_ONLY_FLAG(vrr_bugfix_dropped_frame);
     DUMP_READ_ONLY_FLAG(restore_blur_step);
     DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro);
     DUMP_READ_ONLY_FLAG(protected_if_client);
@@ -242,6 +243,7 @@
 FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "")
 FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "")
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_24q4, "");
+FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_dropped_frame, "")
 FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
 FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite")
 FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index ab7a474..8799295 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -73,6 +73,7 @@
     bool screenshot_fence_preservation() const;
     bool vulkan_renderengine() const;
     bool vrr_bugfix_24q4() const;
+    bool vrr_bugfix_dropped_frame() const;
     bool renderable_buffer_usage() const;
     bool restore_blur_step() const;
     bool dont_skip_on_early_ro() const;
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index f4d4ee9..919ec17 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -136,4 +136,15 @@
   }
 } # vrr_bugfix_24q4
 
+flag {
+  name: "vrr_bugfix_dropped_frame"
+  namespace: "core_graphics"
+  description: "bug fix for VRR dropped frame"
+  bug: "343603085"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # vrr_bugfix_dropped_frame
+
 # IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index ebe11fb..d355e72 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -26,6 +26,7 @@
 #include <private/android_filesystem_config.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <ui/DisplayMode.h>
+#include <ui/DisplayState.h>
 #include <ui/DynamicDisplayInfo.h>
 #include <utils/String8.h>
 #include <functional>
@@ -276,7 +277,7 @@
 TEST_F(CredentialsTest, CaptureLayersTest) {
     setupBackgroundSurface();
     sp<GraphicBuffer> outBuffer;
-    std::function<status_t()> condition = [=]() {
+    std::function<status_t()> condition = [=, this]() {
         LayerCaptureArgs captureArgs;
         captureArgs.layerHandle = mBGSurfaceControl->getHandle();
         captureArgs.sourceCrop = {0, 0, 1, 1};
@@ -396,6 +397,56 @@
     }
 }
 
+TEST_F(CredentialsTest, DisplayTransactionPermissionTest) {
+    const auto display = getFirstDisplayToken();
+
+    ui::DisplayState displayState;
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    const ui::Rotation initialOrientation = displayState.orientation;
+
+    // Set display orientation from an untrusted process. This should fail silently.
+    {
+        UIDFaker f{AID_BIN};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation + ui::ROTATION_90,
+                                         layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+
+    // Verify that the display orientation did not change.
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation, displayState.orientation);
+
+    // Set display orientation from a trusted process.
+    {
+        UIDFaker f{AID_SYSTEM};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation + ui::ROTATION_90,
+                                         layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+
+    // Verify that the display orientation did change.
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation + ui::ROTATION_90, displayState.orientation);
+
+    // Reset orientation
+    {
+        UIDFaker f{AID_SYSTEM};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation, layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation, displayState.orientation);
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 8b9ac93..54d4659 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -291,13 +291,106 @@
     transactions.back().states.front().layerId = 1;
     transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
     mLifecycleManager.applyTransactions(transactions);
-    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::GameMode);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::GameMode | RequestedLayerState::Changes::Metadata);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
     EXPECT_EQ(static_cast<int32_t>(getSnapshot(1)->gameMode), 42);
     EXPECT_EQ(static_cast<int32_t>(getSnapshot(11)->gameMode), 42);
 }
 
+TEST_F(LayerSnapshotTest, UpdateMetadata) {
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+    // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly,
+    // and not using stale data.
+    transactions.back().states.front().state.metadata = LayerMetadata();
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789);
+
+    transactions.back().states.front().layerId = 1;
+    transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+
+    mLifecycleManager.applyTransactions(transactions);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Metadata);
+
+    // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = true,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    update(mSnapshotBuilder, args);
+
+    EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789);
+}
+
+TEST_F(LayerSnapshotTest, UpdateMetadataOfHiddenLayers) {
+    hideLayer(1);
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+    // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly,
+    // and not using stale data.
+    transactions.back().states.front().state.metadata = LayerMetadata();
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789);
+
+    transactions.back().states.front().layerId = 1;
+    transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+
+    mLifecycleManager.applyTransactions(transactions);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::Metadata | RequestedLayerState::Changes::Visibility |
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::AffectsChildren);
+
+    // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = true,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    update(mSnapshotBuilder, args);
+
+    EXPECT_EQ(static_cast<int64_t>(getSnapshot(1)->clientChanges),
+              layer_state_t::eMetadataChanged | layer_state_t::eFlagsChanged);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789);
+}
+
 TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) {
     // ROOT
     // ├── 1
@@ -1304,6 +1397,17 @@
     EXPECT_TRUE(foundInputLayer);
 }
 
+TEST_F(LayerSnapshotTest, ForEachSnapshotsWithPredicate) {
+    std::vector<uint32_t> visitedUniqueSequences;
+    mSnapshotBuilder.forEachSnapshot(
+            [&](const std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                visitedUniqueSequences.push_back(snapshot->uniqueSequence);
+            },
+            [](const frontend::LayerSnapshot& snapshot) { return snapshot.uniqueSequence == 111; });
+    EXPECT_EQ(visitedUniqueSequences.size(), 1u);
+    EXPECT_EQ(visitedUniqueSequences[0], 111u);
+}
+
 TEST_F(LayerSnapshotTest, canOccludePresentation) {
     setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation);
     LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index cf9a7d3..06c4e30 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1562,7 +1562,7 @@
             // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
             {0_Hz, FrameRateCategory::High, 90_Hz},
             {0_Hz, FrameRateCategory::Normal, 60_Hz},
-            {0_Hz, FrameRateCategory::Low, 30_Hz},
+            {0_Hz, FrameRateCategory::Low, 60_Hz},
             {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
 
             // Cases that have both desired frame rate and frame rate category requirements.
@@ -1609,6 +1609,77 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_120_vrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    // Device with VRR config mode
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+    };
+
+    // Prepare a table with the vote and the expected refresh rate
+    const std::initializer_list<Case> testCases = {
+            // Cases that only have frame rate category requirements, but no desired frame rate.
+            // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
+            {0_Hz, FrameRateCategory::High, 120_Hz},
+            {0_Hz, FrameRateCategory::Normal, 60_Hz},
+            {0_Hz, FrameRateCategory::Low, 48_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 120_Hz},
+
+            // Cases that have both desired frame rate and frame rate category requirements.
+            {24_Hz, FrameRateCategory::High, 120_Hz},
+            {30_Hz, FrameRateCategory::High, 120_Hz},
+            {12_Hz, FrameRateCategory::Normal, 60_Hz},
+            {24_Hz, FrameRateCategory::Low, 48_Hz},
+            {30_Hz, FrameRateCategory::NoPreference, 30_Hz},
+
+            // Cases that only have desired frame rate.
+            {30_Hz, FrameRateCategory::Default, 30_Hz},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+              to_string(testCase.desiredFrameRate).c_str(),
+              ftl::enum_string(testCase.frameRateCategory).c_str());
+
+        if (testCase.desiredFrameRate.isValid()) {
+            std::stringstream ss;
+            ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitDefault,
+                                      .desiredRefreshRate = testCase.desiredFrameRate,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers).fps)
+                << "Did not get expected frame rate for frameRate="
+                << to_string(testCase.desiredFrameRate)
+                << " category=" << ftl::enum_string(testCase.frameRateCategory);
+    }
+}
+
 TEST_P(RefreshRateSelectorTest,
        getBestFrameRateMode_withFrameRateCategoryMultiLayers_30_60_90_120) {
     auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
@@ -2140,14 +2211,14 @@
             // These layers may switch modes because smoothSwitchOnly=false.
             {FrameRateCategory::Default, false, 120_Hz, kModeId120},
             {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, false, 30_Hz, kModeId60},
+            {FrameRateCategory::Low, false, 60_Hz, kModeId60},
             {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
             {FrameRateCategory::High, false, 120_Hz, kModeId120},
 
             // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
             // active mode (120Hz).
             {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 120_Hz, kModeId120},
             {FrameRateCategory::Normal, true, 120_Hz, kModeId120},
             {FrameRateCategory::High, true, 120_Hz, kModeId120},
     };
@@ -2207,13 +2278,13 @@
             {FrameRateCategory::Default, false, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
             {FrameRateCategory::NoPreference, false, 120_Hz},
-            {FrameRateCategory::Low, false, 30_Hz},
+            {FrameRateCategory::Low, false, 48_Hz},
             {FrameRateCategory::Normal, false, 60_Hz},
             {FrameRateCategory::High, false, 120_Hz},
             {FrameRateCategory::Default, true, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
             {FrameRateCategory::NoPreference, true, 120_Hz},
-            {FrameRateCategory::Low, true, 30_Hz},
+            {FrameRateCategory::Low, true, 48_Hz},
             {FrameRateCategory::Normal, true, 60_Hz},
             {FrameRateCategory::High, true, 120_Hz},
     };
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 4fb0690..fc54a8b 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -343,12 +343,15 @@
 }
 
 TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+    constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1;
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
-                                                                      kDisplay1Mode60->getId()));
+                                                                      kDisplay1Mode60->getId()),
+                                kActiveDisplayId);
     mScheduler->registerDisplay(kDisplayId2,
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
-                                                                      kDisplay2Mode60->getId()));
+                                                                      kDisplay2Mode60->getId()),
+                                kActiveDisplayId);
 
     mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
     mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON);
@@ -411,10 +414,10 @@
     {
         // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120
         // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz.
-        mScheduler
-                ->registerDisplay(kDisplayId3,
-                                  std::make_shared<RefreshRateSelector>(kDisplay3Modes,
-                                                                        kDisplay3Mode60->getId()));
+        mScheduler->registerDisplay(kDisplayId3,
+                                    std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+                                                                          kDisplay3Mode60->getId()),
+                                    kActiveDisplayId);
         mScheduler->setDisplayPowerMode(kDisplayId3, hal::PowerMode::ON);
 
         const GlobalSignals globalSignals = {.touch = true};
@@ -457,12 +460,15 @@
 }
 
 TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) {
+    constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1;
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
-                                                                      kDisplay1Mode60->getId()));
+                                                                      kDisplay1Mode60->getId()),
+                                kActiveDisplayId);
     mScheduler->registerDisplay(kDisplayId2,
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
-                                                                      kDisplay2Mode60->getId()));
+                                                                      kDisplay2Mode60->getId()),
+                                kActiveDisplayId);
 
     using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
 
@@ -585,7 +591,8 @@
                                 mFlinger.getTimeStats(),
                                 mSchedulerCallback};
 
-    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
+    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, std::nullopt,
+                              vrrTracker);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
     scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
     vrrTracker->addVsyncTimestamp(0);
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 198a5de..f063809 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -53,7 +53,7 @@
                       factory, selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
-                        std::move(tracker));
+                        std::move(tracker), displayId);
 
         ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
             // Execute task to prevent broken promise exception on destruction.
@@ -85,14 +85,16 @@
 
     void registerDisplay(
             PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+            std::optional<PhysicalDisplayId> activeDisplayIdOpt = {},
             std::shared_ptr<VSyncTracker> vsyncTracker = std::make_shared<mock::VSyncTracker>()) {
         registerDisplay(displayId, std::move(selectorPtr),
-                        std::make_unique<mock::VsyncController>(), vsyncTracker);
+                        std::make_unique<mock::VsyncController>(), vsyncTracker,
+                        activeDisplayIdOpt.value_or(displayId));
     }
 
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
                          std::unique_ptr<VsyncController> controller,
-                         std::shared_ptr<VSyncTracker> tracker) {
+                         std::shared_ptr<VSyncTracker> tracker, PhysicalDisplayId activeDisplayId) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr),
                                            std::shared_ptr<VsyncSchedule>(
@@ -101,16 +103,12 @@
                                                                              mock::VSyncDispatch>(),
                                                                      std::move(controller),
                                                                      mockRequestHardwareVsync
-                                                                             .AsStdFunction())));
+                                                                             .AsStdFunction())),
+                                           activeDisplayId);
     }
 
     testing::MockFunction<void(PhysicalDisplayId, bool)> mockRequestHardwareVsync;
 
-    void unregisterDisplay(PhysicalDisplayId displayId) {
-        ftl::FakeGuard guard(kMainThreadContext);
-        Scheduler::unregisterDisplay(displayId);
-    }
-
     void setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::setDisplayPowerMode(displayId, powerMode);
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 007383b..b5b36be 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -528,7 +528,7 @@
 
     auto setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
             bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -1118,8 +1118,8 @@
                 if (mFlinger.scheduler() && mSchedulerRegistration) {
                     mFlinger.scheduler()->registerDisplay(*physicalId,
                                                           mCreationArgs.refreshRateSelector,
-                                                          std::move(controller),
-                                                          std::move(tracker));
+                                                          std::move(controller), std::move(tracker),
+                                                          mFlinger.mutableActiveDisplayId());
                 }
             }
 
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 5109ea6..f36a8a6 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -673,6 +673,36 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
+TEST_F(VSyncPredictorTest, setRenderRateWhenRenderRateGoesDown) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::vrr_bugfix_24q4, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    Fps frameRate = Fps::fromPeriodNsecs(1000);
+    vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    frameRate = Fps::fromPeriodNsecs(3000);
+    vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false);
+    EXPECT_TRUE(vrrTracker.isVSyncInPhase(2000, frameRate));
+}
+
 TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
 
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index a9706bc..a3fe33e 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -178,7 +178,7 @@
     INIT_PROC(false, instance, GetPhysicalDeviceExternalSemaphoreProperties);
     INIT_PROC(false, instance, GetPhysicalDeviceExternalFenceProperties);
     INIT_PROC(false, instance, EnumeratePhysicalDeviceGroups);
-    INIT_PROC_EXT(KHR_swapchain, false, instance, GetPhysicalDevicePresentRectanglesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, instance, GetPhysicalDevicePresentRectanglesKHR);
     INIT_PROC(false, instance, GetPhysicalDeviceToolProperties);
     // clang-format on
 
@@ -325,9 +325,9 @@
     INIT_PROC(false, dev, BindBufferMemory2);
     INIT_PROC(false, dev, BindImageMemory2);
     INIT_PROC(false, dev, CmdSetDeviceMask);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, GetDeviceGroupPresentCapabilitiesKHR);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, GetDeviceGroupSurfacePresentModesKHR);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, AcquireNextImage2KHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, GetDeviceGroupPresentCapabilitiesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, GetDeviceGroupSurfacePresentModesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, AcquireNextImage2KHR);
     INIT_PROC(false, dev, CmdDispatchBase);
     INIT_PROC(false, dev, CreateDescriptorUpdateTemplate);
     INIT_PROC(false, dev, DestroyDescriptorUpdateTemplate);
@@ -659,6 +659,8 @@
         "vkGetDrmDisplayEXT",
         "vkGetInstanceProcAddr",
         "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT",
+        "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR",
+        "vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR",
         "vkGetPhysicalDeviceDisplayPlaneProperties2KHR",
         "vkGetPhysicalDeviceDisplayProperties2KHR",
         "vkGetPhysicalDeviceExternalBufferProperties",
@@ -703,6 +705,7 @@
         "vkGetPhysicalDeviceToolProperties",
         "vkGetPhysicalDeviceToolPropertiesEXT",
         "vkGetPhysicalDeviceVideoCapabilitiesKHR",
+        "vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR",
         "vkGetPhysicalDeviceVideoFormatPropertiesKHR",
         "vkSubmitDebugUtilsMessageEXT",
     };
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 9e67725..00e987f 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1473,23 +1473,14 @@
         image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
         image_format_properties.pNext = &ahb_usage;
 
-        if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) {
-            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2(
-                pdev, &image_format_info, &image_format_properties);
-            if (result != VK_SUCCESS) {
-                ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
-        }
-        else {
-            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR(
-                pdev, &image_format_info,
-                &image_format_properties);
-            if (result != VK_SUCCESS) {
-                ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d",
-                    result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
+        VkResult result = GetPhysicalDeviceImageFormatProperties2(
+            pdev, &image_format_info, &image_format_properties);
+        if (result != VK_SUCCESS) {
+            ALOGE(
+                "VkGetPhysicalDeviceImageFormatProperties2 for AHB usage "
+                "failed: %d",
+                result);
+            return VK_ERROR_SURFACE_LOST_KHR;
         }
 
         // Determine if USAGE_FRONT_BUFFER is needed.
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index 866c1b7..6b4cbad 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -351,9 +351,50 @@
                           'external', 'vulkan-headers', 'registry', 'vk.xml')
   tree = element_tree.parse(registry)
   root = tree.getroot()
+
+  for exts in root.iter('extensions'):
+    for extension in exts.iter('extension'):
+      if 'vulkan' not in extension.get('supported').split(','):
+        # ANDROID_native_buffer is a weird special case -- it's declared in vk.xml as
+        # disabled but we _do_ want to generate plumbing for it in the Android loader.
+        if extension.get('name') != 'VK_ANDROID_native_buffer':
+          print('skip extension disabled or not for vulkan: ' + extension.get('name'))
+          continue
+
+      apiversion = 'VK_VERSION_1_0'
+      if extension.tag == 'extension':
+        extname = extension.get('name')
+        if (extension.get('type') == 'instance' and
+            extension.get('promotedto') is not None):
+          promoted_inst_ext_dict[extname] = \
+              version_2_api_version(extension.get('promotedto'))
+        for req in extension.iter('require'):
+          if req.get('feature') is not None:
+            apiversion = req.get('feature')
+          for commands in req:
+            if commands.tag == 'command':
+              cmd_name = commands.get('name')
+              if cmd_name not in extension_dict:
+                extension_dict[cmd_name] = extname
+                version_dict[cmd_name] = apiversion
+
+  for feature in root.iter('feature'):
+    if 'vulkan' not in feature.get('api').split(','):
+      continue
+
+    apiversion = feature.get('name')
+    for req in feature.iter('require'):
+      for command in req:
+        if command.tag == 'command':
+          cmd_name = command.get('name')
+          version_dict[cmd_name] = apiversion
+
   for commands in root.iter('commands'):
     for command in commands:
       if command.tag == 'command':
+        if command.get('api') == 'vulkansc':
+          continue
+
         parameter_list = []
         protoset = False
         cmd_name = ''
@@ -361,12 +402,18 @@
         if command.get('alias') is not None:
           alias = command.get('alias')
           cmd_name = command.get('name')
-          alias_dict[cmd_name] = alias
-          command_list.append(cmd_name)
-          param_dict[cmd_name] = param_dict[alias].copy()
-          return_type_dict[cmd_name] = return_type_dict[alias]
+          # At this stage all valid commands have been added to the version
+          # dict so we can use it to filter valid commands
+          if cmd_name in version_dict:
+            alias_dict[cmd_name] = alias
+            command_list.append(cmd_name)
+            param_dict[cmd_name] = param_dict[alias].copy()
+            return_type_dict[cmd_name] = return_type_dict[alias]
         for params in command:
           if params.tag == 'param':
+            if params.get('api') == 'vulkansc':
+              # skip SC-only param variant
+              continue
             param_type = ''
             if params.text is not None and params.text.strip():
               param_type = params.text.strip() + ' '
@@ -387,39 +434,13 @@
                 cmd_type = c.text
               if c.tag == 'name':
                 cmd_name = c.text
-                protoset = True
-                command_list.append(cmd_name)
-                return_type_dict[cmd_name] = cmd_type
+                if cmd_name in version_dict:
+                  protoset = True
+                  command_list.append(cmd_name)
+                  return_type_dict[cmd_name] = cmd_type
         if protoset:
           param_dict[cmd_name] = parameter_list.copy()
 
-  for exts in root.iter('extensions'):
-    for extension in exts:
-      apiversion = 'VK_VERSION_1_0'
-      if extension.tag == 'extension':
-        extname = extension.get('name')
-        if (extension.get('type') == 'instance' and
-            extension.get('promotedto') is not None):
-          promoted_inst_ext_dict[extname] = \
-              version_2_api_version(extension.get('promotedto'))
-        for req in extension:
-          if req.get('feature') is not None:
-            apiversion = req.get('feature')
-          for commands in req:
-            if commands.tag == 'command':
-              cmd_name = commands.get('name')
-              if cmd_name not in extension_dict:
-                extension_dict[cmd_name] = extname
-                version_dict[cmd_name] = apiversion
-
-  for feature in root.iter('feature'):
-    apiversion = feature.get('name')
-    for req in feature:
-      for command in req:
-        if command.tag == 'command':
-          cmd_name = command.get('name')
-          if cmd_name in command_list:
-            version_dict[cmd_name] = apiversion
 
   version_code_set = set()
   for version in version_dict.values():
diff --git a/vulkan/scripts/null_generator.py b/vulkan/scripts/null_generator.py
index e9faef6..3624c1d 100644
--- a/vulkan/scripts/null_generator.py
+++ b/vulkan/scripts/null_generator.py
@@ -89,6 +89,8 @@
     f.write(gencom.copyright_and_warning(2015))
 
     f.write("""\
+#include <android/hardware_buffer.h>
+
 #include <algorithm>
 
 #include "null_driver_gen.h"