Merge "Add HEIC_ULTRAHDR image format" into main
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index b8e5ce1..17ae0aa 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -67,6 +67,8 @@
     MOCK_METHOD2(unregisterForNotifications, status_t(const String16&,
                                              const sp<LocalRegistrationCallback>&));
     MOCK_METHOD0(getServiceDebugInfo, std::vector<ServiceDebugInfo>());
+    MOCK_METHOD1(enableAddServiceCache, void(bool));
+
   protected:
     MOCK_METHOD0(onAsBinder, IBinder*());
 };
diff --git a/data/etc/android.hardware.bluetooth_le.channel_sounding.xml b/data/etc/android.hardware.bluetooth_le.channel_sounding.xml
new file mode 100644
index 0000000..f0ee5d9
--- /dev/null
+++ b/data/etc/android.hardware.bluetooth_le.channel_sounding.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Adds the feature indicating support for the BLE Channel sounding -->
+<permissions>
+    <feature name="android.hardware.bluetooth_le.channel_sounding" />
+</permissions>
diff --git a/data/etc/android.hardware.vulkan.version-1_4.xml b/data/etc/android.hardware.vulkan.version-1_4.xml
new file mode 100644
index 0000000..010f6da
--- /dev/null
+++ b/data/etc/android.hardware.vulkan.version-1_4.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device has a Vulkan
+     driver that supports API version 1.4 (0x00404000) -->
+<permissions>
+    <feature name="android.hardware.vulkan.version" version="4210688" />
+</permissions>
diff --git a/include/android/OWNERS b/include/android/OWNERS
index 64d7e54..5b014d0 100644
--- a/include/android/OWNERS
+++ b/include/android/OWNERS
@@ -5,4 +5,8 @@
 per-file input_transfer_token.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
 
 # CoGS
-per-file *luts* = file:platform/frameworks/base:/graphics/java/android/graphics/OWNERS
\ No newline at end of file
+per-file *luts* = file:platform/frameworks/base:/graphics/java/android/graphics/OWNERS
+
+# ADPF
+per-file performance_hint.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file thermal.h = file:platform/frameworks/base:/ADPF_OWNERS
diff --git a/include/android/display_luts.h b/include/android/display_luts.h
new file mode 100644
index 0000000..eae2bfd
--- /dev/null
+++ b/include/android/display_luts.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @addtogroup NativeActivity Native Activity
+ * @{
+ */
+
+/**
+ * @file display_luts.h
+ */
+#pragma once
+
+#include <inttypes.h>
+
+__BEGIN_DECLS
+
+/**
+ * The dimension of the lut
+ */
+enum ADisplayLuts_Dimension : int32_t {
+    ADISPLAYLUTS_ONE_DIMENSION = 1,
+    ADISPLAYLUTS_THREE_DIMENSION = 3,
+};
+typedef enum ADisplayLuts_Dimension ADisplayLuts_Dimension;
+
+/**
+ * The sampling key used by the lut
+ */
+enum ADisplayLuts_SamplingKey : int32_t {
+    ADISPLAYLUTS_SAMPLINGKEY_RGB = 0,
+    ADISPLAYLUTS_SAMPLINGKEY_MAX_RGB = 1,
+    ADISPLAYLUTS_SAMPLINGKEY_CIE_Y = 2,
+};
+typedef enum ADisplayLuts_SamplingKey ADisplayLuts_SamplingKey;
+
+/**
+ * Used to get and set display luts entry
+ */
+typedef struct ADisplayLutsEntry ADisplayLutsEntry;
+
+/**
+ * Used to get and set display luts
+ */
+typedef struct ADisplayLuts ADisplayLuts;
+
+/**
+ * Creates a \a ADisplayLutsEntry entry.
+ *
+ * You are responsible for mamanging the memory of the returned object.
+ * Always call \a ADisplayLutsEntry_destroy to release it after use.
+ *
+ * Functions like \a ADisplayLuts_set create their own copies of entries,
+ * therefore they don't take the ownership of the instance created by
+ * \a ADisplayLutsEntry_create.
+ *
+ * @param buffer The lut raw buffer. The function creates a copy of it and does not need to
+ * outlive the life of the ADisplayLutsEntry.
+ * @param length The length of lut raw buffer
+ * @param dimension The dimension of the lut. see \a ADisplayLuts_Dimension
+ * @param key The sampling key used by the lut. see \a ADisplayLuts_SamplingKey
+ * @return a new \a ADisplayLutsEntry instance.
+ */
+ADisplayLutsEntry* _Nonnull ADisplayLutsEntry_createEntry(float* _Nonnull buffer,
+    int32_t length, ADisplayLuts_Dimension dimension, ADisplayLuts_SamplingKey key)
+    __INTRODUCED_IN(36);
+
+/**
+ * Destroy the \a ADisplayLutsEntry instance.
+ *
+ * @param entry The entry to be destroyed
+ */
+void ADisplayLutsEntry_destroy(ADisplayLutsEntry* _Nullable entry) __INTRODUCED_IN(36);
+
+/**
+ * Gets the dimension of the entry.
+ *
+ * The function is only valid for the lifetime of the `entry`.
+ *
+ * @param entry The entry to query
+ * @return the dimension of the lut
+ */
+ADisplayLuts_Dimension ADisplayLutsEntry_getDimension(const ADisplayLutsEntry* _Nonnull entry)
+        __INTRODUCED_IN(36);
+
+/**
+ * Gets the size for each dimension of the entry.
+ *
+ * The function is only valid for the lifetime of the `entry`.
+ *
+ * @param entry The entry to query
+ * @return the size of each dimension of the lut
+ */
+int32_t ADisplayLutsEntry_getSize(const ADisplayLutsEntry* _Nonnull entry)
+        __INTRODUCED_IN(36);
+
+/**
+ * Gets the sampling key used by the entry.
+ *
+ * The function is only valid for the lifetime of the `entry`.
+ *
+ * @param entry The entry to query
+ * @return the sampling key used by the lut
+ */
+ADisplayLuts_SamplingKey ADisplayLutsEntry_getSamplingKey(const ADisplayLutsEntry* _Nonnull entry)
+        __INTRODUCED_IN(36);
+
+/**
+ * Gets the lut buffer of the entry.
+ *
+ * The function is only valid for the lifetime of the `entry`.
+ *
+ * @param entry The entry to query
+ * @return a pointer to the raw lut buffer
+ */
+const float* _Nonnull ADisplayLutsEntry_getBuffer(const ADisplayLutsEntry* _Nonnull entry)
+        __INTRODUCED_IN(36);
+
+/**
+ * Creates a \a ADisplayLuts instance.
+ *
+ * You are responsible for mamanging the memory of the returned object.
+ * Always call \a ADisplayLuts_destroy to release it after use. E.g., after calling
+ * the function \a ASurfaceTransaction_setLuts.
+ *
+ * @return a new \a ADisplayLuts instance
+ */
+ADisplayLuts* _Nonnull ADisplayLuts_create() __INTRODUCED_IN(36);
+
+/**
+ * Sets Luts in order to be applied.
+ *
+ * The function accepts a single 1D Lut, or a single 3D Lut or both 1D and 3D Lut in order.
+ * And the function will replace any previously set lut(s).
+ * If you want to clear the previously set lut(s), set `entries` to be nullptr,
+ * and `numEntries` will be internally ignored.
+ *
+ * @param luts the pointer of the \a ADisplayLuts instance
+ * @param entries the pointer of the array of lut entries to be applied
+ * @param numEntries the number of lut entries. The value should be either 1 or 2.
+ */
+void ADisplayLuts_setEntries(ADisplayLuts* _Nonnull luts,
+        ADisplayLutsEntry* _Nullable *_Nullable entries, int32_t numEntries) __INTRODUCED_IN(36);
+
+/**
+ * Deletes the \a ADisplayLuts instance.
+ *
+ * @param luts The luts to be destroyed
+ */
+void ADisplayLuts_destroy(ADisplayLuts* _Nullable luts) __INTRODUCED_IN(36);
+
+__END_DECLS
+
+/** @} */
\ No newline at end of file
diff --git a/include/android/looper.h b/include/android/looper.h
index d80a366..8cf1396 100644
--- a/include/android/looper.h
+++ b/include/android/looper.h
@@ -90,20 +90,23 @@
     ALOOPER_POLL_WAKE = -1,
 
     /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * One or more callbacks were executed.
+     * Result from ALooper_pollOnce():
+     * One or more callbacks were executed. The poll may also have been
+     * explicitly woken by ALooper_wake().
      */
     ALOOPER_POLL_CALLBACK = -2,
 
     /**
      * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * The timeout expired.
+     * The timeout expired. The poll may also have been explicitly woken by
+     * ALooper_wake().
      */
     ALOOPER_POLL_TIMEOUT = -3,
 
     /**
      * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * An error occurred.
+     * An error occurred. The poll may also have been explicitly woken by
+     * ALooper_wake(()).
      */
     ALOOPER_POLL_ERROR = -4,
 };
@@ -182,10 +185,13 @@
  * If the timeout is zero, returns immediately without blocking.
  * If the timeout is negative, waits indefinitely until an event appears.
  *
+ * **All return values may also imply ALOOPER_POLL_WAKE.** If you call this in a
+ * loop, you must treat all return values as if they also indicated
+ * ALOOPER_POLL_WAKE.
+ *
  * Returns ALOOPER_POLL_WAKE if the poll was awoken using ALooper_wake() before
  * the timeout expired and no callbacks were invoked and no other file
- * descriptors were ready. **All return values may also imply
- * ALOOPER_POLL_WAKE.**
+ * descriptors were ready.
  *
  * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked. The poll
  * may also have been explicitly woken by ALooper_wake.
@@ -214,9 +220,9 @@
  * data has been consumed or a file descriptor is available with no callback.
  * This function will never return ALOOPER_POLL_CALLBACK.
  *
- * This API cannot be used safely, but a safe alternative exists (see below). As
- * such, new builds will not be able to call this API and must migrate to the
- * safe API. Binary compatibility is preserved to support already-compiled apps.
+ * This API will not reliably respond to ALooper_wake. As such, this API is
+ * hidden and callers should migrate to ALooper_pollOnce. Binary compatibility
+ * is preserved to support already-compiled apps.
  *
  * \bug ALooper_pollAll will not wake in response to ALooper_wake calls if it
  * also handles another event at the same time.
@@ -235,6 +241,8 @@
  *
  * This method can be called on any thread.
  * This method returns immediately.
+ *
+ * \bug ALooper_pollAll will not reliably wake in response to ALooper_wake.
  */
 void ALooper_wake(ALooper* looper);
 
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 976c7d6..ca86c27 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -49,6 +49,7 @@
 #define ANDROID_NATIVE_PERFORMANCE_HINT_H
 
 #include <sys/cdefs.h>
+#include <jni.h>
 
 /******************************************************************
  *
@@ -75,6 +76,8 @@
 struct APerformanceHintManager;
 struct APerformanceHintSession;
 struct AWorkDuration;
+struct ANativeWindow;
+struct ASurfaceControl;
 
 /**
  * {@link AWorkDuration} is an opaque type that represents the breakdown of the
@@ -110,9 +113,24 @@
 typedef struct APerformanceHintManager APerformanceHintManager;
 
 /**
+ * An opaque type representing a handle to a performance hint session creation configuration.
+ * It is consumed by {@link APerformanceHint_createSessionUsingConfig}.
+ *
+ * A session creation config encapsulates the required information for a session.
+ * Additionally, the caller can set various settings for the session,
+ * to be passed during creation, streamlining the session setup process.
+ *
+ * The caller may reuse this object and modify the settings in it
+ * to create additional sessions.
+ *
+ */
+typedef struct ASessionCreationConfig ASessionCreationConfig;
+
+/**
  * An opaque type representing a handle to a performance hint session.
  * A session can only be acquired from a {@link APerformanceHintManager}
- * with {@link APerformanceHint_createSession}. It must be
+ * with {@link APerformanceHint_createSession}
+ * or {@link APerformanceHint_createSessionUsingConfig}. It must be
  * freed with {@link APerformanceHint_closeSession} after use.
  *
  * A Session represents a group of threads with an inter-related workload such that hints for
@@ -134,6 +152,9 @@
  */
 typedef struct APerformanceHintSession APerformanceHintSession;
 
+typedef struct ANativeWindow ANativeWindow;
+typedef struct ASurfaceControl ASurfaceControl;
+
 /**
   * Acquire an instance of the performance hint manager.
   *
@@ -152,7 +173,7 @@
  * @param size The size of the list of threadIds.
  * @param initialTargetWorkDurationNanos The target duration in nanoseconds for the new session.
  *     This must be positive if using the work duration API, or 0 otherwise.
- * @return APerformanceHintManager instance on success, nullptr on failure.
+ * @return APerformanceHintSession pointer on success, nullptr on failure.
  */
 APerformanceHintSession* _Nullable APerformanceHint_createSession(
         APerformanceHintManager* _Nonnull manager,
@@ -160,6 +181,20 @@
         int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
+ * Creates a session for the given set of threads that are graphics pipeline threads
+ * and set their initial target work duration.
+ *
+ * @param manager The performance hint manager instance.
+ * @param config The configuration struct containing required information
+ *        to create a session.
+ * @return APerformanceHintSession pointer on success, nullptr on failure.
+ */
+APerformanceHintSession* _Nullable APerformanceHint_createSessionUsingConfig(
+        APerformanceHintManager* _Nonnull manager,
+        ASessionCreationConfig* _Nonnull config)
+        __INTRODUCED_IN(36);
+
+/**
  * Get preferred update rate information for this device.
  *
  * @param manager The performance hint manager instance.
@@ -169,6 +204,15 @@
         APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
+ * Get maximum number of graphics pipieline threads per-app for this device.
+ *
+ * @param manager The performance hint manager instance.
+ * @return the maximum number of graphics pipeline threads supported by device.
+ */
+ int APerformanceHint_getMaxGraphicsPipelineThreadsCount(
+        APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(36);
+
+/**
  * Updates this session's target duration for each cycle of work.
  *
  * @param session The performance hint session instance to update.
@@ -202,6 +246,9 @@
  * Release the performance hint manager pointer acquired via
  * {@link APerformanceHint_createSession}.
  *
+ * This cannot be used to close a Java PerformanceHintManager.Session, as its
+ * lifecycle is tied to the object in the SDK.
+ *
  * @param session The performance hint session instance to release.
  */
 void APerformanceHint_closeSession(
@@ -312,16 +359,49 @@
         bool cpu, bool gpu, const char* _Nonnull debugName) __INTRODUCED_IN(36);
 
 /**
+ * Associates a session with any {@link ASurfaceControl} or {@link ANativeWindow}
+ * instances managed by this session.
+ *
+ * This method is primarily intended for sessions that manage the timing of an entire
+ * graphics pipeline end-to-end, such as those using the
+ * {@link ASessionCreationConfig_setGraphicsPipeline} API. However, any session directly
+ * or indirectly managing a graphics pipeline should still associate themselves with
+ * directly relevant ASurfaceControl or ANativeWindow instances for better optimization.
+ *
+ * To see any benefit from this method, the client must make sure they are updating the framerate
+ * of attached surfaces using methods such as {@link ANativeWindow_setFrameRate}, or by updating
+ * any associated ASurfaceControls with transactions that have {ASurfaceTransaction_setFrameRate}.
+ *
+ * @param session The {@link APerformanceHintSession} instance to update.
+ * @param nativeWindows A pointer to a list of ANativeWindows associated with this session.
+ *        nullptr can be passed to indicate there are no associated ANativeWindows.
+ * @param nativeWindowsSize The number of ANativeWindows in the list.
+ * @param surfaceControls A pointer to a list of ASurfaceControls associated with this session.
+ *        nullptr can be passed to indicate there are no associated ASurfaceControls.
+ * @param surfaceControlsSize The number of ASurfaceControls in the list.
+ *
+ * @return 0 on success.
+ *         EPIPE if communication has failed.
+ *         ENOTSUP if unsupported.
+ *         EINVAL if invalid or empty arguments passed.
+ */
+
+int APerformanceHint_setNativeSurfaces(APerformanceHintSession* _Nonnull session,
+        ANativeWindow* _Nonnull* _Nullable nativeWindows, int nativeWindowsSize,
+        ASurfaceControl* _Nonnull* _Nullable surfaceControls, int surfaceControlsSize)
+        __INTRODUCED_IN(36);
+
+/**
  * Creates a new AWorkDuration. When the client finishes using {@link AWorkDuration}, it should
  * call {@link AWorkDuration_release()} to destroy {@link AWorkDuration} and release all resources
  * associated with it.
  *
- * @return AWorkDuration on success and nullptr otherwise.
+ * @return AWorkDuration pointer.
  */
 AWorkDuration* _Nonnull AWorkDuration_create() __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
- * Destroys {@link AWorkDuration} and free all resources associated to it.
+ * Destroys a {@link AWorkDuration} and frees all resources associated with it.
  *
  * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
  */
@@ -370,6 +450,171 @@
 void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
         int64_t actualGpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
 
+/**
+ * Return the APerformanceHintSession wrapped by a Java PerformanceHintManager.Session object.
+ *
+ * The Java session maintains ownership over the wrapped native session, so it cannot be
+ * closed using {@link APerformanceHint_closeSession}.
+ *
+ * @param env The Java environment where the PerformanceHintManager.Session lives.
+ * @param sessionObj The Java Session to unwrap.
+ *
+ * @return A pointer to the APerformanceHintManager that backs the Java Session.
+ */
+APerformanceHintSession* _Nonnull APerformanceHint_borrowSessionFromJava(
+        JNIEnv* _Nonnull env, jobject _Nonnull sessionObj) __INTRODUCED_IN(36);
+
+/*
+ * Creates a new ASessionCreationConfig.
+ *
+ * When the client finishes using {@link ASessionCreationConfig}, it should
+ * call {@link ASessionCreationConfig_release()} to destroy
+ * {@link ASessionCreationConfig} and release all resources
+ * associated with it.
+ *
+ * @return ASessionCreationConfig pointer.
+ */
+ASessionCreationConfig* _Nonnull ASessionCreationConfig_create()
+                __INTRODUCED_IN(36);
+
+
+/**
+ * Destroys a {@link ASessionCreationConfig} and frees all
+ * resources associated with it.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ */
+void ASessionCreationConfig_release(
+                ASessionCreationConfig* _Nonnull config) __INTRODUCED_IN(36);
+
+/**
+ * Sets the tids to be associated with the session to be created.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}
+ * @param tids The list of tids to be associated with this session. They must be part of
+ *        this process' thread group.
+ * @param size The size of the list of tids.
+ *
+ * @return 0 on success.
+ *         EINVAL if invalid array pointer or the value of size
+ */
+int ASessionCreationConfig_setTids(
+        ASessionCreationConfig* _Nonnull config,
+        const pid_t* _Nonnull tids, size_t size)  __INTRODUCED_IN(36);
+
+/**
+ * Sets the initial target work duration in nanoseconds for the session to be created.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param targetWorkDurationNanos The parameter to specify a target duration
+ *        in nanoseconds for the new session; this value must be positive to use
+ *        the work duration API.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported
+ *         EINVAL if invalid value
+ */
+int ASessionCreationConfig_setTargetWorkDurationNanos(
+        ASessionCreationConfig* _Nonnull config,
+        int64_t targetWorkDurationNanos)  __INTRODUCED_IN(36);
+
+/**
+ * Sets whether power efficiency mode will be enabled for the session.
+ * This tells the session that these threads can be
+ * safely scheduled to prefer power efficiency over performance.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param enabled Whether power efficiency mode will be enabled.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported
+ *         EINVAL if invalid pointer to creation config
+ */
+int ASessionCreationConfig_setPreferPowerEfficiency(
+        ASessionCreationConfig* _Nonnull config, bool enabled)  __INTRODUCED_IN(36);
+
+/**
+ * Sessions setting this hint are expected to time the critical path of
+ * graphics pipeline from end to end, with the total work duration
+ * representing the time from the start of frame production until the
+ * buffer is fully finished drawing.
+ *
+ * It should include any threads on the critical path of that pipeline,
+ * up to a limit accessible from {@link getMaxGraphicsPipelineThreadsCount()}.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param enabled Whether this session manages a graphics pipeline's critical path.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported
+ *         EINVAL if invalid pointer to creation config or maximum threads for graphics
+                  pipeline is reached.
+ */
+int ASessionCreationConfig_setGraphicsPipeline(
+        ASessionCreationConfig* _Nonnull config, bool enabled)  __INTRODUCED_IN(36);
+
+/**
+ * Associates a session with any {@link ASurfaceControl} or {@link ANativeWindow}
+ * instances managed by this session. See {@link APerformanceHint_setNativeSurfaces}
+ * for more details.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param nativeWindows A pointer to a list of ANativeWindows associated with this session.
+ *        nullptr can be passed to indicate there are no associated ANativeWindows.
+ * @param nativeWindowsSize The number of ANativeWindows in the list.
+ * @param surfaceControls A pointer to a list of ASurfaceControls associated with this session.
+ *        nullptr can be passed to indicate there are no associated ASurfaceControls.
+ * @param surfaceControlsSize The number of ASurfaceControls in the list.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported.
+ *         EINVAL if invalid or empty arguments passed.
+ */
+int ASessionCreationConfig_setNativeSurfaces(
+        ASessionCreationConfig* _Nonnull config,
+        ANativeWindow* _Nonnull* _Nullable nativeWindows, int nativeWindowsSize,
+        ASurfaceControl* _Nonnull* _Nullable surfaceControls, int surfaceControlsSize)
+        __INTRODUCED_IN(36);
+
+/**
+ * Enable automatic timing mode for sessions using the GRAPHICS_PIPELINE API with an attached
+ * surface. In this mode, sessions do not need to report actual durations and only need
+ * to keep their thread list up-to-date, set a native surface, call
+ * {@link ASessionCreationConfig_setGraphicsPipeline()} to signal that the session is in
+ * "graphics pipeline" mode, and then set whether automatic timing is desired for the
+ * CPU, GPU, or both, using this method.
+ *
+ * It is still be beneficial to set an accurate target time, as this may help determine
+ * timing information for some workloads where there is less information available from
+ * the framework, such as games. Additionally, reported CPU durations will be ignored
+ * while automatic CPU timing is enabled, and similarly GPU durations will be ignored
+ * when automatic GPU timing is enabled. When both are enabled, the entire
+ * reportActualWorkDuration call will be ignored, and the session will be managed
+ * completely automatically.
+ *
+ * This mode will not work unless the client makes sure they are updating the framerate
+ * of attached surfaces with methods such as {@link ANativeWindow_setFrameRate}, or updating
+ * any associated ASurfaceControls with transactions that have {ASurfaceTransaction_setFrameRate}.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param cpu Whether to enable automatic timing for the CPU for this session.
+ * @param gpu Whether to enable automatic timing for the GPU for this session.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported.
+ */
+int ASessionCreationConfig_setUseAutoTiming(
+        ASessionCreationConfig* _Nonnull config,
+        bool cpu, bool gpu)
+        __INTRODUCED_IN(36);
+
 __END_DECLS
 
 #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index fe38e86..6323333 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -28,6 +28,7 @@
 
 #include <sys/cdefs.h>
 
+#include <android/display_luts.h>
 #include <android/choreographer.h>
 #include <android/data_space.h>
 #include <android/hardware_buffer.h>
@@ -713,6 +714,23 @@
         __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
+ * Sets the Lut(s) to be applied for the layer.
+ *
+ * The function makes a deep copy of the provided `luts`.
+ * Any modifications made to the `luts` object after calling this function
+ * will not affect the Lut(s) applied to the layer.
+ *
+ * @param surface_control The layer where Lut(s) is being applied
+ * @param luts The Lut(s) to be applied
+ *
+ * Available since API level 36.
+ */
+void ASurfaceTransaction_setLuts(ASurfaceTransaction* _Nonnull transaction,
+                                 ASurfaceControl* _Nonnull surface_control,
+                                 const struct ADisplayLuts* _Nullable luts)
+        __INTRODUCED_IN(36);
+
+/**
  * Same as ASurfaceTransaction_setFrameRateWithChangeStrategy(transaction, surface_control,
  * frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS).
  *
@@ -763,69 +781,6 @@
                                                         __INTRODUCED_IN(31);
 
 /**
- * Sets the intended frame rate for the given \a surface_control.
- *
- * On devices that are capable of running the display at different frame rates,
- * the system may choose a display refresh rate to better match this surface's frame
- * rate. Usage of this API won't introduce frame rate throttling, or affect other
- * aspects of the application's frame production pipeline. However, because the system
- * may change the display refresh rate, calls to this function may result in changes
- * to Choreographer callback timings, and changes to the time interval at which the
- * system releases buffers back to the application.
- *
- * You can register for changes in the refresh rate using
- * \a AChoreographer_registerRefreshRateCallback.
- *
- * See ASurfaceTransaction_clearFrameRate().
- *
- * Available since API level 36.
- *
- * \param desiredMinRate The desired minimum frame rate (inclusive) for the surface, specifying that
- * the surface prefers the device render rate to be at least `desiredMinRate`.
- *
- * <p>Set `desiredMinRate` = `desiredMaxRate` to indicate the surface prefers an exact frame rate.
- *
- * <p>Set `desiredMinRate` = 0 to indicate the surface has no preference
- * and any frame rate is acceptable.
- *
- * <p>The value should be greater than or equal to 0.
- *
- * \param desiredMaxRate The desired maximum frame rate (inclusive) for the surface, specifying that
- * the surface prefers the device render rate to be at most `desiredMaxRate`.
- *
- * <p>Set `desiredMaxRate` = `desiredMinRate` to indicate the surface prefers an exact frame rate.
- *
- * <p>Set `desiredMaxRate` = positive infinity to indicate the surface has no preference
- * and any frame rate is acceptable.
- *
- * <p>The value should be greater than or equal to `desiredMinRate`.
- *
- * \param fixedSourceRate The "fixed source" frame rate of the surface if the content has an
- * inherently fixed frame rate, e.g. a video that has a specific frame rate.
- *
- * <p>When the frame rate chosen for the surface is the `fixedSourceRate` or a
- * multiple, the surface can render without frame pulldown, for optimal smoothness. For
- * example, a 30 fps video (`fixedSourceRate`=30) renders just as smoothly on 30 fps,
- * 60 fps, 90 fps, 120 fps, and so on.
- *
- * <p>Setting the fixed source rate can also be used together with a desired
- * frame rate min and max via setting `desiredMinRate` and `desiredMaxRate`. This still
- * means the surface's content has a fixed frame rate of `fixedSourceRate`, but additionally
- * specifies the preference to be in the range [`desiredMinRate`, `desiredMaxRate`]. For example, an
- * app might want to specify there is 30 fps video (`fixedSourceRate`=30) as well as a smooth
- * animation on the same surface which looks good when drawing within a frame rate range such as
- * [`desiredMinRate`, `desiredMaxRate`] = [60,120].
- *
- * \param changeFrameRateStrategy Whether display refresh rate transitions caused by this surface
- * should be seamless. A seamless transition is one that doesn't have any visual interruptions, such
- * as a black screen for a second or two.
- */
-void ASurfaceTransaction_setFrameRateParams(
-        ASurfaceTransaction* _Nonnull transaction, ASurfaceControl* _Nonnull surface_control,
-        float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
-        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) __INTRODUCED_IN(36);
-
-/**
  * Clears the frame rate which is set for \a surface_control.
  *
  * This is equivalent to calling
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 7f9d2ed..e5d46b5 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -140,45 +140,47 @@
   * Available since API level 30.
   *
   * @param manager The manager instance to use to query the thermal status.
-  * Acquired via {@link AThermal_acquireManager}.
+  *                Acquired via {@link AThermal_acquireManager}.
   *
   * @return current thermal status, ATHERMAL_STATUS_ERROR on failure.
   */
 AThermalStatus
-AThermal_getCurrentThermalStatus(AThermalManager* _Nonnull manager) __INTRODUCED_IN(30);
+AThermal_getCurrentThermalStatus(AThermalManager *_Nonnull manager) __INTRODUCED_IN(30);
 
 /**
- * Register the thermal status listener for thermal status change.
+ * Register a thermal status listener for thermal status change.
  *
  * Available since API level 30.
  *
  * @param manager The manager instance to use to register.
- * Acquired via {@link AThermal_acquireManager}.
- * @param callback The callback function to be called when thermal status updated.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param callback The callback function to be called on system binder thread pool when thermal
+ *                 status updated.
  * @param data The data pointer to be passed when callback is called.
  *
  * @return 0 on success
  *         EINVAL if the listener and data pointer were previously added and not removed.
- *         EPERM if the required permission is not held.
- *         EPIPE if communication with the system service has failed.
+ *         EPIPE if communication with the system service has failed, the listener will not get
+ *               removed and this call should be retried
  */
-int AThermal_registerThermalStatusListener(AThermalManager* _Nonnull manager,
+int AThermal_registerThermalStatusListener(AThermalManager *_Nonnull manager,
                                            AThermal_StatusCallback _Nullable callback,
                                            void* _Nullable data) __INTRODUCED_IN(30);
 
 /**
- * Unregister the thermal status listener previously resgistered.
+ * Unregister a thermal status listener previously registered.
+ *
+ * No subsequent invocations of the callback will occur after this function returns successfully.
  *
  * Available since API level 30.
  *
  * @param manager The manager instance to use to unregister.
- * Acquired via {@link AThermal_acquireManager}.
- * @param callback The callback function to be called when thermal status updated.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param callback The callback function that was previously registered.
  * @param data The data pointer to be passed when callback is called.
  *
  * @return 0 on success
  *         EINVAL if the listener and data pointer were not previously added.
- *         EPERM if the required permission is not held.
  *         EPIPE if communication with the system service has failed.
  */
 int AThermal_unregisterThermalStatusListener(AThermalManager* _Nonnull manager,
@@ -254,7 +256,7 @@
  * The headroom threshold is used to interpret the possible thermal throttling status based on
  * the headroom prediction. For example, if the headroom threshold for
  * {@link ATHERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75
- * (or {@code AThermal_getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system
+ * (or `AThermal_getThermalHeadroom(10)=0.75}`, one can expect that in 10 seconds the system
  * could be in lightly throttled state if the workload remains the same. The app can consider
  * taking actions according to the nearest throttling status the difference between the headroom and
  * the threshold.
@@ -262,24 +264,30 @@
  * For new devices it's guaranteed to have a single sensor, but for older devices with multiple
  * sensors reporting different threshold values, the minimum threshold is taken to be conservative
  * on predictions. Thus, when reading real-time headroom, it's not guaranteed that a real-time value
- * of 0.75 (or {@code AThermal_getThermalHeadroom(0)}=0.75) exceeding the threshold of 0.7 above
+ * of 0.75 (or `AThermal_getThermalHeadroom(0)=0.75`) exceeding the threshold of 0.7 above
  * will always come with lightly throttled state
- * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT}) but it can be lower
- * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE}).
+ * (or `AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT`) but it can be lower
+ * (or `AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE`).
  * While it's always guaranteed that the device won't be throttled heavier than the unmet
  * threshold's state, so a real-time headroom of 0.75 will never come with
  * {@link #ATHERMAL_STATUS_MODERATE} but always lower, and 0.65 will never come with
  * {@link ATHERMAL_STATUS_LIGHT} but {@link #ATHERMAL_STATUS_NONE}.
  * <p>
- * The returned list of thresholds is cached on first successful query and owned by the thermal
- * manager, which will not change between calls to this function. The caller should only need to
- * free the manager with {@link AThermal_releaseManager}.
+ * Starting in Android 16, this polling API may return different results when called depending on
+ * the device. The new headroom listener API {@link #AThermal_HeadroomCallback} can be used to
+ * detect headroom thresholds changes.
+ * <p>
+ * Before API level 36 the returned list of thresholds is cached on first successful query and owned
+ * by the thermal manager, which will not change between calls to this function. The caller should
+ * only need to free the manager with {@link AThermal_releaseManager}.
+ * <p>
  *
  * @param manager The manager instance to use.
  *                Acquired via {@link AThermal_acquireManager}.
  * @param outThresholds non-null output pointer to null AThermalHeadroomThreshold pointer, which
- *                will be set to the cached array of thresholds if thermal thresholds are supported
- *                by the system or device, otherwise nullptr or unmodified.
+ *                will be set to a new array of thresholds if thermal thresholds are supported
+ *                by the system or device, otherwise nullptr or unmodified. The client should
+ *                clean up the thresholds by array-deleting the threshold pointer.
  * @param size non-null output pointer whose value will be set to the size of the threshold array
  *             or 0 if it's not supported.
  * @return 0 on success
@@ -292,6 +300,71 @@
                                           * _Nullable outThresholds,
                                           size_t* _Nonnull size) __INTRODUCED_IN(35);
 
+/**
+ * Prototype of the function that is called when thermal headroom or thresholds changes.
+ * It's passed the updated thermal headroom and thresholds as parameters, as well as the
+ * pointer provided by the client that registered a callback.
+ *
+ * @param data The data pointer to be passed when callback is called.
+ * @param headroom The current non-negative normalized headroom value, also see
+ *                 {@link AThermal_getThermalHeadroom}.
+ * @param forecastHeadroom The forecasted non-negative normalized headroom value, also see
+ *                         {@link AThermal_getThermalHeadroom}.
+ * @param forecastSeconds The seconds used for the forecast by the system.
+ * @param thresholds The current headroom thresholds. The thresholds pointer will be a constant
+ *                   shared across all callbacks registered from the same process, and it will be
+ *                   destroyed after all the callbacks are finished. If the client intents to
+ *                   persist the values, it should make a copy of it during the callback.
+ * @param thresholdsCount The count of thresholds.
+ */
+typedef void (*AThermal_HeadroomCallback)(void *_Nullable data,
+                                          float headroom,
+                                          float forecastHeadroom,
+                                          int forecastSeconds,
+                                          const AThermalHeadroomThreshold* _Nullable thresholds,
+                                          size_t thresholdsCount);
+
+/**
+ * Register a thermal headroom listener for thermal headroom or thresholds change.
+ *
+ * Available since API level 36.
+ *
+ * @param manager The manager instance to use to register.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param callback The callback function to be called on system binder thread pool when thermal
+ *                 headroom or thresholds update.
+ * @param data The data pointer to be passed when callback is called.
+ *
+ * @return 0 on success
+ *         EINVAL if the listener and data pointer were previously added and not removed.
+ *         EPIPE if communication with the system service has failed.
+ */
+int AThermal_registerThermalHeadroomListener(AThermalManager* _Nonnull manager,
+                                             AThermal_HeadroomCallback _Nullable callback,
+                                             void* _Nullable data) __INTRODUCED_IN(36);
+
+/**
+ * Unregister a thermal headroom listener previously registered.
+ *
+ * No subsequent invocations of the callback will occur after this function returns successfully.
+ *
+ * Available since API level 36.
+ *
+ * @param manager The manager instance to use to unregister.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param callback The callback function that was previously registered.
+ * @param data The data pointer that was previously registered.
+ *
+ * @return 0 on success
+ *         EINVAL if the listener and data pointer were not previously added.
+ *         EPIPE if communication with the system service has failed, the listener will not get
+ *               removed and this call should be retried
+ */
+
+int AThermal_unregisterThermalHeadroomListener(AThermalManager* _Nonnull manager,
+                                               AThermal_HeadroomCallback _Nullable callback,
+                                               void* _Nullable data) __INTRODUCED_IN(36);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/audiomanager/AudioManager.h b/include/audiomanager/AudioManager.h
index 917d9a7..203623d 100644
--- a/include/audiomanager/AudioManager.h
+++ b/include/audiomanager/AudioManager.h
@@ -22,6 +22,7 @@
 // must be kept in sync with definitions in AudioPlaybackConfiguration.java
 #define PLAYER_PIID_INVALID -1
 
+// TODO (b/309532236) remove manual IAudioManager impl in favor of AIDL.
 typedef enum {
     PLAYER_TYPE_SLES_AUDIOPLAYER_BUFFERQUEUE = 11,
     PLAYER_TYPE_SLES_AUDIOPLAYER_URI_FD = 12,
@@ -60,6 +61,7 @@
     PLAYER_MUTE_CLIENT_VOLUME = (1 << 4),
     PLAYER_MUTE_VOLUME_SHAPER = (1 << 5),
     PLAYER_MUTE_PORT_VOLUME = (1 << 6),
+    PLAYER_MUTE_OP_AUDIO_CONTROL = (1 << 7),
 };
 
 struct mute_state_t {
@@ -77,6 +79,8 @@
     bool muteFromVolumeShaper = false;
     /** Flag used when volume is muted by port volume. */
     bool muteFromPortVolume = false;
+    /** Flag used when volume is muted by audio control op. */
+    bool muteFromOpAudioControl = false;
 
     explicit operator int() const
     {
@@ -87,6 +91,7 @@
         result |= muteFromClientVolume * PLAYER_MUTE_CLIENT_VOLUME;
         result |= muteFromVolumeShaper * PLAYER_MUTE_VOLUME_SHAPER;
         result |= muteFromPortVolume * PLAYER_MUTE_PORT_VOLUME;
+        result |= muteFromOpAudioControl * PLAYER_MUTE_OP_AUDIO_CONTROL;
         return result;
     }
 
diff --git a/include/ftl/flags.h b/include/ftl/flags.h
index dbe3148..a2a22eb 100644
--- a/include/ftl/flags.h
+++ b/include/ftl/flags.h
@@ -22,6 +22,7 @@
 #include <bitset>
 #include <cstdint>
 #include <iterator>
+#include <initializer_list>
 #include <string>
 #include <type_traits>
 
@@ -40,6 +41,7 @@
 
 public:
     constexpr Flags(F f) : mFlags(static_cast<U>(f)) {}
+    constexpr Flags(std::initializer_list<F> fs) : mFlags(combine(fs)) {}
     constexpr Flags() : mFlags(0) {}
     constexpr Flags(const Flags<F>& f) : mFlags(f.mFlags) {}
 
@@ -197,6 +199,14 @@
 private:
     U mFlags;
 
+    static constexpr U combine(std::initializer_list<F> fs) {
+        U result = 0;
+        for (const F f : fs) {
+            result |= static_cast<U>(f);
+        }
+        return result;
+    }
+
     static void appendFlag(std::string& str, const std::string_view& flag, bool& first) {
         if (first) {
             first = false;
diff --git a/include/input/CoordinateFilter.h b/include/input/CoordinateFilter.h
index f36472d..8f2e605 100644
--- a/include/input/CoordinateFilter.h
+++ b/include/input/CoordinateFilter.h
@@ -44,7 +44,7 @@
      * the previous call.
      * @param coords Coordinates to be overwritten by the corresponding filtered coordinates.
      */
-    void filter(std::chrono::duration<float> timestamp, PointerCoords& coords);
+    void filter(std::chrono::nanoseconds timestamp, PointerCoords& coords);
 
 private:
     OneEuroFilter mXFilter;
diff --git a/include/input/Input.h b/include/input/Input.h
index 127046d..2cabd56 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -996,6 +996,15 @@
     std::vector<PointerProperties> mPointerProperties;
     std::vector<nsecs_t> mSampleEventTimes;
     std::vector<PointerCoords> mSamplePointerCoords;
+
+private:
+    /**
+     * Create a human-readable string representation of the event's data for debugging purposes.
+     *
+     * Unlike operator<<, this method does not assume that the event data is valid or consistent, or
+     * call any accessor methods that might themselves call safeDump in the case of invalid data.
+     */
+    std::string safeDump() const;
 };
 
 std::ostream& operator<<(std::ostream& out, const MotionEvent& event);
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 6b45dd3..ea1e4ae 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -111,12 +111,12 @@
 };
 
 enum class InputDeviceSensorAccuracy : int32_t {
-    ACCURACY_NONE = 0,
-    ACCURACY_LOW = 1,
-    ACCURACY_MEDIUM = 2,
-    ACCURACY_HIGH = 3,
+    NONE = 0,
+    LOW = 1,
+    MEDIUM = 2,
+    HIGH = 3,
 
-    ftl_last = ACCURACY_HIGH,
+    ftl_last = HIGH,
 };
 
 enum class InputDeviceSensorReportingMode : int32_t {
diff --git a/include/input/OneEuroFilter.h b/include/input/OneEuroFilter.h
index a0168e4..bdd82b2 100644
--- a/include/input/OneEuroFilter.h
+++ b/include/input/OneEuroFilter.h
@@ -56,7 +56,7 @@
      * provided in the previous call.
      * @param rawPosition Position to be filtered.
      */
-    float filter(std::chrono::duration<float> timestamp, float rawPosition);
+    float filter(std::chrono::nanoseconds timestamp, float rawPosition);
 
 private:
     /**
@@ -67,7 +67,7 @@
 
     /**
      * Slope of the cutoff frequency criterion. This is the term scaling the absolute value of the
-     * filtered signal's speed. The data member is dimensionless, that is, it does not have units.
+     * filtered signal's speed. Units are 1 / position.
      */
     const float mBeta;
 
@@ -78,9 +78,9 @@
     const float mSpeedCutoffFreq;
 
     /**
-     * The timestamp from the previous call. Units are seconds.
+     * The timestamp from the previous call.
      */
-    std::optional<std::chrono::duration<float>> mPrevTimestamp;
+    std::optional<std::chrono::nanoseconds> mPrevTimestamp;
 
     /**
      * The raw position from the previous call.
@@ -88,7 +88,7 @@
     std::optional<float> mPrevRawPosition;
 
     /**
-     * The filtered velocity from the previous call. Units are position per second.
+     * The filtered velocity from the previous call. Units are position per nanosecond.
      */
     std::optional<float> mPrevFilteredVelocity;
 
diff --git a/include/powermanager/OWNERS b/include/powermanager/OWNERS
new file mode 100644
index 0000000..9f40e27
--- /dev/null
+++ b/include/powermanager/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:/ADPF_OWNERS
\ No newline at end of file
diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h
index 7e0bd5b..f4f4d5e 100644
--- a/include/powermanager/PowerHalController.h
+++ b/include/powermanager/PowerHalController.h
@@ -72,6 +72,7 @@
     virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(
             int tgid, int uid) override;
     virtual HalResult<void> closeSessionChannel(int tgid, int uid) override;
+    virtual HalResult<aidl::android::hardware::power::SupportInfo> getSupportInfo() override;
 
 private:
     std::mutex mConnectedHalMutex;
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index 6e347a9..4290182 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -63,6 +63,7 @@
     virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                        int uid) = 0;
     virtual HalResult<void> closeSessionChannel(int tgid, int uid) = 0;
+    virtual HalResult<aidl::android::hardware::power::SupportInfo> getSupportInfo() = 0;
 };
 
 // Empty Power HAL wrapper that ignores all api calls.
@@ -85,6 +86,7 @@
     HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                int uid) override;
     HalResult<void> closeSessionChannel(int tgid, int uid) override;
+    HalResult<aidl::android::hardware::power::SupportInfo> getSupportInfo() override;
 
 protected:
     virtual const char* getUnsupportedMessage();
@@ -170,6 +172,7 @@
     HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                int uid) override;
     HalResult<void> closeSessionChannel(int tgid, int uid) override;
+    HalResult<aidl::android::hardware::power::SupportInfo> getSupportInfo() override;
 
 protected:
     const char* getUnsupportedMessage() override;
diff --git a/include/private/OWNERS b/include/private/OWNERS
new file mode 100644
index 0000000..37da96d
--- /dev/null
+++ b/include/private/OWNERS
@@ -0,0 +1,3 @@
+# ADPF
+per-file thermal_private.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file performance_hint_private.h = file:platform/frameworks/base:/ADPF_OWNERS
diff --git a/include/private/display_luts_private.h b/include/private/display_luts_private.h
new file mode 100644
index 0000000..c347a0c
--- /dev/null
+++ b/include/private/display_luts_private.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/display_luts.h>
+#include <stdint.h>
+#include <vector>
+#include <utils/RefBase.h>
+
+using namespace android;
+
+__BEGIN_DECLS
+
+struct ADisplayLutsEntry_buffer {
+    std::vector<float> data;
+};
+
+struct ADisplayLutsEntry_properties {
+    ADisplayLuts_Dimension dimension;
+    int32_t size;
+    ADisplayLuts_SamplingKey samplingKey;
+};
+
+struct ADisplayLutsEntry: public RefBase {
+    struct ADisplayLutsEntry_buffer buffer;
+    struct ADisplayLutsEntry_properties properties;
+    ADisplayLutsEntry() {}
+
+    // copy constructor
+    ADisplayLutsEntry(const ADisplayLutsEntry& other) :
+        buffer(other.buffer),
+        properties(other.properties) {}
+
+    // copy operator
+    ADisplayLutsEntry& operator=(const ADisplayLutsEntry& other) {
+        if (this != &other) { // Protect against self-assignment
+            buffer = other.buffer;
+            properties = other.properties;
+        }
+        return *this;
+    }
+};
+
+struct ADisplayLuts: public RefBase {
+    int32_t totalBufferSize;
+    std::vector<int32_t> offsets;
+    std::vector<sp<ADisplayLutsEntry>> entries;
+
+    ADisplayLuts() : totalBufferSize(0) {}
+};
+
+__END_DECLS
\ No newline at end of file
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index 3229e45..f150fb1 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -108,6 +108,24 @@
 APerformanceHintSession* APerformanceHint_createSessionInternal(APerformanceHintManager* manager,
                                         const int32_t* threadIds, size_t size,
                                         int64_t initialTargetWorkDurationNanos, SessionTag tag);
+/**
+ * Creates a session using ASessionCreationConfig
+ */
+APerformanceHintSession* APerformanceHint_createSessionUsingConfigInternal(
+        APerformanceHintManager* manager, ASessionCreationConfig* sessionCreationConfig,
+        SessionTag tag);
+
+/**
+ * Creates a session from the Java SDK implementation
+ */
+APerformanceHintSession* APerformanceHint_createSessionFromJava(APerformanceHintManager* manager,
+                                        const int32_t* threadIds, size_t size,
+                                        int64_t initialTargetWorkDurationNanos);
+
+/**
+ * Special method for Java SDK implementation to kill sessions
+ */
+void APerformanceHint_closeSessionFromJava(APerformanceHintSession* session);
 
 /**
  * Forces FMQ to be enabled or disabled, for testing only.
@@ -125,6 +143,10 @@
  */
 void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior);
 
+/*
+ * Forces the graphics pipeline flag to be enabled or disabled, for testing only.
+ */
+void APerformanceHint_setUseGraphicsPipelineForTesting(bool enabled);
 
 __END_DECLS
 
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 0a61178..e10e4dd 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -477,9 +477,34 @@
     },
 }
 
+soong_config_module_type {
+    name: "libbinder_addservice_cache_config",
+    module_type: "cc_defaults",
+    config_namespace: "libbinder",
+    bool_variables: ["release_libbinder_addservice_cache"],
+    properties: [
+        "cflags",
+    ],
+}
+
+libbinder_addservice_cache_config {
+    name: "libbinder_addservice_cache_flag",
+    soong_config_variables: {
+        release_libbinder_addservice_cache: {
+            cflags: ["-DLIBBINDER_ADDSERVICE_CACHE"],
+            conditions_default: {
+                cflags: ["-DNO_LIBBINDER_ADDSERVICE_CACHE"],
+            },
+        },
+    },
+}
+
 cc_defaults {
     name: "libbinder_kernel_defaults",
-    defaults: ["libbinder_client_cache_flag"],
+    defaults: [
+        "libbinder_client_cache_flag",
+        "libbinder_addservice_cache_flag",
+    ],
     srcs: [
         "BufferedTextOutput.cpp",
         "BackendUnifiedServiceManager.cpp",
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index d32eecd..123dfd2 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -30,6 +30,12 @@
 constexpr bool kUseCache = false;
 #endif
 
+#ifdef LIBBINDER_ADDSERVICE_CACHE
+constexpr bool kUseCacheInAddService = true;
+#else
+constexpr bool kUseCacheInAddService = false;
+#endif
+
 using AidlServiceManager = android::os::IServiceManager;
 using android::os::IAccessor;
 using binder::Status;
@@ -125,31 +131,36 @@
     if (!kUseCache) {
         return Status::ok();
     }
+
+    if (service.getTag() == os::Service::Tag::binder) {
+        return updateCache(serviceName, service.get<os::Service::Tag::binder>());
+    }
+    return Status::ok();
+}
+
+Status BackendUnifiedServiceManager::updateCache(const std::string& serviceName,
+                                                 const sp<IBinder>& binder) {
     std::string traceStr;
     if (atrace_is_tag_enabled(ATRACE_TAG_AIDL)) {
         traceStr = "BinderCacheWithInvalidation::updateCache : " + serviceName;
     }
     binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL, traceStr.c_str());
-
-    if (service.getTag() == os::Service::Tag::binder) {
-        sp<IBinder> binder = service.get<os::Service::Tag::binder>();
-        if (!binder) {
-            binder::ScopedTrace
-                    aidlTrace(ATRACE_TAG_AIDL,
-                              "BinderCacheWithInvalidation::updateCache failed: binder_null");
-        } else if (!binder->isBinderAlive()) {
-            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
-                                          "BinderCacheWithInvalidation::updateCache failed: "
-                                          "isBinderAlive_false");
-        } else if (mCacheForGetService->isClientSideCachingEnabled(serviceName)) {
-            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
-                                          "BinderCacheWithInvalidation::updateCache successful");
-            return mCacheForGetService->setItem(serviceName, binder);
-        } else {
-            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
-                                          "BinderCacheWithInvalidation::updateCache failed: "
-                                          "caching_not_enabled");
-        }
+    if (!binder) {
+        binder::ScopedTrace
+                aidlTrace(ATRACE_TAG_AIDL,
+                          "BinderCacheWithInvalidation::updateCache failed: binder_null");
+    } else if (!binder->isBinderAlive()) {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache failed: "
+                                      "isBinderAlive_false");
+    } else if (mCacheForGetService->isClientSideCachingEnabled(serviceName)) {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache successful");
+        return mCacheForGetService->setItem(serviceName, binder);
+    } else {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache failed: "
+                                      "caching_not_enabled");
     }
     return Status::ok();
 }
@@ -277,7 +288,12 @@
 Status BackendUnifiedServiceManager::addService(const ::std::string& name,
                                                 const sp<IBinder>& service, bool allowIsolated,
                                                 int32_t dumpPriority) {
-    return mTheRealServiceManager->addService(name, service, allowIsolated, dumpPriority);
+    Status status = mTheRealServiceManager->addService(name, service, allowIsolated, dumpPriority);
+    // mEnableAddServiceCache is true by default.
+    if (kUseCacheInAddService && mEnableAddServiceCache && status.isOk()) {
+        return updateCache(name, service);
+    }
+    return status;
 }
 Status BackendUnifiedServiceManager::listServices(int32_t dumpPriority,
                                                   ::std::vector<::std::string>* _aidl_return) {
diff --git a/libs/binder/BackendUnifiedServiceManager.h b/libs/binder/BackendUnifiedServiceManager.h
index abc0eda..b46bf19 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -105,8 +105,7 @@
         binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
                                       "BinderCacheWithInvalidation::setItem Successfully Cached");
         std::lock_guard<std::mutex> lock(mCacheMutex);
-        Entry entry = {.service = item, .deathRecipient = deathRecipient};
-        mCache[key] = entry;
+        mCache[key] = {.service = item, .deathRecipient = deathRecipient};
         return binder::Status::ok();
     }
 
@@ -147,17 +146,20 @@
                                         const sp<IBinder>& service) override;
     binder::Status getServiceDebugInfo(::std::vector<os::ServiceDebugInfo>* _aidl_return) override;
 
+    void enableAddServiceCache(bool value) { mEnableAddServiceCache = value; }
     // for legacy ABI
     const String16& getInterfaceDescriptor() const override {
         return mTheRealServiceManager->getInterfaceDescriptor();
     }
 
 private:
+    bool mEnableAddServiceCache = true;
     std::shared_ptr<BinderCacheWithInvalidation> mCacheForGetService;
     sp<os::IServiceManager> mTheRealServiceManager;
     binder::Status toBinderService(const ::std::string& name, const os::Service& in,
                                    os::Service* _out);
     binder::Status updateCache(const std::string& serviceName, const os::Service& service);
+    binder::Status updateCache(const std::string& serviceName, const sp<IBinder>& binder);
     bool returnIfCached(const std::string& serviceName, os::Service* _out);
 };
 
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 39d8c24..61866d7 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -127,6 +127,8 @@
     }
     IBinder* onAsBinder() override { return IInterface::asBinder(mUnifiedServiceManager).get(); }
 
+    void enableAddServiceCache(bool value) { mUnifiedServiceManager->enableAddServiceCache(value); }
+
 protected:
     sp<BackendUnifiedServiceManager> mUnifiedServiceManager;
     // AidlRegistrationCallback -> services that its been registered for
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 96d821e..a5f416f 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -2223,9 +2223,7 @@
         const char* eos = reinterpret_cast<const char*>(memchr(str, 0, avail));
         if (eos) {
             const size_t len = eos - str;
-            mDataPos += pad_size(len+1);
-            ALOGV("readCString Setting data pos of %p to %zu", this, mDataPos);
-            return str;
+            return static_cast<const char*>(readInplace(len + 1));
         }
     }
     return nullptr;
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index ab44957..9e5e79f 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -67,15 +67,10 @@
       "name": "fuzz_service_test"
     },
     {
-      "name": "CtsOsTestCases",
-      "options": [
-        {
-          "include-filter": "android.os.cts.BinderTest"
-        },
-        {
-          "include-filter": "android.os.cts.ParcelTest"
-        }
-      ]
+      "name": "CtsOsTestCases_ParcelAndBinderTests"
+    },
+    {
+      "name": "FrameworksCoreTests_all_binder"
     },
     {
       "name": "libbinder_rs-internal_test"
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 81f7cdb..ca26a57 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -150,6 +150,12 @@
         int pid;
     };
     virtual std::vector<ServiceDebugInfo> getServiceDebugInfo() = 0;
+
+    /**
+     * Directly enable or disable caching binder during addService calls.
+     * Only used for testing.
+     */
+    virtual void enableAddServiceCache(bool value) = 0;
 };
 
 LIBBINDER_EXPORTED sp<IServiceManager> defaultServiceManager();
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index ceab20a..0c7366e 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -178,7 +178,8 @@
     LIBBINDER_EXPORTED status_t writeUint64(uint64_t val);
     LIBBINDER_EXPORTED status_t writeFloat(float val);
     LIBBINDER_EXPORTED status_t writeDouble(double val);
-    LIBBINDER_EXPORTED status_t writeCString(const char* str);
+    LIBBINDER_EXPORTED status_t writeCString(const char* str)
+            __attribute__((deprecated("use AIDL, writeString* instead")));
     LIBBINDER_EXPORTED status_t writeString8(const String8& str);
     LIBBINDER_EXPORTED status_t writeString8(const char* str, size_t len);
     LIBBINDER_EXPORTED status_t writeString16(const String16& str);
@@ -434,7 +435,8 @@
     LIBBINDER_EXPORTED status_t readUtf8FromUtf16(std::unique_ptr<std::string>* str) const
             __attribute__((deprecated("use std::optional version instead")));
 
-    LIBBINDER_EXPORTED const char* readCString() const;
+    LIBBINDER_EXPORTED const char* readCString() const
+            __attribute__((deprecated("use AIDL, use readString*")));
     LIBBINDER_EXPORTED String8 readString8() const;
     LIBBINDER_EXPORTED status_t readString8(String8* pArg) const;
     LIBBINDER_EXPORTED const char* readString8Inplace(size_t* outLen) const;
diff --git a/libs/binder/ndk/binder_rpc.cpp b/libs/binder/ndk/binder_rpc.cpp
index 53ab68e..bb5989d 100644
--- a/libs/binder/ndk/binder_rpc.cpp
+++ b/libs/binder/ndk/binder_rpc.cpp
@@ -107,16 +107,20 @@
         ABinderRpc_AccessorProvider_getAccessorCallback provider,
         const char* const* const instances, size_t numInstances, void* data,
         ABinderRpc_AccessorProviderUserData_deleteCallback onDelete) {
-    if (provider == nullptr) {
-        ALOGE("Null provider passed to ABinderRpc_registerAccessorProvider");
-        return nullptr;
-    }
     if (data && onDelete == nullptr) {
         ALOGE("If a non-null data ptr is passed to ABinderRpc_registerAccessorProvider, then a "
               "ABinderRpc_AccessorProviderUserData_deleteCallback must also be passed to delete "
               "the data object once the ABinderRpc_AccessorProvider is removed.");
         return nullptr;
     }
+    // call the onDelete when the last reference of this goes away (when the
+    // last reference to the generate std::function goes away).
+    std::shared_ptr<OnDeleteProviderHolder> onDeleteHolder =
+            std::make_shared<OnDeleteProviderHolder>(data, onDelete);
+    if (provider == nullptr) {
+        ALOGE("Null provider passed to ABinderRpc_registerAccessorProvider");
+        return nullptr;
+    }
     if (numInstances == 0 || instances == nullptr) {
         ALOGE("No instances passed to ABinderRpc_registerAccessorProvider. numInstances: %zu",
               numInstances);
@@ -126,10 +130,6 @@
     for (size_t i = 0; i < numInstances; i++) {
         instanceStrings.emplace(instances[i]);
     }
-    // call the onDelete when the last reference of this goes away (when the
-    // last reference to the generate std::function goes away).
-    std::shared_ptr<OnDeleteProviderHolder> onDeleteHolder =
-            std::make_shared<OnDeleteProviderHolder>(data, onDelete);
     android::RpcAccessorProvider generate = [provider,
                                              onDeleteHolder](const String16& name) -> sp<IBinder> {
         ABinderRpc_Accessor* accessor = provider(String8(name).c_str(), onDeleteHolder->mData);
diff --git a/libs/binder/ndk/include_platform/android/binder_rpc.h b/libs/binder/ndk/include_platform/android/binder_rpc.h
index 7d54e2d..1318889 100644
--- a/libs/binder/ndk/include_platform/android/binder_rpc.h
+++ b/libs/binder/ndk/include_platform/android/binder_rpc.h
@@ -139,6 +139,8 @@
  *         registered. In the error case of duplicate instances, if data was
  *         provided with a ABinderRpc_AccessorProviderUserData_deleteCallback,
  *         the callback will be called to delete the data.
+ *         If nullptr is returned, ABinderRpc_AccessorProviderUserData_deleteCallback
+ *         will be called on data immediately.
  *         Otherwise returns a pointer to the ABinderRpc_AccessorProvider that
  *         can be used to remove with ABinderRpc_unregisterAccessorProvider.
  */
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 4d691f8..a637165 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -164,96 +164,53 @@
 LIBBINDER_NDK35 { # introduced=VanillaIceCream
   global:
     APersistableBundle_readFromParcel;
-    APersistableBundle_readFromParcel; # llndk=202404
     APersistableBundle_writeToParcel;
-    APersistableBundle_writeToParcel; # llndk=202404
     APersistableBundle_new;
-    APersistableBundle_new; # llndk=202404
     APersistableBundle_dup;
-    APersistableBundle_dup; # llndk=202404
     APersistableBundle_delete;
-    APersistableBundle_delete; # llndk=202404
     APersistableBundle_isEqual;
-    APersistableBundle_isEqual; # llndk=202404
     APersistableBundle_size;
-    APersistableBundle_size; # llndk=202404
     APersistableBundle_erase;
-    APersistableBundle_erase; # llndk=202404
     APersistableBundle_putBoolean;
-    APersistableBundle_putBoolean; # llndk=202404
     APersistableBundle_putInt;
-    APersistableBundle_putInt; # llndk=202404
     APersistableBundle_putLong;
-    APersistableBundle_putLong; # llndk=202404
     APersistableBundle_putDouble;
-    APersistableBundle_putDouble; # llndk=202404
     APersistableBundle_putString;
-    APersistableBundle_putString; # llndk=202404
     APersistableBundle_putBooleanVector;
-    APersistableBundle_putBooleanVector; # llndk=202404
     APersistableBundle_putIntVector;
-    APersistableBundle_putIntVector; # llndk=202404
     APersistableBundle_putLongVector;
-    APersistableBundle_putLongVector; # llndk=202404
     APersistableBundle_putDoubleVector;
-    APersistableBundle_putDoubleVector; # llndk=202404
     APersistableBundle_putStringVector;
-    APersistableBundle_putStringVector; # llndk=202404
     APersistableBundle_putPersistableBundle;
-    APersistableBundle_putPersistableBundle; # llndk=202404
     APersistableBundle_getBoolean;
-    APersistableBundle_getBoolean; # llndk=202404
     APersistableBundle_getInt;
-    APersistableBundle_getInt; # llndk=202404
     APersistableBundle_getLong;
-    APersistableBundle_getLong; # llndk=202404
     APersistableBundle_getDouble;
-    APersistableBundle_getDouble; # llndk=202404
     APersistableBundle_getString;
-    APersistableBundle_getString; # llndk=202404
     APersistableBundle_getBooleanVector;
-    APersistableBundle_getBooleanVector; # llndk=202404
     APersistableBundle_getIntVector;
-    APersistableBundle_getIntVector; # llndk=202404
     APersistableBundle_getLongVector;
-    APersistableBundle_getLongVector; # llndk=202404
     APersistableBundle_getDoubleVector;
-    APersistableBundle_getDoubleVector; # llndk=202404
     APersistableBundle_getStringVector;
-    APersistableBundle_getStringVector; # llndk=202404
     APersistableBundle_getPersistableBundle;
-    APersistableBundle_getPersistableBundle; # llndk=202404
     APersistableBundle_getBooleanKeys;
-    APersistableBundle_getBooleanKeys; # llndk=202404
     APersistableBundle_getIntKeys;
-    APersistableBundle_getIntKeys; # llndk=202404
     APersistableBundle_getLongKeys;
-    APersistableBundle_getLongKeys; # llndk=202404
     APersistableBundle_getDoubleKeys;
-    APersistableBundle_getDoubleKeys; # llndk=202404
     APersistableBundle_getStringKeys;
-    APersistableBundle_getStringKeys; # llndk=202404
     APersistableBundle_getBooleanVectorKeys;
-    APersistableBundle_getBooleanVectorKeys; # llndk=202404
     APersistableBundle_getIntVectorKeys;
-    APersistableBundle_getIntVectorKeys; # llndk=202404
     APersistableBundle_getLongVectorKeys;
-    APersistableBundle_getLongVectorKeys; # llndk=202404
     APersistableBundle_getDoubleVectorKeys;
-    APersistableBundle_getDoubleVectorKeys; # llndk=202404
     APersistableBundle_getStringVectorKeys;
-    APersistableBundle_getStringVectorKeys; # llndk=202404
     APersistableBundle_getPersistableBundleKeys;
-    APersistableBundle_getPersistableBundleKeys; # llndk=202404
-    AServiceManager_openDeclaredPassthroughHal; # systemapi llndk=202404
+    AServiceManager_openDeclaredPassthroughHal; # systemapi llndk
 };
 
 LIBBINDER_NDK36 { # introduced=36
   global:
     AIBinder_Class_setTransactionCodeToFunctionNameMap;
-    AIBinder_Class_setTransactionCodeToFunctionNameMap; # llndk=202504
     AIBinder_Class_getFunctionName;
-    AIBinder_Class_getFunctionName; # llndk=202504
     ABinderRpc_registerAccessorProvider; # systemapi
     ABinderRpc_unregisterAccessorProvider; # systemapi
     ABinderRpc_Accessor_new; # systemapi
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 14493db..1b24b0a 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -126,7 +126,7 @@
 #[cfg(not(any(trusty, android_ndk)))]
 pub use state::{ProcessState, ThreadState};
 #[cfg(not(any(android_vendor, android_vndk, android_ndk)))]
-pub use system_only::{delegate_accessor, Accessor, ConnectionInfo};
+pub use system_only::{delegate_accessor, Accessor, AccessorProvider, ConnectionInfo};
 
 /// Binder result containing a [`Status`] on error.
 pub type Result<T> = std::result::Result<T, Status>;
diff --git a/libs/binder/rust/src/system_only.rs b/libs/binder/rust/src/system_only.rs
index 9833cbe..1a58d6b 100644
--- a/libs/binder/rust/src/system_only.rs
+++ b/libs/binder/rust/src/system_only.rs
@@ -91,6 +91,32 @@
         Accessor { accessor }
     }
 
+    /// Creates a new Accessor instance based on an existing Accessor's binder.
+    /// This is useful when the Accessor instance is hosted in another process
+    /// that has the permissions to create the socket connection FD.
+    ///
+    /// The `instance` argument must match the instance that the original Accessor
+    /// is responsible for.
+    /// `instance` must not contain null bytes and is used to create a CString to
+    /// pass through FFI.
+    /// The `binder` argument must be a valid binder from an Accessor
+    pub fn from_binder(instance: &str, binder: SpIBinder) -> Option<Accessor> {
+        let inst = CString::new(instance).unwrap();
+
+        // Safety: All `SpIBinder` objects (the `binder` argument) hold a valid pointer
+        // to an `AIBinder` that is guaranteed to remain valid for the lifetime of the
+        // SpIBinder. `ABinderRpc_Accessor_fromBinder` creates a new pointer to that binder
+        // that it is responsible for.
+        // The `inst` argument is a new CString that will copied by
+        // `ABinderRpc_Accessor_fromBinder` and not modified.
+        let accessor =
+            unsafe { sys::ABinderRpc_Accessor_fromBinder(inst.as_ptr(), binder.as_raw()) };
+        if accessor.is_null() {
+            return None;
+        }
+        Some(Accessor { accessor })
+    }
+
     /// Get the underlying binder for this Accessor for when it needs to be either
     /// registered with service manager or sent to another process.
     pub fn as_binder(&self) -> Option<SpIBinder> {
@@ -100,17 +126,36 @@
         unsafe { SpIBinder::from_raw(sys::ABinderRpc_Accessor_asBinder(self.accessor)) }
     }
 
+    /// Release the underlying ABinderRpc_Accessor pointer for use with the ndk API
+    /// This gives up ownership of the ABinderRpc_Accessor and it is the responsibility of
+    /// the caller to delete it with ABinderRpc_Accessor_delete
+    ///
+    /// # Safety
+    ///
+    /// - The returned `ABinderRpc_Accessor` pointer is now owned by the caller, who must
+    ///   call `ABinderRpc_Accessor_delete` to delete the object.
+    /// - This `Accessor` object is now useless after `release` so it can be dropped.
+    unsafe fn release(mut self) -> *mut sys::ABinderRpc_Accessor {
+        if self.accessor.is_null() {
+            log::error!("Attempting to release an Accessor that was already released");
+            return ptr::null_mut();
+        }
+        let ptr = self.accessor;
+        self.accessor = ptr::null_mut();
+        ptr
+    }
+
     /// Callback invoked from C++ when the connection info is needed.
     ///
     /// # Safety
     ///
-    /// The `instance` parameter must be a non-null pointer to a valid C string for
-    /// CStr::from_ptr. The memory must contain a valid null terminator at the end of
-    /// the string within isize::MAX from the pointer. The memory must not be mutated for
-    /// the duration of this function  call and must be valid for reads from the pointer
-    /// to the null terminator.
-    /// The `cookie` parameter must be the cookie for an `Arc<F>` and
-    /// the caller must hold a ref-count to it.
+    /// - The `instance` parameter must be a non-null pointer to a valid C string for
+    ///   CStr::from_ptr. The memory must contain a valid null terminator at the end of
+    ///   the string within isize::MAX from the pointer. The memory must not be mutated for
+    ///   the duration of this function  call and must be valid for reads from the pointer
+    ///   to the null terminator.
+    /// - The `cookie` parameter must be the cookie for an `Arc<F>` and
+    ///   the caller must hold a ref-count to it.
     unsafe extern "C" fn connection_info<F>(
         instance: *const c_char,
         cookie: *mut c_void,
@@ -172,8 +217,8 @@
     ///
     /// # Safety
     ///
-    /// The `cookie` parameter must be the cookie for an `Arc<F>` and
-    /// the owner must give up a ref-count to it.
+    /// - The `cookie` parameter must be the cookie for an `Arc<F>` and
+    ///   the owner must give up a ref-count to it.
     unsafe extern "C" fn cookie_decr_refcount<F>(cookie: *mut c_void)
     where
         F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
@@ -185,6 +230,10 @@
 
 impl Drop for Accessor {
     fn drop(&mut self) {
+        if self.accessor.is_null() {
+            // This Accessor was already released.
+            return;
+        }
         // Safety: `self.accessor` is always a valid, owned
         // `ABinderRpc_Accessor` pointer returned by
         // `ABinderRpc_Accessor_new` when `self` was created. This delete
@@ -218,3 +267,140 @@
     // point, so can be safely passed to `SpIBinder::from_raw`.
     Ok(unsafe { SpIBinder::from_raw(delegator).expect("Expected valid binder at this point") })
 }
+
+/// Rust wrapper around ABinderRpc_AccessorProvider objects for RPC binder service management.
+///
+/// Dropping the `AccessorProvider` will drop/unregister the underlying object.
+#[derive(Debug)]
+pub struct AccessorProvider {
+    accessor_provider: *mut sys::ABinderRpc_AccessorProvider,
+}
+
+/// Safety: A `AccessorProvider` is a wrapper around `ABinderRpc_AccessorProvider` which is
+/// `Sync` and `Send`. As
+/// `ABinderRpc_AccessorProvider` is threadsafe, this structure is too.
+/// The Fn owned the AccessorProvider has `Sync` and `Send` properties
+unsafe impl Send for AccessorProvider {}
+
+/// Safety: A `AccessorProvider` is a wrapper around `ABinderRpc_AccessorProvider` which is
+/// `Sync` and `Send`. As `ABinderRpc_AccessorProvider` is threadsafe, this structure is too.
+/// The Fn owned the AccessorProvider has `Sync` and `Send` properties
+unsafe impl Sync for AccessorProvider {}
+
+impl AccessorProvider {
+    /// Create a new `AccessorProvider` that will give libbinder `Accessors` in order to
+    /// connect to binder services over sockets.
+    ///
+    /// `instances` is a list of all instances that this `AccessorProvider` is responsible for.
+    /// It is declaring these instances as available to this process and will return
+    /// `Accessor` objects for them when libbinder calls the `provider` callback.
+    /// `provider` is the callback that libbinder will call when a service is being requested.
+    /// The callback takes a `&str` argument representing the service that is being requested.
+    /// See the `ABinderRpc_AccessorProvider_getAccessorCallback` for the C++ equivalent.
+    pub fn new<F>(instances: &[String], provider: F) -> Option<AccessorProvider>
+    where
+        F: Fn(&str) -> Option<Accessor> + Send + Sync + 'static,
+    {
+        let callback: *mut c_void = Arc::into_raw(Arc::new(provider)) as *mut c_void;
+        let c_str_instances: Vec<CString> =
+            instances.iter().map(|s| CString::new(s.as_bytes()).unwrap()).collect();
+        let mut c_instances: Vec<*const c_char> =
+            c_str_instances.iter().map(|s| s.as_ptr()).collect();
+        let num_instances: usize = c_instances.len();
+        // Safety:
+        // - The function pointer for the first argument is a valid `get_accessor` callback.
+        // - This call returns an owned `ABinderRpc_AccessorProvider` pointer which
+        //   must be destroyed via `ABinderRpc_unregisterAccessorProvider` when no longer
+        //   needed.
+        // - When the underlying ABinderRpc_AccessorProvider is deleted, it will call
+        //   the `cookie_decr_refcount` callback on the `callback` pointer to release its
+        //   strong ref.
+        // - The `c_instances` vector is not modified by the function
+        let accessor_provider = unsafe {
+            sys::ABinderRpc_registerAccessorProvider(
+                Some(Self::get_accessor::<F>),
+                c_instances.as_mut_ptr(),
+                num_instances,
+                callback,
+                Some(Self::accessor_cookie_decr_refcount::<F>),
+            )
+        };
+
+        if accessor_provider.is_null() {
+            return None;
+        }
+        Some(AccessorProvider { accessor_provider })
+    }
+
+    /// Callback invoked from C++ when an Accessor is needed.
+    ///
+    /// # Safety
+    ///
+    /// - libbinder guarantees the `instance` argument is a valid C string if it's not null.
+    /// - The `cookie` pointer is same pointer that we pass to ABinderRpc_registerAccessorProvider
+    ///   in AccessorProvider.new() which is the closure that we will delete with
+    ///   self.accessor_cookie_decr_refcount when unregistering the AccessorProvider.
+    unsafe extern "C" fn get_accessor<F>(
+        instance: *const c_char,
+        cookie: *mut c_void,
+    ) -> *mut binder_ndk_sys::ABinderRpc_Accessor
+    where
+        F: Fn(&str) -> Option<Accessor> + Send + Sync + 'static,
+    {
+        if cookie.is_null() || instance.is_null() {
+            log::error!("Cookie({cookie:p}) or instance({instance:p}) is null!");
+            return ptr::null_mut();
+        }
+        // Safety: The caller promises that `cookie` is for an Arc<F>.
+        let callback = unsafe { (cookie as *const F).as_ref().unwrap() };
+
+        let inst = {
+            // Safety: The caller in libbinder_ndk will have already verified this is a valid
+            // C string
+            match unsafe { CStr::from_ptr(instance) }.to_str() {
+                Ok(s) => s,
+                Err(err) => {
+                    log::error!("Failed to get a valid C string! {err:?}");
+                    return ptr::null_mut();
+                }
+            }
+        };
+
+        match callback(inst) {
+            Some(a) => {
+                // Safety: This is giving up ownership of this ABinderRpc_Accessor
+                // to the caller of this function (libbinder) and it is responsible
+                // for deleting it.
+                unsafe { a.release() }
+            }
+            None => ptr::null_mut(),
+        }
+    }
+
+    /// Callback that decrements the ref-count.
+    /// This is invoked from C++ when the provider is unregistered.
+    ///
+    /// # Safety
+    ///
+    /// - The `cookie` parameter must be the cookie for an `Arc<F>` and
+    ///   the owner must give up a ref-count to it.
+    unsafe extern "C" fn accessor_cookie_decr_refcount<F>(cookie: *mut c_void)
+    where
+        F: Fn(&str) -> Option<Accessor> + Send + Sync + 'static,
+    {
+        // Safety: The caller promises that `cookie` is for an Arc<F>.
+        unsafe { Arc::decrement_strong_count(cookie as *const F) };
+    }
+}
+
+impl Drop for AccessorProvider {
+    fn drop(&mut self) {
+        // Safety: `self.accessor_provider` is always a valid, owned
+        // `ABinderRpc_AccessorProvider` pointer returned by
+        // `ABinderRpc_registerAccessorProvider` when `self` was created. This delete
+        // method can only be called once when `self` is dropped.
+        unsafe {
+            sys::ABinderRpc_unregisterAccessorProvider(self.accessor_provider);
+        }
+    }
+}
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 489fa0a..da4f128 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -384,8 +384,8 @@
     use std::time::Duration;
 
     use binder::{
-        Accessor, BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder,
-        StatusCode, Strong,
+        Accessor, AccessorProvider, BinderFeatures, DeathRecipient, FromIBinder, IBinder,
+        Interface, SpIBinder, StatusCode, Strong,
     };
     // Import from impl API for testing only, should not be necessary as long as
     // you are using AIDL.
@@ -982,6 +982,90 @@
         assert_eq!(delegator_binder, Err(StatusCode::NAME_NOT_FOUND));
     }
 
+    #[test]
+    fn test_accessor_provider_simple() {
+        let instances: Vec<String> = vec!["foo.service".to_owned(), "foo.other_service".to_owned()];
+        let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+        assert!(accessor.is_some());
+    }
+
+    #[test]
+    fn test_accessor_provider_no_instance() {
+        let instances: Vec<String> = vec![];
+        let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+        assert!(accessor.is_none());
+    }
+
+    #[test]
+    fn test_accessor_provider_double_register() {
+        let instances: Vec<String> = vec!["foo.service".to_owned(), "foo.other_service".to_owned()];
+        let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+        assert!(accessor.is_some());
+        let accessor2 = AccessorProvider::new(&instances, move |_inst: &str| None);
+        assert!(accessor2.is_none());
+    }
+
+    #[test]
+    fn test_accessor_provider_register_drop_register() {
+        let instances: Vec<String> = vec!["foo.service".to_owned(), "foo.other_service".to_owned()];
+        {
+            let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+            assert!(accessor.is_some());
+            // accessor drops and unregisters the provider
+        }
+        {
+            let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+            assert!(accessor.is_some());
+        }
+    }
+
+    #[test]
+    fn test_accessor_provider_callback_destruction() {
+        let deleted: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
+        let instances: Vec<String> = vec!["foo.service".to_owned(), "foo.other_service".to_owned()];
+        {
+            let accessor: Option<AccessorProvider>;
+            {
+                let helper = ToBeDeleted { deleted: deleted.clone() };
+                accessor = AccessorProvider::new(&instances, move |_inst: &str| {
+                    let _ = &helper;
+                    None
+                });
+            }
+            assert!(accessor.is_some());
+            assert!(!deleted.load(Ordering::Relaxed));
+        }
+        assert!(deleted.load(Ordering::Relaxed));
+    }
+
+    #[test]
+    fn test_accessor_from_accessor_binder() {
+        let get_connection_info = move |_instance: &str| None;
+        let accessor = Accessor::new("foo.service", get_connection_info);
+        let accessor2 =
+            Accessor::from_binder("foo.service", accessor.as_binder().unwrap()).unwrap();
+        assert_eq!(accessor.as_binder(), accessor2.as_binder());
+    }
+
+    #[test]
+    fn test_accessor_from_non_accessor_binder() {
+        let service_name = "rust_test_ibinder";
+        let _process = ScopedServiceProcess::new(service_name);
+        let binder = binder::get_service(service_name).unwrap();
+        assert!(binder.is_binder_alive());
+
+        let accessor = Accessor::from_binder("rust_test_ibinder", binder);
+        assert!(accessor.is_none());
+    }
+
+    #[test]
+    fn test_accessor_from_wrong_accessor_binder() {
+        let get_connection_info = move |_instance: &str| None;
+        let accessor = Accessor::new("foo.service", get_connection_info);
+        let accessor2 = Accessor::from_binder("NOT.foo.service", accessor.as_binder().unwrap());
+        assert!(accessor2.is_none());
+    }
+
     #[tokio::test]
     async fn reassociate_rust_binder_async() {
         let service_name = "testing_service";
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 28a3f65..642fbf3 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -70,7 +70,10 @@
     static_libs: [
         "libfakeservicemanager",
     ],
-    defaults: ["libbinder_client_cache_flag"],
+    defaults: [
+        "libbinder_client_cache_flag",
+        "libbinder_addservice_cache_flag",
+    ],
     test_suites: ["general-tests"],
     require_root: true,
 }
diff --git a/libs/binder/tests/binderCacheUnitTest.cpp b/libs/binder/tests/binderCacheUnitTest.cpp
index c5ad793..3195c55 100644
--- a/libs/binder/tests/binderCacheUnitTest.cpp
+++ b/libs/binder/tests/binderCacheUnitTest.cpp
@@ -34,6 +34,12 @@
 constexpr bool kUseLibbinderCache = false;
 #endif
 
+#ifdef LIBBINDER_ADDSERVICE_CACHE
+constexpr bool kUseCacheInAddService = true;
+#else
+constexpr bool kUseCacheInAddService = false;
+#endif
+
 // A service name which is in the static list of cachable services
 const String16 kCachedServiceName = String16("isub");
 
@@ -74,14 +80,58 @@
                 innerSm.addService(String16(name.c_str()), service, allowIsolated, dumpPriority));
     }
 
+    void clearServices() { innerSm.clear(); }
+
     FakeServiceManager innerSm;
 };
 
+class LibbinderCacheAddServiceTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        fakeServiceManager = sp<MockAidlServiceManager>::make();
+        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(fakeServiceManager);
+        mServiceManager->enableAddServiceCache(true);
+    }
+
+    void TearDown() override {}
+
+public:
+    void cacheAddServiceAndConfirmCacheHit(const sp<IBinder>& binder1) {
+        // Add a service. This also caches it.
+        EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder1));
+        // remove services from fakeservicemanager
+        fakeServiceManager->clearServices();
+
+        sp<IBinder> result = mServiceManager->checkService(kCachedServiceName);
+        if (kUseCacheInAddService && kUseLibbinderCache) {
+            // If cache is enabled, we should get the binder.
+            EXPECT_EQ(binder1, result);
+        } else {
+            // If cache is disabled, then we should get the null binder
+            EXPECT_EQ(nullptr, result);
+        }
+    }
+    sp<MockAidlServiceManager> fakeServiceManager;
+    sp<android::IServiceManager> mServiceManager;
+};
+
+TEST_F(LibbinderCacheAddServiceTest, AddLocalServiceAndConfirmCacheHit) {
+    sp<IBinder> binder1 = sp<BBinder>::make();
+    cacheAddServiceAndConfirmCacheHit(binder1);
+}
+
+TEST_F(LibbinderCacheAddServiceTest, AddRemoteServiceAndConfirmCacheHit) {
+    sp<IBinder> binder1 = defaultServiceManager()->checkService(kServerName);
+    ASSERT_NE(binder1, nullptr);
+    cacheAddServiceAndConfirmCacheHit(binder1);
+}
+
 class LibbinderCacheTest : public ::testing::Test {
 protected:
     void SetUp() override {
-        sp<MockAidlServiceManager> sm = sp<MockAidlServiceManager>::make();
-        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(sm);
+        fakeServiceManager = sp<MockAidlServiceManager>::make();
+        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(fakeServiceManager);
+        mServiceManager->enableAddServiceCache(false);
     }
 
     void TearDown() override {}
@@ -108,6 +158,7 @@
         }
     }
 
+    sp<MockAidlServiceManager> fakeServiceManager;
     sp<android::IServiceManager> mServiceManager;
 };
 
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 970852c..9e92f95 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -223,6 +223,8 @@
             ASSERT_GT(m_serverpid, 0);
 
             sp<IServiceManager> sm = defaultServiceManager();
+            // disable caching during addService.
+            sm->enableAddServiceCache(false);
             //printf("%s: pid %d, get service\n", __func__, m_pid);
             LIBBINDER_IGNORE("-Wdeprecated-declarations")
             m_server = sm->getService(binderLibTestServiceName);
@@ -298,6 +300,9 @@
         virtual void SetUp() {
             m_server = static_cast<BinderLibTestEnv *>(binder_env)->getServer();
             IPCThreadState::self()->restoreCallingWorkSource(0);
+            sp<IServiceManager> sm = defaultServiceManager();
+            // disable caching during addService.
+            sm->enableAddServiceCache(false);
         }
         virtual void TearDown() {
         }
@@ -1765,6 +1770,7 @@
 
 TEST(ServiceNotifications, Unregister) {
     auto sm = defaultServiceManager();
+    sm->enableAddServiceCache(false);
     using LocalRegistrationCallback = IServiceManager::LocalRegistrationCallback;
     class LocalRegistrationCallbackImpl : public virtual LocalRegistrationCallback {
         void onServiceRegistration(const String16 &, const sp<IBinder> &) override {}
@@ -2529,6 +2535,8 @@
 
     status_t ret;
     sp<IServiceManager> sm = defaultServiceManager();
+    sm->enableAddServiceCache(false);
+
     BinderLibTestService* testServicePtr;
     {
         sp<BinderLibTestService> testService = new BinderLibTestService(index);
diff --git a/libs/binder/tests/binderParcelUnitTest.cpp b/libs/binder/tests/binderParcelUnitTest.cpp
index 32a70e5..6259d9d 100644
--- a/libs/binder/tests/binderParcelUnitTest.cpp
+++ b/libs/binder/tests/binderParcelUnitTest.cpp
@@ -33,6 +33,38 @@
 using android::binder::Status;
 using android::binder::unique_fd;
 
+static void checkCString(const char* str) {
+    for (size_t i = 0; i < 3; i++) {
+        Parcel p;
+
+        for (size_t j = 0; j < i; j++) p.writeInt32(3);
+
+        p.writeCString(str);
+        int32_t pos = p.dataPosition();
+
+        p.setDataPosition(0);
+
+        for (size_t j = 0; j < i; j++) p.readInt32();
+        const char* str2 = p.readCString();
+
+        ASSERT_EQ(std::string(str), str2);
+        ASSERT_EQ(pos, p.dataPosition());
+    }
+}
+
+TEST(Parcel, TestReadCString) {
+    // we should remove the *CString APIs, but testing them until
+    // they are deleted.
+    checkCString("");
+    checkCString("a");
+    checkCString("\n");
+    checkCString("32");
+    checkCString("321");
+    checkCString("3210");
+    checkCString("3210b");
+    checkCString("123434");
+}
+
 TEST(Parcel, NonNullTerminatedString8) {
     String8 kTestString = String8("test-is-good");
 
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 506fc71..da5a8e3 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -1400,6 +1400,26 @@
     EXPECT_TRUE(isDeleted);
 }
 
+TEST_F(BinderARpcNdk, ARpcProviderDeleteOnError) {
+    bool isDeleted = false;
+    AccessorProviderData* data = new AccessorProviderData{{}, 0, &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices, 0, data,
+                                                accessorProviderDataOnDelete);
+
+    ASSERT_EQ(provider, nullptr);
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcProvideOnErrorNoDeleteCbNoCrash) {
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices, 0, nullptr,
+                                                nullptr);
+
+    ASSERT_EQ(provider, nullptr);
+}
+
 TEST_F(BinderARpcNdk, ARpcProviderDuplicateInstance) {
     const char* instance = "some.instance.name.IFoo/default";
     const uint32_t numInstances = 2;
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
index 690c39a..4008c56 100644
--- a/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
@@ -59,6 +59,11 @@
         darwin: {
             enabled: false,
         },
+        host: {
+            data_libs: [
+                "libc++",
+            ],
+        },
     },
     test_suites: ["general-tests"],
 }
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh b/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
index 5d68fe1..b623c5f 100755
--- a/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
@@ -39,6 +39,7 @@
     else
         echo -e "${color_failed}Failed: Unable to find successful fuzzing output from test_service_fuzzer_should_crash"
         echo "${color_reset}"
+        cat "$FUZZER_OUT"
         exit 1
     fi
 done
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 17919c2..0580046 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -151,8 +151,10 @@
 
 int RpcServerTrusty::handleMessageInternal(void* ctx) {
     auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
-    LOG_ALWAYS_FATAL_IF(channelContext == nullptr,
-                        "bad state: message received on uninitialized channel");
+    if (channelContext == nullptr) {
+        LOG_RPC_DETAIL("bad state: message received on uninitialized channel");
+        return ERR_BAD_STATE;
+    }
 
     auto& session = channelContext->session;
     auto& connection = channelContext->connection;
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
index f62241d..f2b2aa7 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
@@ -65,6 +65,7 @@
 
     std::vector<IServiceManager::ServiceDebugInfo> getServiceDebugInfo() override;
 
+    void enableAddServiceCache(bool /*value*/) override {}
     // Clear all of the registered services
     void clear();
 
diff --git a/libs/ftl/flags_test.cpp b/libs/ftl/flags_test.cpp
index 1279d11..bb43e8d 100644
--- a/libs/ftl/flags_test.cpp
+++ b/libs/ftl/flags_test.cpp
@@ -17,7 +17,7 @@
 #include <ftl/flags.h>
 #include <gtest/gtest.h>
 
-#include <type_traits>
+#include <initializer_list>
 
 namespace android::test {
 
@@ -59,6 +59,18 @@
     ASSERT_FALSE(flags.all(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE));
 }
 
+TEST(Flags, ImplicitConstructionAndAssignmentFromInitializerList) {
+    Flags<TestFlags> flags = {TestFlags::ONE, TestFlags::THREE};
+    ASSERT_TRUE(flags.test(TestFlags::ONE));
+    ASSERT_FALSE(flags.test(TestFlags::TWO));
+    ASSERT_TRUE(flags.test(TestFlags::THREE));
+
+    flags = {};
+    ASSERT_FALSE(flags.test(TestFlags::ONE));
+    ASSERT_FALSE(flags.test(TestFlags::TWO));
+    ASSERT_FALSE(flags.test(TestFlags::THREE));
+}
+
 TEST(Flags, DefaultConstructor_hasNoFlagsSet) {
     Flags<TestFlags> flags;
     ASSERT_FALSE(flags.any(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE));
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 1e33abb..052b519 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -264,6 +264,7 @@
         "DisplayEventDispatcher.cpp",
         "DisplayEventReceiver.cpp",
         "FenceMonitor.cpp",
+        "Flags.cpp",
         "GLConsumer.cpp",
         "IConsumerListener.cpp",
         "IGraphicBufferConsumer.cpp",
diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp
index bfe3d6e..8566419 100644
--- a/libs/gui/BufferItemConsumer.cpp
+++ b/libs/gui/BufferItemConsumer.cpp
@@ -140,7 +140,7 @@
         BI_LOGE("Failed to addReleaseFenceLocked");
     }
 
-    err = releaseBufferLocked(slotIndex, buffer, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+    err = releaseBufferLocked(slotIndex, buffer);
     if (err != OK && err != IGraphicBufferConsumer::STALE_BUFFER_SLOT) {
         BI_LOGE("Failed to release buffer: %s (%d)",
                 strerror(-err), err);
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index d0607bf..9855b5b 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -28,6 +28,10 @@
 #define VALIDATE_CONSISTENCY()
 #endif
 
+#define EGL_EGLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
@@ -486,6 +490,27 @@
         return BAD_VALUE;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    if (eglFence != EGL_NO_SYNC_KHR) {
+        // Most platforms will be using native fences, so it's unlikely that we'll ever have to
+        // process an eglFence. Ideally we can remove this code eventually. In the mean time, do our
+        // best to wait for it so the buffer stays valid, otherwise return an error to the caller.
+        //
+        // EGL_SYNC_FLUSH_COMMANDS_BIT_KHR so that we don't wait forever on a fence that hasn't
+        // shown up on the GPU yet.
+        EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
+                                             1000000000);
+        if (result == EGL_FALSE) {
+            BQ_LOGE("releaseBuffer: error %#x waiting for fence", eglGetError());
+            return UNKNOWN_ERROR;
+        } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+            BQ_LOGE("releaseBuffer: timeout waiting for fence");
+            return UNKNOWN_ERROR;
+        }
+        eglDestroySyncKHR(eglDisplay, eglFence);
+    }
+#endif
+
     sp<IProducerListener> listener;
     { // Autolock scope
         std::lock_guard<std::mutex> lock(mCore->mMutex);
@@ -507,8 +532,10 @@
             return BAD_VALUE;
         }
 
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
         mSlots[slot].mEglDisplay = eglDisplay;
         mSlots[slot].mEglFence = eglFence;
+#endif
         mSlots[slot].mFence = releaseFence;
         mSlots[slot].mBufferState.release();
 
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index d52cf70..5a09399 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -262,14 +262,16 @@
     mSlots[slot].mFrameNumber = 0;
     mSlots[slot].mAcquireCalled = false;
     mSlots[slot].mNeedsReallocation = true;
+    mSlots[slot].mFence = Fence::NO_FENCE;
 
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
     // Destroy fence as BufferQueue now takes ownership
     if (mSlots[slot].mEglFence != EGL_NO_SYNC_KHR) {
         eglDestroySyncKHR(mSlots[slot].mEglDisplay, mSlots[slot].mEglFence);
         mSlots[slot].mEglFence = EGL_NO_SYNC_KHR;
     }
-    mSlots[slot].mFence = Fence::NO_FENCE;
     mSlots[slot].mEglDisplay = EGL_NO_DISPLAY;
+#endif
 
     if (mLastQueuedSlot == slot) {
         mLastQueuedSlot = INVALID_BUFFER_SLOT;
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 39209f9..2e7cef0 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -451,8 +451,10 @@
     }
 
     status_t returnFlags = NO_ERROR;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
     EGLDisplay eglDisplay = EGL_NO_DISPLAY;
     EGLSyncKHR eglFence = EGL_NO_SYNC_KHR;
+#endif
     bool attachedByConsumer = false;
 
     sp<IConsumerListener> listener;
@@ -569,8 +571,10 @@
             mSlots[found].mAcquireCalled = false;
             mSlots[found].mGraphicBuffer = nullptr;
             mSlots[found].mRequestBufferCalled = false;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
             mSlots[found].mEglDisplay = EGL_NO_DISPLAY;
             mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
+#endif
             mSlots[found].mFence = Fence::NO_FENCE;
             mCore->mBufferAge = 0;
             mCore->mIsAllocating = true;
@@ -595,14 +599,18 @@
                     found, buffer->width, buffer->height, buffer->format);
         }
 
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
         eglDisplay = mSlots[found].mEglDisplay;
         eglFence = mSlots[found].mEglFence;
+#endif
         // Don't return a fence in shared buffer mode, except for the first
         // frame.
         *outFence = (mCore->mSharedBufferMode &&
                 mCore->mSharedBufferSlot == found) ?
                 Fence::NO_FENCE : mSlots[found].mFence;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
         mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
+#endif
         mSlots[found].mFence = Fence::NO_FENCE;
 
         // If shared buffer mode has just been enabled, cache the slot of the
@@ -691,6 +699,7 @@
         returnFlags |= BUFFER_NEEDS_REALLOCATION;
     }
 
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
     if (eglFence != EGL_NO_SYNC_KHR) {
         EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, 0,
                 1000000000);
@@ -705,6 +714,7 @@
         }
         eglDestroySyncKHR(eglDisplay, eglFence);
     }
+#endif
 
     BQ_LOGV("dequeueBuffer: returning slot=%d/%" PRIu64 " buf=%p flags=%#x",
             *outSlot,
@@ -908,7 +918,9 @@
 
     mSlots[*outSlot].mGraphicBuffer = buffer;
     mSlots[*outSlot].mBufferState.attachProducer();
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
     mSlots[*outSlot].mEglFence = EGL_NO_SYNC_KHR;
+#endif
     mSlots[*outSlot].mFence = Fence::NO_FENCE;
     mSlots[*outSlot].mRequestBufferCalled = true;
     mSlots[*outSlot].mAcquireCalled = false;
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 0c8f3fa..ba50bf8 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -369,6 +369,10 @@
           this, to_string(displayId).c_str(), connectedLevel, maxLevel);
 }
 
+void Choreographer::dispatchModeRejected(PhysicalDisplayId, int32_t) {
+    LOG_ALWAYS_FATAL("dispatchModeRejected was called but was never registered");
+}
+
 void Choreographer::handleMessage(const Message& message) {
     switch (message.what) {
         case MSG_SCHEDULE_CALLBACKS:
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index c46f9c5..68f10f4 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -211,6 +211,9 @@
                                               ev.hdcpLevelsChange.connectedLevel,
                                               ev.hdcpLevelsChange.maxLevel);
                     break;
+                case DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION:
+                    dispatchModeRejected(ev.header.displayId, ev.modeRejection.modeId);
+                    break;
                 default:
                     ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
                     break;
diff --git a/libs/gui/Flags.cpp b/libs/gui/Flags.cpp
new file mode 100644
index 0000000..85ee2cd
--- /dev/null
+++ b/libs/gui/Flags.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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 <gui/Flags.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
+#include <gui/view/Surface.h>
+
+namespace android {
+namespace flagtools {
+sp<SurfaceType> surfaceToSurfaceType(const sp<Surface>& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    return surface;
+#else
+    return surface->getIGraphicBufferProducer();
+#endif
+}
+
+sp<IGraphicBufferProducer> surfaceTypeToIGBP(const sp<SurfaceType>& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    return surface->getIGraphicBufferProducer();
+#else
+    return surface;
+#endif
+}
+
+bool isSurfaceTypeValid(const sp<SurfaceType>& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    return Surface::isValid(surface);
+#else
+    return surface != nullptr;
+#endif
+}
+
+ParcelableSurfaceType toParcelableSurfaceType(const view::Surface& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    return surface;
+#else
+    return surface.graphicBufferProducer;
+#endif
+}
+
+ParcelableSurfaceType convertSurfaceTypeToParcelable(sp<SurfaceType> surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    return view::Surface::fromSurface(surface);
+#else
+    return surface;
+#endif
+}
+
+sp<SurfaceType> convertParcelableSurfaceTypeToSurface(const ParcelableSurfaceType& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    return surface.toSurface();
+#else
+    return surface;
+#endif
+}
+
+} // namespace flagtools
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
index 01aa7ed..5c4879c 100644
--- a/libs/gui/FrameRateUtils.cpp
+++ b/libs/gui/FrameRateUtils.cpp
@@ -42,7 +42,7 @@
 
     if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
         compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
-        compatibility != ANATIVEWINDOW_FRAME_RATE_GTE &&
+        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE &&
         (!privileged ||
          (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
           compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp
index 95cce5c..f2173cd 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -314,7 +314,7 @@
             // so... basically, nothing more to do here.
         }
 
-        err = releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+        err = releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer);
         if (err < NO_ERROR) {
             GLC_LOGE("releaseTexImage: failed to release buffer: %s (%d)",
                     strerror(-err), err);
@@ -418,16 +418,14 @@
     if (!mAttached) {
         GLC_LOGE("updateAndRelease: GLConsumer is not attached to an OpenGL "
                 "ES context");
-        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
-                mEglDisplay, EGL_NO_SYNC_KHR);
+        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
         return INVALID_OPERATION;
     }
 
     // Confirm state.
     err = checkAndUpdateEglStateLocked();
     if (err != NO_ERROR) {
-        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
-                mEglDisplay, EGL_NO_SYNC_KHR);
+        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
         return err;
     }
 
@@ -440,8 +438,7 @@
     if (err != NO_ERROR) {
         GLC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d",
                 mEglDisplay, slot);
-        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
-                mEglDisplay, EGL_NO_SYNC_KHR);
+        releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
         return UNKNOWN_ERROR;
     }
 
@@ -453,8 +450,7 @@
             // release the old buffer, so instead we just drop the new frame.
             // As we are still under lock since acquireBuffer, it is safe to
             // release by slot.
-            releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
-                    mEglDisplay, EGL_NO_SYNC_KHR);
+            releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
             return err;
         }
     }
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
index c705d39..282957b 100644
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ b/libs/gui/IGraphicBufferConsumer.cpp
@@ -26,6 +26,7 @@
 
 #include <utils/NativeHandle.h>
 #include <utils/String8.h>
+#include <cstdint>
 
 namespace android {
 
@@ -84,7 +85,8 @@
                            EGLDisplay display __attribute__((unused)),
                            EGLSyncKHR fence __attribute__((unused)),
                            const sp<Fence>& releaseFence) override {
-        return callRemote<ReleaseBuffer>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence);
+        using Signature = status_t (IGraphicBufferConsumer::*)(int, uint64_t, const sp<Fence>&);
+        return callRemote<Signature>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence);
     }
 
     status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) override {
@@ -188,8 +190,10 @@
             return callLocal(data, reply, &IGraphicBufferConsumer::detachBuffer);
         case Tag::ATTACH_BUFFER:
             return callLocal(data, reply, &IGraphicBufferConsumer::attachBuffer);
-        case Tag::RELEASE_BUFFER:
-            return callLocal(data, reply, &IGraphicBufferConsumer::releaseHelper);
+        case Tag::RELEASE_BUFFER: {
+            using Signature = status_t (IGraphicBufferConsumer::*)(int, uint64_t, const sp<Fence>&);
+            return callLocal<Signature>(data, reply, &IGraphicBufferConsumer::releaseBuffer);
+        }
         case Tag::CONSUMER_CONNECT:
             return callLocal(data, reply, &IGraphicBufferConsumer::consumerConnect);
         case Tag::CONSUMER_DISCONNECT:
diff --git a/libs/gui/StreamSplitter.cpp b/libs/gui/StreamSplitter.cpp
index 2f8e104..653b91b 100644
--- a/libs/gui/StreamSplitter.cpp
+++ b/libs/gui/StreamSplitter.cpp
@@ -234,8 +234,7 @@
     LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
             "attaching buffer to input failed (%d)", status);
 
-    status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, tracker->getMergedFence());
+    status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0, tracker->getMergedFence());
     LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
             "releasing buffer to input failed (%d)", status);
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 61aabaa..be88b11 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2873,6 +2873,11 @@
     outInfo->hasArrSupport = ginfo.hasArrSupport;
     outInfo->frameRateCategoryRate = ui::FrameRateCategoryRate(ginfo.frameRateCategoryRate.normal,
                                                                ginfo.frameRateCategoryRate.high);
+    outInfo->supportedRefreshRates.clear();
+    outInfo->supportedRefreshRates.reserve(ginfo.supportedRefreshRates.size());
+    for (const auto rate : ginfo.supportedRefreshRates) {
+        outInfo->supportedRefreshRates.push_back(static_cast<float>(rate));
+    }
 }
 
 status_t SurfaceComposerClient::getDynamicDisplayInfoFromId(int64_t displayId,
@@ -3056,6 +3061,14 @@
     ComposerServiceAIDL::getComposerService()->setPowerMode(token, mode);
 }
 
+status_t SurfaceComposerClient::getMaxLayerPictureProfiles(const sp<IBinder>& token,
+                                                           int32_t* outMaxProfiles) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getMaxLayerPictureProfiles(token,
+                                                                                  outMaxProfiles);
+    return statusTFromBinderStatus(status);
+}
+
 status_t SurfaceComposerClient::getCompositionPreference(
         ui::Dataspace* defaultDataspace, ui::PixelFormat* defaultPixelFormat,
         ui::Dataspace* wideColorGamutDataspace, ui::PixelFormat* wideColorGamutPixelFormat) {
@@ -3280,6 +3293,13 @@
     return statusTFromBinderStatus(status);
 }
 
+status_t SurfaceComposerClient::setActivePictureListener(
+        const sp<gui::IActivePictureListener>& listener) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->setActivePictureListener(listener);
+    return statusTFromBinderStatus(status);
+}
+
 status_t SurfaceComposerClient::notifyPowerBoost(int32_t boostId) {
     binder::Status status = ComposerServiceAIDL::getComposerService()->notifyPowerBoost(boostId);
     return statusTFromBinderStatus(status);
diff --git a/libs/gui/aidl/android/gui/ActivePicture.aidl b/libs/gui/aidl/android/gui/ActivePicture.aidl
new file mode 100644
index 0000000..9b83be1
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ActivePicture.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+/**
+ * Visible content that is using picture processing.
+ * @hide
+ */
+parcelable ActivePicture {
+    /** The layer ID that is using picture processing. */
+    int layerId;
+
+    /** UID that owns layer using picture processing. */
+    int ownerUid;
+
+    /** ID of the picture profile that was used to configure the picture processing. */
+    long pictureProfileId;
+}
diff --git a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
index 67cc273..26c12c5 100644
--- a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
+++ b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
@@ -50,4 +50,7 @@
 
     // Represents frame rate for FrameRateCategory Normal and High.
     FrameRateCategoryRate frameRateCategoryRate;
+
+    // All the refresh rates supported for the default display mode.
+    float[] supportedRefreshRates;
 }
diff --git a/libs/gui/aidl/android/gui/IActivePictureListener.aidl b/libs/gui/aidl/android/gui/IActivePictureListener.aidl
new file mode 100644
index 0000000..ad310bb
--- /dev/null
+++ b/libs/gui/aidl/android/gui/IActivePictureListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+import android.gui.ActivePicture;
+
+/**
+ * Receive callbacks whenever the visible content using picture profiles changes.
+ * @hide
+ */
+interface IActivePictureListener {
+    /**
+     * Callback reporting the visible content on the screen using picture profiles.
+     */
+    oneway void onActivePicturesChanged(in ActivePicture[] activePictures);
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index ac14138..8c19bbb 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -33,6 +33,7 @@
 import android.gui.FrameStats;
 import android.gui.HdrConversionCapability;
 import android.gui.HdrConversionStrategy;
+import android.gui.IActivePictureListener;
 import android.gui.IDisplayEventConnection;
 import android.gui.IFpsListener;
 import android.gui.IHdrLayerInfoListener;
@@ -228,6 +229,11 @@
     void setGameContentType(IBinder display, boolean on);
 
     /**
+     * Gets the maximum number of picture profiles supported by the display.
+     */
+    int getMaxLayerPictureProfiles(IBinder display);
+
+    /**
      * Capture the specified screen. This requires READ_FRAME_BUFFER
      * permission.  This function will fail if there is a secure window on
      * screen and DisplayCaptureArgs.captureSecureLayers is false.
@@ -599,4 +605,10 @@
      * past the provided VSync.
      */
     oneway void removeJankListener(int layerId, IJankListener listener, long afterVsync);
+
+    /**
+     * Sets the listener used to monitor visible content that is being processed with picture
+     * profiles.
+     */
+    oneway void setActivePictureListener(IActivePictureListener listener);
 }
diff --git a/libs/gui/aidl/android/gui/LutProperties.aidl b/libs/gui/aidl/android/gui/LutProperties.aidl
index 87b878c..84c7013 100644
--- a/libs/gui/aidl/android/gui/LutProperties.aidl
+++ b/libs/gui/aidl/android/gui/LutProperties.aidl
@@ -27,6 +27,6 @@
 
     int size;
     @Backing(type="int")
-    enum SamplingKey { RGB, MAX_RGB }
+    enum SamplingKey { RGB, MAX_RGB, CIE_Y }
     SamplingKey[] samplingKeys;
 }
\ No newline at end of file
diff --git a/libs/gui/include/gui/BufferSlot.h b/libs/gui/include/gui/BufferSlot.h
index 5b32710..e83d2e3 100644
--- a/libs/gui/include/gui/BufferSlot.h
+++ b/libs/gui/include/gui/BufferSlot.h
@@ -174,26 +174,30 @@
 };
 
 struct BufferSlot {
-
     BufferSlot()
-    : mGraphicBuffer(nullptr),
-      mEglDisplay(EGL_NO_DISPLAY),
-      mBufferState(),
-      mRequestBufferCalled(false),
-      mFrameNumber(0),
-      mEglFence(EGL_NO_SYNC_KHR),
-      mFence(Fence::NO_FENCE),
-      mAcquireCalled(false),
-      mNeedsReallocation(false) {
+          : mGraphicBuffer(nullptr),
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+            mEglDisplay(EGL_NO_DISPLAY),
+#endif
+            mBufferState(),
+            mRequestBufferCalled(false),
+            mFrameNumber(0),
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+            mEglFence(EGL_NO_SYNC_KHR),
+#endif
+            mFence(Fence::NO_FENCE),
+            mAcquireCalled(false),
+            mNeedsReallocation(false) {
     }
 
     // mGraphicBuffer points to the buffer allocated for this slot or is NULL
     // if no buffer has been allocated.
     sp<GraphicBuffer> mGraphicBuffer;
 
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
     // mEglDisplay is the EGLDisplay used to create EGLSyncKHR objects.
     EGLDisplay mEglDisplay;
-
+#endif
     // mBufferState is the current state of this buffer slot.
     BufferState mBufferState;
 
@@ -207,12 +211,14 @@
     // may be released before their release fence is signaled).
     uint64_t mFrameNumber;
 
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
     // mEglFence is the EGL sync object that must signal before the buffer
     // associated with this buffer slot may be dequeued. It is initialized
     // to EGL_NO_SYNC_KHR when the buffer is created and may be set to a
     // new sync object in releaseBuffer.  (This is deprecated in favor of
     // mFence, below.)
     EGLSyncKHR mEglFence;
+#endif
 
     // mFence is a fence which will signal when work initiated by the
     // previous owner of the buffer is finished. When the buffer is FREE,
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 2e5aa4a..a93ba14 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -127,6 +127,7 @@
                                     std::vector<FrameRateOverride> overrides) override;
     void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                                    int32_t maxLevel) override;
+    void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) override;
 
     void scheduleCallbacks();
 
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index 82cd50c..b06ad07 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -68,6 +68,8 @@
     virtual void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                                            int32_t maxLevel) = 0;
 
+    virtual void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) = 0;
+
     bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
                               uint32_t* outCount, VsyncEventData* outVsyncEventData);
 
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 40a6e79..ab6a6b7 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -62,6 +62,7 @@
         DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
         DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
         DISPLAY_EVENT_MODE_CHANGE = fourcc('m', 'o', 'd', 'e'),
+        DISPLAY_EVENT_MODE_REJECTION = fourcc('r', 'e', 'j', 'e'),
         DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
@@ -96,6 +97,10 @@
             nsecs_t vsyncPeriod __attribute__((aligned(8)));
         };
 
+        struct ModeRejection {
+            int32_t modeId;
+        };
+
         struct FrameRateOverride {
             uid_t uid __attribute__((aligned(8)));
             float frameRateHz __attribute__((aligned(8)));
@@ -117,6 +122,7 @@
             ModeChange modeChange;
             FrameRateOverride frameRateOverride;
             HdcpLevelsChange hdcpLevelsChange;
+            ModeRejection modeRejection;
         };
     };
     static_assert(sizeof(Event) == 224);
diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/include/gui/Flags.h
index 34350d2..845bc54 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/libs/gui/include/gui/Flags.h
@@ -17,8 +17,15 @@
 #pragma once
 
 #include <com_android_graphics_libgui_flags.h>
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/Surface.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class IGraphicBufferProducer;
+class Surface;
+namespace view {
+class Surface;
+}
 
 #define WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES                  \
     (COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CAMERA3_AND_PROCESSORS) && \
@@ -31,6 +38,19 @@
 
 #if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
 typedef android::Surface SurfaceType;
+typedef android::view::Surface ParcelableSurfaceType;
 #else
 typedef android::IGraphicBufferProducer SurfaceType;
-#endif
\ No newline at end of file
+typedef android::sp<android::IGraphicBufferProducer> ParcelableSurfaceType;
+#endif
+
+namespace flagtools {
+sp<SurfaceType> surfaceToSurfaceType(const sp<Surface>& surface);
+ParcelableSurfaceType toParcelableSurfaceType(const view::Surface& surface);
+sp<IGraphicBufferProducer> surfaceTypeToIGBP(const sp<SurfaceType>& surface);
+bool isSurfaceTypeValid(const sp<SurfaceType>& surface);
+ParcelableSurfaceType convertSurfaceTypeToParcelable(sp<SurfaceType> surface);
+sp<SurfaceType> convertParcelableSurfaceTypeToSurface(const ParcelableSurfaceType& surface);
+} // namespace flagtools
+
+} // namespace android
diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h
index bfe3eb3..8a66dc0 100644
--- a/libs/gui/include/gui/GLConsumer.h
+++ b/libs/gui/include/gui/GLConsumer.h
@@ -268,9 +268,9 @@
 
     // releaseBufferLocked overrides the ConsumerBase method to update the
     // mEglSlots array in addition to the ConsumerBase.
-    virtual status_t releaseBufferLocked(int slot,
-            const sp<GraphicBuffer> graphicBuffer,
-            EGLDisplay display, EGLSyncKHR eglFence) override;
+    virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
+                                         EGLDisplay display = EGL_NO_DISPLAY,
+                                         EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override;
 
     status_t releaseBufferLocked(int slot,
             const sp<GraphicBuffer> graphicBuffer, EGLSyncKHR eglFence) {
diff --git a/libs/gui/include/gui/IGraphicBufferConsumer.h b/libs/gui/include/gui/IGraphicBufferConsumer.h
index 0b92e7d..18f5488 100644
--- a/libs/gui/include/gui/IGraphicBufferConsumer.h
+++ b/libs/gui/include/gui/IGraphicBufferConsumer.h
@@ -137,16 +137,9 @@
     virtual status_t releaseBuffer(int buf, uint64_t frameNumber, EGLDisplay display,
                                    EGLSyncKHR fence, const sp<Fence>& releaseFence) = 0;
 
-    status_t releaseHelper(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
+    status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
         return releaseBuffer(buf, frameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, releaseFence);
     }
-    // This is explicitly *not* the actual signature of IGBC::releaseBuffer, but:
-    //     1) We have no easy way to send the EGL objects across Binder
-    //     2) This has always been broken, probably because
-    //     3) IGBC is rarely remoted
-    // For now, we will choose to bury our heads in the sand and ignore this problem until such time
-    // as we can finally finish converting away from EGL sync to native Android sync
-    using ReleaseBuffer = decltype(&IGraphicBufferConsumer::releaseHelper);
 
     // consumerConnect connects a consumer to the BufferQueue. Only one consumer may be connected,
     // and when that consumer disconnects the BufferQueue is placed into the "abandoned" state,
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 0d7f8c2..0ce0c0a 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -298,6 +298,8 @@
     static status_t removeHdrLayerInfoListener(const sp<IBinder>& displayToken,
                                                const sp<gui::IHdrLayerInfoListener>& listener);
 
+    static status_t setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+
     /*
      * Sends a power boost to the composer. This function is asynchronous.
      *
@@ -343,6 +345,15 @@
 
     static bool flagEdgeExtensionEffectUseShader();
 
+    /**
+     * Returns how many picture profiles are supported by the display.
+     *
+     * displayToken
+     *      The token of the display.
+     */
+    static status_t getMaxLayerPictureProfiles(const sp<IBinder>& displayToken,
+                                               int32_t* outMaxProfiles);
+
     // ------------------------------------------------------------------------
     // surface creation / destruction
 
diff --git a/libs/gui/include/gui/view/Surface.h b/libs/gui/include/gui/view/Surface.h
index 7c762d3..bd8704d 100644
--- a/libs/gui/include/gui/view/Surface.h
+++ b/libs/gui/include/gui/view/Surface.h
@@ -54,6 +54,22 @@
     sp<android::Surface> toSurface() const;
 
     status_t getUniqueId(/* out */ uint64_t* id) const;
+
+    bool isEmpty() const;
+
+    bool operator==(const Surface& other) const {
+        return graphicBufferProducer == other.graphicBufferProducer;
+    }
+    bool operator!=(const Surface& other) const { return !(*this == other); }
+    bool operator==(const sp<android::Surface> other) const {
+        if (other == nullptr) return graphicBufferProducer == nullptr;
+        return graphicBufferProducer == other->getIGraphicBufferProducer();
+    }
+    bool operator!=(const sp<android::Surface> other) const { return !(*this == other); }
+    bool operator<(const Surface& other) const {
+        return graphicBufferProducer < other.graphicBufferProducer;
+    }
+    bool operator>(const Surface& other) const { return other < *this; }
 #endif
 
     virtual status_t writeToParcel(Parcel* parcel) const override;
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index b026e64..1606099 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -425,8 +425,7 @@
     ASSERT_EQ(BAD_VALUE, mConsumer->attachBuffer(&newSlot, nullptr));
     ASSERT_EQ(OK, mConsumer->attachBuffer(&newSlot, item.mGraphicBuffer));
 
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, EGL_NO_DISPLAY,
-            EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, Fence::NO_FENCE));
 
     ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
               mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -609,8 +608,7 @@
     ASSERT_EQ(true, item.mQueuedBuffer);
     ASSERT_EQ(false, item.mAutoRefresh);
 
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // attempt to acquire a second time should return no buffer available
     ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
@@ -653,8 +651,7 @@
         ASSERT_EQ(i == 0, item.mQueuedBuffer);
         ASSERT_EQ(true, item.mAutoRefresh);
 
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     }
 
     // Repeatedly queue and dequeue a buffer from the producer side, it should
@@ -684,8 +681,7 @@
         ASSERT_EQ(i == 0, item.mQueuedBuffer);
         ASSERT_EQ(true, item.mAutoRefresh);
 
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     }
 }
 
@@ -735,8 +731,7 @@
     ASSERT_EQ(true, item.mQueuedBuffer);
     ASSERT_EQ(false, item.mAutoRefresh);
 
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // attempt to acquire a second time should return no buffer available
     ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
@@ -874,8 +869,7 @@
     for (size_t i = 0; i < 2; ++i) {
         BufferItem item;
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                    EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     }
 
     // Make sure we got the second buffer back
@@ -929,8 +923,7 @@
                                            nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
         std::this_thread::sleep_for(16ms);
     }
 
@@ -946,8 +939,7 @@
                                            nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
         std::this_thread::sleep_for(16ms);
     }
     ASSERT_EQ(OK,
@@ -959,12 +951,10 @@
                                        nullptr));
     ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     std::this_thread::sleep_for(16ms);
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // Sleep between segments
     std::this_thread::sleep_for(500ms);
@@ -981,13 +971,11 @@
                                            nullptr, nullptr));
         ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
         ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                    EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
         std::this_thread::sleep_for(16ms);
     }
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // Now we read the segments
     std::vector<OccupancyTracker::Segment> history;
@@ -1108,8 +1096,7 @@
 
     // Acquire and free 1 buffer
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-                    EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
     int releasedSlot = item.mSlot;
 
     // Acquire 1 buffer, leaving 1 filled buffer in queue
@@ -1376,8 +1363,7 @@
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
     ASSERT_EQ(slot, item.mSlot);
     ASSERT_NE(nullptr, item.mGraphicBuffer.get());
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // Dequeue and queue the buffer again
     ASSERT_EQ(OK,
@@ -1390,8 +1376,7 @@
     ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
     ASSERT_EQ(slot, item.mSlot);
     ASSERT_EQ(nullptr, item.mGraphicBuffer.get());
-    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // Dequeue and queue the buffer again
     ASSERT_EQ(OK,
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 0e84d68..dcda1ee 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -75,7 +75,7 @@
     if (input == nullptr) {
         ALOGE("Failed to link to input service");
     } else {
-        ALOGE("Linked to input");
+        ALOGI("Linked to input");
     }
     return interface_cast<IInputFlinger>(input);
 }
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
index 04bfb28..5d3287d 100644
--- a/libs/gui/tests/FrameRateUtilsTest.cpp
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -34,7 +34,7 @@
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
     EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_GTE,
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
     // Privileged APIs.
diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp
index f34b03e..1c439cd 100644
--- a/libs/gui/tests/StreamSplitter_test.cpp
+++ b/libs/gui/tests/StreamSplitter_test.cpp
@@ -95,8 +95,7 @@
     ASSERT_EQ(*dataOut, TEST_DATA);
     ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
 
-    ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
-            EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
 
     // This should succeed even with allocation disabled since it will have
     // received the buffer back from the output BufferQueue
@@ -168,9 +167,9 @@
         ASSERT_EQ(*dataOut, TEST_DATA);
         ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
 
-        ASSERT_EQ(OK, outputConsumers[output]->releaseBuffer(item.mSlot,
-                    item.mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
-                    Fence::NO_FENCE));
+        ASSERT_EQ(OK,
+                  outputConsumers[output]->releaseBuffer(item.mSlot, item.mFrameNumber,
+                                                         Fence::NO_FENCE));
     }
 
     // This should succeed even with allocation disabled since it will have
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 88893b6..76362ff 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -21,6 +21,7 @@
 #include <gtest/gtest.h>
 
 #include <SurfaceFlingerProperties.h>
+#include <android/gui/IActivePictureListener.h>
 #include <android/gui/IDisplayEventConnection.h>
 #include <android/gui/ISurfaceComposer.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
@@ -201,9 +202,9 @@
         releasedItems.resize(1+extraDiscardedBuffers);
         for (size_t i = 0; i < releasedItems.size(); i++) {
             ASSERT_EQ(NO_ERROR, consumer->acquireBuffer(&releasedItems[i], 0));
-            ASSERT_EQ(NO_ERROR, consumer->releaseBuffer(releasedItems[i].mSlot,
-                    releasedItems[i].mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
-                    Fence::NO_FENCE));
+            ASSERT_EQ(NO_ERROR,
+                      consumer->releaseBuffer(releasedItems[i].mSlot, releasedItems[i].mFrameNumber,
+                                              Fence::NO_FENCE));
         }
         int32_t expectedReleaseCb = (enableReleasedCb ? releasedItems.size() : 0);
         if (hasSurfaceListener) {
@@ -1015,6 +1016,15 @@
         return binder::Status::ok();
     }
 
+    binder::Status setActivePictureListener(const sp<gui::IActivePictureListener>&) {
+        return binder::Status::ok();
+    }
+
+    binder::Status getMaxLayerPictureProfiles(const sp<IBinder>& /*display*/,
+                                              int32_t* /*outMaxProfiles*/) {
+        return binder::Status::ok();
+    }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp
index 9f57923..2cf7081 100644
--- a/libs/gui/view/Surface.cpp
+++ b/libs/gui/view/Surface.cpp
@@ -151,6 +151,10 @@
     }
     return OK;
 }
+
+bool Surface::isEmpty() const {
+    return graphicBufferProducer == nullptr;
+}
 #endif
 
 std::string Surface::toString() const {
diff --git a/libs/input/CoordinateFilter.cpp b/libs/input/CoordinateFilter.cpp
index d231474..a32685b 100644
--- a/libs/input/CoordinateFilter.cpp
+++ b/libs/input/CoordinateFilter.cpp
@@ -23,7 +23,7 @@
 CoordinateFilter::CoordinateFilter(float minCutoffFreq, float beta)
       : mXFilter{minCutoffFreq, beta}, mYFilter{minCutoffFreq, beta} {}
 
-void CoordinateFilter::filter(std::chrono::duration<float> timestamp, PointerCoords& coords) {
+void CoordinateFilter::filter(std::chrono::nanoseconds timestamp, PointerCoords& coords) {
     coords.setAxisValue(AMOTION_EVENT_AXIS_X, mXFilter.filter(timestamp, coords.getX()));
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, mYFilter.filter(timestamp, coords.getY()));
 }
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index a2bb345..65a088e 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -685,11 +685,12 @@
 
 const PointerCoords* MotionEvent::getRawPointerCoords(size_t pointerIndex) const {
     if (CC_UNLIKELY(pointerIndex < 0 || pointerIndex >= getPointerCount())) {
-        LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " << *this;
+        LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for "
+                   << safeDump();
     }
     const size_t position = getHistorySize() * getPointerCount() + pointerIndex;
     if (CC_UNLIKELY(position < 0 || position >= mSamplePointerCoords.size())) {
-        LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << *this;
+        LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << safeDump();
     }
     return &mSamplePointerCoords[position];
 }
@@ -705,15 +706,16 @@
 const PointerCoords* MotionEvent::getHistoricalRawPointerCoords(
         size_t pointerIndex, size_t historicalIndex) const {
     if (CC_UNLIKELY(pointerIndex < 0 || pointerIndex >= getPointerCount())) {
-        LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " << *this;
+        LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for "
+                   << safeDump();
     }
     if (CC_UNLIKELY(historicalIndex < 0 || historicalIndex > getHistorySize())) {
         LOG(FATAL) << __func__ << ": Invalid historical index " << historicalIndex << " for "
-                   << *this;
+                   << safeDump();
     }
     const size_t position = historicalIndex * getPointerCount() + pointerIndex;
     if (CC_UNLIKELY(position < 0 || position >= mSamplePointerCoords.size())) {
-        LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << *this;
+        LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << safeDump();
     }
     return &mSamplePointerCoords[position];
 }
@@ -1144,6 +1146,53 @@
     // clang-format on
 }
 
+std::string MotionEvent::safeDump() const {
+    std::stringstream out;
+    // Field names have the m prefix here to make it easy to distinguish safeDump output from
+    // operator<< output in logs.
+    out << "MotionEvent { mAction=" << MotionEvent::actionToString(mAction);
+    if (mActionButton != 0) {
+        out << ", mActionButton=" << mActionButton;
+    }
+    if (mButtonState != 0) {
+        out << ", mButtonState=" << mButtonState;
+    }
+    if (mClassification != MotionClassification::NONE) {
+        out << ", mClassification=" << motionClassificationToString(mClassification);
+    }
+    if (mMetaState != 0) {
+        out << ", mMetaState=" << mMetaState;
+    }
+    if (mFlags != 0) {
+        out << ", mFlags=0x" << std::hex << mFlags << std::dec;
+    }
+    if (mEdgeFlags != 0) {
+        out << ", mEdgeFlags=" << mEdgeFlags;
+    }
+    out << ", mDownTime=" << mDownTime;
+    out << ", mDeviceId=" << mDeviceId;
+    out << ", mSource=" << inputEventSourceToString(mSource);
+    out << ", mDisplayId=" << mDisplayId;
+    out << ", mEventId=0x" << std::hex << mId << std::dec;
+    // Since we're not assuming the data is at all valid, we also limit the number of items that
+    // might be printed from vectors, in case the vector's size field is corrupted.
+    out << ", mPointerProperties=(" << mPointerProperties.size() << ")[";
+    for (size_t i = 0; i < mPointerProperties.size() && i < MAX_POINTERS; i++) {
+        out << (i > 0 ? ", " : "") << mPointerProperties.at(i);
+    }
+    out << "], mSampleEventTimes=(" << mSampleEventTimes.size() << ")[";
+    for (size_t i = 0; i < mSampleEventTimes.size() && i < 256; i++) {
+        out << (i > 0 ? ", " : "") << mSampleEventTimes.at(i);
+    }
+    out << "], mSamplePointerCoords=(" << mSamplePointerCoords.size() << ")[";
+    for (size_t i = 0; i < mSamplePointerCoords.size() && i < MAX_POINTERS; i++) {
+        const PointerCoords& coords = mSamplePointerCoords.at(i);
+        out << (i > 0 ? ", " : "") << "(" << coords.getX() << ", " << coords.getY() << ")";
+    }
+    out << "] }";
+    return out.str();
+}
+
 std::ostream& operator<<(std::ostream& out, const MotionEvent& event) {
     out << "MotionEvent { action=" << MotionEvent::actionToString(event.getAction());
     if (event.getActionButton() != 0) {
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index 2c0f77a..cd85821 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -169,6 +169,12 @@
     msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
     return msg;
 }
+
+std::ostream& operator<<(std::ostream& out, const InputMessage& msg) {
+    out << ftl::enum_string(msg.header.type);
+    return out;
+}
+
 } // namespace
 
 // --- InputConsumerNoResampling ---
@@ -272,6 +278,15 @@
             return; // try again later
         }
 
+        if (result == DEAD_OBJECT) {
+            // If there's no one to receive events in the channel, there's no point in sending them.
+            // Drop all outbound events.
+            LOG(INFO) << "Channel " << mChannel->getName() << " died. Dropping outbound event "
+                      << outboundMsg;
+            mOutboundQueue.pop();
+            setFdEvents(0);
+            continue;
+        }
         // Some other error. Give up
         LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName()
                    << "'.  status=" << statusToString(result) << "(" << result << ")";
diff --git a/libs/input/OneEuroFilter.cpp b/libs/input/OneEuroFilter.cpp
index 400d7c9..7b0d104 100644
--- a/libs/input/OneEuroFilter.cpp
+++ b/libs/input/OneEuroFilter.cpp
@@ -25,16 +25,24 @@
 namespace android {
 namespace {
 
+using namespace std::literals::chrono_literals;
+
+const float kHertzPerGigahertz = 1E9f;
+const float kGigahertzPerHertz = 1E-9f;
+
+// filteredSpeed's units are position per nanosecond. beta's units are 1 / position.
 inline float cutoffFreq(float minCutoffFreq, float beta, float filteredSpeed) {
-    return minCutoffFreq + beta * std::abs(filteredSpeed);
+    return kHertzPerGigahertz *
+            ((minCutoffFreq * kGigahertzPerHertz) + beta * std::abs(filteredSpeed));
 }
 
-inline float smoothingFactor(std::chrono::duration<float> samplingPeriod, float cutoffFreq) {
-    return samplingPeriod.count() / (samplingPeriod.count() + (1.0 / (2.0 * M_PI * cutoffFreq)));
+inline float smoothingFactor(std::chrono::nanoseconds samplingPeriod, float cutoffFreq) {
+    const float constant = 2.0f * M_PI * samplingPeriod.count() * (cutoffFreq * kGigahertzPerHertz);
+    return constant / (constant + 1);
 }
 
-inline float lowPassFilter(float rawPosition, float prevFilteredPosition, float smoothingFactor) {
-    return smoothingFactor * rawPosition + (1 - smoothingFactor) * prevFilteredPosition;
+inline float lowPassFilter(float rawValue, float prevFilteredValue, float smoothingFactor) {
+    return smoothingFactor * rawValue + (1 - smoothingFactor) * prevFilteredValue;
 }
 
 } // namespace
@@ -42,17 +50,17 @@
 OneEuroFilter::OneEuroFilter(float minCutoffFreq, float beta, float speedCutoffFreq)
       : mMinCutoffFreq{minCutoffFreq}, mBeta{beta}, mSpeedCutoffFreq{speedCutoffFreq} {}
 
-float OneEuroFilter::filter(std::chrono::duration<float> timestamp, float rawPosition) {
-    LOG_IF(FATAL, mPrevFilteredPosition.has_value() && (timestamp <= *mPrevTimestamp))
-            << "Timestamp must be greater than mPrevTimestamp";
+float OneEuroFilter::filter(std::chrono::nanoseconds timestamp, float rawPosition) {
+    LOG_IF(FATAL, mPrevTimestamp.has_value() && (*mPrevTimestamp >= timestamp))
+            << "Timestamp must be greater than mPrevTimestamp. Timestamp: " << timestamp.count()
+            << "ns. mPrevTimestamp: " << mPrevTimestamp->count() << "ns";
 
-    const std::chrono::duration<float> samplingPeriod = (mPrevTimestamp.has_value())
-            ? (timestamp - *mPrevTimestamp)
-            : std::chrono::duration<float>{1.0};
+    const std::chrono::nanoseconds samplingPeriod =
+            (mPrevTimestamp.has_value()) ? (timestamp - *mPrevTimestamp) : 1s;
 
     const float rawVelocity = (mPrevFilteredPosition.has_value())
-            ? ((rawPosition - *mPrevFilteredPosition) / samplingPeriod.count())
-            : 0.0;
+            ? ((rawPosition - *mPrevFilteredPosition) / (samplingPeriod.count()))
+            : 0.0f;
 
     const float speedSmoothingFactor = smoothingFactor(samplingPeriod, mSpeedCutoffFreq);
 
diff --git a/libs/input/rust/data_store.rs b/libs/input/rust/data_store.rs
index 6bdcefd..beb6e23 100644
--- a/libs/input/rust/data_store.rs
+++ b/libs/input/rust/data_store.rs
@@ -17,7 +17,7 @@
 //! Contains the DataStore, used to store input related data in a persistent way.
 
 use crate::input::KeyboardType;
-use log::{debug, error};
+use log::{debug, error, info};
 use serde::{Deserialize, Serialize};
 use std::fs::File;
 use std::io::{Read, Write};
@@ -157,7 +157,7 @@
         let path = Path::new(&self.filepath);
         let mut fs_string = String::new();
         match File::open(path) {
-            Err(e) => error!("couldn't open {:?}: {}", path, e),
+            Err(e) => info!("couldn't open {:?}: {}", path, e),
             Ok(mut file) => match file.read_to_string(&mut fs_string) {
                 Err(e) => error!("Couldn't read from {:?}: {}", path, e),
                 Ok(_) => debug!("Successfully read from file {:?}", path),
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 46e8190..d1c564d 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -17,6 +17,7 @@
         "IdGenerator_test.cpp",
         "InputChannel_test.cpp",
         "InputConsumer_test.cpp",
+        "InputConsumerFilteredResampling_test.cpp",
         "InputConsumerResampling_test.cpp",
         "InputDevice_test.cpp",
         "InputEvent_test.cpp",
diff --git a/libs/input/tests/InputConsumerFilteredResampling_test.cpp b/libs/input/tests/InputConsumerFilteredResampling_test.cpp
new file mode 100644
index 0000000..757cd18
--- /dev/null
+++ b/libs/input/tests/InputConsumerFilteredResampling_test.cpp
@@ -0,0 +1,218 @@
+/**
+ * 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 <input/InputConsumerNoResampling.h>
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <queue>
+
+#include <TestEventMatchers.h>
+#include <TestInputChannel.h>
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/InputEventBuilders.h>
+#include <input/Resampler.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace {
+
+using std::chrono::nanoseconds;
+
+using ::testing::AllOf;
+using ::testing::Matcher;
+
+const int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+const int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+
+struct Pointer {
+    int32_t id{0};
+    ToolType toolType{ToolType::FINGER};
+    float x{0.0f};
+    float y{0.0f};
+    bool isResampled{false};
+
+    PointerBuilder asPointerBuilder() const {
+        return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled);
+    }
+};
+
+} // namespace
+
+class InputConsumerFilteredResamplingTest : public ::testing::Test, public InputConsumerCallbacks {
+protected:
+    InputConsumerFilteredResamplingTest()
+          : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")},
+            mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} {
+        Looper::setForThread(mLooper);
+        mConsumer = std::make_unique<
+                InputConsumerNoResampling>(mClientTestChannel, mLooper, *this, []() {
+            return std::make_unique<FilteredLegacyResampler>(/*minCutoffFreq=*/4.7, /*beta=*/0.01);
+        });
+    }
+
+    void invokeLooperCallback() const {
+        sp<LooperCallback> callback;
+        ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
+                                             /*events=*/nullptr, &callback, /*data=*/nullptr));
+        ASSERT_NE(callback, nullptr);
+        callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr);
+    }
+
+    void assertOnBatchedInputEventPendingWasCalled() {
+        ASSERT_GT(mOnBatchedInputEventPendingInvocationCount, 0UL)
+                << "onBatchedInputEventPending was not called";
+        --mOnBatchedInputEventPendingInvocationCount;
+    }
+
+    void assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) {
+        ASSERT_TRUE(!mMotionEvents.empty()) << "No motion events were received";
+        std::unique_ptr<MotionEvent> motionEvent = std::move(mMotionEvents.front());
+        mMotionEvents.pop();
+        ASSERT_NE(motionEvent, nullptr) << "The consumed motion event must not be nullptr";
+        EXPECT_THAT(*motionEvent, matcher);
+    }
+
+    InputMessage nextPointerMessage(nanoseconds eventTime, int32_t action, const Pointer& pointer);
+
+    std::shared_ptr<TestInputChannel> mClientTestChannel;
+    sp<Looper> mLooper;
+    std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+    // Batched input events
+    std::queue<std::unique_ptr<KeyEvent>> mKeyEvents;
+    std::queue<std::unique_ptr<MotionEvent>> mMotionEvents;
+    std::queue<std::unique_ptr<FocusEvent>> mFocusEvents;
+    std::queue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+    std::queue<std::unique_ptr<DragEvent>> mDragEvents;
+    std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+private:
+    // InputConsumer callbacks
+    void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
+        mKeyEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, /*handled=*/true);
+    }
+
+    void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
+        mMotionEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, /*handled=*/true);
+    }
+
+    void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+        if (!mConsumer->probablyHasInput()) {
+            ADD_FAILURE() << "Should deterministically have input because there is a batch";
+        }
+        ++mOnBatchedInputEventPendingInvocationCount;
+    }
+
+    void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
+        mFocusEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, /*handled=*/true);
+    }
+
+    void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
+        mCaptureEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, /*handled=*/true);
+    }
+
+    void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
+        mDragEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, /*handled=*/true);
+    }
+
+    void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
+        mTouchModeEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, /*handled=*/true);
+    }
+
+    uint32_t mLastSeq{0};
+    size_t mOnBatchedInputEventPendingInvocationCount{0};
+};
+
+InputMessage InputConsumerFilteredResamplingTest::nextPointerMessage(nanoseconds eventTime,
+                                                                     int32_t action,
+                                                                     const Pointer& pointer) {
+    ++mLastSeq;
+    return InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq}
+            .eventTime(eventTime.count())
+            .source(AINPUT_SOURCE_TOUCHSCREEN)
+            .action(action)
+            .pointer(pointer.asPointerBuilder())
+            .build();
+}
+
+TEST_F(InputConsumerFilteredResamplingTest, NeighboringTimestampsDoNotResultInZeroDivision) {
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage(0ms, ACTION_DOWN, Pointer{.x = 0.0f, .y = 0.0f}));
+
+    invokeLooperCallback();
+
+    assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithSampleCount(1)));
+
+    const std::chrono::nanoseconds initialTime{56'821'700'000'000};
+
+    mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 4'929'000ns, ACTION_MOVE,
+                                                          Pointer{.x = 1.0f, .y = 1.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 9'352'000ns, ACTION_MOVE,
+                                                          Pointer{.x = 2.0f, .y = 2.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 14'531'000ns, ACTION_MOVE,
+                                                          Pointer{.x = 3.0f, .y = 3.0f}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(initialTime.count() + 18'849'395 /*ns*/);
+
+    assertOnBatchedInputEventPendingWasCalled();
+    // Three samples are expected. The first two of the batch, and the resampled one. The
+    // coordinates of the resampled sample are hardcoded because the matcher requires them. However,
+    // the primary intention here is to check that the last sample is resampled.
+    assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithSampleCount(3),
+                                    WithSample(/*sampleIndex=*/2,
+                                               Sample{initialTime + 13'849'395ns,
+                                                      {PointerArgs{.x = 1.3286f,
+                                                                   .y = 1.3286f,
+                                                                   .isResampled = true}}})));
+
+    mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 20'363'000ns, ACTION_MOVE,
+                                                          Pointer{.x = 4.0f, .y = 4.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 25'745'000ns, ACTION_MOVE,
+                                                          Pointer{.x = 5.0f, .y = 5.0f}));
+    // This sample is part of the stream of messages, but should not be consumed because its
+    // timestamp is greater than the ajusted frame time.
+    mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 31'337'000ns, ACTION_MOVE,
+                                                          Pointer{.x = 6.0f, .y = 6.0f}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(initialTime.count() + 35'516'062 /*ns*/);
+
+    assertOnBatchedInputEventPendingWasCalled();
+    // Four samples are expected because the last sample of the previous batch was not consumed.
+    assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithSampleCount(4)));
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/5, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/6, /*handled=*/true);
+}
+
+} // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index 1210f71..1dadae9 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
+#include <TestEventMatchers.h>
 #include <android-base/logging.h>
 #include <attestation/HmacKeyManager.h>
 #include <ftl/enum.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/BlockingQueue.h>
 #include <input/InputConsumerNoResampling.h>
 #include <input/InputTransport.h>
 
 using android::base::Result;
+using ::testing::Matcher;
 
 namespace android {
 
@@ -278,7 +281,7 @@
     void SetUp() override {
         std::unique_ptr<InputChannel> serverChannel;
         status_t result =
-                InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel);
+                InputChannel::openInputChannelPair("test channel", serverChannel, mClientChannel);
         ASSERT_EQ(OK, result);
 
         mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
@@ -316,6 +319,8 @@
 
 protected:
     // Interaction with the looper thread
+    void blockLooper();
+    void unblockLooper();
     enum class LooperMessage : int {
         CALL_PROBABLY_HAS_INPUT,
         CREATE_CONSUMER,
@@ -336,6 +341,8 @@
     // accessed on the test thread.
     BlockingQueue<bool> mProbablyHasInputResponses;
 
+    std::unique_ptr<MotionEvent> assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher);
+
 private:
     sp<MessageHandler> mMessageHandler;
     void handleMessage(const Message& message);
@@ -384,11 +391,45 @@
     };
 };
 
+void InputPublisherAndConsumerNoResamplingTest::blockLooper() {
+    {
+        std::scoped_lock l(mLock);
+        mLooperMayProceed = false;
+    }
+    sendMessage(LooperMessage::BLOCK_LOOPER);
+    {
+        std::unique_lock l(mLock);
+        mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; });
+    }
+}
+
+void InputPublisherAndConsumerNoResamplingTest::unblockLooper() {
+    {
+        std::scoped_lock l(mLock);
+        mLooperMayProceed = true;
+    }
+    mNotifyLooperMayProceed.notify_all();
+}
+
 void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) {
     Message msg{ftl::to_underlying(message)};
     mLooper->sendMessage(mMessageHandler, msg);
 }
 
+std::unique_ptr<MotionEvent> InputPublisherAndConsumerNoResamplingTest::assertReceivedMotionEvent(
+        const Matcher<MotionEvent>& matcher) {
+    std::optional<std::unique_ptr<MotionEvent>> event = mMotionEvents.popWithTimeout(TIMEOUT);
+    if (!event) {
+        ADD_FAILURE() << "No event was received, but expected motion " << matcher;
+        return nullptr;
+    }
+    if (*event == nullptr) {
+        LOG(FATAL) << "Event was received, but it was null";
+    }
+    EXPECT_THAT(**event, matcher);
+    return std::move(*event);
+}
+
 void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) {
     switch (static_cast<LooperMessage>(message.what)) {
         case LooperMessage::CALL_PROBABLY_HAS_INPUT: {
@@ -572,8 +613,7 @@
     const nsecs_t publishTimeOfDown = systemTime(SYSTEM_TIME_MONOTONIC);
     publishMotionEvent(*mPublisher, argsDown);
 
-    // Consume the DOWN event.
-    ASSERT_TRUE(mMotionEvents.popWithTimeout(TIMEOUT).has_value());
+    assertReceivedMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
 
     verifyFinishedSignal(*mPublisher, mSeq, publishTimeOfDown);
 
@@ -582,15 +622,7 @@
     std::queue<uint32_t> publishedSequenceNumbers;
 
     // Block Looper to increase the chance of batching events
-    {
-        std::scoped_lock l(mLock);
-        mLooperMayProceed = false;
-    }
-    sendMessage(LooperMessage::BLOCK_LOOPER);
-    {
-        std::unique_lock l(mLock);
-        mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; });
-    }
+    blockLooper();
 
     uint32_t firstSampleId;
     for (size_t i = 0; i < nSamples; ++i) {
@@ -611,21 +643,16 @@
 
     std::vector<MotionEvent> singleSampledMotionEvents;
 
-    // Unblock Looper
-    {
-        std::scoped_lock l(mLock);
-        mLooperMayProceed = true;
-    }
-    mNotifyLooperMayProceed.notify_all();
+    unblockLooper();
 
     // We have no control over the socket behavior, so the consumer can receive
     // the motion as a batched event, or as a sequence of multiple single-sample MotionEvents (or a
     // mix of those)
     while (singleSampledMotionEvents.size() != nSamples) {
-        const std::optional<std::unique_ptr<MotionEvent>> batchedMotionEvent =
-                mMotionEvents.popWithTimeout(TIMEOUT);
+        const std::unique_ptr<MotionEvent> batchedMotionEvent =
+                assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
         // The events received by these calls are never null
-        std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(**batchedMotionEvent);
+        std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(*batchedMotionEvent);
         singleSampledMotionEvents.insert(singleSampledMotionEvents.end(), splitMotionEvents.begin(),
                                          splitMotionEvents.end());
     }
@@ -681,10 +708,7 @@
     }
     mNotifyLooperMayProceed.notify_all();
 
-    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
-    ASSERT_TRUE(optMotion.has_value());
-    std::unique_ptr<MotionEvent> motion = std::move(*optMotion);
-    ASSERT_EQ(ACTION_MOVE, motion->getAction());
+    assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
 
     verifyFinishedSignal(*mPublisher, seq, publishTime);
 }
@@ -696,9 +720,7 @@
     nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
     publishMotionEvent(*mPublisher, args);
 
-    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
-    ASSERT_TRUE(optMotion.has_value());
-    std::unique_ptr<MotionEvent> event = std::move(*optMotion);
+    std::unique_ptr<MotionEvent> event = assertReceivedMotionEvent(WithMotionAction(action));
 
     verifyArgsEqualToEvent(args, *event);
 
@@ -796,6 +818,15 @@
     verifyFinishedSignal(*mPublisher, seq, publishTime);
 }
 
+/**
+ * If the publisher has died, consumer should not crash when trying to send an outgoing message.
+ */
+TEST_F(InputPublisherAndConsumerNoResamplingTest, ConsumerWritesAfterPublisherDies) {
+    mPublisher.reset(); // The publisher has died
+    mReportTimelineArgs.emplace(/*inputEventId=*/10, /*gpuCompletedTime=*/20, /*presentTime=*/30);
+    sendMessage(LooperMessage::CALL_REPORT_TIMELINE);
+}
+
 TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) {
     const int32_t inputEventId = 20;
     const nsecs_t gpuCompletedTime = 30;
diff --git a/libs/input/tests/OneEuroFilter_test.cpp b/libs/input/tests/OneEuroFilter_test.cpp
index 270e789..8645508 100644
--- a/libs/input/tests/OneEuroFilter_test.cpp
+++ b/libs/input/tests/OneEuroFilter_test.cpp
@@ -98,7 +98,10 @@
         std::vector<Sample> filteredSignal;
         for (const Sample& sample : signal) {
             filteredSignal.push_back(
-                    Sample{sample.timestamp, mFilter.filter(sample.timestamp, sample.value)});
+                    Sample{sample.timestamp,
+                           mFilter.filter(std::chrono::duration_cast<std::chrono::nanoseconds>(
+                                                  sample.timestamp),
+                                          sample.value)});
         }
         return filteredSignal;
     }
diff --git a/libs/input/tests/TestEventMatchers.h b/libs/input/tests/TestEventMatchers.h
index 290a97d..56eaefd 100644
--- a/libs/input/tests/TestEventMatchers.h
+++ b/libs/input/tests/TestEventMatchers.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <chrono>
+#include <cmath>
 #include <ostream>
 #include <vector>
 
@@ -156,14 +157,18 @@
              ++pointerIndex) {
             const PointerCoords& pointerCoords =
                     *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, mSampleIndex));
-            if ((pointerCoords.getX() != mSample.pointers[pointerIndex].x) ||
-                (pointerCoords.getY() != mSample.pointers[pointerIndex].y)) {
+
+            if ((std::abs(pointerCoords.getX() - mSample.pointers[pointerIndex].x) >
+                 MotionEvent::ROUNDING_PRECISION) ||
+                (std::abs(pointerCoords.getY() - mSample.pointers[pointerIndex].y) >
+                 MotionEvent::ROUNDING_PRECISION)) {
                 *os << "sample coordinates mismatch at pointer index " << pointerIndex
                     << ". sample: (" << pointerCoords.getX() << ", " << pointerCoords.getY()
                     << ") expected: (" << mSample.pointers[pointerIndex].x << ", "
                     << mSample.pointers[pointerIndex].y << ")";
                 return false;
             }
+
             if (motionEvent.isResampled(pointerIndex, mSampleIndex) !=
                 mSample.pointers[pointerIndex].isResampled) {
                 *os << "resampling flag mismatch. sample: "
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index f1453bd..006a785 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -344,7 +344,8 @@
      * mEglSlots array in addition to the ConsumerBase.
      */
     virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
-                                         EGLDisplay display, EGLSyncKHR eglFence) override;
+                                         EGLDisplay display = EGL_NO_DISPLAY,
+                                         EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override;
 
     /**
      * freeBufferLocked frees up the given buffer slot. If the slot has been
diff --git a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
index 275b7a4..3959fce 100644
--- a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
@@ -150,8 +150,7 @@
             }
         }
 
-        err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer, mEglDisplay,
-                                     EGL_NO_SYNC_KHR);
+        err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer);
         if (err < NO_ERROR) {
             EGC_LOGE("releaseTexImage: failed to release buffer: %s (%d)", strerror(-err), err);
             return err;
@@ -234,14 +233,14 @@
     if (st.mOpMode != SurfaceTexture::OpMode::attachedToGL) {
         EGC_LOGE("updateAndRelease: EGLConsumer is not attached to an OpenGL "
                  "ES context");
-        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
         return INVALID_OPERATION;
     }
 
     // Confirm state.
     err = checkAndUpdateEglStateLocked(st);
     if (err != NO_ERROR) {
-        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
         return err;
     }
 
@@ -254,7 +253,7 @@
     if (err != NO_ERROR) {
         EGC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", mEglDisplay,
                  slot);
-        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
         return UNKNOWN_ERROR;
     }
 
@@ -266,8 +265,7 @@
             // release the old buffer, so instead we just drop the new frame.
             // As we are still under lock since acquireBuffer, it is safe to
             // release by slot.
-            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay,
-                                   EGL_NO_SYNC_KHR);
+            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
             return err;
         }
     }
diff --git a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
index 32b229d..60e87b5 100644
--- a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
@@ -64,8 +64,7 @@
         // Wait on the producer fence for the buffer to be ready.
         err = fenceWait(item.mFence->get(), fencePassThroughHandle);
         if (err != OK) {
-            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
-                                   EGL_NO_SYNC_KHR);
+            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
             return nullptr;
         }
     }
@@ -79,8 +78,7 @@
         err = createFence(st.mUseFenceSync, &mImageSlots[slot].eglFence(), &display,
                           &releaseFenceId, fencePassThroughHandle);
         if (OK != err) {
-            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
-                                   EGL_NO_SYNC_KHR);
+            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
             return nullptr;
         }
 
@@ -91,8 +89,7 @@
                                                     releaseFence);
             if (err != OK) {
                 IMG_LOGE("dequeueImage: error adding release fence: %s (%d)", strerror(-err), err);
-                st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
-                                       EGL_NO_SYNC_KHR);
+                st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
                 return nullptr;
             }
         }
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index 5ce4076..f5a1db5 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -265,16 +265,6 @@
     return native_window_set_frame_rate(window, frameRate, compatibility, changeFrameRateStrategy);
 }
 
-int32_t ANativeWindow_setFrameRateParams(
-        ANativeWindow* window, float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
-        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) {
-    if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
-        return -EINVAL;
-    }
-    return native_window_set_frame_rate_params(window, desiredMinRate, desiredMaxRate,
-                                               fixedSourceRate, changeFrameRateStrategy);
-}
-
 /**************************************************************************************************
  * vndk-stable
  **************************************************************************************************/
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index bd8d67a..ed3e8c1 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -33,8 +33,8 @@
 #ifndef ANDROID_NATIVE_WINDOW_H
 #define ANDROID_NATIVE_WINDOW_H
 
-#include <stdbool.h>
 #include <stdint.h>
+#include <stdbool.h>
 #include <sys/cdefs.h>
 
 #include <android/data_space.h>
@@ -243,8 +243,7 @@
      * There are no inherent restrictions on the frame rate of this window. When
      * the system selects a frame rate other than what the app requested, the
      * app will be able to run at the system frame rate without requiring pull
-     * down. This value should be used when displaying game content, UIs, and
-     * anything that isn't video.
+     * down. This value should be used when displaying game content.
      */
     ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT = 0,
     /**
@@ -256,7 +255,14 @@
      * stuttering) than it would be if the system had chosen the app's requested
      * frame rate. This value should be used for video content.
      */
-    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1
+    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1,
+
+    /**
+     * The window requests a frame rate that is greater than or equal to the specified frame rate.
+     * This value should be used for UIs, animations, scrolling, and anything that is not a game
+     * or video.
+     */
+    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE = 2
 };
 
 /**
@@ -282,7 +288,7 @@
 void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) __INTRODUCED_IN(30);
 
 /** Change frame rate strategy value for ANativeWindow_setFrameRate. */
-typedef enum ANativeWindow_ChangeFrameRateStrategy : int8_t {
+enum ANativeWindow_ChangeFrameRateStrategy {
     /**
      * Change the frame rate only if the transition is going to be seamless.
      */
@@ -292,7 +298,7 @@
      * i.e. with visual interruptions for the user.
      */
     ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS = 1
-} ANativeWindow_ChangeFrameRateStrategy __INTRODUCED_IN(31);
+} __INTRODUCED_IN(31);
 
 /**
  * Sets the intended frame rate for this window.
@@ -345,76 +351,6 @@
         __INTRODUCED_IN(31);
 
 /**
- * Sets the intended frame rate for this window.
- *
- * On devices that are capable of running the display at different frame rates,
- * the system may choose a display refresh rate to better match this surface's frame
- * rate. Usage of this API won't introduce frame rate throttling, or affect other
- * aspects of the application's frame production pipeline. However, because the system
- * may change the display refresh rate, calls to this function may result in changes
- * to Choreographer callback timings, and changes to the time interval at which the
- * system releases buffers back to the application.
- *
- * Note that this only has an effect for surfaces presented on the display. If this
- * surface is consumed by something other than the system compositor, e.g. a media
- * codec, this call has no effect.
- *
- * You can register for changes in the refresh rate using
- * \a AChoreographer_registerRefreshRateCallback.
- *
- * See ANativeWindow_clearFrameRate().
- *
- * Available since API level 36.
- *
- * \param window pointer to an ANativeWindow object.
- *
- * \param desiredMinRate The desired minimum frame rate (inclusive) for the window, specifying that
- * the surface prefers the device render rate to be at least `desiredMinRate`.
- *
- * <p>Set `desiredMinRate` = `desiredMaxRate` to indicate the surface prefers an exact frame rate.
- *
- * <p>Set `desiredMinRate` = 0 to indicate the window has no preference
- * and any frame rate is acceptable.
- *
- * <p>The value should be greater than or equal to 0.
- *
- * \param desiredMaxRate The desired maximum frame rate (inclusive) for the window, specifying that
- * the surface prefers the device render rate to be at most `desiredMaxRate`.
- *
- * <p>Set `desiredMaxRate` = `desiredMinRate` to indicate the surface prefers an exact frame rate.
- *
- * <p>Set `desiredMaxRate` = positive infinity to indicate the window has no preference
- * and any frame rate is acceptable.
- *
- * <p>The value should be greater than or equal to `desiredMinRate`.
- *
- * \param fixedSourceRate The "fixed source" frame rate of the window if the content has an
- * inherently fixed frame rate, e.g. a video that has a specific frame rate.
- *
- * <p>When the frame rate chosen for the surface is the `fixedSourceRate` or a
- * multiple, the surface can render without frame pulldown, for optimal smoothness. For
- * example, a 30 fps video (`fixedSourceRate`=30) renders just as smoothly on 30 fps,
- * 60 fps, 90 fps, 120 fps, and so on.
- *
- * <p>Setting the fixed source rate can also be used together with a desired
- * frame rate min and max via setting `desiredMinRate` and `desiredMaxRate`. This still
- * means the window's content has a fixed frame rate of `fixedSourceRate`, but additionally
- * specifies the preference to be in the range [`desiredMinRate`, `desiredMaxRate`]. For example, an
- * app might want to specify there is 30 fps video (`fixedSourceRate`=30) as well as a smooth
- * animation on the same window which looks good when drawing within a frame rate range such as
- * [`desiredMinRate`, `desiredMaxRate`] = [60,120].
- *
- * \param changeFrameRateStrategy Whether display refresh rate transitions caused by this surface
- * should be seamless. A seamless transition is one that doesn't have any visual interruptions, such
- * as a black screen for a second or two.
- *
- * \return 0 for success, -EINVAL if the arguments are invalid.
- */
-int32_t ANativeWindow_setFrameRateParams(
-        ANativeWindow* window, float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
-        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) __INTRODUCED_IN(36);
-
-/**
  * Clears the frame rate which is set for this window.
  *
  * This is equivalent to calling
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 05f49ad..f669f77 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1060,12 +1060,7 @@
     /**
      * This surface will vote for the minimum refresh rate.
      */
-    ANATIVEWINDOW_FRAME_RATE_MIN,
-
-    /**
-     * The surface requests a frame rate that is greater than or equal to `frameRate`.
-     */
-    ANATIVEWINDOW_FRAME_RATE_GTE
+    ANATIVEWINDOW_FRAME_RATE_MIN
 };
 
 /*
@@ -1156,23 +1151,6 @@
                            (int)compatibility, (int)changeFrameRateStrategy);
 }
 
-static inline int native_window_set_frame_rate_params(struct ANativeWindow* window,
-                                                      float desiredMinRate, float desiredMaxRate,
-                                                      float fixedSourceRate,
-                                                      int8_t changeFrameRateStrategy) {
-    // TODO(b/362798998): Fix plumbing to send whole params
-    int compatibility = fixedSourceRate == 0 ? ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT
-                                             : ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-    double frameRate = compatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
-            ? fixedSourceRate
-            : desiredMinRate;
-    if (desiredMaxRate < desiredMinRate) {
-        return -EINVAL;
-    }
-    return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, frameRate, compatibility,
-                           changeFrameRateStrategy);
-}
-
 struct ANativeWindowFrameTimelineInfo {
     // Frame Id received from ANativeWindow_getNextFrameId.
     uint64_t frameNumber;
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index 071e354..e29d5a6 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -53,7 +53,6 @@
     ANativeWindow_setBuffersTransform;
     ANativeWindow_setDequeueTimeout; # systemapi introduced=30
     ANativeWindow_setFrameRate; # introduced=30
-    ANativeWindow_setFrameRateParams; # introduced=36
     ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31
     ANativeWindow_setSharedBufferMode; # llndk
     ANativeWindow_setSwapInterval; # llndk
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 9876362..d760285 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -30,8 +30,8 @@
     StatusCode,
 };
 use ffi::{
-    AHardwareBuffer, AHardwareBuffer_Desc, AHardwareBuffer_readFromParcel,
-    AHardwareBuffer_writeToParcel, ARect,
+    AHardwareBuffer, AHardwareBuffer_Desc, AHardwareBuffer_Plane, AHardwareBuffer_Planes,
+    AHardwareBuffer_readFromParcel, AHardwareBuffer_writeToParcel, ARect,
 };
 use std::ffi::c_void;
 use std::fmt::{self, Debug, Formatter};
@@ -313,6 +313,57 @@
         })
     }
 
+    /// Lock a potentially multi-planar hardware buffer for direct CPU access.
+    ///
+    /// # Safety
+    ///
+    /// - If `fence` is `None`, the caller must ensure that all writes to the buffer have completed
+    ///   before calling this function.
+    /// - If the buffer has `AHARDWAREBUFFER_FORMAT_BLOB`, multiple threads or process may lock the
+    ///   buffer simultaneously, but the caller must ensure that they don't access it simultaneously
+    ///   and break Rust's aliasing rules, like any other shared memory.
+    /// - Otherwise if `usage` includes `AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY` or
+    ///   `AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN`, the caller must ensure that no other threads or
+    ///   processes lock the buffer simultaneously for any usage.
+    /// - Otherwise, the caller must ensure that no other threads lock the buffer for writing
+    ///   simultaneously.
+    /// - If `rect` is not `None`, the caller must not modify the buffer outside of that rectangle.
+    pub unsafe fn lock_planes<'a>(
+        &'a self,
+        usage: AHardwareBuffer_UsageFlags,
+        fence: Option<BorrowedFd>,
+        rect: Option<&ARect>,
+    ) -> Result<Vec<PlaneGuard<'a>>, StatusCode> {
+        let fence = if let Some(fence) = fence { fence.as_raw_fd() } else { -1 };
+        let rect = rect.map(ptr::from_ref).unwrap_or(null());
+        let mut planes = AHardwareBuffer_Planes {
+            planeCount: 0,
+            planes: [const { AHardwareBuffer_Plane { data: null_mut(), pixelStride: 0, rowStride: 0 } };
+                4],
+        };
+
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the various out
+        // pointers are valid because they come from references. Our caller promises that writes have
+        // completed and there will be no simultaneous read/write locks.
+        let status = unsafe {
+            ffi::AHardwareBuffer_lockPlanes(self.0.as_ptr(), usage.0, fence, rect, &mut planes)
+        };
+        status_result(status)?;
+        let plane_count = planes.planeCount.try_into().unwrap();
+        Ok(planes.planes[..plane_count]
+            .iter()
+            .map(|plane| PlaneGuard {
+                guard: HardwareBufferGuard {
+                    buffer: self,
+                    address: NonNull::new(plane.data)
+                        .expect("AHardwareBuffer_lockAndGetInfo set a null outVirtualAddress"),
+                },
+                pixel_stride: plane.pixelStride,
+                row_stride: plane.rowStride,
+            })
+            .collect())
+    }
+
     /// Locks the hardware buffer for direct CPU access, returning information about the bytes per
     /// pixel and stride as well.
     ///
@@ -510,6 +561,18 @@
     pub stride: u32,
 }
 
+/// A guard for a single plane of a locked `HardwareBuffer`, with additional information about the
+/// stride.
+#[derive(Debug)]
+pub struct PlaneGuard<'a> {
+    /// The locked buffer guard.
+    pub guard: HardwareBufferGuard<'a>,
+    /// The stride in bytes between the color channel for one pixel to the next pixel.
+    pub pixel_stride: u32,
+    /// The stride in bytes between rows in the buffer.
+    pub row_stride: u32,
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 0fd982e..95c4d03 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -38,6 +38,14 @@
 #define PROPERTY_DEBUG_RENDERENGINE_BACKEND "debug.renderengine.backend"
 
 /**
+ * Allows opting particular devices into an initial preview rollout of RenderEngine on Graphite.
+ *
+ * Only applicable within SurfaceFlinger, and if relevant aconfig flags are enabled.
+ */
+#define PROPERTY_DEBUG_RENDERENGINE_GRAPHITE_PREVIEW_OPTIN \
+    "debug.renderengine.graphite_preview_optin"
+
+/**
  * Turns on recording of skia commands in SkiaGL version of the RE. This property
  * defines number of milliseconds for the recording to take place. A non zero value
  * turns on the recording.
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index a93f6c3..b3284e4 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -545,6 +545,7 @@
 
     if (graphicBuffer && parameters.layer.luts) {
         shader = mLutShader.lutShader(shader, parameters.layer.luts,
+                                      parameters.layer.sourceDataspace,
                                       toSkColorSpace(parameters.outputDataSpace));
     }
 
@@ -572,8 +573,10 @@
         }
 
         // disable tonemapping if we already locally tonemapped
-        auto inputDataspace =
-                usingLocalTonemap ? parameters.outputDataSpace : parameters.layer.sourceDataspace;
+        // skip tonemapping if the luts is in use
+        auto inputDataspace = usingLocalTonemap || (graphicBuffer && parameters.layer.luts)
+                ? parameters.outputDataSpace
+                : parameters.layer.sourceDataspace;
         auto effect =
                 shaders::LinearEffect{.inputDataspace = inputDataspace,
                                       .outputDataspace = parameters.outputDataSpace,
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
index db0b133..da47aae 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -96,13 +96,17 @@
 void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input,
                                     const float inverseScale, const float radius,
                                     const float alpha) const {
-    SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
-    blurBuilder.child("child") = std::move(input);
-    blurBuilder.uniform("in_inverseScale") = inverseScale;
-    blurBuilder.uniform("in_blurOffset") = radius;
-    blurBuilder.uniform("in_crossFade") = alpha;
     SkPaint paint;
-    paint.setShader(blurBuilder.makeShader(nullptr));
+    if (radius == 0) {
+        paint.setShader(std::move(input));
+        paint.setAlphaf(alpha);
+    } else {
+        SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+        blurBuilder.child("child") = std::move(input);
+        blurBuilder.uniform("in_blurOffset") = radius;
+        blurBuilder.uniform("in_crossFade") = alpha;
+        paint.setShader(blurBuilder.makeShader(nullptr));
+    }
     paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver);
     drawSurface->getCanvas()->drawPaint(paint);
 }
@@ -116,32 +120,35 @@
 
     // Use a variable number of blur passes depending on the radius. The non-integer part of this
     // calculation is used to mix the final pass into the second-last with an alpha blend.
-    constexpr int kMaxSurfaces = 4;
-    const float filterDepth =
-            std::min(kMaxSurfaces - 1.0f, 1.0f + std::max(0.0f, log2f(radius * kInputScale)));
+    constexpr int kMaxSurfaces = 3;
+    const float filterDepth = std::min(kMaxSurfaces - 1.0f, radius * kInputScale / 2.5f);
     const int filterPasses = std::min(kMaxSurfaces - 1, static_cast<int>(ceil(filterDepth)));
 
-    // Render into surfaces downscaled by 1x, 1x, 2x, and 4x from the initial downscale.
+    // Render into surfaces downscaled by 1x, 2x, and 4x from the initial downscale.
     sk_sp<SkSurface> surfaces[kMaxSurfaces] =
             {filterPasses >= 0 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
-             filterPasses >= 1 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
-             filterPasses >= 2 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr,
-             filterPasses >= 3 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr};
+             filterPasses >= 1 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr,
+             filterPasses >= 2 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr};
 
-    // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 600.
-    static const float kWeights[7] = {1.0f, 2.0f, 3.5f, 1.0f, 2.0f, 2.0f, 2.0f};
+    // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 250.
+    static const float kWeights[5] = {
+            1.0f, // 1st downsampling pass
+            1.0f, // 2nd downsampling pass
+            1.0f, // 3rd downsampling pass
+            0.0f, // 1st upscaling pass. Set to zero to upscale without blurring for performance.
+            1.0f, // 2nd upscaling pass
+    };
 
     // Kawase is an approximation of Gaussian, but behaves differently because it is made up of many
     // simpler blurs. A transformation is required to approximate the same effect as Gaussian.
-    float sumSquaredR = powf(kWeights[0] * powf(2.0f, 1), 2.0f);
+    float sumSquaredR = powf(kWeights[0], 2.0f);
     for (int i = 0; i < filterPasses; i++) {
         const float alpha = std::min(1.0f, filterDepth - i);
-        sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[1 + i], 2.0f);
-        sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[6 - i], 2.0f);
+        sumSquaredR += powf(powf(2.0f, i) * alpha * kWeights[1 + i] / kInputScale, 2.0f);
+        sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[4 - i] / kInputScale, 2.0f);
     }
-    // Solve for R = sqrt(sum(r_i^2)). Divide R by hypot(1,1) to find some (x,y) offsets.
-    const float step = M_SQRT1_2 *
-            sqrtf(max(0.0f, (powf(radius, 2.0f) - powf(kInverseInputScale, 2.0f)) / sumSquaredR));
+    // Solve for R = sqrt(sum(r_i^2)).
+    const float step = radius * sqrt(1.0f / sumSquaredR);
 
     // Start by downscaling and doing the first blur pass.
     {
@@ -162,7 +169,7 @@
     }
     // Finally blur+upscale back to our original size.
     for (int i = filterPasses - 1; i >= 0; i--) {
-        blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[6 - i] * step,
+        blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[4 - i] * step,
                  std::min(1.0f, filterDepth - i));
     }
     return surfaces[0]->makeImageSnapshot();
diff --git a/libs/renderengine/skia/filters/LutShader.cpp b/libs/renderengine/skia/filters/LutShader.cpp
index 1e43ff3..5e9dfbb 100644
--- a/libs/renderengine/skia/filters/LutShader.cpp
+++ b/libs/renderengine/skia/filters/LutShader.cpp
@@ -15,11 +15,13 @@
  */
 #include "LutShader.h"
 
+#include <SkM44.h>
 #include <SkTileMode.h>
 #include <common/trace.h>
 #include <cutils/ashmem.h>
 #include <math/half.h>
 #include <sys/mman.h>
+#include <ui/ColorSpace.h>
 
 #include "include/core/SkColorSpace.h"
 #include "src/core/SkColorFilterPriv.h"
@@ -36,6 +38,8 @@
     uniform int size;
     uniform int key;
     uniform int dimension;
+    uniform vec3 luminanceCoefficients; // for CIE_Y
+
     vec4 main(vec2 xy) {
         float4 rgba = image.eval(xy);
         float3 linear = toLinearSrgb(rgba.rgb);
@@ -51,12 +55,16 @@
                 return float4(linear.r * gainR, linear.g * gainG, linear.b * gainB, rgba.a);
             // MAX_RGB
             } else if (key == 1) {
-                float4 rgba = image.eval(xy);
-                float3 linear = toLinearSrgb(rgba.rgb);
                 float maxRGB = max(linear.r, max(linear.g, linear.b));
                 float index = maxRGB * float(size - 1);
                 float gain = lut.eval(vec2(index, 0.0) + 0.5).r;
                 return float4(linear * gain, rgba.a);
+            // CIE_Y
+            } else if (key == 2) {
+                float y = dot(linear, luminanceCoefficients) / 3.0;
+                float index = y * float(size - 1);
+                float gain = lut.eval(vec2(index, 0.0) + 0.5).r;
+                return float4(linear * gain, rgba.a);
             }
         } else if (dimension == 3) {
             if (key == 0) {
@@ -110,11 +118,37 @@
         return rgba;
     })");
 
+// same as shader::toColorSpace function
+// TODO: put this function in a general place
+static ColorSpace toColorSpace(ui::Dataspace dataspace) {
+    switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
+        case HAL_DATASPACE_STANDARD_BT709:
+            return ColorSpace::sRGB();
+        case HAL_DATASPACE_STANDARD_DCI_P3:
+            return ColorSpace::DisplayP3();
+        case HAL_DATASPACE_STANDARD_BT2020:
+        case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE:
+            return ColorSpace::BT2020();
+        case HAL_DATASPACE_STANDARD_ADOBE_RGB:
+            return ColorSpace::AdobeRGB();
+        case HAL_DATASPACE_STANDARD_BT601_625:
+        case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED:
+        case HAL_DATASPACE_STANDARD_BT601_525:
+        case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED:
+        case HAL_DATASPACE_STANDARD_BT470M:
+        case HAL_DATASPACE_STANDARD_FILM:
+        case HAL_DATASPACE_STANDARD_UNSPECIFIED:
+        default:
+            return ColorSpace::sRGB();
+    }
+}
+
 sk_sp<SkShader> LutShader::generateLutShader(sk_sp<SkShader> input,
                                              const std::vector<float>& buffers,
                                              const int32_t offset, const int32_t length,
                                              const int32_t dimension, const int32_t size,
-                                             const int32_t samplingKey) {
+                                             const int32_t samplingKey,
+                                             ui::Dataspace srcDataspace) {
     SFTRACE_NAME("lut shader");
     std::vector<half> buffer(length * 4); // 4 is for RGBA
     auto d = static_cast<LutProperties::Dimension>(dimension);
@@ -133,12 +167,16 @@
         }
     }
     /**
-     * 1D Lut(rgba)
+     * 1D Lut RGB/MAX_RGB
      * (R0, 0, 0, 0)
      * (R1, 0, 0, 0)
+     *
+     * 1D Lut CIE_Y
+     * (Y0, 0, 0, 0)
+     * (Y1, 0, 0, 0)
      * ...
      *
-     * 3D Lut
+     * 3D Lut MAX_RGB
      * (R0, G0, B0, 0)
      * (R1, G1, B1, 0)
      * ...
@@ -162,6 +200,14 @@
     const int uSize = static_cast<int>(size);
     const int uKey = static_cast<int>(samplingKey);
     const int uDimension = static_cast<int>(dimension);
+    if (static_cast<LutProperties::SamplingKey>(samplingKey) == LutProperties::SamplingKey::CIE_Y) {
+        // Use predefined colorspaces of input dataspace so that we can get D65 illuminant
+        mat3 toXYZMatrix(toColorSpace(srcDataspace).getRGBtoXYZ());
+        mBuilder->uniform("luminanceCoefficients") =
+                SkV3{toXYZMatrix[0][1], toXYZMatrix[1][1], toXYZMatrix[2][1]};
+    } else {
+        mBuilder->uniform("luminanceCoefficients") = SkV3{1.f, 1.f, 1.f};
+    }
     mBuilder->uniform("size") = uSize;
     mBuilder->uniform("key") = uKey;
     mBuilder->uniform("dimension") = uDimension;
@@ -170,6 +216,7 @@
 
 sk_sp<SkShader> LutShader::lutShader(sk_sp<SkShader>& input,
                                      std::shared_ptr<gui::DisplayLuts> displayLuts,
+                                     ui::Dataspace srcDataspace,
                                      sk_sp<SkColorSpace> outColorSpace) {
     if (mBuilder == nullptr) {
         const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(kShader);
@@ -218,7 +265,7 @@
             }
             input = generateLutShader(input, buffers, offsets[i], bufferSizePerLut,
                                       lutProperties[i].dimension, lutProperties[i].size,
-                                      lutProperties[i].samplingKey);
+                                      lutProperties[i].samplingKey, srcDataspace);
         }
 
         auto colorXformLutToDst =
diff --git a/libs/renderengine/skia/filters/LutShader.h b/libs/renderengine/skia/filters/LutShader.h
index ce3e059..7c62fca 100644
--- a/libs/renderengine/skia/filters/LutShader.h
+++ b/libs/renderengine/skia/filters/LutShader.h
@@ -21,6 +21,7 @@
 
 #include <aidl/android/hardware/graphics/composer3/LutProperties.h>
 #include <gui/DisplayLuts.h>
+#include <ui/GraphicTypes.h>
 
 namespace android {
 namespace renderengine {
@@ -29,13 +30,13 @@
 class LutShader {
 public:
     sk_sp<SkShader> lutShader(sk_sp<SkShader>& input, std::shared_ptr<gui::DisplayLuts> displayLuts,
-                              sk_sp<SkColorSpace> outColorSpace);
+                              ui::Dataspace srcDataspace, sk_sp<SkColorSpace> outColorSpace);
 
 private:
     sk_sp<SkShader> generateLutShader(sk_sp<SkShader> input, const std::vector<float>& buffers,
                                       const int32_t offset, const int32_t length,
                                       const int32_t dimension, const int32_t size,
-                                      const int32_t samplingKey);
+                                      const int32_t samplingKey, ui::Dataspace srcDataspace);
     std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
 };
 
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
index c4f8663..a58bc77 100644
--- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -236,6 +236,7 @@
   std::call_once(registration, [test]() {
     struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
     args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM;
+    args.shmem_size_hint_kb = 1024;
     PerfettoProducerInit(args);
     PerfettoTeInit();
     PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES);
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index 8b13d78..8d6f74b 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -19,9 +19,12 @@
 
 #include <algorithm>
 #include <cctype>
+#include <cstdint>
 #include <numeric>
 #include <optional>
 #include <span>
+#include <string>
+#include <string_view>
 
 #include <ftl/hash.h>
 #include <log/log.h>
@@ -194,6 +197,21 @@
     const uint16_t productId =
             static_cast<uint16_t>(edid[kProductIdOffset] | (edid[kProductIdOffset + 1] << 8));
 
+    //   Bytes 12-15: display serial number, in little-endian (LSB). This field is
+    //   optional and its absence is marked by having all bytes set to 0x00.
+    //   Values do not represent ASCII characters.
+    constexpr size_t kSerialNumberOffset = 12;
+    if (edid.size() < kSerialNumberOffset + sizeof(uint32_t)) {
+        ALOGE("Invalid EDID: block zero S/N is truncated.");
+        return {};
+    }
+    const uint32_t blockZeroSerialNumber = edid[kSerialNumberOffset] +
+            (edid[kSerialNumberOffset + 1] << 8) + (edid[kSerialNumberOffset + 2] << 16) +
+            (edid[kSerialNumberOffset + 3] << 24);
+    const auto hashedBlockZeroSNOpt = blockZeroSerialNumber == 0
+            ? std::nullopt
+            : ftl::stable_hash(std::string_view(std::to_string(blockZeroSerialNumber)));
+
     constexpr size_t kManufactureWeekOffset = 16;
     if (edid.size() < kManufactureWeekOffset + sizeof(uint8_t)) {
         ALOGE("Invalid EDID: manufacture week is truncated.");
@@ -212,6 +230,15 @@
     ALOGW_IF(manufactureOrModelYear <= 0xf,
              "Invalid EDID: model year or manufacture year cannot be in the range [0x0, 0xf].");
 
+    constexpr size_t kMaxHorizontalPhysicalSizeOffset = 21;
+    constexpr size_t kMaxVerticalPhysicalSizeOffset = 22;
+    if (edid.size() < kMaxVerticalPhysicalSizeOffset + sizeof(uint8_t)) {
+        ALOGE("Invalid EDID: display's physical size is truncated.");
+        return {};
+    }
+    ui::Size maxPhysicalSizeInCm(edid[kMaxHorizontalPhysicalSizeOffset],
+                                 edid[kMaxVerticalPhysicalSizeOffset]);
+
     constexpr size_t kDescriptorOffset = 54;
     if (edid.size() < kDescriptorOffset) {
         ALOGE("Invalid EDID: descriptors are missing.");
@@ -222,7 +249,8 @@
     view = view.subspan(kDescriptorOffset);
 
     std::string_view displayName;
-    std::string_view serialNumber;
+    std::string_view descriptorBlockSerialNumber;
+    std::optional<uint64_t> hashedDescriptorBlockSNOpt = std::nullopt;
     std::string_view asciiText;
     ui::Size preferredDTDPixelSize;
     ui::Size preferredDTDPhysicalSize;
@@ -247,7 +275,10 @@
                     asciiText = parseEdidText(descriptor);
                     break;
                 case 0xff:
-                    serialNumber = parseEdidText(descriptor);
+                    descriptorBlockSerialNumber = parseEdidText(descriptor);
+                    hashedDescriptorBlockSNOpt = descriptorBlockSerialNumber.empty()
+                            ? std::nullopt
+                            : ftl::stable_hash(descriptorBlockSerialNumber);
                     break;
             }
         } else if (isDetailedTimingDescriptor(view)) {
@@ -288,7 +319,7 @@
 
     if (modelString.empty()) {
         ALOGW("Invalid EDID: falling back to serial number due to missing display name.");
-        modelString = serialNumber;
+        modelString = descriptorBlockSerialNumber;
     }
     if (modelString.empty()) {
         ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number.");
@@ -341,11 +372,14 @@
     return Edid{
             .manufacturerId = manufacturerId,
             .productId = productId,
+            .hashedBlockZeroSerialNumberOpt = hashedBlockZeroSNOpt,
+            .hashedDescriptorBlockSerialNumberOpt = hashedDescriptorBlockSNOpt,
             .pnpId = *pnpId,
             .modelHash = modelHash,
             .displayName = displayName,
             .manufactureOrModelYear = manufactureOrModelYear,
             .manufactureWeek = manufactureWeek,
+            .physicalSizeInCm = maxPhysicalSizeInCm,
             .cea861Block = cea861Block,
             .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
     };
diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index 648e024..cf67d7b 100644
--- a/libs/ui/include/ui/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -69,11 +69,14 @@
 struct Edid {
     uint16_t manufacturerId;
     uint16_t productId;
+    std::optional<uint64_t> hashedBlockZeroSerialNumberOpt;
+    std::optional<uint64_t> hashedDescriptorBlockSerialNumberOpt;
     PnpId pnpId;
     uint32_t modelHash;
     std::string_view displayName;
     uint8_t manufactureOrModelYear;
     uint8_t manufactureWeek;
+    ui::Size physicalSizeInCm;
     std::optional<Cea861ExtensionBlock> cea861Block;
     std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
 };
diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h
index af494dc..9d97151 100644
--- a/libs/ui/include/ui/DynamicDisplayInfo.h
+++ b/libs/ui/include/ui/DynamicDisplayInfo.h
@@ -59,6 +59,9 @@
 
     // Represents frame rate for FrameRateCategory Normal and High.
     ui::FrameRateCategoryRate frameRateCategoryRate;
+
+    // All the refresh rates supported for the default display mode.
+    std::vector<float> supportedRefreshRates;
 };
 
 } // namespace android::ui
diff --git a/libs/ui/include_types/ui/HdrRenderTypeUtils.h b/libs/ui/include_types/ui/HdrRenderTypeUtils.h
index b0af878..70c50f0 100644
--- a/libs/ui/include_types/ui/HdrRenderTypeUtils.h
+++ b/libs/ui/include_types/ui/HdrRenderTypeUtils.h
@@ -61,4 +61,24 @@
     return HdrRenderType::SDR;
 }
 
-} // namespace android
\ No newline at end of file
+/**
+ * Returns the maximum headroom allowed for this content under "idealized"
+ * display conditions (low surround luminance, high-enough display brightness).
+ *
+ * TODO: take into account hdr metadata, but square it with the fact that some
+ * HLG content has CTA.861-3 metadata
+ */
+inline float getIdealizedMaxHeadroom(ui::Dataspace dataspace) {
+    const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+
+    switch (transfer) {
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            return 10000.0f / 203.0f;
+        case HAL_DATASPACE_TRANSFER_HLG:
+            return 1000.0f / 203.0f;
+        default:
+            return 1.0f;
+    }
+}
+
+} // namespace android
diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
index 76e3f66..d1699e7 100644
--- a/libs/ui/tests/DisplayIdentification_test.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -33,7 +33,7 @@
 namespace {
 
 const unsigned char kInternalEdid[] =
-        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00"
+        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x4e\x61\xbc\x00"
         "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27"
         "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
         "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30"
@@ -54,7 +54,7 @@
 
 // Extended EDID with timing extension.
 const unsigned char kExternalEedid[] =
-        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\x00\x00\x00\x00"
+        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\xb1\x7f\x39\x05"
         "\x29\x15\x01\x03\x80\x10\x09\x78\x0a\xee\x91\xa3\x54\x4c\x99\x26"
         "\x0f\x50\x54\xbd\xef\x80\x71\x4f\x81\xc0\x81\x00\x81\x80\x95\x00"
         "\xa9\xc0\xb3\x00\x01\x01\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c"
@@ -112,7 +112,7 @@
         "\x07";
 
 const unsigned char kCtlDisplayEdid[] =
-        "\x00\xff\xff\xff\xff\xff\xff\x00\x0e\x8c\x9d\x24\x00\x00\x00\x00"
+        "\x00\xff\xff\xff\xff\xff\xff\x00\x0e\x8c\x9d\x24\x30\x41\xab\x00"
         "\xff\x17\x01\x04\xa5\x34\x1d\x78\x3a\xa7\x25\xa4\x57\x51\xa0\x26"
         "\x10\x50\x54\xbf\xef\x80\xb3\x00\xa9\x40\x95\x00\x81\x40\x81\x80"
         "\x95\x0f\x71\x4f\x90\x40\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c"
@@ -191,8 +191,13 @@
     EXPECT_EQ(hash("121AT11-801"), 626564263);
     EXPECT_TRUE(edid->displayName.empty());
     EXPECT_EQ(12610, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("12345678"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(21, edid->manufactureOrModelYear);
     EXPECT_EQ(0, edid->manufactureWeek);
+    EXPECT_EQ(26, edid->physicalSizeInCm.width);
+    EXPECT_EQ(16, edid->physicalSizeInCm.height);
     EXPECT_FALSE(edid->cea861Block);
     EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
     EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
@@ -207,8 +212,14 @@
     EXPECT_EQ(hash("HP ZR30w"), 918492362);
     EXPECT_EQ("HP ZR30w", edid->displayName);
     EXPECT_EQ(10348, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("16843009"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_TRUE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("CN4202137Q"), edid->hashedDescriptorBlockSerialNumberOpt.value());
     EXPECT_EQ(22, edid->manufactureOrModelYear);
     EXPECT_EQ(2, edid->manufactureWeek);
+    EXPECT_EQ(64, edid->physicalSizeInCm.width);
+    EXPECT_EQ(40, edid->physicalSizeInCm.height);
     EXPECT_FALSE(edid->cea861Block);
     EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
     EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
@@ -223,8 +234,13 @@
     EXPECT_EQ(hash("SAMSUNG"), 1201368132);
     EXPECT_EQ("SAMSUNG", edid->displayName);
     EXPECT_EQ(2302, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("87654321"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(21, edid->manufactureOrModelYear);
     EXPECT_EQ(41, edid->manufactureWeek);
+    EXPECT_EQ(16, edid->physicalSizeInCm.width);
+    EXPECT_EQ(9, edid->physicalSizeInCm.height);
     ASSERT_TRUE(edid->cea861Block);
     ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
     auto physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -245,8 +261,13 @@
     EXPECT_EQ(hash("Panasonic-TV"), 3876373262);
     EXPECT_EQ("Panasonic-TV", edid->displayName);
     EXPECT_EQ(41622, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("16843009"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(29, edid->manufactureOrModelYear);
     EXPECT_EQ(0, edid->manufactureWeek);
+    EXPECT_EQ(128, edid->physicalSizeInCm.width);
+    EXPECT_EQ(72, edid->physicalSizeInCm.height);
     ASSERT_TRUE(edid->cea861Block);
     ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
     physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -267,8 +288,12 @@
     EXPECT_EQ(hash("Hisense"), 2859844809);
     EXPECT_EQ("Hisense", edid->displayName);
     EXPECT_EQ(0, edid->productId);
+    EXPECT_FALSE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(29, edid->manufactureOrModelYear);
     EXPECT_EQ(18, edid->manufactureWeek);
+    EXPECT_EQ(0, edid->physicalSizeInCm.width);
+    EXPECT_EQ(0, edid->physicalSizeInCm.height);
     ASSERT_TRUE(edid->cea861Block);
     ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
     physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -289,8 +314,13 @@
     EXPECT_EQ(hash("LP2361"), 1523181158);
     EXPECT_EQ("LP2361", edid->displayName);
     EXPECT_EQ(9373, edid->productId);
+    EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+    EXPECT_EQ(ftl::stable_hash("11223344"), edid->hashedBlockZeroSerialNumberOpt.value());
+    EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
     EXPECT_EQ(23, edid->manufactureOrModelYear);
     EXPECT_EQ(0xff, edid->manufactureWeek);
+    EXPECT_EQ(52, edid->physicalSizeInCm.width);
+    EXPECT_EQ(29, edid->physicalSizeInCm.height);
     ASSERT_TRUE(edid->cea861Block);
     EXPECT_FALSE(edid->cea861Block->hdmiVendorDataBlock);
     EXPECT_EQ(1360, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
@@ -447,4 +477,4 @@
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index b19a862..91250b9 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -63,6 +63,18 @@
     unversioned_until: "current",
 }
 
+aconfig_declarations {
+    name: "egl_flags",
+    package: "com.android.graphics.egl.flags",
+    container: "system",
+    srcs: ["EGL/egl_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libegl_flags",
+    aconfig_declarations: "egl_flags",
+}
+
 cc_defaults {
     name: "gl_libs_defaults",
     cflags: [
@@ -136,6 +148,7 @@
     ],
     export_include_dirs: ["EGL"],
     shared_libs: [
+        "libegl_flags",
         "libz",
     ],
 }
@@ -166,6 +179,7 @@
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
         "libbase",
+        "libegl_flags",
         "libhidlbase",
         "libnativebridge_lazy",
         "libnativeloader_lazy",
@@ -202,6 +216,7 @@
         "EGL/MultifileBlobCache_test.cpp",
     ],
     shared_libs: [
+        "libegl_flags",
         "libutils",
         "libz",
     ],
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index f7e33b3..04c525e 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -38,11 +38,20 @@
 
 #include <utils/JenkinsHash.h>
 
+#include <com_android_graphics_egl_flags.h>
+
+using namespace com::android::graphics::egl;
+
 using namespace std::literals;
 
 constexpr uint32_t kMultifileMagic = 'MFB$';
 constexpr uint32_t kCrcPlaceholder = 0;
 
+// When removing files, what fraction of the overall limit should be reached when removing files
+// A divisor of two will decrease the cache to 50%, four to 25% and so on
+// We use the same limit to manage size and entry count
+constexpr uint32_t kCacheLimitDivisor = 2;
+
 namespace {
 
 // Helper function to close entries or free them
@@ -72,6 +81,7 @@
         mMaxTotalEntries(maxTotalEntries),
         mTotalCacheSize(0),
         mTotalCacheEntries(0),
+        mTotalCacheSizeDivisor(kCacheLimitDivisor),
         mHotCacheLimit(0),
         mHotCacheSize(0),
         mWorkerThreadIdle(true) {
@@ -80,8 +90,13 @@
         return;
     }
 
-    // Set the cache version, override if debug value set
+    // Set the cache version
     mCacheVersion = kMultifileBlobCacheVersion;
+    // Bump the version if we're using flagged features
+    if (flags::multifile_blobcache_advanced_usage()) {
+        mCacheVersion++;
+    }
+    // Override if debug value set
     int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
     if (debugCacheVersion >= 0) {
         ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
@@ -122,7 +137,7 @@
     // Check that our cacheVersion and buildId match
     struct stat st;
     if (stat(mMultifileDirName.c_str(), &st) == 0) {
-        if (checkStatus(mMultifileDirName.c_str())) {
+        if (checkStatus(mMultifileDirName)) {
             statusGood = true;
         } else {
             ALOGV("INIT: Cache status has changed, clearing the cache");
@@ -237,11 +252,9 @@
 
                 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
 
-                // Track details for rapid lookup later
-                trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
-
-                // Track the total size
-                increaseTotalCacheSize(fileSize);
+                // Track details for rapid lookup later and update total size
+                // Note access time is a full timespec instead of just seconds
+                trackEntry(entryHash, header.valueSize, fileSize, st.st_atim);
 
                 // Preload the entry for fast retrieval
                 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
@@ -317,6 +330,28 @@
     // Generate a hash of the key and use it to track this entry
     uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
 
+    std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
+
+    // See if we already have this file
+    if (flags::multifile_blobcache_advanced_usage() && contains(entryHash)) {
+        // Remove previous entry from hot cache
+        removeFromHotCache(entryHash);
+
+        // Remove previous entry and update the overall cache size
+        removeEntry(entryHash);
+
+        // If valueSize is zero, this is an indication that the user wants to remove the entry from
+        // cache It has already been removed from tracking, now remove it from disk It is safe to do
+        // this immediately because we drained the write queue in removeFromHotCache
+        if (valueSize == 0) {
+            ALOGV("SET: Zero size detected for existing entry, removing %u from cache", entryHash);
+            if (remove(fullPath.c_str()) != 0) {
+                ALOGW("SET: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+            }
+            return;
+        }
+    }
+
     size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
 
     // If we're going to be over the cache limit, kick off a trim to clear space
@@ -339,13 +374,12 @@
     memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
            static_cast<const void*>(value), valueSize);
 
-    std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
-
-    // Track the size and access time for quick recall
-    trackEntry(entryHash, valueSize, fileSize, time(0));
-
-    // Update the overall cache size
-    increaseTotalCacheSize(fileSize);
+    // Track the size and access time for quick recall and update the overall cache size
+    struct timespec time = {0, 0};
+    if (flags::multifile_blobcache_advanced_usage()) {
+        clock_gettime(CLOCK_REALTIME, &time);
+    }
+    trackEntry(entryHash, valueSize, fileSize, time);
 
     // Keep the entry in hot cache for quick retrieval
     ALOGV("SET: Adding %u to hot cache.", entryHash);
@@ -427,6 +461,14 @@
     if (mHotCache.find(entryHash) != mHotCache.end()) {
         ALOGV("GET: HotCache HIT for entry %u", entryHash);
         cacheEntry = mHotCache[entryHash].entryBuffer;
+
+        if (flags::multifile_blobcache_advanced_usage()) {
+            // Update last access time on disk
+            struct timespec times[2];
+            times[0].tv_nsec = UTIME_NOW;
+            times[1].tv_nsec = UTIME_OMIT;
+            utimensat(0, fullPath.c_str(), times, 0);
+        }
     } else {
         ALOGV("GET: HotCache MISS for entry: %u", entryHash);
 
@@ -455,6 +497,14 @@
         cacheEntry =
                 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
 
+        if (flags::multifile_blobcache_advanced_usage()) {
+            // Update last access time and omit last modify time
+            struct timespec times[2];
+            times[0].tv_nsec = UTIME_NOW;
+            times[1].tv_nsec = UTIME_OMIT;
+            futimens(fd, times);
+        }
+
         // We can close the file now and the mmap will remain
         close(fd);
 
@@ -491,6 +541,13 @@
         return 0;
     }
 
+    if (flags::multifile_blobcache_advanced_usage()) {
+        // Update the entry time for this hash, so it reflects LRU
+        struct timespec time;
+        clock_gettime(CLOCK_REALTIME, &time);
+        updateEntryTime(entryHash, time);
+    }
+
     // Remaining entry following the key is the value
     uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
     memcpy(value, cachedValue, cachedValueSize);
@@ -626,9 +683,47 @@
 }
 
 void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
-                                    time_t accessTime) {
+                                    const timespec& accessTime) {
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    // When we add this entry to the map, it is sorted by accessTime
+    MultifileEntryStatsMapIter entryStatsIter =
+            mEntryStats.emplace(std::piecewise_construct, std::forward_as_tuple(accessTime),
+                                std::forward_as_tuple(entryHash, valueSize, fileSize));
+
+    // Track all entries with quick access to its stats
+    mEntries.emplace(entryHash, entryStatsIter);
+#else
+    (void)accessTime;
     mEntries.insert(entryHash);
-    mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
+    mEntryStats[entryHash] = {entryHash, valueSize, fileSize};
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+
+    increaseTotalCacheSize(fileSize);
+}
+
+bool MultifileBlobCache::removeEntry(uint32_t entryHash) {
+    auto entryIter = mEntries.find(entryHash);
+    if (entryIter == mEntries.end()) {
+        return false;
+    }
+
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
+    MultifileEntryStats entryStats = entryStatsIter->second;
+    decreaseTotalCacheSize(entryStats.fileSize);
+#else
+    auto entryStatsIter = mEntryStats.find(entryHash);
+    if (entryStatsIter == mEntryStats.end()) {
+        ALOGE("Failed to remove entryHash (%u) from mEntryStats", entryHash);
+        return false;
+    }
+    decreaseTotalCacheSize(entryStatsIter->second.fileSize);
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+
+    mEntryStats.erase(entryStatsIter);
+    mEntries.erase(entryIter);
+
+    return true;
 }
 
 bool MultifileBlobCache::contains(uint32_t hashEntry) const {
@@ -636,7 +731,40 @@
 }
 
 MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    auto entryIter = mEntries.find(entryHash);
+    if (entryIter == mEntries.end()) {
+        return {};
+    }
+
+    MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
+    MultifileEntryStats entryStats = entryStatsIter->second;
+    return entryStats;
+#else
     return mEntryStats[entryHash];
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+}
+
+void MultifileBlobCache::updateEntryTime(uint32_t entryHash, const timespec& newTime) {
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    // This function updates the ordering of the map by removing the old iterators
+    // and re-adding them. If should be perforant as it does not perform a full re-sort.
+    // First, pull out the old entryStats
+    auto entryIter = mEntries.find(entryHash);
+    MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
+    MultifileEntryStats entryStats = std::move(entryStatsIter->second);
+
+    // Remove the old iterators
+    mEntryStats.erase(entryStatsIter);
+    mEntries.erase(entryIter);
+
+    // Insert the new with updated time
+    entryStatsIter = mEntryStats.emplace(std::make_pair(newTime, std::move(entryStats)));
+    mEntries.emplace(entryHash, entryStatsIter);
+#else
+    (void)entryHash;
+    (void)newTime;
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
 }
 
 void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
@@ -718,31 +846,32 @@
 bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
     // Walk through our map of sorted last access times and remove files until under the limit
     for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+        const MultifileEntryStats& entryStats = cacheEntryIter->second;
+        uint32_t entryHash = entryStats.entryHash;
+#else
         uint32_t entryHash = cacheEntryIter->first;
+        const MultifileEntryStats& entryStats = cacheEntryIter->second;
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
 
         ALOGV("LRU: Removing entryHash %u", entryHash);
 
-        // Track the overall size
-        MultifileEntryStats entryStats = getEntryStats(entryHash);
-        decreaseTotalCacheSize(entryStats.fileSize);
-
         // Remove it from hot cache if present
         removeFromHotCache(entryHash);
 
         // Remove it from the system
         std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
         if (remove(entryPath.c_str()) != 0) {
-            ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
-            return false;
+            // Continue evicting invalid item (app's cache might be cleared)
+            ALOGW("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
         }
 
         // Increment the iterator before clearing the entry
         cacheEntryIter++;
 
-        // Delete the entry from our tracking
-        size_t count = mEntryStats.erase(entryHash);
-        if (count != 1) {
-            ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
+        // Delete the entry from our tracking and update the overall cache size
+        if (!removeEntry(entryHash)) {
+            ALOGE("LRU: Failed to remove entryHash %u", entryHash);
             return false;
         }
 
@@ -794,11 +923,6 @@
     return true;
 }
 
-// When removing files, what fraction of the overall limit should be reached when removing files
-// A divisor of two will decrease the cache to 50%, four to 25% and so on
-// We use the same limit to manage size and entry count
-constexpr uint32_t kCacheLimitDivisor = 2;
-
 // Calculate the cache size and remove old entries until under the limit
 void MultifileBlobCache::trimCache() {
     // Wait for all deferred writes to complete
@@ -806,8 +930,10 @@
     waitForWorkComplete();
 
     ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
-          mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
-    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
+          mMaxTotalSize / mTotalCacheSizeDivisor, mMaxTotalEntries / mTotalCacheSizeDivisor);
+
+    if (!applyLRU(mMaxTotalSize / mTotalCacheSizeDivisor,
+                  mMaxTotalEntries / mTotalCacheSizeDivisor)) {
         ALOGE("Error when clearing multifile shader cache");
         return;
     }
@@ -830,9 +956,36 @@
             // Create the file or reset it if already present, read+write for user only
             int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
             if (fd == -1) {
-                ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
-                      fullPath.c_str(), std::strerror(errno));
-                return;
+                if (flags::multifile_blobcache_advanced_usage()) {
+                    struct stat st;
+                    if (stat(mMultifileDirName.c_str(), &st) == -1) {
+                        ALOGW("Cache directory missing (app's cache cleared?). Recreating...");
+
+                        // Restore the multifile directory
+                        if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
+                            ALOGE("Cache error in SET - Unable to create directory (%s), errno "
+                                  "(%i)",
+                                  mMultifileDirName.c_str(), errno);
+                            return;
+                        }
+
+                        // Create new status file
+                        if (!createStatus(mMultifileDirName.c_str())) {
+                            ALOGE("Cache error in SET - Failed to create status file!");
+                            return;
+                        }
+
+                        // Try to open the file again
+                        fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
+                                  S_IRUSR | S_IWUSR);
+                    }
+                }
+
+                if (fd == -1) {
+                    ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
+                          fullPath.c_str(), std::strerror(errno));
+                    return;
+                }
             }
 
             ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
@@ -849,6 +1002,14 @@
                 return;
             }
 
+            if (flags::multifile_blobcache_advanced_usage()) {
+                // Update last access time and last modify time
+                struct timespec times[2];
+                times[0].tv_nsec = UTIME_NOW;
+                times[1].tv_nsec = UTIME_NOW;
+                futimens(fd, times);
+            }
+
             ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
             close(fd);
 
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
index 65aa2db..3bd393f 100644
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -32,6 +32,10 @@
 
 #include "FileBlobCache.h"
 
+#include <com_android_graphics_egl_flags.h>
+
+using namespace com::android::graphics::egl;
+
 namespace android {
 
 constexpr uint32_t kMultifileBlobCacheVersion = 2;
@@ -45,9 +49,9 @@
 };
 
 struct MultifileEntryStats {
+    uint32_t entryHash;
     EGLsizeiANDROID valueSize;
     size_t fileSize;
-    time_t accessTime;
 };
 
 struct MultifileStatus {
@@ -100,6 +104,26 @@
     size_t mBufferSize;
 };
 
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+struct MultifileTimeLess {
+    bool operator()(const struct timespec& t1, const struct timespec& t2) const {
+        if (t1.tv_sec == t2.tv_sec) {
+            // If seconds are equal, check nanoseconds
+            return t1.tv_nsec < t2.tv_nsec;
+        } else {
+            // Otherwise, compare seconds
+            return t1.tv_sec < t2.tv_sec;
+        }
+    }
+};
+
+// The third parameter here causes all entries to be sorted by access time,
+// so oldest will be accessed first in applyLRU
+using MultifileEntryStatsMap =
+        std::multimap<struct timespec, MultifileEntryStats, MultifileTimeLess>;
+using MultifileEntryStatsMapIter = MultifileEntryStatsMap::iterator;
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+
 class MultifileBlobCache {
 public:
     MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
@@ -115,6 +139,7 @@
 
     size_t getTotalSize() const { return mTotalCacheSize; }
     size_t getTotalEntries() const { return mTotalCacheEntries; }
+    size_t getTotalCacheSizeDivisor() const { return mTotalCacheSizeDivisor; }
 
     const std::string& getCurrentBuildId() const { return mBuildId; }
     void setCurrentBuildId(const std::string& buildId) { mBuildId = buildId; }
@@ -124,10 +149,11 @@
 
 private:
     void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
-                    time_t accessTime);
+                    const timespec& accessTime);
     bool contains(uint32_t entryHash) const;
     bool removeEntry(uint32_t entryHash);
     MultifileEntryStats getEntryStats(uint32_t entryHash);
+    void updateEntryTime(uint32_t entryHash, const timespec& newTime);
 
     bool createStatus(const std::string& baseDir);
     bool checkStatus(const std::string& baseDir);
@@ -151,8 +177,14 @@
     std::string mBuildId;
     uint32_t mCacheVersion;
 
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    std::unordered_map<uint32_t, MultifileEntryStatsMapIter> mEntries;
+    MultifileEntryStatsMap mEntryStats;
+#else
     std::unordered_set<uint32_t> mEntries;
     std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+
     std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
 
     size_t mMaxKeySize;
@@ -161,6 +193,7 @@
     size_t mMaxTotalEntries;
     size_t mTotalCacheSize;
     size_t mTotalCacheEntries;
+    size_t mTotalCacheSizeDivisor;
     size_t mHotCacheLimit;
     size_t mHotCacheEntryLimit;
     size_t mHotCacheSize;
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index 90a0f1e..85fb29e 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -21,10 +21,15 @@
 #include <fcntl.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
+#include <utils/JenkinsHash.h>
 
 #include <fstream>
 #include <memory>
 
+#include <com_android_graphics_egl_flags.h>
+
+using namespace com::android::graphics::egl;
+
 using namespace std::literals;
 
 namespace android {
@@ -55,6 +60,7 @@
     std::vector<std::string> getCacheEntries();
 
     void clearProperties();
+    bool clearCache();
 
     std::unique_ptr<TemporaryFile> mTempFile;
     std::unique_ptr<MultifileBlobCache> mMBC;
@@ -314,7 +320,7 @@
 
     struct stat info;
     if (stat(multifileDirName.c_str(), &info) == 0) {
-        // We have a multifile dir. Skip the status file and return the only entry.
+        // We have a multifile dir. Skip the status file and return the entries.
         DIR* dir;
         struct dirent* entry;
         if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
@@ -325,6 +331,7 @@
                 if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
                     continue;
                 }
+                // printf("Found entry: %s\n", entry->d_name);
                 cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
             }
         } else {
@@ -458,6 +465,8 @@
     // Set one entry
     mMBC->set("abcd", 4, "efgh", 4);
 
+    uint32_t initialCacheVersion = mMBC->getCurrentCacheVersion();
+
     // Close the cache so everything writes out
     mMBC->finish();
     mMBC.reset();
@@ -466,7 +475,7 @@
     ASSERT_EQ(getCacheEntries().size(), 1);
 
     // Set a debug cacheVersion
-    std::string newCacheVersion = std::to_string(kMultifileBlobCacheVersion + 1);
+    std::string newCacheVersion = std::to_string(initialCacheVersion + 1);
     ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
     ASSERT_TRUE(
             base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
@@ -503,4 +512,404 @@
     ASSERT_EQ(getCacheEntries().size(), 0);
 }
 
+// Ensure cache is correct when a key is reused
+TEST_F(MultifileBlobCacheTest, SameKeyDifferentValues) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+
+    size_t startingSize = mMBC->getTotalSize();
+
+    // New cache should be empty
+    ASSERT_EQ(startingSize, 0);
+
+    // Set an initial value
+    mMBC->set("ab", 2, "cdef", 4);
+
+    // Grab the new size
+    size_t firstSize = mMBC->getTotalSize();
+
+    // Ensure the size went up
+    // Note: Checking for an exact size is challenging, as the
+    // file size can differ between platforms.
+    ASSERT_GT(firstSize, startingSize);
+
+    // Verify the cache is correct
+    ASSERT_EQ(size_t(4), mMBC->get("ab", 2, buf, 4));
+    ASSERT_EQ('c', buf[0]);
+    ASSERT_EQ('d', buf[1]);
+    ASSERT_EQ('e', buf[2]);
+    ASSERT_EQ('f', buf[3]);
+
+    // Now reuse the key with a smaller value
+    mMBC->set("ab", 2, "gh", 2);
+
+    // Grab the new size
+    size_t secondSize = mMBC->getTotalSize();
+
+    // Ensure it decreased in size
+    ASSERT_LT(secondSize, firstSize);
+
+    // Verify the cache is correct
+    ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+    ASSERT_EQ('g', buf[0]);
+    ASSERT_EQ('h', buf[1]);
+
+    // Now put back the original value
+    mMBC->set("ab", 2, "cdef", 4);
+
+    // And we should get back a stable size
+    size_t finalSize = mMBC->getTotalSize();
+    ASSERT_EQ(firstSize, finalSize);
+}
+
+// Ensure cache is correct when a key is reused with large value size
+TEST_F(MultifileBlobCacheTest, SameKeyLargeValues) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    // Create the cache with larger limits to stress test reuse
+    constexpr uint32_t kLocalMaxKeySize = 1 * 1024 * 1024;
+    constexpr uint32_t kLocalMaxValueSize = 4 * 1024 * 1024;
+    constexpr uint32_t kLocalMaxTotalSize = 32 * 1024 * 1024;
+    mMBC.reset(new MultifileBlobCache(kLocalMaxKeySize, kLocalMaxValueSize, kLocalMaxTotalSize,
+                                      kMaxTotalEntries, &mTempFile->path[0]));
+
+    constexpr uint32_t kLargeValueCount = 8;
+    constexpr uint32_t kLargeValueSize = 64 * 1024;
+
+    // Create a several really large values
+    unsigned char largeValue[kLargeValueCount][kLargeValueSize];
+    for (int i = 0; i < kLargeValueCount; i++) {
+        for (int j = 0; j < kLargeValueSize; j++) {
+            // Fill the value with the index for uniqueness
+            largeValue[i][j] = i;
+        }
+    }
+
+    size_t startingSize = mMBC->getTotalSize();
+
+    // New cache should be empty
+    ASSERT_EQ(startingSize, 0);
+
+    // Cycle through the values and set them all in sequence
+    for (int i = 0; i < kLargeValueCount; i++) {
+        mMBC->set("abcd", 4, largeValue[i], kLargeValueSize);
+    }
+
+    // Ensure we get the last one back
+    unsigned char outBuf[kLargeValueSize];
+    mMBC->get("abcd", 4, outBuf, kLargeValueSize);
+
+    for (int i = 0; i < kLargeValueSize; i++) {
+        // Buffer should contain highest index value
+        ASSERT_EQ(kLargeValueCount - 1, outBuf[i]);
+    }
+}
+
+// Ensure cache eviction is LRU
+TEST_F(MultifileBlobCacheTest, CacheEvictionIsLRU) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    // Fill the cache with exactly how much it can hold
+    int entry = 0;
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        // Use the index as the key and value
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+
+        int result = 0;
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // Ensure the cache is full
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
+
+    // Add one more entry to trigger eviction
+    size_t overflowEntry = kMaxTotalEntries;
+    mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry));
+
+    // Verify it contains the right amount, which will be one more than reduced size
+    // because we evict the cache before adding a new entry
+    size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor();
+    ASSERT_EQ(mMBC->getTotalEntries(), evictionLimit + 1);
+
+    // Ensure cache is as expected, with old entries removed, newer entries remaining
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        int result = 0;
+        mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
+
+        if (entry < evictionLimit) {
+            // We should get no hits on evicted entries, i.e. the first added
+            ASSERT_EQ(result, 0);
+        } else {
+            // Above the limit should still be present
+            ASSERT_EQ(result, entry);
+        }
+    }
+}
+
+// Ensure calling GET on an entry updates its access time, even if already in hotcache
+TEST_F(MultifileBlobCacheTest, GetUpdatesAccessTime) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    // Fill the cache with exactly how much it can hold
+    int entry = 0;
+    int result = 0;
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        // Use the index as the key and value
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // Ensure the cache is full
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
+
+    // GET the first few entries to update their access time
+    std::vector<int> accessedEntries = {1, 2, 3};
+    for (int i = 0; i < accessedEntries.size(); i++) {
+        entry = accessedEntries[i];
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+    }
+
+    // Add one more entry to trigger eviction
+    size_t overflowEntry = kMaxTotalEntries;
+    mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry));
+
+    size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor();
+
+    // Ensure cache is as expected, with old entries removed, newer entries remaining
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        int result = 0;
+        mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
+
+        if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) !=
+            accessedEntries.end()) {
+            // If this is one of the handful we accessed after filling the cache,
+            // they should still be in the cache because LRU
+            ASSERT_EQ(result, entry);
+        } else if (entry >= (evictionLimit + accessedEntries.size())) {
+            // If they were above the eviction limit (plus three for our updated entries),
+            // they should still be present
+            ASSERT_EQ(result, entry);
+        } else {
+            // Otherwise, they shold be evicted and no longer present
+            ASSERT_EQ(result, 0);
+        }
+    }
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Open the cache again
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Check the cache again, ensuring the updated access time made it to disk
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        int result = 0;
+        mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
+        if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) !=
+            accessedEntries.end()) {
+            ASSERT_EQ(result, entry);
+        } else if (entry >= (evictionLimit + accessedEntries.size())) {
+            ASSERT_EQ(result, entry);
+        } else {
+            ASSERT_EQ(result, 0);
+        }
+    }
+}
+
+bool MultifileBlobCacheTest::clearCache() {
+    std::string cachePath = &mTempFile->path[0];
+    std::string multifileDirName = cachePath + ".multifile";
+
+    DIR* dir = opendir(multifileDirName.c_str());
+    if (dir == nullptr) {
+        printf("Error opening directory: %s\n", multifileDirName.c_str());
+        return false;
+    }
+
+    struct dirent* entry;
+    while ((entry = readdir(dir)) != nullptr) {
+        // Skip "." and ".." entries
+        if (std::string(entry->d_name) == "." || std::string(entry->d_name) == "..") {
+            continue;
+        }
+
+        std::string entryPath = multifileDirName + "/" + entry->d_name;
+
+        // Delete the entry (we assert it's a file, nothing nested here)
+        if (unlink(entryPath.c_str()) != 0) {
+            printf("Error deleting file: %s\n", entryPath.c_str());
+            closedir(dir);
+            return false;
+        }
+    }
+
+    closedir(dir);
+
+    // Delete the empty directory itself
+    if (rmdir(multifileDirName.c_str()) != 0) {
+        printf("Error deleting directory %s, error %s\n", multifileDirName.c_str(),
+               std::strerror(errno));
+        return false;
+    }
+
+    return true;
+}
+
+// Recover from lost cache in the case of app clearing it
+TEST_F(MultifileBlobCacheTest, RecoverFromLostCache) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    int entry = 0;
+    int result = 0;
+
+    uint32_t kEntryCount = 10;
+
+    // Add some entries
+    for (entry = 0; entry < kEntryCount; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // For testing, wait until the entries have completed writing
+    mMBC->finish();
+
+    // Manually delete the cache!
+    ASSERT_TRUE(clearCache());
+
+    // Cache should not contain any entries
+    for (entry = 0; entry < kEntryCount; entry++) {
+        ASSERT_EQ(size_t(0), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+    }
+
+    // Ensure we can still add new ones
+    for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Open the cache again
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Before fixes, writing the second entries to disk should have failed due to missing
+    // cache dir.  But now they should have survived our shutdown above.
+    for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+}
+
+// Ensure cache eviction succeeds if the cache is deleted
+TEST_F(MultifileBlobCacheTest, EvictAfterLostCache) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    int entry = 0;
+    int result = 0;
+
+    uint32_t kEntryCount = 10;
+
+    // Add some entries
+    for (entry = 0; entry < kEntryCount; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // For testing, wait until the entries have completed writing
+    mMBC->finish();
+
+    // Manually delete the cache!
+    ASSERT_TRUE(clearCache());
+
+    // Now start adding entries to trigger eviction, cache should survive
+    for (entry = kEntryCount; entry < 2 * kMaxTotalEntries; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // We should have triggered multiple evictions above and remain at or below the
+    // max amount of entries
+    ASSERT_LE(getCacheEntries().size(), kMaxTotalEntries);
+}
+
+// Remove from cache when size is zero
+TEST_F(MultifileBlobCacheTest, ZeroSizeRemovesEntry) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    // Put some entries in
+    int entry = 0;
+    int result = 0;
+
+    uint32_t kEntryCount = 20;
+
+    // Add some entries
+    for (entry = 0; entry < kEntryCount; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // Send some of them again with size zero
+    std::vector<int> removedEntries = {5, 10, 18};
+    for (int i = 0; i < removedEntries.size(); i++) {
+        entry = removedEntries[i];
+        mMBC->set(&entry, sizeof(entry), nullptr, 0);
+    }
+
+    // Ensure they do not get a hit
+    for (int i = 0; i < removedEntries.size(); i++) {
+        entry = removedEntries[i];
+        ASSERT_EQ(size_t(0), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+    }
+
+    // And have been removed from disk
+    std::vector<std::string> diskEntries = getCacheEntries();
+    ASSERT_EQ(diskEntries.size(), kEntryCount - removedEntries.size());
+    for (int i = 0; i < removedEntries.size(); i++) {
+        entry = removedEntries[i];
+        // Generate a hash for our removed entries and ensure they are not contained
+        // Note our entry and key and the same here, so we're hashing the key just like
+        // the multifile blobcache does.
+        uint32_t entryHash =
+                android::JenkinsHashMixBytes(0, reinterpret_cast<uint8_t*>(&entry), sizeof(entry));
+        ASSERT_EQ(std::find(diskEntries.begin(), diskEntries.end(), std::to_string(entryHash)),
+                  diskEntries.end());
+    }
+
+    // Ensure the others are still present
+    for (entry = 0; entry < kEntryCount; entry++) {
+        if (std::find(removedEntries.begin(), removedEntries.end(), entry) ==
+            removedEntries.end()) {
+            ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+            ASSERT_EQ(result, entry);
+        }
+    }
+}
+
 } // namespace android
diff --git a/opengl/libs/EGL/egl_flags.aconfig b/opengl/libs/EGL/egl_flags.aconfig
new file mode 100644
index 0000000..1157970
--- /dev/null
+++ b/opengl/libs/EGL/egl_flags.aconfig
@@ -0,0 +1,13 @@
+package: "com.android.graphics.egl.flags"
+container: "system"
+
+flag {
+  name: "multifile_blobcache_advanced_usage"
+  namespace: "gpu"
+  description: "This flag controls new behaviors to address bugs found via advanced usage"
+  bug: "380483358"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp
index 4947e5f..fe5f2a6 100644
--- a/opengl/libs/EGL/fuzzer/Android.bp
+++ b/opengl/libs/EGL/fuzzer/Android.bp
@@ -37,6 +37,7 @@
     ],
 
     shared_libs: [
+        "libegl_flags",
         "libz",
     ],
 
diff --git a/opengl/tests/EGLTest/Android.bp b/opengl/tests/EGLTest/Android.bp
index aebd3f2..ed46efd 100644
--- a/opengl/tests/EGLTest/Android.bp
+++ b/opengl/tests/EGLTest/Android.bp
@@ -26,6 +26,7 @@
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
         "libEGL",
+        "libegl_flags",
         "libbase",
         "libcutils",
         "libbinder",
diff --git a/services/automotive/display/Android.bp b/services/automotive/display/Android.bp
index 72bd292..b63e919 100644
--- a/services/automotive/display/Android.bp
+++ b/services/automotive/display/Android.bp
@@ -23,6 +23,11 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+vintf_fragment {
+    name: "manifest_android.frameworks.automotive.display@1.0.xml",
+    src: "manifest_android.frameworks.automotive.display@1.0.xml",
+}
+
 cc_binary {
     name: "android.frameworks.automotive.display@1.0-service",
     defaults: ["hidl_defaults"],
@@ -50,7 +55,7 @@
         "-DLOG_TAG=\"AutomotiveDisplayService\""
     ],
 
-    vintf_fragments: [
+    vintf_fragment_modules: [
         "manifest_android.frameworks.automotive.display@1.0.xml",
     ],
 }
diff --git a/services/gpuservice/gpuwork/bpfprogs/gpuWork.c b/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
index f470189..94abc69 100644
--- a/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
+++ b/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
@@ -20,11 +20,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#ifdef MOCK_BPF
-#include <test/mock_bpf_helpers.h>
-#else
 #include <bpf_helpers.h>
-#endif
 
 #define S_IN_NS (1000000000)
 #define SMALL_TIME_GAP_LIMIT_NS (S_IN_NS)
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 006d507..811692f 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -64,9 +64,8 @@
              !isFromSource(sources, AINPUT_SOURCE_STYLUS));
 }
 
-inline void notifyPointerDisplayChange(
-        std::optional<std::tuple<ui::LogicalDisplayId, FloatPoint>> change,
-        PointerChoreographerPolicyInterface& policy) {
+inline void notifyPointerDisplayChange(std::optional<std::tuple<ui::LogicalDisplayId, vec2>> change,
+                                       PointerChoreographerPolicyInterface& policy) {
     if (!change) {
         return;
     }
@@ -139,23 +138,24 @@
         mShowTouchesEnabled(false),
         mStylusPointerIconEnabled(false),
         mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT),
+        mIsWindowInfoListenerRegistered(false),
+        mWindowInfoListener(sp<PointerChoreographerDisplayInfoListener>::make(this)),
         mRegisterListener(registerListener),
         mUnregisterListener(unregisterListener) {}
 
 PointerChoreographer::~PointerChoreographer() {
-    std::scoped_lock _l(mLock);
-    if (mWindowInfoListener == nullptr) {
-        return;
+    if (mIsWindowInfoListenerRegistered) {
+        mUnregisterListener(mWindowInfoListener);
+        mIsWindowInfoListenerRegistered = false;
     }
     mWindowInfoListener->onPointerChoreographerDestroyed();
-    mUnregisterListener(mWindowInfoListener);
 }
 
 void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
     PointerDisplayChange pointerDisplayChange;
 
     { // acquire lock
-        std::scoped_lock _l(mLock);
+        std::scoped_lock _l(getLock());
 
         mInputDeviceInfos = args.inputDeviceInfos;
         pointerDisplayChange = updatePointerControllersLocked();
@@ -191,7 +191,7 @@
         return;
     }
 
-    std::scoped_lock _l(mLock);
+    std::scoped_lock _l(getLock());
     ui::LogicalDisplayId targetDisplay = args.displayId;
     if (targetDisplay == ui::LogicalDisplayId::INVALID) {
         targetDisplay = mCurrentFocusedDisplay;
@@ -204,20 +204,30 @@
 }
 
 NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
-    std::scoped_lock _l(mLock);
+    NotifyMotionArgs newArgs(args);
+    PointerDisplayChange pointerDisplayChange;
+    { // acquire lock
+        std::scoped_lock _l(getLock());
+        if (isFromMouse(args)) {
+            newArgs = processMouseEventLocked(args);
+            pointerDisplayChange = calculatePointerDisplayChangeToNotify();
+        } else if (isFromTouchpad(args)) {
+            newArgs = processTouchpadEventLocked(args);
+            pointerDisplayChange = calculatePointerDisplayChangeToNotify();
+        } else if (isFromDrawingTablet(args)) {
+            processDrawingTabletEventLocked(args);
+        } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
+            processStylusHoverEventLocked(args);
+        } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
+            processTouchscreenAndStylusEventLocked(args);
+        }
+    } // release lock
 
-    if (isFromMouse(args)) {
-        return processMouseEventLocked(args);
-    } else if (isFromTouchpad(args)) {
-        return processTouchpadEventLocked(args);
-    } else if (isFromDrawingTablet(args)) {
-        processDrawingTabletEventLocked(args);
-    } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
-        processStylusHoverEventLocked(args);
-    } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
-        processTouchscreenAndStylusEventLocked(args);
+    if (pointerDisplayChange) {
+        // pointer display may have changed if mouse crossed display boundary
+        notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
     }
-    return args;
+    return newArgs;
 }
 
 NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
@@ -234,24 +244,18 @@
     if (MotionEvent::isValidCursorPosition(args.xCursorPosition, args.yCursorPosition)) {
         // This is an absolute mouse device that knows about the location of the cursor on the
         // display, so set the cursor position to the specified location.
-        const auto [x, y] = pc.getPosition();
-        const float deltaX = args.xCursorPosition - x;
-        const float deltaY = args.yCursorPosition - y;
+        const auto position = pc.getPosition();
+        const float deltaX = args.xCursorPosition - position.x;
+        const float deltaY = args.yCursorPosition - position.y;
         newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
         newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
         pc.setPosition(args.xCursorPosition, args.yCursorPosition);
     } else {
         // This is a relative mouse, so move the cursor by the specified amount.
-        const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-        const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-        pc.move(deltaX, deltaY);
-        const auto [x, y] = pc.getPosition();
-        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-        newArgs.xCursorPosition = x;
-        newArgs.yCursorPosition = y;
+        processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
     }
-    if (canUnfadeOnDisplay(displayId)) {
+    // Note displayId may have changed if the cursor moved to a different display
+    if (canUnfadeOnDisplay(newArgs.displayId)) {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
     return newArgs;
@@ -265,37 +269,132 @@
     newArgs.displayId = displayId;
     if (args.getPointerCount() == 1 && args.classification == MotionClassification::NONE) {
         // This is a movement of the mouse pointer.
-        const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-        const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-        pc.move(deltaX, deltaY);
-        if (canUnfadeOnDisplay(displayId)) {
-            pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
-        }
-
-        const auto [x, y] = pc.getPosition();
-        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-        newArgs.xCursorPosition = x;
-        newArgs.yCursorPosition = y;
+        processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
     } else {
         // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
-        if (canUnfadeOnDisplay(displayId)) {
-            pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
-        }
-
-        const auto [x, y] = pc.getPosition();
+        const auto position = pc.getPosition();
         for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
             newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                                  args.pointerCoords[i].getX() + x);
+                                                  args.pointerCoords[i].getX() + position.x);
             newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                                  args.pointerCoords[i].getY() + y);
+                                                  args.pointerCoords[i].getY() + position.y);
         }
-        newArgs.xCursorPosition = x;
-        newArgs.yCursorPosition = y;
+        newArgs.xCursorPosition = position.x;
+        newArgs.yCursorPosition = position.y;
+    }
+
+    // Note displayId may have changed if the cursor moved to a different display
+    if (canUnfadeOnDisplay(newArgs.displayId)) {
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
     return newArgs;
 }
 
+void PointerChoreographer::processPointerDeviceMotionEventLocked(NotifyMotionArgs& newArgs,
+                                                                 PointerControllerInterface& pc) {
+    const float deltaX = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float deltaY = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+
+    vec2 unconsumedDelta = pc.move(deltaX, deltaY);
+    if (com::android::input::flags::connected_displays_cursor() &&
+        (std::abs(unconsumedDelta.x) > 0 || std::abs(unconsumedDelta.y) > 0)) {
+        handleUnconsumedDeltaLocked(pc, unconsumedDelta);
+        // pointer may have moved to a different viewport
+        newArgs.displayId = pc.getDisplayId();
+    }
+
+    const auto position = pc.getPosition();
+    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, position.x);
+    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y);
+    newArgs.xCursorPosition = position.x;
+    newArgs.yCursorPosition = position.y;
+}
+
+void PointerChoreographer::handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
+                                                       const vec2& unconsumedDelta) {
+    // Display topology is in rotated coordinate space and Pointer controller returns and expects
+    // values in the un-rotated coordinate space. So we need to transform delta and cursor position
+    // back to the rotated coordinate space to lookup adjacent display in the display topology.
+    const auto& sourceDisplayTransform = pc.getDisplayTransform();
+    const vec2 rotatedUnconsumedDelta =
+            transformWithoutTranslation(sourceDisplayTransform, unconsumedDelta);
+    const vec2 cursorPosition = pc.getPosition();
+    const vec2 rotatedCursorPosition = sourceDisplayTransform.transform(cursorPosition);
+
+    // To find out the boundary that cursor is crossing we are checking delta in x and y direction
+    // respectively. This prioritizes x direction over y.
+    // In practise, majority of cases we only have non-zero values in either x or y coordinates,
+    // except sometimes near the corners.
+    // In these cases this behaviour is not noticeable. We also do not apply unconsumed delta on
+    // the destination display for the same reason.
+    DisplayPosition sourceBoundary;
+    float cursorOffset = 0.0f;
+    if (rotatedUnconsumedDelta.x > 0) {
+        sourceBoundary = DisplayPosition::RIGHT;
+        cursorOffset = rotatedCursorPosition.y;
+    } else if (rotatedUnconsumedDelta.x < 0) {
+        sourceBoundary = DisplayPosition::LEFT;
+        cursorOffset = rotatedCursorPosition.y;
+    } else if (rotatedUnconsumedDelta.y > 0) {
+        sourceBoundary = DisplayPosition::BOTTOM;
+        cursorOffset = rotatedCursorPosition.x;
+    } else {
+        sourceBoundary = DisplayPosition::TOP;
+        cursorOffset = rotatedCursorPosition.x;
+    }
+
+    const ui::LogicalDisplayId sourceDisplayId = pc.getDisplayId();
+    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> destination =
+            findDestinationDisplayLocked(sourceDisplayId, sourceBoundary, cursorOffset);
+    if (!destination.has_value()) {
+        // No matching adjacent display
+        return;
+    }
+
+    const DisplayViewport& destinationViewport = *destination->first;
+    const float destinationOffset = destination->second;
+    if (mMousePointersByDisplay.find(destinationViewport.displayId) !=
+        mMousePointersByDisplay.end()) {
+        LOG(FATAL) << "A cursor already exists on destination display"
+                   << destinationViewport.displayId;
+    }
+    mDefaultMouseDisplayId = destinationViewport.displayId;
+    auto pcNode = mMousePointersByDisplay.extract(sourceDisplayId);
+    pcNode.key() = destinationViewport.displayId;
+    mMousePointersByDisplay.insert(std::move(pcNode));
+
+    // Before updating the viewport and moving the cursor to appropriate location in the destination
+    // viewport, we need to temporarily hide the cursor. This will prevent it from appearing at the
+    // center of the display in any intermediate frames.
+    pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+    pc.setDisplayViewport(destinationViewport);
+    vec2 destinationPosition =
+            calculateDestinationPosition(destinationViewport, cursorOffset - destinationOffset,
+                                         sourceBoundary);
+
+    // Transform position back to un-rotated coordinate space before sending it to controller
+    destinationPosition = pc.getDisplayTransform().inverse().transform(destinationPosition.x,
+                                                                       destinationPosition.y);
+    pc.setPosition(destinationPosition.x, destinationPosition.y);
+    pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+}
+
+vec2 PointerChoreographer::calculateDestinationPosition(const DisplayViewport& destinationViewport,
+                                                        float pointerOffset,
+                                                        DisplayPosition sourceBoundary) {
+    // destination is opposite of the source boundary
+    switch (sourceBoundary) {
+        case DisplayPosition::RIGHT:
+            return {0, pointerOffset}; // left edge
+        case DisplayPosition::TOP:
+            return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
+        case DisplayPosition::LEFT:
+            return {destinationViewport.logicalRight, pointerOffset}; // right edge
+        case DisplayPosition::BOTTOM:
+            return {pointerOffset, 0}; // top edge
+    }
+}
+
 void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
     if (args.displayId == ui::LogicalDisplayId::INVALID) {
         return;
@@ -433,7 +532,7 @@
 }
 
 void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) {
-    std::scoped_lock _l(mLock);
+    std::scoped_lock _l(getLock());
     mTouchPointersByDevice.erase(args.deviceId);
     mStylusPointersByDevice.erase(args.deviceId);
     mDrawingTabletPointersByDevice.erase(args.deviceId);
@@ -441,23 +540,29 @@
 }
 
 void PointerChoreographer::onControllerAddedOrRemovedLocked() {
-    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
+    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
+        !com::android::input::flags::connected_displays_cursor()) {
         return;
     }
     bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
             !mDrawingTabletPointersByDevice.empty() || !mStylusPointersByDevice.empty();
 
-    if (requireListener && mWindowInfoListener == nullptr) {
-        mWindowInfoListener = sp<PointerChoreographerDisplayInfoListener>::make(this);
-        mWindowInfoListener->setInitialDisplayInfos(mRegisterListener(mWindowInfoListener));
-        onPrivacySensitiveDisplaysChangedLocked(mWindowInfoListener->getPrivacySensitiveDisplays());
-    } else if (!requireListener && mWindowInfoListener != nullptr) {
+    // PointerChoreographer uses Listener's lock which is already held by caller
+    base::ScopedLockAssertion assumeLocked(mWindowInfoListener->mLock);
+
+    if (requireListener && !mIsWindowInfoListenerRegistered) {
+        mIsWindowInfoListenerRegistered = true;
+        mWindowInfoListener->setInitialDisplayInfosLocked(mRegisterListener(mWindowInfoListener));
+        onPrivacySensitiveDisplaysChangedLocked(
+                mWindowInfoListener->getPrivacySensitiveDisplaysLocked());
+    } else if (!requireListener && mIsWindowInfoListenerRegistered) {
+        mIsWindowInfoListenerRegistered = false;
         mUnregisterListener(mWindowInfoListener);
-        mWindowInfoListener = nullptr;
-    } else if (requireListener && mWindowInfoListener != nullptr) {
+    } else if (requireListener) {
         // controller may have been added to an existing privacy sensitive display, we need to
         // update all controllers again
-        onPrivacySensitiveDisplaysChangedLocked(mWindowInfoListener->getPrivacySensitiveDisplays());
+        onPrivacySensitiveDisplaysChangedLocked(
+                mWindowInfoListener->getPrivacySensitiveDisplaysLocked());
     }
 }
 
@@ -494,7 +599,7 @@
 void PointerChoreographer::notifyPointerCaptureChanged(
         const NotifyPointerCaptureChangedArgs& args) {
     if (args.request.isEnable()) {
-        std::scoped_lock _l(mLock);
+        std::scoped_lock _l(getLock());
         for (const auto& [_, mousePointerController] : mMousePointersByDisplay) {
             mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
         }
@@ -502,14 +607,15 @@
     mNextListener.notify(args);
 }
 
-void PointerChoreographer::onPrivacySensitiveDisplaysChanged(
-        const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays) {
-    std::scoped_lock _l(mLock);
-    onPrivacySensitiveDisplaysChangedLocked(privacySensitiveDisplays);
+void PointerChoreographer::setDisplayTopology(
+        const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
+                displayTopology) {
+    std::scoped_lock _l(getLock());
+    mTopology = displayTopology;
 }
 
 void PointerChoreographer::dump(std::string& dump) {
-    std::scoped_lock _l(mLock);
+    std::scoped_lock _l(getLock());
 
     dump += "PointerChoreographer:\n";
     dump += StringPrintf(INDENT "Show Touches Enabled: %s\n",
@@ -579,6 +685,10 @@
     return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end();
 }
 
+std::mutex& PointerChoreographer::getLock() const {
+    return mWindowInfoListener->mLock;
+}
+
 PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerControllersLocked() {
     std::set<ui::LogicalDisplayId /*displayId*/> mouseDisplaysToKeep;
     std::set<DeviceId> touchDevicesToKeep;
@@ -641,7 +751,7 @@
     std::erase_if(mDrawingTabletPointersByDevice, [&drawingTabletDevicesToKeep](const auto& pair) {
         return drawingTabletDevicesToKeep.find(pair.first) == drawingTabletDevicesToKeep.end();
     });
-    std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(mLock) {
+    std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(getLock()) {
         return std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
                             [id](const auto& info) { return info.getId() == id; }) ==
                 mInputDeviceInfos.end();
@@ -656,7 +766,7 @@
 PointerChoreographer::PointerDisplayChange
 PointerChoreographer::calculatePointerDisplayChangeToNotify() {
     ui::LogicalDisplayId displayIdToNotify = ui::LogicalDisplayId::INVALID;
-    FloatPoint cursorPosition = {0, 0};
+    vec2 cursorPosition = {0, 0};
     if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
         it != mMousePointersByDisplay.end()) {
         const auto& pointerController = it->second;
@@ -677,7 +787,7 @@
     PointerDisplayChange pointerDisplayChange;
 
     { // acquire lock
-        std::scoped_lock _l(mLock);
+        std::scoped_lock _l(getLock());
 
         mDefaultMouseDisplayId = displayId;
         pointerDisplayChange = updatePointerControllersLocked();
@@ -690,7 +800,7 @@
     PointerDisplayChange pointerDisplayChange;
 
     { // acquire lock
-        std::scoped_lock _l(mLock);
+        std::scoped_lock _l(getLock());
         for (const auto& viewport : viewports) {
             const ui::LogicalDisplayId displayId = viewport.displayId;
             if (const auto it = mMousePointersByDisplay.find(displayId);
@@ -719,7 +829,7 @@
 
 std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice(
         ui::LogicalDisplayId associatedDisplayId) {
-    std::scoped_lock _l(mLock);
+    std::scoped_lock _l(getLock());
     const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
     if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) {
         return *viewport;
@@ -727,8 +837,8 @@
     return std::nullopt;
 }
 
-FloatPoint PointerChoreographer::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
-    std::scoped_lock _l(mLock);
+vec2 PointerChoreographer::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
+    std::scoped_lock _l(getLock());
     const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
     if (auto it = mMousePointersByDisplay.find(resolvedDisplayId);
         it != mMousePointersByDisplay.end()) {
@@ -741,7 +851,7 @@
     PointerDisplayChange pointerDisplayChange;
 
     { // acquire lock
-        std::scoped_lock _l(mLock);
+        std::scoped_lock _l(getLock());
         if (mShowTouchesEnabled == enabled) {
             return;
         }
@@ -756,7 +866,7 @@
     PointerDisplayChange pointerDisplayChange;
 
     { // acquire lock
-        std::scoped_lock _l(mLock);
+        std::scoped_lock _l(getLock());
         if (mStylusPointerIconEnabled == enabled) {
             return;
         }
@@ -770,7 +880,7 @@
 bool PointerChoreographer::setPointerIcon(
         std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
         ui::LogicalDisplayId displayId, DeviceId deviceId) {
-    std::scoped_lock _l(mLock);
+    std::scoped_lock _l(getLock());
     if (deviceId < 0) {
         LOG(WARNING) << "Invalid device id " << deviceId << ". Cannot set pointer icon.";
         return false;
@@ -821,7 +931,7 @@
 }
 
 void PointerChoreographer::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) {
-    std::scoped_lock lock(mLock);
+    std::scoped_lock lock(getLock());
     if (visible) {
         mDisplaysWithPointersHidden.erase(displayId);
         // We do not unfade the icons here, because we don't know when the last event happened.
@@ -843,14 +953,14 @@
 }
 
 void PointerChoreographer::setFocusedDisplay(ui::LogicalDisplayId displayId) {
-    std::scoped_lock lock(mLock);
+    std::scoped_lock lock(getLock());
     mCurrentFocusedDisplay = displayId;
 }
 
 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
         ui::LogicalDisplayId displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
-            [this, displayId]() REQUIRES(mLock) {
+            [this, displayId]() REQUIRES(getLock()) {
                 auto pc = mPolicy.createPointerController(
                         PointerControllerInterface::ControllerType::MOUSE);
                 if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
@@ -864,7 +974,7 @@
 PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor(
         ui::LogicalDisplayId displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
-            [this, displayId]() REQUIRES(mLock) {
+            [this, displayId]() REQUIRES(getLock()) {
                 auto pc = mPolicy.createPointerController(
                         PointerControllerInterface::ControllerType::STYLUS);
                 if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
@@ -875,35 +985,130 @@
     return ConstructorDelegate(std::move(ctor));
 }
 
+void PointerChoreographer::populateFakeDisplayTopologyLocked(
+        const std::vector<gui::DisplayInfo>& displayInfos) {
+    if (!com::android::input::flags::connected_displays_cursor()) {
+        return;
+    }
+
+    if (displayInfos.size() == mTopology.size()) {
+        bool displaysChanged = false;
+        for (const auto& displayInfo : displayInfos) {
+            if (mTopology.find(displayInfo.displayId) == mTopology.end()) {
+                displaysChanged = true;
+                break;
+            }
+        }
+
+        if (!displaysChanged) {
+            return;
+        }
+    }
+
+    // create a fake topology assuming following order
+    // default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
+    // This also adds a 100px offset on corresponding edge for better manual testing
+    //   ┌────────┐
+    //   │ next   ├─────────┐
+    // ┌─└───────┐┤ next 2  │ ...
+    // │ default │└─────────┘
+    // └─────────┘
+    mTopology.clear();
+
+    // treat default display as base, in real topology it should be the primary-display
+    ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT;
+    for (const auto& displayInfo : displayInfos) {
+        if (displayInfo.displayId == ui::LogicalDisplayId::DEFAULT) {
+            continue;
+        }
+        if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
+            mTopology[previousDisplay].push_back(
+                    {displayInfo.displayId, DisplayPosition::TOP, 100});
+            mTopology[displayInfo.displayId].push_back(
+                    {previousDisplay, DisplayPosition::BOTTOM, -100});
+        } else {
+            mTopology[previousDisplay].push_back(
+                    {displayInfo.displayId, DisplayPosition::RIGHT, 100});
+            mTopology[displayInfo.displayId].push_back(
+                    {previousDisplay, DisplayPosition::LEFT, -100});
+        }
+        previousDisplay = displayInfo.displayId;
+    }
+
+    // update default pointer display. In real topology it should be the primary-display
+    if (mTopology.find(mDefaultMouseDisplayId) == mTopology.end()) {
+        mDefaultMouseDisplayId = ui::LogicalDisplayId::DEFAULT;
+    }
+}
+
+std::optional<std::pair<const DisplayViewport*, float /*offset*/>>
+PointerChoreographer::findDestinationDisplayLocked(const ui::LogicalDisplayId sourceDisplayId,
+                                                   const DisplayPosition sourceBoundary,
+                                                   float cursorOffset) const {
+    const auto& sourceNode = mTopology.find(sourceDisplayId);
+    if (sourceNode == mTopology.end()) {
+        // Topology is likely out of sync with viewport info, wait for it to be updated
+        LOG(WARNING) << "Source display missing from topology " << sourceDisplayId;
+        return std::nullopt;
+    }
+    for (const AdjacentDisplay& adjacentDisplay : sourceNode->second) {
+        if (adjacentDisplay.position != sourceBoundary) {
+            continue;
+        }
+        const DisplayViewport* destinationViewport =
+                findViewportByIdLocked(adjacentDisplay.displayId);
+        if (destinationViewport == nullptr) {
+            // Topology is likely out of sync with viewport info, wait for them to be updated
+            LOG(WARNING) << "Cannot find viewport for adjacent display "
+                         << adjacentDisplay.displayId << "of source display " << sourceDisplayId;
+            continue;
+        }
+        // target position must be within target display boundary
+        const int32_t edgeSize =
+                sourceBoundary == DisplayPosition::TOP || sourceBoundary == DisplayPosition::BOTTOM
+                ? (destinationViewport->logicalRight - destinationViewport->logicalLeft)
+                : (destinationViewport->logicalBottom - destinationViewport->logicalTop);
+        if (cursorOffset >= adjacentDisplay.offsetPx &&
+            cursorOffset <= adjacentDisplay.offsetPx + edgeSize) {
+            return std::make_pair(destinationViewport, adjacentDisplay.offsetPx);
+        }
+    }
+    return std::nullopt;
+}
+
+// --- PointerChoreographer::PointerChoreographerDisplayInfoListener ---
+
 void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
         const gui::WindowInfosUpdate& windowInfosUpdate) {
-    std::scoped_lock _l(mListenerLock);
+    std::scoped_lock _l(mLock);
     if (mPointerChoreographer == nullptr) {
         return;
     }
     auto newPrivacySensitiveDisplays =
             getPrivacySensitiveDisplaysFromWindowInfos(windowInfosUpdate.windowInfos);
+
+    // PointerChoreographer uses Listener's lock.
+    base::ScopedLockAssertion assumeLocked(mPointerChoreographer->getLock());
     if (newPrivacySensitiveDisplays != mPrivacySensitiveDisplays) {
         mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
-        mPointerChoreographer->onPrivacySensitiveDisplaysChanged(mPrivacySensitiveDisplays);
+        mPointerChoreographer->onPrivacySensitiveDisplaysChangedLocked(mPrivacySensitiveDisplays);
     }
+    mPointerChoreographer->populateFakeDisplayTopologyLocked(windowInfosUpdate.displayInfos);
 }
 
-void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfos(
+void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked(
         const std::vector<gui::WindowInfo>& windowInfos) {
-    std::scoped_lock _l(mListenerLock);
     mPrivacySensitiveDisplays = getPrivacySensitiveDisplaysFromWindowInfos(windowInfos);
 }
 
 std::unordered_set<ui::LogicalDisplayId /*displayId*/>
-PointerChoreographer::PointerChoreographerDisplayInfoListener::getPrivacySensitiveDisplays() {
-    std::scoped_lock _l(mListenerLock);
+PointerChoreographer::PointerChoreographerDisplayInfoListener::getPrivacySensitiveDisplaysLocked() {
     return mPrivacySensitiveDisplays;
 }
 
 void PointerChoreographer::PointerChoreographerDisplayInfoListener::
         onPointerChoreographerDestroyed() {
-    std::scoped_lock _l(mListenerLock);
+    std::scoped_lock _l(mLock);
     mPointerChoreographer = nullptr;
 }
 
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 635487b..939529f 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -59,7 +59,7 @@
     virtual void setDisplayViewports(const std::vector<DisplayViewport>& viewports) = 0;
     virtual std::optional<DisplayViewport> getViewportForPointerDevice(
             ui::LogicalDisplayId associatedDisplayId = ui::LogicalDisplayId::INVALID) = 0;
-    virtual FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) = 0;
+    virtual vec2 getMouseCursorPosition(ui::LogicalDisplayId displayId) = 0;
     virtual void setShowTouchesEnabled(bool enabled) = 0;
     virtual void setStylusPointerIconEnabled(bool enabled) = 0;
     /**
@@ -96,7 +96,7 @@
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override;
     std::optional<DisplayViewport> getViewportForPointerDevice(
             ui::LogicalDisplayId associatedDisplayId) override;
-    FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) override;
+    vec2 getMouseCursorPosition(ui::LogicalDisplayId displayId) override;
     void setShowTouchesEnabled(bool enabled) override;
     void setStylusPointerIconEnabled(bool enabled) override;
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
@@ -113,36 +113,79 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
+    // TODO(b/362719483) remove these when real topology is available
+    enum class DisplayPosition {
+        RIGHT,
+        TOP,
+        LEFT,
+        BOTTOM,
+        ftl_last = BOTTOM,
+    };
+
+    struct AdjacentDisplay {
+        ui::LogicalDisplayId displayId;
+        DisplayPosition position;
+        float offsetPx;
+    };
+    void setDisplayTopology(
+            const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
+                    displayTopology);
+
     void dump(std::string& dump) override;
 
 private:
-    using PointerDisplayChange = std::optional<
-            std::tuple<ui::LogicalDisplayId /*displayId*/, FloatPoint /*cursorPosition*/>>;
-    [[nodiscard]] PointerDisplayChange updatePointerControllersLocked() REQUIRES(mLock);
-    [[nodiscard]] PointerDisplayChange calculatePointerDisplayChangeToNotify() REQUIRES(mLock);
+    using PointerDisplayChange =
+            std::optional<std::tuple<ui::LogicalDisplayId /*displayId*/, vec2 /*cursorPosition*/>>;
+
+    // PointerChoreographer's DisplayInfoListener can outlive the PointerChoreographer because when
+    // the listener is registered and called from display thread, a strong pointer to the listener
+    // (which can extend its lifecycle) is given away.
+    // If we use two locks it can also cause deadlocks due to race in acquiring them between the
+    // display and reader thread.
+    // To avoid these problems we use DisplayInfoListener's lock in PointerChoreographer.
+    std::mutex& getLock() const;
+
+    [[nodiscard]] PointerDisplayChange updatePointerControllersLocked() REQUIRES(getLock());
+    [[nodiscard]] PointerDisplayChange calculatePointerDisplayChangeToNotify() REQUIRES(getLock());
     const DisplayViewport* findViewportByIdLocked(ui::LogicalDisplayId displayId) const
-            REQUIRES(mLock);
+            REQUIRES(getLock());
     ui::LogicalDisplayId getTargetMouseDisplayLocked(ui::LogicalDisplayId associatedDisplayId) const
-            REQUIRES(mLock);
+            REQUIRES(getLock());
     std::pair<ui::LogicalDisplayId /*displayId*/, PointerControllerInterface&>
-    ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) REQUIRES(mLock);
-    InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
-    bool canUnfadeOnDisplay(ui::LogicalDisplayId displayId) REQUIRES(mLock);
+    ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) REQUIRES(getLock());
+    InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(getLock());
+    bool canUnfadeOnDisplay(ui::LogicalDisplayId displayId) REQUIRES(getLock());
 
     void fadeMouseCursorOnKeyPress(const NotifyKeyArgs& args);
     NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
-    NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
-    NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
-    void processDrawingTabletEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
-    void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
-    void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(getLock());
+    NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(getLock());
+    void processDrawingTabletEventLocked(const NotifyMotionArgs& args) REQUIRES(getLock());
+    void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(getLock());
+    void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(getLock());
+    void processPointerDeviceMotionEventLocked(NotifyMotionArgs& newArgs,
+                                               PointerControllerInterface& pc) REQUIRES(getLock());
     void processDeviceReset(const NotifyDeviceResetArgs& args);
-    void onControllerAddedOrRemovedLocked() REQUIRES(mLock);
+    void onControllerAddedOrRemovedLocked() REQUIRES(getLock());
     void onPrivacySensitiveDisplaysChangedLocked(
             const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays)
-            REQUIRES(mLock);
-    void onPrivacySensitiveDisplaysChanged(
-            const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays);
+            REQUIRES(getLock());
+
+    void handleUnconsumedDeltaLocked(PointerControllerInterface& pc, const vec2& unconsumedDelta)
+            REQUIRES(getLock());
+
+    void populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo>& displayInfos)
+            REQUIRES(getLock());
+
+    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> findDestinationDisplayLocked(
+            const ui::LogicalDisplayId sourceDisplayId, const DisplayPosition sourceBoundary,
+            float cursorOffset) const REQUIRES(getLock());
+
+    static vec2 calculateDestinationPosition(const DisplayViewport& destinationViewport,
+                                             float pointerOffset, DisplayPosition sourceBoundary);
+
+    std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>> mTopology
+            GUARDED_BY(getLock());
 
     /* This listener keeps tracks of visible privacy sensitive displays and updates the
      * choreographer if there are any changes.
@@ -156,49 +199,50 @@
         explicit PointerChoreographerDisplayInfoListener(PointerChoreographer* pc)
               : mPointerChoreographer(pc){};
         void onWindowInfosChanged(const gui::WindowInfosUpdate&) override;
-        void setInitialDisplayInfos(const std::vector<gui::WindowInfo>& windowInfos);
-        std::unordered_set<ui::LogicalDisplayId /*displayId*/> getPrivacySensitiveDisplays();
+        void setInitialDisplayInfosLocked(const std::vector<gui::WindowInfo>& windowInfos)
+                REQUIRES(mLock);
+        std::unordered_set<ui::LogicalDisplayId> getPrivacySensitiveDisplaysLocked()
+                REQUIRES(mLock);
         void onPointerChoreographerDestroyed();
 
+        // This lock is also used by PointerChoreographer. See PointerChoreographer::getLock().
+        std::mutex mLock;
+
     private:
-        std::mutex mListenerLock;
-        PointerChoreographer* mPointerChoreographer GUARDED_BY(mListenerLock);
+        PointerChoreographer* mPointerChoreographer GUARDED_BY(mLock);
         std::unordered_set<ui::LogicalDisplayId /*displayId*/> mPrivacySensitiveDisplays
-                GUARDED_BY(mListenerLock);
+                GUARDED_BY(mLock);
     };
-    sp<PointerChoreographerDisplayInfoListener> mWindowInfoListener GUARDED_BY(mLock);
 
     using ControllerConstructor =
             ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
-    ControllerConstructor mTouchControllerConstructor GUARDED_BY(mLock);
+    ControllerConstructor mTouchControllerConstructor GUARDED_BY(getLock());
     ControllerConstructor getMouseControllerConstructor(ui::LogicalDisplayId displayId)
-            REQUIRES(mLock);
+            REQUIRES(getLock());
     ControllerConstructor getStylusControllerConstructor(ui::LogicalDisplayId displayId)
-            REQUIRES(mLock);
-
-    std::mutex mLock;
+            REQUIRES(getLock());
 
     InputListenerInterface& mNextListener;
     PointerChoreographerPolicyInterface& mPolicy;
 
     std::map<ui::LogicalDisplayId, std::shared_ptr<PointerControllerInterface>>
-            mMousePointersByDisplay GUARDED_BY(mLock);
+            mMousePointersByDisplay GUARDED_BY(getLock());
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice
-            GUARDED_BY(mLock);
+            GUARDED_BY(getLock());
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
-            GUARDED_BY(mLock);
+            GUARDED_BY(getLock());
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mDrawingTabletPointersByDevice
-            GUARDED_BY(mLock);
+            GUARDED_BY(getLock());
 
-    ui::LogicalDisplayId mDefaultMouseDisplayId GUARDED_BY(mLock);
-    ui::LogicalDisplayId mNotifiedPointerDisplayId GUARDED_BY(mLock);
-    std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock);
-    std::set<DeviceId> mMouseDevices GUARDED_BY(mLock);
-    std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
-    bool mShowTouchesEnabled GUARDED_BY(mLock);
-    bool mStylusPointerIconEnabled GUARDED_BY(mLock);
+    ui::LogicalDisplayId mDefaultMouseDisplayId GUARDED_BY(getLock());
+    ui::LogicalDisplayId mNotifiedPointerDisplayId GUARDED_BY(getLock());
+    std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(getLock());
+    std::set<DeviceId> mMouseDevices GUARDED_BY(getLock());
+    std::vector<DisplayViewport> mViewports GUARDED_BY(getLock());
+    bool mShowTouchesEnabled GUARDED_BY(getLock());
+    bool mStylusPointerIconEnabled GUARDED_BY(getLock());
     std::set<ui::LogicalDisplayId /*displayId*/> mDisplaysWithPointersHidden;
-    ui::LogicalDisplayId mCurrentFocusedDisplay GUARDED_BY(mLock);
+    ui::LogicalDisplayId mCurrentFocusedDisplay GUARDED_BY(getLock());
 
 protected:
     using WindowListenerRegisterConsumer = std::function<std::vector<gui::WindowInfo>(
@@ -211,6 +255,10 @@
                                   const WindowListenerUnregisterConsumer& unregisterListener);
 
 private:
+    // WindowInfoListener object should always exist while PointerChoreographer exists, because we
+    // need to use the lock from it. But we don't always need to register the listener.
+    bool mIsWindowInfoListenerRegistered GUARDED_BY(getLock());
+    const sp<PointerChoreographerDisplayInfoListener> mWindowInfoListener;
     const WindowListenerRegisterConsumer mRegisterListener;
     const WindowListenerUnregisterConsumer mUnregisterListener;
 };
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 10fec74..43eaf67 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -35,109 +35,34 @@
       ]
     },
     {
-      "name": "CtsHardwareTestCases",
-      "options": [
-        {
-          "include-filter": "android.hardware.input.cts.tests"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsHardwareTestCases_cts_tests"
     },
     {
       "name": "CtsInputTestCases"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.input"
-        }
-      ]
+      "name": "CtsViewTestCases_cts_input"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.HoverTest"
-        },
-        {
-          "include-filter": "android.view.cts.MotionEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.PointerCaptureTest"
-        },
-        {
-          "include-filter": "android.view.cts.TooltipTest"
-        },
-        {
-          "include-filter": "android.view.cts.TouchDelegateTest"
-        },
-        {
-          "include-filter": "android.view.cts.VerifyInputEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewUnbufferedTest"
-        }
-      ]
+      "name": "CtsViewTestCases_input_related"
     },
     {
-      "name": "CtsWidgetTestCases",
-      "options": [
-        {
-          "include-filter": "android.widget.cts.NumberPickerTest"
-        },
-        {
-          "include-filter": "android.widget.cts.SeekBarTest"
-        }
-      ]
+      "name": "CtsWidgetTestCases_seekbar_and_numberpicker"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.hardware.input"
-        }
-      ]
+      "name": "FrameworksCoreTests_hardware_input"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.view.VerifiedKeyEventTest"
-        },
-        {
-          "include-filter": "android.view.VerifiedMotionEventTest"
-        }
-      ]
+      "name": "FrameworksCoreTests_view_verified"
     },
     {
-      "name": "CtsAppTestCases",
-      "options": [
-        {
-          "include-filter": "android.app.cts.ToolbarActionBarTest"
-        }
-      ]
+      "name": "CtsAppTestCases_cts_toolbaractionbartest"
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.input"
-        }
-      ]
+      "name": "FrameworksServicesTests_android_server_input"
     },
     {
-      "name": "CtsSecurityTestCases",
-      "options": [
-        {
-          "include-filter": "android.security.cts.MotionEventTest"
-        }
-      ]
+      "name": "CtsSecurityTestCases_cts_motioneventtest"
     },
     {
       "name": "CtsSecurityBulletinHostTestCases",
@@ -156,12 +81,7 @@
   ],
   "postsubmit": [
     {
-      "name": "CtsWindowManagerDeviceWindow",
-      "options": [
-        {
-          "include-filter": "android.server.wm.window.WindowInputTests"
-        }
-      ]
+      "name": "CtsWindowManagerDeviceWindow_window_windowinputtests"
     },
     {
       "name": "libinput_tests"
@@ -187,98 +107,31 @@
       ]
     },
     {
-      "name": "CtsHardwareTestCases",
-      "options": [
-        {
-          "include-filter": "android.hardware.input.cts.tests"
-        }
-      ]
+      "name": "CtsHardwareTestCases_cts_tests"
     },
     {
       "name": "CtsInputTestCases"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.input"
-        }
-      ]
+      "name": "CtsViewTestCases_cts_input"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.HoverTest"
-        },
-        {
-          "include-filter": "android.view.cts.MotionEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.PointerCaptureTest"
-        },
-        {
-          "include-filter": "android.view.cts.TooltipTest"
-        },
-        {
-          "include-filter": "android.view.cts.TouchDelegateTest"
-        },
-        {
-          "include-filter": "android.view.cts.VerifyInputEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewUnbufferedTest"
-        }
-      ]
+      "name": "CtsViewTestCases_input_related"
     },
     {
-      "name": "CtsWidgetTestCases",
-      "options": [
-        {
-          "include-filter": "android.widget.cts.NumberPickerTest"
-        },
-        {
-          "include-filter": "android.widget.cts.SeekBarTest"
-        }
-      ]
+      "name": "CtsWidgetTestCases_seekbar_and_numberpicker"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.view.VerifiedKeyEventTest"
-        },
-        {
-          "include-filter": "android.view.VerifiedMotionEventTest"
-        }
-      ]
+      "name": "FrameworksCoreTests_view_verified"
     },
     {
-      "name": "CtsAppTestCases",
-      "options": [
-        {
-          "include-filter": "android.app.cts.ToolbarActionBarTest"
-        }
-      ]
+      "name": "CtsAppTestCases_cts_toolbaractionbartest"
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.input"
-        }
-      ]
+      "name": "FrameworksServicesTests_server_input"
     },
     {
-      "name": "CtsSecurityTestCases",
-      "options": [
-        {
-          "include-filter": "android.security.cts.MotionEventTest"
-        }
-      ]
+      "name": "CtsSecurityTestCases_cts_motioneventtest"
     },
     {
       "name": "CtsSecurityBulletinHostTestCases",
diff --git a/services/inputflinger/dispatcher/EventLogTags.logtags b/services/inputflinger/dispatcher/EventLogTags.logtags
index 2c5fe21..4dd6073 100644
--- a/services/inputflinger/dispatcher/EventLogTags.logtags
+++ b/services/inputflinger/dispatcher/EventLogTags.logtags
@@ -31,7 +31,7 @@
 # 6: Percent
 # Default value for data of type int/long is 2 (bytes).
 #
-# See system/core/logcat/event.logtags for the master copy of the tags.
+# See system/logging/logcat/event.logtags for the master copy of the tags.
 
 # 62000 - 62199 reserved for inputflinger
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 755995c..cd4ed5c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -6805,14 +6805,15 @@
         // The fallback keycode cannot change at any other point in the lifecycle.
         if (initialDown) {
             if (fallback) {
-                *fallbackKeyCode = event.getKeyCode();
+                fallbackKeyCode = event.getKeyCode();
             } else {
-                *fallbackKeyCode = AKEYCODE_UNKNOWN;
+                fallbackKeyCode = AKEYCODE_UNKNOWN;
             }
             connection->inputState.setFallbackKey(originalKeyCode, *fallbackKeyCode);
         }
 
-        ALOG_ASSERT(fallbackKeyCode);
+        LOG_IF(FATAL, !fallbackKeyCode)
+                << "fallbackKeyCode is not initialized. initialDown = " << initialDown;
 
         // Cancel the fallback key if the policy decides not to send it anymore.
         // We will continue to dispatch the key to the policy but we will no
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 305feab..4d6b6c7 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -245,6 +245,9 @@
     // True to use three-finger tap as a customizable shortcut; false to use it as a middle-click.
     bool touchpadThreeFingerTapShortcutEnabled;
 
+    // True to enable system gestures (three- and four-finger swipes) on touchpads.
+    bool touchpadSystemGesturesEnabled;
+
     // The set of currently disabled input devices.
     std::set<int32_t> disabledDevices;
 
@@ -297,6 +300,7 @@
             shouldNotifyTouchpadHardwareState(false),
             touchpadRightClickZoneEnabled(false),
             touchpadThreeFingerTapShortcutEnabled(false),
+            touchpadSystemGesturesEnabled(true),
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false),
             mouseReverseVerticalScrollingEnabled(false),
@@ -362,6 +366,9 @@
     /* Toggle Caps Lock */
     virtual void toggleCapsLockState(int32_t deviceId) = 0;
 
+    /* Resets locked modifier state */
+    virtual void resetLockedModifierState() = 0;
+
     /* Determine whether physical keys exist for the given framework-domain key codes. */
     virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
                          const std::vector<int32_t>& keyCodes, uint8_t* outFlags) = 0;
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index e1f8fda..36614b2 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -54,7 +54,7 @@
      * @param position The new position of the mouse cursor on the logical display
      */
     virtual void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
-                                               const FloatPoint& position) = 0;
+                                               const vec2& position) = 0;
 
     /* Returns true if any InputConnection is currently active. */
     virtual bool isInputMethodConnectionActive() = 0;
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 8f3d9ca..bd46490 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -24,20 +24,6 @@
 
 struct SpriteIcon;
 
-struct FloatPoint {
-    float x;
-    float y;
-
-    inline FloatPoint(float x, float y) : x(x), y(y) {}
-
-    inline explicit FloatPoint(vec2 p) : x(p.x), y(p.y) {}
-
-    template <typename T, typename U>
-    operator std::tuple<T, U>() {
-        return {x, y};
-    }
-};
-
 /**
  * Interface for tracking a mouse / touch pad pointer and touch pad spots.
  *
@@ -72,14 +58,18 @@
     /* Dumps the state of the pointer controller. */
     virtual std::string dump() = 0;
 
-    /* Move the pointer. */
-    virtual void move(float deltaX, float deltaY) = 0;
+    /* Move the pointer and return unconsumed delta if the pointer has crossed the current
+     * viewport bounds.
+     *
+     * Return value may be used to move pointer to corresponding adjacent display, if it exists in
+     * the display-topology */
+    [[nodiscard]] virtual vec2 move(float deltaX, float deltaY) = 0;
 
     /* Sets the absolute location of the pointer. */
     virtual void setPosition(float x, float y) = 0;
 
     /* Gets the absolute location of the pointer. */
-    virtual FloatPoint getPosition() const = 0;
+    virtual vec2 getPosition() const = 0;
 
     enum class Transition {
         // Fade/unfade immediately.
@@ -145,6 +135,8 @@
 
     /* Resets the flag to skip screenshot of the pointer indicators for all displays. */
     virtual void clearSkipScreenshotFlags() = 0;
+
+    virtual ui::Transform getDisplayTransform() const = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index ada6653..24919b6 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -590,6 +590,11 @@
     }
 }
 
+void InputReader::resetLockedModifierState() {
+    std::scoped_lock _l(mLock);
+    updateLedMetaStateLocked(0);
+}
+
 bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask,
                           const std::vector<int32_t>& keyCodes, uint8_t* outFlags) {
     std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index 9eeb2b2..7434ae4 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -47,10 +47,6 @@
     return (brightness & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
 }
 
-static inline bool isKeyboardBacklightCustomLevelsEnabled() {
-    return sysprop::InputProperties::enable_keyboard_backlight_custom_levels().value_or(true);
-}
-
 /**
  * Input controller owned by InputReader device, implements the native API for querying input
  * lights, getting and setting the lights brightness and color, by interacting with EventHub
@@ -289,8 +285,7 @@
 std::set<BrightnessLevel> PeripheralController::getPreferredBrightnessLevels(
         const Light* light) const {
     std::set<BrightnessLevel> levels;
-    if (!isKeyboardBacklightCustomLevelsEnabled() ||
-        light->type != InputDeviceLightType::KEYBOARD_BACKLIGHT) {
+    if (light->type != InputDeviceLightType::KEYBOARD_BACKLIGHT) {
         return levels;
     }
     std::optional<std::string> keyboardBacklightLevels =
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 7614a05..1403ca2 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -69,6 +69,8 @@
 
     void toggleCapsLockState(int32_t deviceId) override;
 
+    void resetLockedModifierState() override;
+
     bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector<int32_t>& keyCodes,
                  uint8_t* outFlags) override;
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index 4233f78..1f6600d 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -212,7 +212,7 @@
     // One input device can only have 1 sensor for each sensor Type.
     InputDeviceSensorInfo sensorInfo(identifier.name, std::to_string(identifier.vendor),
                                      identifier.version, sensorType,
-                                     InputDeviceSensorAccuracy::ACCURACY_HIGH,
+                                     InputDeviceSensorAccuracy::HIGH,
                                      /*maxRange=*/axis.max, /*resolution=*/axis.scale,
                                      /*power=*/config.getFloat(prefix + ".power").value_or(0.0f),
                                      /*minDelay=*/config.getInt(prefix + ".minDelay").value_or(0),
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
index 63bc151..7974efe 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.h
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -101,7 +101,7 @@
         std::array<int32_t, SENSOR_VEC_LEN> dataVec;
         void resetValue() {
             this->enabled = false;
-            this->accuracy = InputDeviceSensorAccuracy::ACCURACY_NONE;
+            this->accuracy = InputDeviceSensorAccuracy::NONE;
             this->samplingPeriod = std::chrono::nanoseconds(0);
             this->maxBatchReportLatency = std::chrono::nanoseconds(0);
             this->lastSampleTimeNs = std::nullopt;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index ca8266b..0c094e6 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -351,6 +351,7 @@
 
         bumpGeneration();
     }
+    std::list<NotifyArgs> out;
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) {
         mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve")
                 .setBoolValues({true});
@@ -375,11 +376,11 @@
         mPropertyProvider.getProperty("Button Right Click Zone Enable")
                 .setBoolValues({config.touchpadRightClickZoneEnabled});
         mTouchpadHardwareStateNotificationsEnabled = config.shouldNotifyTouchpadHardwareState;
-
         mGestureConverter.setThreeFingerTapShortcutEnabled(
                 config.touchpadThreeFingerTapShortcutEnabled);
+        out += mGestureConverter.setEnableSystemGestures(when,
+                                                         config.touchpadSystemGesturesEnabled);
     }
-    std::list<NotifyArgs> out;
     if ((!changes.any() && config.pointerCaptureRequest.isEnable()) ||
         changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) {
         mPointerCaptured = config.pointerCaptureRequest.isEnable();
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 54270eb..6bd949a 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -132,6 +132,15 @@
     return out;
 }
 
+std::list<NotifyArgs> GestureConverter::setEnableSystemGestures(nsecs_t when, bool enable) {
+    std::list<NotifyArgs> out;
+    if (!enable && mCurrentClassification == MotionClassification::MULTI_FINGER_SWIPE) {
+        out += handleMultiFingerSwipeLift(when, when);
+    }
+    mEnableSystemGestures = enable;
+    return out;
+}
+
 void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const {
     info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0);
 
@@ -461,6 +470,9 @@
                                                                              uint32_t fingerCount,
                                                                              float dx, float dy) {
     std::list<NotifyArgs> out = {};
+    if (!mEnableSystemGestures) {
+        return out;
+    }
 
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         // If the user changes the number of fingers mid-way through a swipe (e.g. they start with
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index ad40721..8d92ead 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -59,6 +59,8 @@
         mThreeFingerTapShortcutEnabled = enabled;
     }
 
+    [[nodiscard]] std::list<NotifyArgs> setEnableSystemGestures(nsecs_t when, bool enable);
+
     void populateMotionRanges(InputDeviceInfo& info) const;
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
@@ -104,6 +106,7 @@
     InputReaderContext& mReaderContext;
     const bool mEnableFlingStop;
     const bool mEnableNoFocusChange;
+    bool mEnableSystemGestures{true};
 
     bool mThreeFingerTapShortcutEnabled;
 
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
index 7a1d0ec..3c7f432 100644
--- a/services/inputflinger/rust/sticky_keys_filter.rs
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -47,6 +47,7 @@
     next: Box<dyn Filter + Send + Sync>,
     listener: ModifierStateListener,
     data: Data,
+    down_key_map: HashMap<i32, HashSet<i32>>,
 }
 
 #[derive(Default)]
@@ -69,15 +70,34 @@
         next: Box<dyn Filter + Send + Sync>,
         listener: ModifierStateListener,
     ) -> StickyKeysFilter {
-        Self { next, listener, data: Default::default() }
+        Self { next, listener, data: Default::default(), down_key_map: HashMap::new() }
     }
 }
 
 impl Filter for StickyKeysFilter {
     fn notify_key(&mut self, event: &KeyEvent) {
+        let down = event.action == KeyEventAction::DOWN;
         let up = event.action == KeyEventAction::UP;
         let mut modifier_state = self.data.modifier_state;
         let mut locked_modifier_state = self.data.locked_modifier_state;
+        if down {
+            let down_keys = self.down_key_map.entry(event.deviceId).or_default();
+            down_keys.insert(event.keyCode);
+        } else {
+            if !self.down_key_map.contains_key(&event.deviceId) {
+                self.next.notify_key(event);
+                return;
+            }
+            let down_keys = self.down_key_map.get_mut(&event.deviceId).unwrap();
+            if !down_keys.contains(&event.keyCode) {
+                self.next.notify_key(event);
+                return;
+            }
+            down_keys.remove(&event.keyCode);
+            if down_keys.is_empty() {
+                self.down_key_map.remove(&event.deviceId);
+            }
+        }
         if !is_ephemeral_modifier_key(event.keyCode) {
             // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like
             // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with
@@ -130,6 +150,7 @@
             self.data.locked_modifier_state = ModifierState::None;
             self.listener.modifier_state_changed(ModifierState::None, ModifierState::None);
         }
+        self.down_key_map.retain(|key, _| device_infos.iter().any(|x| *key == x.deviceId));
         self.next.notify_devices_changed(device_infos);
     }
 
@@ -166,6 +187,7 @@
         result += &format!("\tmodifier_state = {:?}\n", self.data.modifier_state);
         result += &format!("\tlocked_modifier_state = {:?}\n", self.data.locked_modifier_state);
         result += &format!("\tcontributing_devices = {:?}\n", self.data.contributing_devices);
+        result += &format!("\tdown_key_map = {:?}\n", self.down_key_map);
         self.next.dump(dump_str + &result)
     }
 }
@@ -322,6 +344,31 @@
     }
 
     #[test]
+    fn test_notify_key_passes_ephemeral_modifier_keys_if_only_key_up_occurs() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let key_codes = &[
+            KEYCODE_ALT_LEFT,
+            KEYCODE_ALT_RIGHT,
+            KEYCODE_CTRL_LEFT,
+            KEYCODE_CTRL_RIGHT,
+            KEYCODE_SHIFT_LEFT,
+            KEYCODE_SHIFT_RIGHT,
+            KEYCODE_META_LEFT,
+            KEYCODE_META_RIGHT,
+        ];
+        for key_code in key_codes.iter() {
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_UP };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+        }
+    }
+
+    #[test]
     fn test_notify_key_passes_non_ephemeral_modifier_keys() {
         let test_filter = TestFilter::new();
         let test_callbacks = TestCallbacks::new();
@@ -437,6 +484,26 @@
     }
 
     #[test]
+    fn test_modifier_state_unchanged_on_non_modifier_key_up_without_down() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
+    }
+
+    #[test]
     fn test_locked_modifier_state_not_cleared_on_non_modifier_key_press() {
         let test_filter = TestFilter::new();
         let test_callbacks = TestCallbacks::new();
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 887a939..f033e57 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -43,7 +43,7 @@
     mY = y;
 }
 
-FloatPoint FakePointerController::getPosition() const {
+vec2 FakePointerController::getPosition() const {
     if (!mEnabled) {
         return {0, 0};
     }
@@ -96,9 +96,9 @@
 }
 
 void FakePointerController::assertPosition(float x, float y) {
-    const auto [actualX, actualY] = getPosition();
-    ASSERT_NEAR(x, actualX, 1);
-    ASSERT_NEAR(y, actualY, 1);
+    const auto actual = getPosition();
+    ASSERT_NEAR(x, actual.x, 1);
+    ASSERT_NEAR(y, actual.y, 1);
 }
 
 void FakePointerController::assertSpotCount(ui::LogicalDisplayId displayId, int32_t count) {
@@ -148,15 +148,20 @@
     return mIsPointerShown;
 }
 
-void FakePointerController::move(float deltaX, float deltaY) {
-    if (!mEnabled) return;
+vec2 FakePointerController::move(float deltaX, float deltaY) {
+    if (!mEnabled) return {0, 0};
 
     mX += deltaX;
+    mY += deltaY;
+
+    const vec2 position(mX, mY);
+
     if (mX < mMinX) mX = mMinX;
     if (mX > mMaxX) mX = mMaxX;
-    mY += deltaY;
     if (mY < mMinY) mY = mMinY;
     if (mY > mMaxY) mY = mMaxY;
+
+    return {position.x - mX, position.y - mY};
 }
 
 void FakePointerController::fade(Transition) {
@@ -190,4 +195,8 @@
     mSpotsByDisplay.clear();
 }
 
+ui::Transform FakePointerController::getDisplayTransform() const {
+    return ui::Transform();
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 9b773a7..c526bb8 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -40,7 +40,7 @@
     const std::map<ui::LogicalDisplayId, std::vector<int32_t>>& getSpots();
 
     void setPosition(float x, float y) override;
-    FloatPoint getPosition() const override;
+    vec2 getPosition() const override;
     ui::LogicalDisplayId getDisplayId() const override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
     void updatePointerIcon(PointerIconStyle iconId) override;
@@ -48,6 +48,7 @@
     void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
     void clearSkipScreenshotFlags() override;
     void fade(Transition) override;
+    ui::Transform getDisplayTransform() const override;
 
     void assertViewportSet(ui::LogicalDisplayId displayId);
     void assertViewportNotSet();
@@ -65,7 +66,7 @@
 
 private:
     std::string dump() override { return ""; }
-    void move(float deltaX, float deltaY) override;
+    vec2 move(float deltaX, float deltaY) override;
     void unfade(Transition) override;
     void setPresentation(Presentation) override {}
     void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index fad8f05..fe40a5e 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -48,6 +48,7 @@
 using testing::AllOf;
 using testing::Each;
 using testing::ElementsAre;
+using testing::IsEmpty;
 using testing::VariantWith;
 
 class GestureConverterTest : public testing::Test {
@@ -849,6 +850,107 @@
                               WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
+TEST_F(GestureConverterTest, DisablingSystemGestures_IgnoresMultiFingerSwipe) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    std::list<NotifyArgs> args = converter.setEnableSystemGestures(ARBITRARY_TIME, false);
+    ASSERT_THAT(args, IsEmpty());
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/10);
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                            /*dy=*/5);
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+
+    args += converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    args += converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    args += converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = converter.setEnableSystemGestures(ARBITRARY_TIME, true);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(
+                                    AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                    1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(
+                                    AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                    2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE))));
+}
+
+TEST_F(GestureConverterTest, DisablingSystemGestures_EndsOngoingMultiFingerSwipe) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/10);
+    std::list<NotifyArgs> args;
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_FALSE(args.empty());
+
+    // Disabling system gestures should end the swipe early.
+    args = converter.setEnableSystemGestures(ARBITRARY_TIME, false);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+
+    // Further movement in the same swipe should be ignored.
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                            /*dy=*/5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args, IsEmpty());
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args, IsEmpty());
+
+    // But single-finger pointer motion should be reported.
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithRelativeMotion(-5, 10), WithButtonState(0)))));
+}
+
 TEST_F(GestureConverterTest, Pinch_Inwards) {
     input_flags::enable_touchpad_no_focus_change(true);
 
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 6f7c2e5..ac616d0 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -188,7 +188,7 @@
     MOCK_METHOD(std::shared_ptr<PointerControllerInterface>, createPointerController,
                 (PointerControllerInterface::ControllerType), (override));
     MOCK_METHOD(void, notifyPointerDisplayIdChanged,
-                (ui::LogicalDisplayId displayId, const FloatPoint& position), (override));
+                (ui::LogicalDisplayId displayId, const vec2& position), (override));
     MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override));
     MOCK_METHOD(void, notifyMouseCursorFadedOnTyping, (), (override));
 };
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 39b583c..1dd32c4 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -926,6 +926,33 @@
     ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
 }
 
+TEST_F(KeyboardInputMapperTest, Process_ResetLockedModifierState) {
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+    // Initial metastate is AMETA_NONE.
+    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
+
+    // Toggle caps lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+
+    // Toggle num lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
+
+    // Toggle scroll lock on.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper.getMetaState());
+
+    mReader->resetLockedModifierState();
+    ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
+}
+
 TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) {
     // keyboard 1.
     mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 411c7ba..27da3d3 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -135,7 +135,7 @@
         });
 
         ON_CALL(mMockPolicy, notifyPointerDisplayIdChanged)
-                .WillByDefault([this](ui::LogicalDisplayId displayId, const FloatPoint& position) {
+                .WillByDefault([this](ui::LogicalDisplayId displayId, const vec2& position) {
                     mPointerDisplayIdNotified = displayId;
                 });
     }
@@ -2601,6 +2601,173 @@
     metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_RIGHT);
 }
 
+using PointerChoreographerDisplayTopologyTestFixtureParam =
+        std::tuple<std::string_view /*name*/, int32_t /*source device*/,
+                   ControllerType /*PointerController*/, ToolType /*pointer tool type*/,
+                   vec2 /*source position*/, vec2 /*hover move X/Y*/,
+                   ui::LogicalDisplayId /*destination display*/, vec2 /*destination position*/>;
+
+class PointerChoreographerDisplayTopologyTestFixture
+      : public PointerChoreographerTest,
+        public testing::WithParamInterface<PointerChoreographerDisplayTopologyTestFixtureParam> {
+public:
+    static constexpr ui::LogicalDisplayId DISPLAY_CENTER_ID = ui::LogicalDisplayId{10};
+    static constexpr ui::LogicalDisplayId DISPLAY_TOP_ID = ui::LogicalDisplayId{20};
+    static constexpr ui::LogicalDisplayId DISPLAY_RIGHT_ID = ui::LogicalDisplayId{30};
+    static constexpr ui::LogicalDisplayId DISPLAY_BOTTOM_ID = ui::LogicalDisplayId{40};
+    static constexpr ui::LogicalDisplayId DISPLAY_LEFT_ID = ui::LogicalDisplayId{50};
+    static constexpr ui::LogicalDisplayId DISPLAY_TOP_RIGHT_CORNER_ID = ui::LogicalDisplayId{60};
+
+    PointerChoreographerDisplayTopologyTestFixture() {
+        com::android::input::flags::connected_displays_cursor(true);
+    }
+
+protected:
+    std::vector<DisplayViewport> mViewports{
+            createViewport(DISPLAY_CENTER_ID, /*width*/ 100, /*height*/ 100, ui::ROTATION_0),
+            createViewport(DISPLAY_TOP_ID, /*width*/ 90, /*height*/ 90, ui::ROTATION_0),
+            createViewport(DISPLAY_RIGHT_ID, /*width*/ 90, /*height*/ 90, ui::ROTATION_90),
+            createViewport(DISPLAY_BOTTOM_ID, /*width*/ 90, /*height*/ 90, ui::ROTATION_180),
+            createViewport(DISPLAY_LEFT_ID, /*width*/ 90, /*height*/ 90, ui::ROTATION_270),
+            createViewport(DISPLAY_TOP_RIGHT_CORNER_ID, /*width*/ 90, /*height*/ 90,
+                           ui::ROTATION_0),
+    };
+
+    std::unordered_map<ui::LogicalDisplayId, std::vector<PointerChoreographer::AdjacentDisplay>>
+            mTopology{
+                    {DISPLAY_CENTER_ID,
+                     {{DISPLAY_TOP_ID, PointerChoreographer::DisplayPosition::TOP, 10.0f},
+                      {DISPLAY_RIGHT_ID, PointerChoreographer::DisplayPosition::RIGHT, 10.0f},
+                      {DISPLAY_BOTTOM_ID, PointerChoreographer::DisplayPosition::BOTTOM, 10.0f},
+                      {DISPLAY_LEFT_ID, PointerChoreographer::DisplayPosition::LEFT, 10.0f},
+                      {DISPLAY_TOP_RIGHT_CORNER_ID, PointerChoreographer::DisplayPosition::RIGHT,
+                       -90.0f}}},
+            };
+
+private:
+    DisplayViewport createViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
+                                   ui::Rotation orientation) {
+        DisplayViewport viewport;
+        viewport.displayId = displayId;
+        viewport.logicalRight = width;
+        viewport.logicalBottom = height;
+        viewport.orientation = orientation;
+        return viewport;
+    }
+};
+
+TEST_P(PointerChoreographerDisplayTopologyTestFixture, PointerChoreographerDisplayTopologyTest) {
+    const auto& [_, device, pointerControllerType, pointerToolType, initialPosition, hoverMove,
+                 destinationDisplay, destinationPosition] = GetParam();
+
+    mChoreographer.setDisplayViewports(mViewports);
+    mChoreographer.setDefaultMouseDisplayId(
+            PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID);
+    mChoreographer.setDisplayTopology(mTopology);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, device, ui::LogicalDisplayId::INVALID)}});
+
+    auto pc = assertPointerControllerCreated(pointerControllerType);
+    ASSERT_EQ(PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+              pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(initialPosition.x, initialPosition.y);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    auto pointerBuilder = PointerBuilder(/*id=*/0, pointerToolType)
+                                  .axis(AMOTION_EVENT_AXIS_RELATIVE_X, hoverMove.x)
+                                  .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, hoverMove.y);
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, device)
+                                        .pointer(pointerBuilder)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(ui::LogicalDisplayId::INVALID)
+                                        .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    ASSERT_TRUE(pc->isPointerShown());
+    ASSERT_EQ(pc->getDisplayId(), destinationDisplay);
+    auto position = pc->getPosition();
+    ASSERT_EQ(position.x, destinationPosition.x);
+    ASSERT_EQ(position.y, destinationPosition.y);
+
+    // Check that x-y coordinates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(destinationPosition.x, destinationPosition.y),
+                  WithDisplayId(destinationDisplay),
+                  WithCursorPosition(destinationPosition.x, destinationPosition.y)));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        PointerChoreographerTest, PointerChoreographerDisplayTopologyTestFixture,
+        testing::Values(
+                // Note: Upon viewport transition cursor will be positioned at the boundary of the
+                // destination, as we drop any unconsumed delta.
+                std::make_tuple("UnchangedDisplay", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE,
+                                ToolType::MOUSE, vec2(50, 50) /* initial x/y */,
+                                vec2(25, 25) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(75, 75) /* destination x/y */),
+                std::make_tuple("TransitionToRightDisplay", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(50, 50) /* initial x/y */, vec2(100, 25) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_RIGHT_ID,
+                                vec2(0,
+                                     50 + 25 - 10) /* Left edge: (0, source + delta - offset) */),
+                std::make_tuple(
+                        "TransitionToLeftDisplay", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE,
+                        ToolType::MOUSE, vec2(50, 50) /* initial x/y */,
+                        vec2(-100, 25) /* delta x/y */,
+                        PointerChoreographerDisplayTopologyTestFixture::DISPLAY_LEFT_ID,
+                        vec2(90, 50 + 25 - 10) /* Right edge: (width, source + delta - offset*/),
+                std::make_tuple("TransitionToTopDisplay",
+                                AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ControllerType::MOUSE,
+                                ToolType::FINGER, vec2(50, 50) /* initial x/y */,
+                                vec2(25, -100) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_TOP_ID,
+                                vec2(50 + 25 - 10,
+                                     90) /* Bottom edge: (source + delta - offset, height) */),
+                std::make_tuple("TransitionToBottomDisplay",
+                                AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ControllerType::MOUSE,
+                                ToolType::FINGER, vec2(50, 50) /* initial x/y */,
+                                vec2(25, 100) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_BOTTOM_ID,
+                                vec2(50 + 25 - 10, 0) /* Top edge: (source + delta - offset, 0) */),
+                std::make_tuple("NoTransitionAtTopOffset", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(5, 50) /* initial x/y */, vec2(0, -100) /* Move Up */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(5, 0) /* Top edge */),
+                std::make_tuple("NoTransitionAtRightOffset", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(95, 5) /* initial x/y */, vec2(100, 0) /* Move Right */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(99, 5) /* Top edge */),
+                std::make_tuple("NoTransitionAtBottomOffset",
+                                AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ControllerType::MOUSE,
+                                ToolType::FINGER, vec2(5, 95) /* initial x/y */,
+                                vec2(0, 100) /* Move Down */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(5, 99) /* Bottom edge */),
+                std::make_tuple("NoTransitionAtLeftOffset",
+                                AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ControllerType::MOUSE,
+                                ToolType::FINGER, vec2(5, 5) /* initial x/y */,
+                                vec2(-100, 0) /* Move Left */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(0, 5) /* Left edge */),
+                std::make_tuple(
+                        "TransitionAtTopRightCorner", AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                        ControllerType::MOUSE, ToolType::FINGER, vec2(95, 5) /* initial x/y */,
+                        vec2(10, -10) /* Move dignally to top right corner */,
+                        PointerChoreographerDisplayTopologyTestFixture::DISPLAY_TOP_RIGHT_CORNER_ID,
+                        vec2(0, 90) /* bottom left corner */)),
+        [](const testing::TestParamInfo<PointerChoreographerDisplayTopologyTestFixtureParam>& p) {
+            return std::string{std::get<0>(p.param)};
+        });
+
 class PointerChoreographerWindowInfoListenerTest : public testing::Test {};
 
 TEST_F_WITH_FLAGS(
diff --git a/services/inputflinger/tests/SensorInputMapper_test.cpp b/services/inputflinger/tests/SensorInputMapper_test.cpp
index 2c12653..ac2e99e 100644
--- a/services/inputflinger/tests/SensorInputMapper_test.cpp
+++ b/services/inputflinger/tests/SensorInputMapper_test.cpp
@@ -125,7 +125,7 @@
     ASSERT_EQ(arg.source, AINPUT_SOURCE_SENSOR);
     ASSERT_EQ(arg.deviceId, DEVICE_ID);
     ASSERT_EQ(arg.sensorType, InputDeviceSensorType::ACCELEROMETER);
-    ASSERT_EQ(arg.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH);
+    ASSERT_EQ(arg.accuracy, InputDeviceSensorAccuracy::HIGH);
     ASSERT_EQ(arg.hwTimestamp, ARBITRARY_TIME);
     ASSERT_EQ(arg.values, values);
     mMapper->flushSensor(InputDeviceSensorType::ACCELEROMETER);
@@ -170,7 +170,7 @@
     ASSERT_EQ(arg.source, AINPUT_SOURCE_SENSOR);
     ASSERT_EQ(arg.deviceId, DEVICE_ID);
     ASSERT_EQ(arg.sensorType, InputDeviceSensorType::GYROSCOPE);
-    ASSERT_EQ(arg.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH);
+    ASSERT_EQ(arg.accuracy, InputDeviceSensorAccuracy::HIGH);
     ASSERT_EQ(arg.hwTimestamp, ARBITRARY_TIME);
     ASSERT_EQ(arg.values, values);
     mMapper->flushSensor(InputDeviceSensorType::GYROSCOPE);
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 64f3c27..6be922d 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -75,6 +75,8 @@
 
     void toggleCapsLockState(int32_t deviceId) { reader->toggleCapsLockState(deviceId); }
 
+    void resetLockedModifierState() { reader->resetLockedModifierState(); }
+
     bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector<int32_t>& keyCodes,
                  uint8_t* outFlags) {
         return reader->hasKeys(deviceId, sourceMask, keyCodes, outFlags);
@@ -226,6 +228,7 @@
                                            fdp->ConsumeIntegral<int32_t>());
                 },
                 [&]() -> void { reader->toggleCapsLockState(fdp->ConsumeIntegral<int32_t>()); },
+                [&]() -> void { reader->resetLockedModifierState(); },
                 [&]() -> void {
                     size_t count = fdp->ConsumeIntegralInRange<size_t>(1, 1024);
                     std::vector<uint8_t> outFlags(count);
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index 40fd097..0ba1909 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -168,6 +168,11 @@
                                           "closeSessionChannel"));
 }
 
+HalResult<aidl::android::hardware::power::SupportInfo> PowerHalController::getSupportInfo() {
+    std::shared_ptr<HalWrapper> handle = initHal();
+    return CACHE_SUPPORT(6, processHalResult(handle->getSupportInfo(), "getSupportInfo"));
+}
+
 } // namespace power
 
 } // namespace android
diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index bd6685c..068c23f 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -18,6 +18,7 @@
 #include <aidl/android/hardware/power/Boost.h>
 #include <aidl/android/hardware/power/IPowerHintSession.h>
 #include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/SupportInfo.h>
 #include <powermanager/HalResult.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
@@ -73,6 +74,11 @@
     return HalResult<void>::unsupported();
 }
 
+HalResult<Aidl::SupportInfo> EmptyHalWrapper::getSupportInfo() {
+    ALOGV("Skipped getSupportInfo because %s", getUnsupportedMessage());
+    return HalResult<Aidl::SupportInfo>::unsupported();
+}
+
 const char* EmptyHalWrapper::getUnsupportedMessage() {
     return "Power HAL is not supported";
 }
@@ -280,6 +286,12 @@
     return HalResult<void>::fromStatus(mHandle->closeSessionChannel(tgid, uid));
 }
 
+HalResult<Aidl::SupportInfo> AidlHalWrapper::getSupportInfo() {
+    Aidl::SupportInfo support;
+    auto result = mHandle->getSupportInfo(&support);
+    return HalResult<Aidl::SupportInfo>::fromStatus(result, std::move(support));
+}
+
 const char* AidlHalWrapper::getUnsupportedMessage() {
     return "Power HAL doesn't support it";
 }
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index ae84d7b..682d1f4 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -28,18 +28,10 @@
 #include <unistd.h>
 #include <thread>
 
-using aidl::android::hardware::power::Boost;
-using aidl::android::hardware::power::ChannelConfig;
-using aidl::android::hardware::power::CpuHeadroomParams;
-using aidl::android::hardware::power::GpuHeadroomParams;
-using aidl::android::hardware::power::IPower;
-using aidl::android::hardware::power::IPowerHintSession;
-using aidl::android::hardware::power::Mode;
-using aidl::android::hardware::power::SessionConfig;
-using aidl::android::hardware::power::SessionTag;
-using aidl::android::hardware::power::SupportInfo;
+
 using android::binder::Status;
 
+using namespace aidl::android::hardware::power;
 using namespace android;
 using namespace android::power;
 using namespace std::chrono_literals;
@@ -74,13 +66,13 @@
     MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
     MOCK_METHOD(bool, isRemote, (), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getCpuHeadroom,
-                (const CpuHeadroomParams& params, std::vector<float>* headroom), (override));
+                (const CpuHeadroomParams& params, CpuHeadroomResult* headroom), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getGpuHeadroom,
-                (const GpuHeadroomParams& params, float* headroom), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t* interval),
-                (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* interval),
-                (override));
+                (const GpuHeadroomParams& params, GpuHeadroomResult* headroom), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, sendCompositionData,
+                (const std::vector<CompositionData>& in_data), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, sendCompositionUpdate,
+                (const CompositionUpdate& in_update), (override));
 };
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/surfaceflinger/ActivePictureUpdater.cpp b/services/surfaceflinger/ActivePictureUpdater.cpp
new file mode 100644
index 0000000..210e948
--- /dev/null
+++ b/services/surfaceflinger/ActivePictureUpdater.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ActivePictureUpdater.h"
+
+#include <algorithm>
+
+#include "Layer.h"
+#include "LayerFE.h"
+
+namespace android {
+
+void ActivePictureUpdater::onLayerComposed(const Layer& layer, const LayerFE& layerFE,
+                                           const CompositionResult& result) {
+    if (result.wasPictureProfileCommitted) {
+        gui::ActivePicture picture;
+        picture.layerId = int32_t(layer.sequence);
+        picture.ownerUid = int32_t(layer.getOwnerUid());
+        // TODO(b/337330263): Why does LayerFE coming from SF have a null composition state?
+        if (layerFE.getCompositionState()) {
+            picture.pictureProfileId = layerFE.getCompositionState()->pictureProfileHandle.getId();
+        } else {
+            picture.pictureProfileId = result.pictureProfileHandle.getId();
+        }
+        mNewActivePictures.push_back(picture);
+    }
+}
+
+bool ActivePictureUpdater::updateAndHasChanged() {
+    bool hasChanged = true;
+    if (mNewActivePictures.size() == mOldActivePictures.size()) {
+        auto compare = [](const gui::ActivePicture& lhs, const gui::ActivePicture& rhs) -> int {
+            if (lhs.layerId == rhs.layerId) {
+                return lhs.pictureProfileId < rhs.pictureProfileId;
+            }
+            return lhs.layerId < rhs.layerId;
+        };
+        std::sort(mNewActivePictures.begin(), mNewActivePictures.end(), compare);
+        if (std::equal(mNewActivePictures.begin(), mNewActivePictures.end(),
+                       mOldActivePictures.begin())) {
+            hasChanged = false;
+        }
+    }
+    std::swap(mOldActivePictures, mNewActivePictures);
+    mNewActivePictures.resize(0);
+    return hasChanged;
+}
+
+const std::vector<gui::ActivePicture>& ActivePictureUpdater::getActivePictures() const {
+    return mOldActivePictures;
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/ActivePictureUpdater.h b/services/surfaceflinger/ActivePictureUpdater.h
new file mode 100644
index 0000000..20779bb
--- /dev/null
+++ b/services/surfaceflinger/ActivePictureUpdater.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <android/gui/ActivePicture.h>
+
+namespace android {
+
+class Layer;
+class LayerFE;
+struct CompositionResult;
+
+// Keeps track of active pictures - layers that are undergoing picture processing.
+class ActivePictureUpdater {
+public:
+    // Called for each visible layer when SurfaceFlinger finishes composing.
+    void onLayerComposed(const Layer& layer, const LayerFE& layerFE,
+                         const CompositionResult& result);
+
+    // Update internals and return whether the set of active pictures have changed.
+    bool updateAndHasChanged();
+
+    // The current set of active pictures.
+    const std::vector<gui::ActivePicture>& getActivePictures() const;
+
+private:
+    std::vector<gui::ActivePicture> mOldActivePictures;
+    std::vector<gui::ActivePicture> mNewActivePictures;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 8a667ae..3f3d2c6 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -199,6 +199,7 @@
     name: "libsurfaceflinger_sources",
     srcs: [
         ":libsurfaceflinger_backend_sources",
+        "ActivePictureUpdater.cpp",
         "BackgroundExecutor.cpp",
         "Client.cpp",
         "ClientCache.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index a5e9dde..cda4edc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -161,6 +161,9 @@
     // Checks if the buffer's release fence has been set
     virtual LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() = 0;
 
+    // Indicates that the picture profile request was applied to this layer.
+    virtual void onPictureProfileCommitted() = 0;
+
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index c7ff704..272fa3e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -58,6 +58,7 @@
     MOCK_CONST_METHOD0(hasRoundedCorners, bool());
     MOCK_CONST_METHOD0(getMetadata, gui::LayerMetadata*());
     MOCK_CONST_METHOD0(getRelativeMetadata, gui::LayerMetadata*());
+    MOCK_METHOD0(onPictureProfileCommitted, void());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index ee813bf..f9ed92d 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -830,11 +830,13 @@
         for (int i = 0; i < getMaxLayerPictureProfiles() && !layersWithProfiles.empty();
              layersWithProfiles.pop(), ++i) {
             layersWithProfiles.top()->commitPictureProfileToCompositionState();
+            layersWithProfiles.top()->getLayerFE().onPictureProfileCommitted();
         }
         // No layer-specific picture processing, so apply the highest priority picture profile to
         // the entire display.
     } else if (!layersWithProfiles.empty()) {
         editState().pictureProfileHandle = layersWithProfiles.top()->getPictureProfileHandle();
+        layersWithProfiles.top()->getLayerFE().onPictureProfileCommitted();
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index f6d9a1a..a040c88 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -26,6 +26,7 @@
 #include <ui/FloatRect.h>
 #include <ui/HdrRenderTypeUtils.h>
 #include <cstdint>
+#include <limits>
 #include "system/graphics-base-v1.0.h"
 
 #include <com_android_graphics_libgui_flags.h>
@@ -398,11 +399,22 @@
     // For hdr content, treat the white point as the display brightness - HDR content should not be
     // boosted or dimmed.
     // If the layer explicitly requests to disable dimming, then don't dim either.
-    if (hdrRenderType == HdrRenderType::GENERIC_HDR ||
-        getOutput().getState().displayBrightnessNits == getOutput().getState().sdrWhitePointNits ||
-        getOutput().getState().displayBrightnessNits == 0.f || !layerFEState->dimmingEnabled) {
+    if (getOutput().getState().displayBrightnessNits == getOutput().getState().sdrWhitePointNits ||
+        getOutput().getState().displayBrightnessNits <= 0.f || !layerFEState->dimmingEnabled) {
         state.dimmingRatio = 1.f;
         state.whitePointNits = getOutput().getState().displayBrightnessNits;
+    } else if (hdrRenderType == HdrRenderType::GENERIC_HDR) {
+        float deviceHeadroom = getOutput().getState().displayBrightnessNits /
+                getOutput().getState().sdrWhitePointNits;
+        float idealizedMaxHeadroom = deviceHeadroom;
+
+        if (FlagManager::getInstance().begone_bright_hlg()) {
+            idealizedMaxHeadroom =
+                    std::min(idealizedMaxHeadroom, getIdealizedMaxHeadroom(state.dataspace));
+        }
+
+        state.dimmingRatio = std::min(idealizedMaxHeadroom / deviceHeadroom, 1.0f);
+        state.whitePointNits = getOutput().getState().displayBrightnessNits * state.dimmingRatio;
     } else {
         float layerBrightnessNits = getOutput().getState().sdrWhitePointNits;
         // RANGE_EXTENDED can "self-promote" to HDR, but is still rendered for a particular
@@ -432,7 +444,7 @@
     }
     const auto* layerState = getLayerFE().getCompositionState();
     if (layerState) {
-        editState().pictureProfileHandle = getLayerFE().getCompositionState()->pictureProfileHandle;
+        editState().pictureProfileHandle = layerState->pictureProfileHandle;
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 99e68eb..442b603 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -5117,6 +5117,11 @@
     // Sets display picture profile to the highest priority layer's profile
     EXPECT_CALL(mHwComposer, setDisplayPictureProfileHandle(_, Eq(profileForLayer2)));
 
+    // Marks only the highest priority layer as committed
+    EXPECT_CALL(*layer1.layerFE, onPictureProfileCommitted).Times(0);
+    EXPECT_CALL(*layer2.layerFE, onPictureProfileCommitted);
+    EXPECT_CALL(*layer3.layerFE, onPictureProfileCommitted).Times(0);
+
     mOutput->editState().isEnabled = true;
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = false;
@@ -5172,6 +5177,11 @@
     EXPECT_CALL(*layer2.outputLayer, commitPictureProfileToCompositionState);
     EXPECT_CALL(*layer3.outputLayer, commitPictureProfileToCompositionState);
 
+    // Marks only the highest priority layers as committed
+    EXPECT_CALL(*layer1.layerFE, onPictureProfileCommitted).Times(0);
+    EXPECT_CALL(*layer2.layerFE, onPictureProfileCommitted);
+    EXPECT_CALL(*layer3.layerFE, onPictureProfileCommitted);
+
     // No display picture profile is sent
     EXPECT_CALL(mHwComposer, setDisplayPictureProfileHandle).Times(0);
 
diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp
index f8b6c6e..a086aee 100644
--- a/services/surfaceflinger/Display/DisplayModeController.cpp
+++ b/services/surfaceflinger/Display/DisplayModeController.cpp
@@ -28,6 +28,7 @@
 #include <ftl/concat.h>
 #include <ftl/expected.h>
 #include <log/log.h>
+#include <utils/Errors.h>
 
 namespace android::display {
 
@@ -177,12 +178,13 @@
     }
 }
 
-bool DisplayModeController::initiateModeChange(PhysicalDisplayId displayId,
-                                               DisplayModeRequest&& desiredMode,
-                                               const hal::VsyncPeriodChangeConstraints& constraints,
-                                               hal::VsyncPeriodChangeTimeline& outTimeline) {
+auto DisplayModeController::initiateModeChange(
+        PhysicalDisplayId displayId, DisplayModeRequest&& desiredMode,
+        const hal::VsyncPeriodChangeConstraints& constraints,
+        hal::VsyncPeriodChangeTimeline& outTimeline) -> ModeChangeResult {
     std::lock_guard lock(mDisplayLock);
-    const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get();
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(ModeChangeResult::Aborted)).get();
 
     // TODO: b/255635711 - Flow the DisplayModeRequest through the desired/pending/active states.
     // For now, `desiredMode` and `desiredModeOpt` are one and the same, but the latter is not
@@ -201,13 +203,17 @@
 
     const auto& mode = *displayPtr->pendingModeOpt->mode.modePtr;
 
-    if (mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(), constraints,
-                                                   &outTimeline) != OK) {
-        return false;
+    const auto error = mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(),
+                                                                  constraints, &outTimeline);
+    switch (error) {
+        case FAILED_TRANSACTION:
+            return ModeChangeResult::Rejected;
+        case OK:
+            SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
+            return ModeChangeResult::Changed;
+        default:
+            return ModeChangeResult::Aborted;
     }
-
-    SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
-    return true;
 }
 
 void DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId, DisplayModeId modeId,
diff --git a/services/surfaceflinger/Display/DisplayModeController.h b/services/surfaceflinger/Display/DisplayModeController.h
index 9ec603d..af3e909 100644
--- a/services/surfaceflinger/Display/DisplayModeController.h
+++ b/services/surfaceflinger/Display/DisplayModeController.h
@@ -70,6 +70,7 @@
     RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
 
     enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
+    enum class ModeChangeResult { Changed, Rejected, Aborted };
 
     DesiredModeAction setDesiredMode(PhysicalDisplayId, DisplayModeRequest&&)
             EXCLUDES(mDisplayLock);
@@ -86,9 +87,9 @@
 
     scheduler::FrameRateMode getActiveMode(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
 
-    bool initiateModeChange(PhysicalDisplayId, DisplayModeRequest&&,
-                            const hal::VsyncPeriodChangeConstraints&,
-                            hal::VsyncPeriodChangeTimeline& outTimeline)
+    ModeChangeResult initiateModeChange(PhysicalDisplayId, DisplayModeRequest&&,
+                                        const hal::VsyncPeriodChangeConstraints&,
+                                        hal::VsyncPeriodChangeTimeline& outTimeline)
             REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     void finalizeModeChange(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps)
diff --git a/services/surfaceflinger/Display/VirtualDisplaySnapshot.h b/services/surfaceflinger/Display/VirtualDisplaySnapshot.h
new file mode 100644
index 0000000..c68020c
--- /dev/null
+++ b/services/surfaceflinger/Display/VirtualDisplaySnapshot.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+
+#include <ui/DisplayId.h>
+
+#include "Utils/Dumper.h"
+
+namespace android::display {
+
+// Immutable state of a virtual display, captured on creation.
+class VirtualDisplaySnapshot {
+public:
+    VirtualDisplaySnapshot(GpuVirtualDisplayId gpuId, std::string uniqueId)
+          : mIsGpu(true), mUniqueId(std::move(uniqueId)), mVirtualId(gpuId) {}
+    VirtualDisplaySnapshot(HalVirtualDisplayId halId, std::string uniqueId)
+          : mIsGpu(false), mUniqueId(std::move(uniqueId)), mVirtualId(halId) {}
+
+    VirtualDisplayId displayId() const { return mVirtualId; }
+    bool isGpu() const { return mIsGpu; }
+
+    void dump(utils::Dumper& dumper) const {
+        using namespace std::string_view_literals;
+
+        dumper.dump("isGpu"sv, mIsGpu ? "true"sv : "false"sv);
+        dumper.dump("uniqueId"sv, mUniqueId);
+    }
+
+private:
+    const bool mIsGpu;
+    const std::string mUniqueId;
+    const VirtualDisplayId mVirtualId;
+};
+
+} // namespace android::display
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 4c8ff58..25f6513 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -37,10 +37,6 @@
 
 namespace android {
 
-using hardware::hidl_handle;
-using hardware::hidl_vec;
-using hardware::Return;
-
 using aidl::android::hardware::graphics::composer3::BnComposerCallback;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
@@ -524,11 +520,15 @@
 
 Error AidlComposer::getDisplayAttribute(Display display, Config config,
                                         IComposerClient::Attribute attribute, int32_t* outValue) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     const auto status =
             mAidlComposerClient->getDisplayAttribute(translate<int64_t>(display),
                                                      translate<int32_t>(config),
                                                      static_cast<AidlDisplayAttribute>(attribute),
                                                      outValue);
+#pragma clang diagnostic pop
+
     if (!status.isOk()) {
         ALOGE("getDisplayAttribute failed %s", status.getDescription().c_str());
         return static_cast<Error>(status.getServiceSpecificError());
@@ -538,8 +538,13 @@
 
 Error AidlComposer::getDisplayConfigs(Display display, std::vector<Config>* outConfigs) {
     std::vector<int32_t> configs;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     const auto status =
             mAidlComposerClient->getDisplayConfigs(translate<int64_t>(display), &configs);
+#pragma clang diagnostic pop
+
     if (!status.isOk()) {
         ALOGE("getDisplayConfigs failed %s", status.getDescription().c_str());
         return static_cast<Error>(status.getServiceSpecificError());
@@ -1384,7 +1389,7 @@
     return V2_4::Error::NONE;
 }
 
-V2_4::Error AidlComposer::setActiveConfigWithConstraints(
+Error AidlComposer::setActiveConfigWithConstraints(
         Display display, Config config,
         const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
         VsyncPeriodChangeTimeline* outTimeline) {
@@ -1398,10 +1403,10 @@
                                                      &timeline);
     if (!status.isOk()) {
         ALOGE("setActiveConfigWithConstraints failed %s", status.getDescription().c_str());
-        return static_cast<V2_4::Error>(status.getServiceSpecificError());
+        return static_cast<Error>(status.getServiceSpecificError());
     }
     *outTimeline = translate<VsyncPeriodChangeTimeline>(timeline);
-    return V2_4::Error::NONE;
+    return Error::NONE;
 }
 
 V2_4::Error AidlComposer::setAutoLowLatencyMode(Display display, bool on) {
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 933e8d1..6b5ebc5 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -27,11 +27,6 @@
 #include <utility>
 #include <vector>
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#pragma clang diagnostic ignored "-Wextra"
-
 #include <android/hardware/graphics/composer/2.4/IComposer.h>
 #include <android/hardware/graphics/composer/2.4/IComposerClient.h>
 
@@ -43,9 +38,6 @@
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
-
 namespace android::Hwc2 {
 
 using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
@@ -206,7 +198,7 @@
     V2_4::Error getDisplayConnectionType(Display display,
                                          IComposerClient::DisplayConnectionType* outType) override;
     V2_4::Error getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) override;
-    V2_4::Error setActiveConfigWithConstraints(
+    Error setActiveConfigWithConstraints(
             Display display, Config config,
             const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
             VsyncPeriodChangeTimeline* outTimeline) override;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index c1333c2..ff292fa 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -32,6 +32,8 @@
 #include <ui/PictureProfileHandle.h>
 #include <utils/StrongPointer.h>
 
+#include "DisplayHardware/Hal.h"
+
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/common/HdrConversionCapability.h>
 #include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h>
@@ -73,9 +75,9 @@
 using types::V1_2::Dataspace;
 using types::V1_2::PixelFormat;
 
+using hardware::graphics::composer::hal::Error;
 using V2_1::Config;
 using V2_1::Display;
-using V2_1::Error;
 using V2_1::Layer;
 using V2_4::CommandReaderBase;
 using V2_4::CommandWriterBase;
@@ -261,7 +263,7 @@
             Display display, IComposerClient::DisplayConnectionType* outType) = 0;
     virtual V2_4::Error getDisplayVsyncPeriod(Display display,
                                               VsyncPeriodNanos* outVsyncPeriod) = 0;
-    virtual V2_4::Error setActiveConfigWithConstraints(
+    virtual Error setActiveConfigWithConstraints(
             Display display, Config config,
             const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
             VsyncPeriodChangeTimeline* outTimeline) = 0;
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index 224f50e..e90b5b7 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -31,10 +31,11 @@
 #include <common/FlagManager.h>
 #include <scheduler/Fps.h>
 
-#include "DisplayHardware/Hal.h"
+#include "Hal.h"
 
 namespace android {
 
+using aidl::android::hardware::graphics::composer3::OutputType;
 namespace hal = android::hardware::graphics::composer::hal;
 
 class DisplayMode;
@@ -114,6 +115,11 @@
             return *this;
         }
 
+        Builder& setHdrOutputType(OutputType type) {
+            mDisplayMode->mHdrOutputType = type;
+            return *this;
+        }
+
     private:
         float getDefaultDensity() {
             // Default density is based on TVs: 1080p displays get XHIGH density, lower-
@@ -166,6 +172,8 @@
     // without visual interruptions such as a black screen.
     int32_t getGroup() const { return mGroup; }
 
+    OutputType getHdrOutputType() const { return mHdrOutputType; }
+
 private:
     explicit DisplayMode(hal::HWConfigId id) : mHwcId(id) {}
 
@@ -179,21 +187,25 @@
     Dpi mDpi;
     int32_t mGroup = -1;
     std::optional<hal::VrrConfig> mVrrConfig;
+    OutputType mHdrOutputType;
 };
 
 inline bool equalsExceptDisplayModeId(const DisplayMode& lhs, const DisplayMode& rhs) {
     return lhs.getHwcId() == rhs.getHwcId() && lhs.getResolution() == rhs.getResolution() &&
             lhs.getVsyncRate().getPeriodNsecs() == rhs.getVsyncRate().getPeriodNsecs() &&
-            lhs.getDpi() == rhs.getDpi() && lhs.getGroup() == rhs.getGroup();
+            lhs.getDpi() == rhs.getDpi() && lhs.getGroup() == rhs.getGroup() &&
+            lhs.getVrrConfig() == rhs.getVrrConfig() &&
+            lhs.getHdrOutputType() == rhs.getHdrOutputType();
 }
 
 inline std::string to_string(const DisplayMode& mode) {
     return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, vsyncRate=%s, "
-                              "dpi=%.2fx%.2f, group=%d, vrrConfig=%s}",
+                              "dpi=%.2fx%.2f, group=%d, vrrConfig=%s, supportedHdrTypes=%s}",
                               ftl::to_underlying(mode.getId()), mode.getHwcId(), mode.getWidth(),
                               mode.getHeight(), to_string(mode.getVsyncRate()).c_str(),
                               mode.getDpi().x, mode.getDpi().y, mode.getGroup(),
-                              to_string(mode.getVrrConfig()).c_str());
+                              to_string(mode.getVrrConfig()).c_str(),
+                              toString(mode.getHdrOutputType()).c_str());
 }
 
 template <typename... DisplayModePtrs>
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index a274995..081f4aa 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -33,6 +33,8 @@
 #include <ui/GraphicBuffer.h>
 #include <ui/PictureProfileHandle.h>
 
+#include "DisplayHardware/Hal.h"
+
 #include <algorithm>
 #include <cinttypes>
 #include <iterator>
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 61d4541..55ccdef 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -27,6 +27,7 @@
 
 #include "HWComposer.h"
 
+#include <aidl/android/hardware/graphics/composer3/IComposerClient.h>
 #include <android-base/properties.h>
 #include <common/trace.h>
 #include <compositionengine/Output.h>
@@ -335,7 +336,8 @@
                                       .height = config.height,
                                       .vsyncPeriod = config.vsyncPeriod,
                                       .configGroup = config.configGroup,
-                                      .vrrConfig = config.vrrConfig};
+                                      .vrrConfig = config.vrrConfig,
+                                      .hdrOutputType = config.hdrOutputType};
 
         const DisplayConfiguration::Dpi estimatedDPI =
                 getEstimatedDotsPerInchFromSize(hwcDisplayId, hwcMode);
@@ -733,7 +735,11 @@
     auto error = mDisplayData[displayId].hwcDisplay->setActiveConfigWithConstraints(hwcModeId,
                                                                                     constraints,
                                                                                     outTimeline);
-    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    if (error == hal::Error::CONFIG_FAILED) {
+        RETURN_IF_HWC_ERROR_FOR("setActiveConfigWithConstraints", error, displayId,
+                                FAILED_TRANSACTION);
+    }
+    RETURN_IF_HWC_ERROR_FOR("setActiveConfigWithConstraints", error, displayId, UNKNOWN_ERROR);
     return NO_ERROR;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index e21ce1d..52662cf 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -55,6 +55,7 @@
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayLuts.h>
 #include <aidl/android/hardware/graphics/composer3/LutProperties.h>
+#include <aidl/android/hardware/graphics/composer3/OutputType.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 namespace android {
@@ -112,12 +113,14 @@
         float dpiY = -1.f;
         int32_t configGroup = -1;
         std::optional<hal::VrrConfig> vrrConfig;
+        OutputType hdrOutputType;
 
         friend std::ostream& operator<<(std::ostream& os, const HWCDisplayMode& mode) {
             return os << "id=" << mode.hwcId << " res=" << mode.width << "x" << mode.height
                       << " vsyncPeriod=" << mode.vsyncPeriod << " dpi=" << mode.dpiX << "x"
                       << mode.dpiY << " group=" << mode.configGroup
-                      << " vrrConfig=" << to_string(mode.vrrConfig).c_str();
+                      << " vrrConfig=" << to_string(mode.vrrConfig).c_str()
+                      << " hdrOutputType=" << toString(mode.hdrOutputType);
         }
     };
 
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index e3d9622..568d758 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -17,16 +17,21 @@
 #pragma once
 
 #include <android/hardware/graphics/common/1.1/types.h>
+#include <android/hardware/graphics/composer/2.1/types.h>
 #include <android/hardware/graphics/composer/2.4/IComposer.h>
 #include <android/hardware/graphics/composer/2.4/IComposerClient.h>
+#include <android/hardware/graphics/composer/2.4/types.h>
 
 #include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <aidl/android/hardware/graphics/common/Hdr.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
+#include <aidl/android/hardware/graphics/composer3/IComposerClient.h>
 #include <aidl/android/hardware/graphics/composer3/VrrConfig.h>
 
+#include <ftl/enum.h>
+
 #define ERROR_HAS_CHANGES 5
 
 namespace android {
@@ -46,7 +51,6 @@
 using types::V1_2::Dataspace;
 using types::V1_2::PixelFormat;
 
-using V2_1::Error;
 using V2_4::IComposer;
 using V2_4::IComposerCallback;
 using V2_4::IComposerClient;
@@ -78,6 +82,22 @@
 using DisplayConfiguration = V3_0::DisplayConfiguration;
 using VrrConfig = V3_0::VrrConfig;
 
+enum class Error : int32_t {
+    NONE = static_cast<int32_t>(V2_1::Error::NONE),
+    BAD_CONFIG = static_cast<int32_t>(V2_1::Error::BAD_CONFIG),
+    BAD_DISPLAY = static_cast<int32_t>(V2_1::Error::BAD_DISPLAY),
+    BAD_LAYER = static_cast<int32_t>(V2_1::Error::BAD_LAYER),
+    BAD_PARAMETER = static_cast<int32_t>(V2_1::Error::BAD_PARAMETER),
+    NO_RESOURCES = static_cast<int32_t>(V2_1::Error::NO_RESOURCES),
+    NOT_VALIDATED = static_cast<int32_t>(V2_1::Error::NOT_VALIDATED),
+    UNSUPPORTED = static_cast<int32_t>(V2_1::Error::UNSUPPORTED),
+    SEAMLESS_NOT_ALLOWED = static_cast<int32_t>(V2_4::Error::SEAMLESS_NOT_ALLOWED),
+    SEAMLESS_NOT_POSSIBLE = static_cast<int32_t>(V2_4::Error::SEAMLESS_NOT_POSSIBLE),
+    CONFIG_FAILED = V3_0::IComposerClient::EX_CONFIG_FAILED,
+    PICTURE_PROFILE_MAX_EXCEEDED = V3_0::IComposerClient::EX_PICTURE_PROFILE_MAX_EXCEEDED,
+    ftl_last = PICTURE_PROFILE_MAX_EXCEEDED
+};
+
 } // namespace hardware::graphics::composer::hal
 
 inline bool hasChangesError(hardware::graphics::composer::hal::Error error) {
@@ -210,7 +230,11 @@
 }
 
 inline std::string to_string(hardware::graphics::composer::hal::Error error) {
-    return to_string(static_cast<hardware::graphics::composer::hal::V2_4::Error>(error));
+    // 5 is reserved for historical reason, during validation 5 means has changes.
+    if (hasChangesError(error)) {
+        return "HAS_CHANGES";
+    }
+    return ftl::enum_string(error);
 }
 
 // For utils::Dumper ADL.
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index e359a26..5703a2d 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -27,6 +27,7 @@
 #include <SurfaceFlingerProperties.h>
 #include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <android/binder_manager.h>
+#include <android/hardware/graphics/composer/2.1/types.h>
 #include <common/trace.h>
 #include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
 #include <hidl/HidlTransportSupport.h>
@@ -173,7 +174,7 @@
 };
 
 // assume NO_RESOURCES when Status::isOk returns false
-constexpr Error kDefaultError = Error::NO_RESOURCES;
+constexpr V2_1::Error kDefaultError = V2_1::Error::NO_RESOURCES;
 constexpr V2_4::Error kDefaultError_2_4 = static_cast<V2_4::Error>(kDefaultError);
 
 template <typename T, typename U>
@@ -181,7 +182,7 @@
     return (ret.isOk()) ? static_cast<T>(ret) : static_cast<T>(default_val);
 }
 
-Error unwrapRet(Return<Error>& ret) {
+V2_1::Error unwrapRet(Return<V2_1::Error>& ret) {
     return unwrapRet(ret, kDefaultError);
 }
 
@@ -235,7 +236,7 @@
         });
     } else if (sp<V2_3::IComposer> composer_2_3 = V2_3::IComposer::castFrom(mComposer)) {
         composer_2_3->createClient_2_3([&](const auto& tmpError, const auto& tmpClient) {
-            if (tmpError == Error::NONE) {
+            if (tmpError == V2_1::Error::NONE) {
                 mClient = tmpClient;
                 mClient_2_2 = tmpClient;
                 mClient_2_3 = tmpClient;
@@ -243,7 +244,7 @@
         });
     } else {
         mComposer->createClient([&](const auto& tmpError, const auto& tmpClient) {
-            if (tmpError != Error::NONE) {
+            if (tmpError != V2_1::Error::NONE) {
                 return;
             }
 
@@ -325,14 +326,14 @@
 Error HidlComposer::createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
                                          Display* outDisplay) {
     const uint32_t bufferSlotCount = 1;
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     if (mClient_2_2) {
         mClient_2_2->createVirtualDisplay_2_2(width, height,
                                               static_cast<types::V1_1::PixelFormat>(*format),
                                               bufferSlotCount,
                                               [&](const auto& tmpError, const auto& tmpDisplay,
                                                   const auto& tmpFormat) {
-                                                  error = tmpError;
+                                                  error = static_cast<Error>(tmpError);
                                                   if (error != Error::NONE) {
                                                       return;
                                                   }
@@ -346,7 +347,7 @@
                                       bufferSlotCount,
                                       [&](const auto& tmpError, const auto& tmpDisplay,
                                           const auto& tmpFormat) {
-                                          error = tmpError;
+                                          error = static_cast<Error>(tmpError);
                                           if (error != Error::NONE) {
                                               return;
                                           }
@@ -361,7 +362,7 @@
 
 Error HidlComposer::destroyVirtualDisplay(Display display) {
     auto ret = mClient->destroyVirtualDisplay(display);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::acceptDisplayChanges(Display display) {
@@ -371,10 +372,10 @@
 }
 
 Error HidlComposer::createLayer(Display display, Layer* outLayer) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->createLayer(display, kMaxLayerBufferCount,
                          [&](const auto& tmpError, const auto& tmpLayer) {
-                             error = tmpError;
+                             error = static_cast<Error>(tmpError);
                              if (error != Error::NONE) {
                                  return;
                              }
@@ -387,13 +388,13 @@
 
 Error HidlComposer::destroyLayer(Display display, Layer layer) {
     auto ret = mClient->destroyLayer(display, layer);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::getActiveConfig(Display display, Config* outConfig) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->getActiveConfig(display, [&](const auto& tmpError, const auto& tmpConfig) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -412,11 +413,11 @@
 }
 
 Error HidlComposer::getColorModes(Display display, std::vector<ColorMode>* outModes) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
 
     if (mClient_2_3) {
         mClient_2_3->getColorModes_2_3(display, [&](const auto& tmpError, const auto& tmpModes) {
-            error = tmpError;
+            error = static_cast<Error>(tmpError);
             if (error != Error::NONE) {
                 return;
             }
@@ -425,7 +426,7 @@
         });
     } else if (mClient_2_2) {
         mClient_2_2->getColorModes_2_2(display, [&](const auto& tmpError, const auto& tmpModes) {
-            error = tmpError;
+            error = static_cast<Error>(tmpError);
             if (error != Error::NONE) {
                 return;
             }
@@ -436,7 +437,7 @@
         });
     } else {
         mClient->getColorModes(display, [&](const auto& tmpError, const auto& tmpModes) {
-            error = tmpError;
+            error = static_cast<Error>(tmpError);
             if (error != Error::NONE) {
                 return;
             }
@@ -451,7 +452,7 @@
 
 Error HidlComposer::getDisplayAttribute(Display display, Config config,
                                         IComposerClient::Attribute attribute, int32_t* outValue) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     if (mClient_2_4) {
         mClient_2_4->getDisplayAttribute_2_4(display, config, attribute,
                                              [&](const auto& tmpError, const auto& tmpValue) {
@@ -466,7 +467,7 @@
         mClient->getDisplayAttribute(display, config,
                                      static_cast<V2_1::IComposerClient::Attribute>(attribute),
                                      [&](const auto& tmpError, const auto& tmpValue) {
-                                         error = tmpError;
+                                         error = static_cast<Error>(tmpError);
                                          if (error != Error::NONE) {
                                              return;
                                          }
@@ -479,9 +480,9 @@
 }
 
 Error HidlComposer::getDisplayConfigs(Display display, std::vector<Config>* outConfigs) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->getDisplayConfigs(display, [&](const auto& tmpError, const auto& tmpConfigs) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -499,9 +500,9 @@
 }
 
 Error HidlComposer::getDisplayName(Display display, std::string* outName) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->getDisplayName(display, [&](const auto& tmpError, const auto& tmpName) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -520,9 +521,9 @@
 }
 
 Error HidlComposer::getDozeSupport(Display display, bool* outSupport) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->getDozeSupport(display, [&](const auto& tmpError, const auto& tmpSupport) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -541,14 +542,14 @@
 Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes,
                                        float* outMaxLuminance, float* outMaxAverageLuminance,
                                        float* outMinLuminance) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     if (mClient_2_3) {
         mClient_2_3->getHdrCapabilities_2_3(display,
                                             [&](const auto& tmpError, const auto& tmpHdrTypes,
                                                 const auto& tmpMaxLuminance,
                                                 const auto& tmpMaxAverageLuminance,
                                                 const auto& tmpMinLuminance) {
-                                                error = tmpError;
+                                                error = static_cast<Error>(tmpError);
                                                 if (error != Error::NONE) {
                                                     return;
                                                 }
@@ -564,7 +565,7 @@
                                         const auto& tmpMaxLuminance,
                                         const auto& tmpMaxAverageLuminance,
                                         const auto& tmpMinLuminance) {
-                                        error = tmpError;
+                                        error = static_cast<Error>(tmpError);
                                         if (error != Error::NONE) {
                                             return;
                                         }
@@ -606,7 +607,7 @@
 
 Error HidlComposer::setActiveConfig(Display display, Config config) {
     auto ret = mClient->setActiveConfig(display, config);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
@@ -625,7 +626,7 @@
 }
 
 Error HidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) {
-    hardware::Return<Error> ret(kDefaultError);
+    hardware::Return<V2_1::Error> ret(kDefaultError);
     if (mClient_2_3) {
         ret = mClient_2_3->setColorMode_2_3(display, mode, renderIntent);
     } else if (mClient_2_2) {
@@ -634,7 +635,7 @@
     } else {
         ret = mClient->setColorMode(display, static_cast<types::V1_0::ColorMode>(mode));
     }
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::setColorTransform(Display display, const float* matrix) {
@@ -654,25 +655,25 @@
 }
 
 Error HidlComposer::setPowerMode(Display display, IComposerClient::PowerMode mode) {
-    Return<Error> ret(Error::UNSUPPORTED);
+    Return<V2_1::Error> ret(V2_1::Error::UNSUPPORTED);
     if (mClient_2_2) {
         ret = mClient_2_2->setPowerMode_2_2(display, mode);
     } else if (mode != IComposerClient::PowerMode::ON_SUSPEND) {
         ret = mClient->setPowerMode(display, static_cast<V2_1::IComposerClient::PowerMode>(mode));
     }
 
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::setVsyncEnabled(Display display, IComposerClient::Vsync enabled) {
     auto ret = mClient->setVsyncEnabled(display, enabled);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::setClientTargetSlotCount(Display display) {
     const uint32_t bufferSlotCount = BufferQueue::NUM_BUFFER_SLOTS;
     auto ret = mClient->setClientTargetSlotCount(display, bufferSlotCount);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
@@ -903,7 +904,7 @@
     // set up new input command queue if necessary
     if (queueChanged) {
         auto ret = mClient->setInputCommandQueue(*mWriter.getMQDescriptor());
-        auto error = unwrapRet(ret);
+        auto error = static_cast<Error>(unwrapRet(ret));
         if (error != Error::NONE) {
             mWriter.reset();
             return error;
@@ -915,17 +916,17 @@
         return Error::NONE;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     hardware::Return<void> ret;
     auto hidl_callback = [&](const auto& tmpError, const auto& tmpOutChanged,
                              const auto& tmpOutLength, const auto& tmpOutHandles) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
 
         // set up new output command queue if necessary
         if (error == Error::NONE && tmpOutChanged) {
-            error = kDefaultError;
+            error = static_cast<Error>(kDefaultError);
             mClient->getOutputCommandQueue([&](const auto& tmpError, const auto& tmpDescriptor) {
-                error = tmpError;
+                error = static_cast<Error>(tmpError);
                 if (error != Error::NONE) {
                     return;
                 }
@@ -1000,11 +1001,11 @@
         return keys;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     if (mClient_2_3) {
         mClient_2_3->getPerFrameMetadataKeys_2_3(display,
                                                  [&](const auto& tmpError, const auto& tmpKeys) {
-                                                     error = tmpError;
+                                                     error = static_cast<Error>(tmpError);
                                                      if (error != Error::NONE) {
                                                          ALOGW("getPerFrameMetadataKeys failed "
                                                                "with %d",
@@ -1016,7 +1017,7 @@
     } else {
         mClient_2_2
                 ->getPerFrameMetadataKeys(display, [&](const auto& tmpError, const auto& tmpKeys) {
-                    error = tmpError;
+                    error = static_cast<Error>(tmpError);
                     if (error != Error::NONE) {
                         ALOGW("getPerFrameMetadataKeys failed with %d", tmpError);
                         return;
@@ -1039,10 +1040,10 @@
         return Error::NONE;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
 
     auto getRenderIntentsLambda = [&](const auto& tmpError, const auto& tmpKeys) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -1066,10 +1067,10 @@
         return Error::NONE;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient_2_2->getDataspaceSaturationMatrix(static_cast<types::V1_1::Dataspace>(dataspace),
                                               [&](const auto& tmpError, const auto& tmpMatrix) {
-                                                  error = tmpError;
+                                                  error = static_cast<Error>(tmpError);
                                                   if (error != Error::NONE) {
                                                       return;
                                                   }
@@ -1087,11 +1088,11 @@
         return Error::UNSUPPORTED;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient_2_3->getDisplayIdentificationData(display,
                                               [&](const auto& tmpError, const auto& tmpPort,
                                                   const auto& tmpData) {
-                                                  error = tmpError;
+                                                  error = static_cast<Error>(tmpError);
                                                   if (error != Error::NONE) {
                                                       return;
                                                   }
@@ -1123,13 +1124,13 @@
     if (!mClient_2_3) {
         return Error::UNSUPPORTED;
     }
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient_2_3->getDisplayedContentSamplingAttributes(display,
                                                        [&](const auto tmpError,
                                                            const auto& tmpFormat,
                                                            const auto& tmpDataspace,
                                                            const auto& tmpComponentMask) {
-                                                           error = tmpError;
+                                                           error = static_cast<Error>(tmpError);
                                                            if (error == Error::NONE) {
                                                                *outFormat = tmpFormat;
                                                                *outDataspace = tmpDataspace;
@@ -1149,8 +1150,9 @@
 
     auto enable = enabled ? V2_3::IComposerClient::DisplayedContentSampling::ENABLE
                           : V2_3::IComposerClient::DisplayedContentSampling::DISABLE;
-    return mClient_2_3->setDisplayedContentSamplingEnabled(display, enable, componentMask,
-                                                           maxFrames);
+    auto ret = mClient_2_3->setDisplayedContentSamplingEnabled(display, enable, componentMask,
+                                                               maxFrames);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::getDisplayedContentSample(Display display, uint64_t maxFrames,
@@ -1161,12 +1163,12 @@
     if (!mClient_2_3) {
         return Error::UNSUPPORTED;
     }
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient_2_3->getDisplayedContentSample(display, maxFrames, timestamp,
                                            [&](const auto tmpError, auto tmpNumFrames,
                                                const auto& tmpSamples0, const auto& tmpSamples1,
                                                const auto& tmpSamples2, const auto& tmpSamples3) {
-                                               error = tmpError;
+                                               error = static_cast<Error>(tmpError);
                                                if (error == Error::NONE) {
                                                    outStats->numFrames = tmpNumFrames;
                                                    outStats->component_0_sample = tmpSamples0;
@@ -1196,7 +1198,8 @@
     if (!mClient_2_3) {
         return Error::UNSUPPORTED;
     }
-    return mClient_2_3->setDisplayBrightness(display, brightness);
+    auto ret = mClient_2_3->setDisplayBrightness(display, brightness);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 // Composer HAL 2.4
@@ -1273,19 +1276,18 @@
     return error;
 }
 
-V2_4::Error HidlComposer::setActiveConfigWithConstraints(
+Error HidlComposer::setActiveConfigWithConstraints(
         Display display, Config config,
         const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
         VsyncPeriodChangeTimeline* outTimeline) {
-    using Error = V2_4::Error;
     if (!mClient_2_4) {
         return Error::UNSUPPORTED;
     }
 
-    Error error = kDefaultError_2_4;
+    Error error = static_cast<Error>(kDefaultError_2_4);
     mClient_2_4->setActiveConfigWithConstraints(display, config, vsyncPeriodChangeConstraints,
                                                 [&](const auto& tmpError, const auto& tmpTimeline) {
-                                                    error = tmpError;
+                                                    error = static_cast<Error>(tmpError);
                                                     if (error != Error::NONE) {
                                                         return;
                                                     }
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 9a89dba..42ba9a9 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -60,7 +60,6 @@
 
 using V2_1::Config;
 using V2_1::Display;
-using V2_1::Error;
 using V2_1::Layer;
 using V2_4::CommandReaderBase;
 using V2_4::CommandWriterBase;
@@ -308,7 +307,7 @@
     V2_4::Error getDisplayConnectionType(Display display,
                                          IComposerClient::DisplayConnectionType* outType) override;
     V2_4::Error getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) override;
-    V2_4::Error setActiveConfigWithConstraints(
+    Error setActiveConfigWithConstraints(
             Display display, Config config,
             const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
             VsyncPeriodChangeTimeline* outTimeline) override;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 7569c1b..4d9a9ca 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -261,20 +261,25 @@
     }
     snapshot.isVisible = visible;
 
-    // TODO(b/238781169) we are ignoring this compat for now, since we will have
-    // to remove any optimization based on visibility.
+    if (FlagManager::getInstance().skip_invisible_windows_in_input()) {
+        snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+    } else {
+        // TODO(b/238781169) we are ignoring this compat for now, since we will have
+        // to remove any optimization based on visibility.
 
-    // For compatibility reasons we let layers which can receive input
-    // receive input before they have actually submitted a buffer. Because
-    // of this we use canReceiveInput instead of isVisible to check the
-    // policy-visibility, ignoring the buffer state. However for layers with
-    // hasInputInfo()==false we can use the real visibility state.
-    // We are just using these layers for occlusion detection in
-    // InputDispatcher, and obviously if they aren't visible they can't occlude
-    // anything.
-    const bool visibleForInput =
-            snapshot.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible;
-    snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visibleForInput);
+        // For compatibility reasons we let layers which can receive input
+        // receive input before they have actually submitted a buffer. Because
+        // of this we use canReceiveInput instead of isVisible to check the
+        // policy-visibility, ignoring the buffer state. However for layers with
+        // hasInputInfo()==false we can use the real visibility state.
+        // We are just using these layers for occlusion detection in
+        // InputDispatcher, and obviously if they aren't visible they can't occlude
+        // anything.
+        const bool visibleForInput =
+                snapshot.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible;
+        snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE,
+                                          !visibleForInput);
+    }
     LLOGV(snapshot.sequence, "updating visibility %s %s", visible ? "true" : "false",
           snapshot.getDebugString().c_str());
 }
@@ -1260,6 +1265,10 @@
     for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) {
         LayerSnapshot& snapshot = *mSnapshots[(size_t)i];
         if (!snapshot.hasInputInfo()) continue;
+        if (FlagManager::getInstance().skip_invisible_windows_in_input() &&
+            snapshot.inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE)) {
+            continue;
+        }
         visitor(snapshot);
     }
 }
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 231b40b..fea7671 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -342,8 +342,15 @@
     caster.shadow = state;
 }
 
-CompositionResult&& LayerFE::stealCompositionResult() {
-    return std::move(mCompositionResult);
+void LayerFE::onPictureProfileCommitted() {
+    mCompositionResult.wasPictureProfileCommitted = true;
+    mCompositionResult.pictureProfileHandle = mSnapshot->pictureProfileHandle;
+}
+
+CompositionResult LayerFE::stealCompositionResult() {
+    CompositionResult result;
+    std::swap(mCompositionResult, result);
+    return result;
 }
 
 const char* LayerFE::getDebugName() const {
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index 5081e10..9483aeb 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -18,6 +18,9 @@
 
 #include <android/gui/CachingHint.h>
 #include <gui/LayerMetadata.h>
+#include <ui/LayerStack.h>
+#include <ui/PictureProfileHandle.h>
+
 #include "FrontEnd/LayerSnapshot.h"
 #include "compositionengine/LayerFE.h"
 #include "compositionengine/LayerFECompositionState.h"
@@ -29,6 +32,10 @@
 
 struct CompositionResult {
     sp<Fence> lastClientCompositionFence = nullptr;
+    bool wasPictureProfileCommitted = false;
+    // TODO(b/337330263): Why does LayerFE coming from SF have a null composition state?
+    // It would be better not to duplicate this information
+    PictureProfileHandle pictureProfileHandle = PictureProfileHandle::NONE;
 };
 
 class LayerFE : public virtual RefBase, public virtual compositionengine::LayerFE {
@@ -47,10 +54,11 @@
     const gui::LayerMetadata* getRelativeMetadata() const override;
     std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
-    CompositionResult&& stealCompositionResult();
+    CompositionResult stealCompositionResult();
     ftl::Future<FenceResult> createReleaseFenceFuture() override;
     void setReleaseFence(const FenceResult& releaseFence) override;
     LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
+    void onPictureProfileCommitted() override;
 
     std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
 
diff --git a/services/surfaceflinger/PowerAdvisor/Android.bp b/services/surfaceflinger/PowerAdvisor/Android.bp
new file mode 100644
index 0000000..4efbcb9
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/Android.bp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// ADPF uses FMQ which can't build to CPP backend, and is thus not
+// compatible with the rest of SF aidl for this reason
+
+aidl_interface {
+    name: "android.adpf.sessionmanager_aidl",
+    srcs: [
+        "aidl/android/adpf/*.aidl",
+    ],
+    local_include_dir: "aidl",
+    unstable: true,
+    backend: {
+        java: {
+            sdk_version: "module_current",
+            enabled: true,
+        },
+        cpp: {
+            enabled: false,
+        },
+        ndk: {
+            enabled: true,
+        },
+    },
+}
+
+cc_defaults {
+    name: "poweradvisor_deps",
+    shared_libs: [
+        "libpowermanager",
+        "android.adpf.sessionmanager_aidl-ndk",
+    ],
+}
diff --git a/services/surfaceflinger/PowerAdvisor/aidl/android/adpf/ISessionManager.aidl b/services/surfaceflinger/PowerAdvisor/aidl/android/adpf/ISessionManager.aidl
new file mode 100644
index 0000000..c1a6a9e
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/aidl/android/adpf/ISessionManager.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.adpf;
+
+/**
+ * Private service for SessionManager to use. Ideally this will
+ * eventually take the role of HintManagerService.
+ */
+interface ISessionManager {
+    oneway void associateSessionToLayers(in int sessionId, in int ownerUid, in IBinder[] layers);
+    oneway void trackedSessionsDied(in int[] sessionId);
+}
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index fff4284..c6d7160 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -43,6 +43,7 @@
 #include <utils/Errors.h>
 
 #include <common/FlagManager.h>
+#include <scheduler/FrameRateMode.h>
 #include <scheduler/VsyncConfig.h>
 #include "FrameTimeline.h"
 #include "VSyncDispatch.h"
@@ -102,6 +103,10 @@
                                 to_string(event.header.displayId).c_str(),
                                 event.hdcpLevelsChange.connectedLevel,
                                 event.hdcpLevelsChange.maxLevel);
+        case DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION:
+            return StringPrintf("ModeRejected{displayId=%s, modeId=%u}",
+                                to_string(event.header.displayId).c_str(),
+                                event.modeRejection.modeId);
         default:
             return "Event{}";
     }
@@ -186,6 +191,18 @@
     };
 }
 
+DisplayEventReceiver::Event makeModeRejection(PhysicalDisplayId displayId, DisplayModeId modeId) {
+    return DisplayEventReceiver::Event{
+            .header =
+                    DisplayEventReceiver::Event::Header{
+                            .type = DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION,
+                            .displayId = displayId,
+                            .timestamp = systemTime(),
+                    },
+            .modeRejection.modeId = ftl::to_underlying(modeId),
+    };
+}
+
 } // namespace
 
 EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
@@ -480,6 +497,13 @@
     mCondition.notify_all();
 }
 
+void EventThread::onModeRejected(PhysicalDisplayId displayId, DisplayModeId modeId) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    mPendingEvents.push_back(makeModeRejection(displayId, modeId));
+    mCondition.notify_all();
+}
+
 // Merge lists of buffer stuffed Uids
 void EventThread::addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) {
     std::lock_guard<std::mutex> lock(mMutex);
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 95632c7..18bf416 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -21,6 +21,7 @@
 #include <gui/DisplayEventReceiver.h>
 #include <private/gui/BitTube.h>
 #include <sys/types.h>
+#include <ui/DisplayId.h>
 #include <utils/Errors.h>
 
 #include <scheduler/FrameRateMode.h>
@@ -32,6 +33,7 @@
 #include <thread>
 #include <vector>
 
+#include "DisplayHardware/DisplayMode.h"
 #include "TracedOrdinal.h"
 #include "VSyncDispatch.h"
 #include "VsyncSchedule.h"
@@ -115,6 +117,9 @@
     // called when SF changes the active mode and apps needs to be notified about the change
     virtual void onModeChanged(const scheduler::FrameRateMode&) = 0;
 
+    // called when SF rejects the mode change request
+    virtual void onModeRejected(PhysicalDisplayId displayId, DisplayModeId modeId) = 0;
+
     // called when SF updates the Frame Rate Override list
     virtual void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
                                              std::vector<FrameRateOverride> overrides) = 0;
@@ -179,6 +184,8 @@
 
     void onModeChanged(const scheduler::FrameRateMode&) override;
 
+    void onModeRejected(PhysicalDisplayId displayId, DisplayModeId modeId) override;
+
     void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
                                      std::vector<FrameRateOverride> overrides) override;
 
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index 2b9e88c..d02d149 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -31,7 +31,7 @@
     virtual void onChoreographerAttached() = 0;
     virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>,
                                              Fps renderRate) = 0;
-    virtual void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) = 0;
+    virtual void onCommitNotComposited() = 0;
     virtual void vrrDisplayIdle(bool idle) = 0;
 
 protected:
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index e45bdfc..171342d 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -308,11 +308,14 @@
                 const auto setFrameRateVoteType =
                         info->isVisible() ? voteType : LayerVoteType::NoVote;
 
-                const bool hasSetFrameRateOpinion = frameRate.isValid() && !frameRate.isNoVote();
+                const bool hasSetFrameRateOpinion =
+                        frameRate.isValuelessType() || frameRate.vote.rate.isValid();
                 const bool hasCategoryOpinion =
                         frameRate.category != FrameRateCategory::NoPreference &&
                         frameRate.category != FrameRateCategory::Default;
-                const bool hasFrameRateOpinion = hasSetFrameRateOpinion || hasCategoryOpinion;
+                const bool hasFrameRateOpinionAboveGameDefault =
+                        hasSetFrameRateOpinion || hasCategoryOpinion;
+                const bool hasFrameRateOpinionArr = frameRate.isValid() && !frameRate.isNoVote();
 
                 if (gameModeFrameRateOverride.isValid()) {
                     info->setLayerVote({gameFrameRateOverrideVoteType, gameModeFrameRateOverride});
@@ -321,7 +324,8 @@
                         trace(*info, gameFrameRateOverrideVoteType,
                               gameModeFrameRateOverride.getIntValue());
                     }
-                } else if (hasFrameRateOpinion && frameRate.isVoteValidForMrr(isVrrDevice)) {
+                } else if (hasFrameRateOpinionAboveGameDefault &&
+                           frameRate.isVoteValidForMrr(isVrrDevice)) {
                     info->setLayerVote({setFrameRateVoteType,
                                         isValuelessVote ? 0_Hz : frameRate.vote.rate,
                                         frameRate.vote.seamlessness, frameRate.category});
@@ -337,8 +341,18 @@
                         trace(*info, gameFrameRateOverrideVoteType,
                               gameDefaultFrameRateOverride.getIntValue());
                     }
+                } else if (hasFrameRateOpinionArr && frameRate.isVoteValidForMrr(isVrrDevice)) {
+                    // This allows NoPreference votes on ARR devices after considering the
+                    // gameDefaultFrameRateOverride (above).
+                    info->setLayerVote({setFrameRateVoteType,
+                                        isValuelessVote ? 0_Hz : frameRate.vote.rate,
+                                        frameRate.vote.seamlessness, frameRate.category});
+                    if (CC_UNLIKELY(mTraceEnabled)) {
+                        trace(*info, gameFrameRateOverrideVoteType,
+                              frameRate.vote.rate.getIntValue());
+                    }
                 } else {
-                    if (hasFrameRateOpinion && !frameRate.isVoteValidForMrr(isVrrDevice)) {
+                    if (hasFrameRateOpinionArr && !frameRate.isVoteValidForMrr(isVrrDevice)) {
                         SFTRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
                                                "%s %s",
                                                info->getName().c_str(),
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index ff1926e..356c30e 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -504,7 +504,7 @@
             return FrameRateCompatibility::Exact;
         case ANATIVEWINDOW_FRAME_RATE_MIN:
             return FrameRateCompatibility::Min;
-        case ANATIVEWINDOW_FRAME_RATE_GTE:
+        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE:
             return FrameRateCompatibility::Gte;
         case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
             return FrameRateCompatibility::NoVote;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index e4069dd..97f1f8f 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -1532,6 +1532,9 @@
 
     mPrimaryFrameRates = filterRefreshRates(policy->primaryRanges, "primary");
     mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
+    mAllFrameRates = filterRefreshRates(FpsRanges(getSupportedFrameRateRangeLocked(),
+                                                  getSupportedFrameRateRangeLocked()),
+                                        "full frame rates");
 }
 
 bool RefreshRateSelector::isVrrDevice() const {
@@ -1557,6 +1560,27 @@
     return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
 }
 
+std::vector<float> RefreshRateSelector::getSupportedFrameRates() const {
+    std::scoped_lock lock(mLock);
+    // TODO(b/356986687) Remove the limit once we have the anchor list implementation.
+    const size_t frameRatesSize = std::min<size_t>(11, mAllFrameRates.size());
+    std::vector<float> supportedFrameRates;
+    supportedFrameRates.reserve(frameRatesSize);
+    std::transform(mAllFrameRates.rbegin(),
+                   mAllFrameRates.rbegin() + static_cast<int>(frameRatesSize),
+                   std::back_inserter(supportedFrameRates),
+                   [](FrameRateMode mode) { return mode.fps.getValue(); });
+    return supportedFrameRates;
+}
+
+FpsRange RefreshRateSelector::getSupportedFrameRateRangeLocked() const {
+    using fps_approx_ops::operator<;
+    if (mMaxRefreshRateModeIt->second->getPeakFps() < kMinSupportedFrameRate) {
+        return {mMaxRefreshRateModeIt->second->getPeakFps(), kMinSupportedFrameRate};
+    }
+    return {kMinSupportedFrameRate, mMaxRefreshRateModeIt->second->getPeakFps()};
+}
+
 auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction {
     std::lock_guard lock(mLock);
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index ee3a4f7..8e173b1 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -441,6 +441,8 @@
 
     std::pair<Fps, Fps> getFrameRateCategoryRates() const { return kFrameRateCategoryRates; }
 
+    std::vector<float> getSupportedFrameRates() const EXCLUDES(mLock);
+
 private:
     friend struct TestableRefreshRateSelector;
 
@@ -551,6 +553,7 @@
     // Display modes that satisfy the Policy's ranges, filtered and sorted by refresh rate.
     std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
     std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
+    std::vector<FrameRateMode> mAllFrameRates GUARDED_BY(mLock);
 
     // Caches whether the device is VRR-compatible based on the active display mode.
     std::atomic_bool mIsVrrDevice = false;
@@ -595,6 +598,9 @@
     // Used to detect (lack of) frame activity.
     ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
     std::atomic<bool> mIdleTimerStarted = false;
+
+    // Returns the range of supported frame rates.
+    FpsRange getSupportedFrameRateRangeLocked() const REQUIRES(mLock);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 2875650..4da76f6 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -242,7 +242,7 @@
             if (FlagManager::getInstance().vrr_config()) {
                 compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
             }
-            mSchedulerCallback.onCommitNotComposited(pacesetterPtr->displayId);
+            mSchedulerCallback.onCommitNotComposited();
             return;
         }
     }
@@ -465,6 +465,12 @@
 }
 #pragma clang diagnostic pop
 
+void Scheduler::onDisplayModeRejected(PhysicalDisplayId displayId, DisplayModeId modeId) {
+    if (hasEventThreads()) {
+        eventThreadFor(Cycle::Render).onModeRejected(displayId, modeId);
+    }
+}
+
 void Scheduler::emitModeChangeIfNeeded() {
     if (!mPolicy.modeOpt || !mPolicy.emittedModeOpt) {
         ALOGW("No mode change to emit");
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 61c68a9..3fdddac 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -36,12 +36,14 @@
 #include <ftl/non_null.h>
 #include <ftl/optional.h>
 #include <scheduler/Features.h>
+#include <scheduler/FrameRateMode.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/Time.h>
 #include <scheduler/VsyncConfig.h>
 #include <ui/DisplayId.h>
 #include <ui/DisplayMap.h>
 
+#include "DisplayHardware/DisplayMode.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "ISchedulerCallback.h"
@@ -153,6 +155,8 @@
     bool onDisplayModeChanged(PhysicalDisplayId, const FrameRateMode&,
                               bool clearContentRequirements) EXCLUDES(mPolicyLock);
 
+    void onDisplayModeRejected(PhysicalDisplayId, DisplayModeId);
+
     void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext);
     void omitVsyncDispatching(bool) REQUIRES(kMainThreadContext);
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 6e36f02..ff360b7 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -458,7 +458,8 @@
 
 Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
                                                       TimePoint lastConfirmedPresentTime) {
-    SFTRACE_CALL();
+    SFTRACE_FORMAT("%s mNumVsyncsForFrame=%d mPastExpectedPresentTimes.size()=%zu", __func__,
+                   mNumVsyncsForFrame, mPastExpectedPresentTimes.size());
 
     if (mNumVsyncsForFrame <= 1) {
         return 0ns;
@@ -470,12 +471,8 @@
 
     auto prev = lastConfirmedPresentTime.ns();
     for (auto& current : mPastExpectedPresentTimes) {
-        if (CC_UNLIKELY(mTraceOn)) {
-            SFTRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
-                                   static_cast<float>(current.ns() -
-                                                      lastConfirmedPresentTime.ns()) /
-                                           1e6f);
-        }
+        SFTRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
+                               static_cast<float>(current.ns() - prev) / 1e6f);
 
         const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod.ns();
         if (minPeriodViolation) {
@@ -522,11 +519,9 @@
         const auto front = mPastExpectedPresentTimes.front().ns();
         const bool frontIsBeforeConfirmed = front < lastConfirmedPresentTime.ns() + threshold;
         if (frontIsBeforeConfirmed) {
-            if (CC_UNLIKELY(mTraceOn)) {
-                SFTRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
-                                       static_cast<float>(lastConfirmedPresentTime.ns() - front) /
-                                               1e6f);
-            }
+            SFTRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
+                                   static_cast<float>(lastConfirmedPresentTime.ns() - front) /
+                                           1e6f);
             mPastExpectedPresentTimes.pop_front();
         } else {
             break;
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index 4f16130..5019949 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -31,6 +31,7 @@
 
 std::pair<bool /* wouldBackpressure */, FrameTarget::PresentFence>
 FrameTarget::expectedSignaledPresentFence(Period vsyncPeriod, Period minFramePeriod) const {
+    SFTRACE_CALL();
     if (!FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
         const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
         return {true, mPresentFencesLegacy[i]};
@@ -40,17 +41,28 @@
     auto expectedPresentTime = mExpectedPresentTime;
     for (size_t i = mPresentFences.size(); i != 0; --i) {
         const auto& fence = mPresentFences[i - 1];
+        SFTRACE_FORMAT_INSTANT("fence at idx: %zu expectedPresentTime in %.2f", i - 1,
+                               ticks<std::milli, float>(fence.expectedPresentTime -
+                                                        TimePoint::now()));
 
         if (fence.expectedPresentTime + minFramePeriod < expectedPresentTime - vsyncPeriod / 2) {
+            SFTRACE_FORMAT_INSTANT("would not backpressure");
             wouldBackpressure = false;
         }
 
         if (fence.expectedPresentTime <= mFrameBeginTime) {
+            SFTRACE_FORMAT_INSTANT("fence at idx: %zu is %.2f before frame begin "
+                                   "(wouldBackpressure=%s)",
+                                   i - 1,
+                                   ticks<std::milli, float>(mFrameBeginTime -
+                                                            fence.expectedPresentTime),
+                                   wouldBackpressure ? "true" : "false");
             return {wouldBackpressure, fence};
         }
 
         expectedPresentTime = fence.expectedPresentTime;
     }
+    SFTRACE_FORMAT_INSTANT("No fence found");
     return {wouldBackpressure, PresentFence{}};
 }
 
@@ -154,6 +166,12 @@
         if (pastPresentTime < 0) return false;
         mLastSignaledFrameTime = {.signalTime = TimePoint::fromNs(pastPresentTime),
                                   .expectedPresentTime = fence.expectedPresentTime};
+        SFTRACE_FORMAT_INSTANT("LastSignaledFrameTime expectedPresentTime %.2f ago, signalTime "
+                               "%.2f ago",
+                               ticks<std::milli, float>(mLastSignaledFrameTime.expectedPresentTime -
+                                                        TimePoint::now()),
+                               ticks<std::milli, float>(mLastSignaledFrameTime.signalTime -
+                                                        TimePoint::now()));
         const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2;
         return lastScheduledPresentTime.ns() < pastPresentTime - frameMissedSlop;
     }();
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a66380b..59b1917 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -126,6 +126,7 @@
 #include <gui/SchedulingPolicy.h>
 #include <gui/SyncScreenCaptureListener.h>
 #include <ui/DisplayIdentification.h>
+#include "ActivePictureUpdater.h"
 #include "BackgroundExecutor.h"
 #include "Client.h"
 #include "ClientCache.h"
@@ -171,6 +172,7 @@
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/OutputType.h>
 #include <aidl/android/hardware/graphics/composer3/RenderIntent.h>
 
 #undef NO_THREAD_SAFETY_ANALYSIS
@@ -372,6 +374,7 @@
 const String16 sRotateSurfaceFlinger("android.permission.ROTATE_SURFACE_FLINGER");
 const String16 sReadFramebuffer("android.permission.READ_FRAME_BUFFER");
 const String16 sControlDisplayBrightness("android.permission.CONTROL_DISPLAY_BRIGHTNESS");
+const String16 sObservePictureProfiles("android.permission.OBSERVE_PICTURE_PROFILES");
 const String16 sDump("android.permission.DUMP");
 const String16 sCaptureBlackoutContent("android.permission.CAPTURE_BLACKOUT_CONTENT");
 const String16 sInternalSystemWindow("android.permission.INTERNAL_SYSTEM_WINDOW");
@@ -647,11 +650,12 @@
     }
 }
 
-VirtualDisplayId SurfaceFlinger::acquireVirtualDisplay(ui::Size resolution,
-                                                       ui::PixelFormat format) {
+VirtualDisplayId SurfaceFlinger::acquireVirtualDisplay(ui::Size resolution, ui::PixelFormat format,
+                                                       const std::string& uniqueId) {
     if (auto& generator = mVirtualDisplayIdGenerators.hal) {
         if (const auto id = generator->generateId()) {
             if (getHwComposer().allocateVirtualDisplay(*id, resolution, &format)) {
+                acquireVirtualDisplaySnapshot(*id, uniqueId);
                 return *id;
             }
 
@@ -665,6 +669,7 @@
 
     const auto id = mVirtualDisplayIdGenerators.gpu.generateId();
     LOG_ALWAYS_FATAL_IF(!id, "Failed to generate ID for GPU virtual display");
+    acquireVirtualDisplaySnapshot(*id, uniqueId);
     return *id;
 }
 
@@ -672,6 +677,7 @@
     if (const auto id = HalVirtualDisplayId::tryCast(displayId)) {
         if (auto& generator = mVirtualDisplayIdGenerators.hal) {
             generator->releaseId(*id);
+            releaseVirtualDisplaySnapshot(*id);
         }
         return;
     }
@@ -679,6 +685,14 @@
     const auto id = GpuVirtualDisplayId::tryCast(displayId);
     LOG_ALWAYS_FATAL_IF(!id);
     mVirtualDisplayIdGenerators.gpu.releaseId(*id);
+    releaseVirtualDisplaySnapshot(*id);
+}
+
+void SurfaceFlinger::releaseVirtualDisplaySnapshot(VirtualDisplayId displayId) {
+    std::lock_guard lock(mVirtualDisplaysMutex);
+    if (!mVirtualDisplays.erase(displayId)) {
+        ALOGW("%s: Virtual display snapshot was not removed", __func__);
+    }
 }
 
 std::vector<PhysicalDisplayId> SurfaceFlinger::getPhysicalDisplayIdsLocked() const {
@@ -795,6 +809,12 @@
     }));
 }
 
+bool shouldUseGraphiteIfCompiledAndSupported() {
+    return FlagManager::getInstance().graphite_renderengine() ||
+            (FlagManager::getInstance().graphite_renderengine_preview_rollout() &&
+             base::GetBoolProperty(PROPERTY_DEBUG_RENDERENGINE_GRAPHITE_PREVIEW_OPTIN, false));
+}
+
 void chooseRenderEngineType(renderengine::RenderEngineCreationArgs::Builder& builder) {
     char prop[PROPERTY_VALUE_MAX];
     property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
@@ -823,14 +843,13 @@
 // is used by layertracegenerator (which also needs SurfaceFlinger.cpp). :)
 #if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_GRAPHITE_RENDERENGINE || \
         COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_FORCE_COMPILE_GRAPHITE_RENDERENGINE
-        const bool useGraphite = FlagManager::getInstance().graphite_renderengine() &&
+        const bool useGraphite = shouldUseGraphiteIfCompiledAndSupported() &&
                 renderengine::RenderEngine::canSupport(kVulkan);
 #else
         const bool useGraphite = false;
-        if (FlagManager::getInstance().graphite_renderengine()) {
-            ALOGE("RenderEngine's Graphite Skia backend was requested with the "
-                  "debug.renderengine.graphite system property, but it is not compiled in this "
-                  "build! Falling back to Ganesh backend selection logic.");
+        if (shouldUseGraphiteIfCompiledAndSupported()) {
+            ALOGE("RenderEngine's Graphite Skia backend was requested, but it is not compiled in "
+                  "this build! Falling back to Ganesh backend selection logic.");
         }
 #endif
         const bool useVulkan = useGraphite ||
@@ -1229,6 +1248,8 @@
     const auto [normal, high] = display->refreshRateSelector().getFrameRateCategoryRates();
     ui::FrameRateCategoryRate frameRateCategoryRate(normal.getValue(), high.getValue());
     info->frameRateCategoryRate = frameRateCategoryRate;
+
+    info->supportedRefreshRates = display->refreshRateSelector().getSupportedFrameRates();
     info->activeColorMode = display->getCompositionDisplay()->getState().colorMode;
     info->hdrCapabilities = filterOut4k30(display->getHdrCapabilities());
 
@@ -1523,8 +1544,15 @@
         constraints.seamlessRequired = false;
         hal::VsyncPeriodChangeTimeline outTimeline;
 
-        if (!mDisplayModeController.initiateModeChange(displayId, std::move(*desiredModeOpt),
-                                                       constraints, outTimeline)) {
+        const auto error =
+                mDisplayModeController.initiateModeChange(displayId, std::move(*desiredModeOpt),
+                                                          constraints, outTimeline);
+        if (error != display::DisplayModeController::ModeChangeResult::Changed) {
+            dropModeRequest(displayId);
+            if (FlagManager::getInstance().display_config_error_hal() &&
+                error == display::DisplayModeController::ModeChangeResult::Rejected) {
+                mScheduler->onDisplayModeRejected(displayId, desiredModeId);
+            }
             continue;
         }
 
@@ -1838,6 +1866,24 @@
     }));
 }
 
+status_t SurfaceFlinger::getMaxLayerPictureProfiles(const sp<IBinder>& displayToken,
+                                                    int32_t* outMaxProfiles) {
+    const char* const whence = __func__;
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
+        const ssize_t index = mCurrentState.displays.indexOfKey(displayToken);
+        if (index < 0) {
+            ALOGE("%s: Invalid display token %p", whence, displayToken.get());
+            return 0;
+        }
+        const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
+        return state.maxLayerPictureProfiles > 0 ? state.maxLayerPictureProfiles
+                : state.hasPictureProcessing     ? 1
+                                                 : 0;
+    });
+    *outMaxProfiles = future.get();
+    return NO_ERROR;
+}
+
 status_t SurfaceFlinger::overrideHdrTypes(const sp<IBinder>& displayToken,
                                           const std::vector<ui::Hdr>& hdrTypes) {
     Mutex::Autolock lock(mStateLock);
@@ -2248,12 +2294,23 @@
         return;
     }
 
-    if (FlagManager::getInstance().hotplug2()) {
-        // TODO(b/311403559): use enum type instead of int
+    if (event < DisplayHotplugEvent::ERROR_LINK_UNSTABLE) {
+        // This needs to be kept in sync with DisplayHotplugEvent to prevent passing new errors.
         const auto errorCode = static_cast<int32_t>(event);
-        ALOGD("%s: Hotplug error %d for hwcDisplayId %" PRIu64, __func__, errorCode, hwcDisplayId);
-        mScheduler->dispatchHotplugError(errorCode);
+        ALOGW("%s: Unknown hotplug error %d for hwcDisplayId %" PRIu64, __func__, errorCode,
+              hwcDisplayId);
+        return;
     }
+
+    if (event == DisplayHotplugEvent::ERROR_LINK_UNSTABLE &&
+        !FlagManager::getInstance().display_config_error_hal()) {
+        return;
+    }
+
+    // TODO(b/311403559): use enum type instead of int
+    const auto errorCode = static_cast<int32_t>(event);
+    ALOGD("%s: Hotplug error %d for hwcDisplayId %" PRIu64, __func__, errorCode, hwcDisplayId);
+    mScheduler->dispatchHotplugError(errorCode);
 }
 
 void SurfaceFlinger::onComposerHalVsyncPeriodTimingChanged(
@@ -2554,7 +2611,7 @@
     }
 
     {
-        SFTRACE_NAME("LLM:commitChanges");
+        SFTRACE_NAME("LayerLifecycleManager:commitChanges");
         mLayerLifecycleManager.commitChanges();
     }
 
@@ -2853,6 +2910,9 @@
         if (compositionResult.lastClientCompositionFence) {
             layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
         }
+        if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+            mActivePictureUpdater.onLayerComposed(*layer, *layerFE, compositionResult);
+        }
     }
 
     SFTRACE_NAME("postComposition");
@@ -3148,30 +3208,6 @@
         layer->releasePendingBuffer(presentTime.ns());
     }
 
-    std::vector<std::pair<std::shared_ptr<compositionengine::Display>, sp<HdrLayerInfoReporter>>>
-            hdrInfoListeners;
-    bool haveNewListeners = false;
-    {
-        Mutex::Autolock lock(mStateLock);
-        if (mFpsReporter) {
-            mFpsReporter->dispatchLayerFps(mLayerHierarchyBuilder.getHierarchy());
-        }
-
-        if (mTunnelModeEnabledReporter) {
-            mTunnelModeEnabledReporter->updateTunnelModeStatus();
-        }
-        hdrInfoListeners.reserve(mHdrLayerInfoListeners.size());
-        for (const auto& [displayId, reporter] : mHdrLayerInfoListeners) {
-            if (reporter && reporter->hasListeners()) {
-                if (const auto display = getDisplayDeviceLocked(displayId)) {
-                    hdrInfoListeners.emplace_back(display->getCompositionDisplay(), reporter);
-                }
-            }
-        }
-        haveNewListeners = mAddingHDRLayerInfoListener; // grab this with state lock
-        mAddingHDRLayerInfoListener = false;
-    }
-
     for (const auto& layerEvent : mLayerEvents) {
         auto result =
                 stats::stats_write(stats::SURFACE_CONTROL_EVENT,
@@ -3182,10 +3218,40 @@
             ALOGW("Failed to report layer event with error: %d", result);
         }
     }
-
     mLayerEvents.clear();
 
-    if (haveNewListeners || mHdrLayerInfoChanged) {
+    std::vector<std::pair<std::shared_ptr<compositionengine::Display>, sp<HdrLayerInfoReporter>>>
+            hdrInfoListeners;
+    bool haveNewHdrInfoListeners = false;
+    sp<gui::IActivePictureListener> activePictureListener;
+    bool haveNewActivePictureListener = false;
+    {
+        Mutex::Autolock lock(mStateLock);
+        if (mFpsReporter) {
+            mFpsReporter->dispatchLayerFps(mLayerHierarchyBuilder.getHierarchy());
+        }
+
+        if (mTunnelModeEnabledReporter) {
+            mTunnelModeEnabledReporter->updateTunnelModeStatus();
+        }
+
+        hdrInfoListeners.reserve(mHdrLayerInfoListeners.size());
+        for (const auto& [displayId, reporter] : mHdrLayerInfoListeners) {
+            if (reporter && reporter->hasListeners()) {
+                if (const auto display = getDisplayDeviceLocked(displayId)) {
+                    hdrInfoListeners.emplace_back(display->getCompositionDisplay(), reporter);
+                }
+            }
+        }
+        haveNewHdrInfoListeners = mAddingHDRLayerInfoListener; // grab this with state lock
+        mAddingHDRLayerInfoListener = false;
+
+        activePictureListener = mActivePictureListener;
+        haveNewActivePictureListener = mHaveNewActivePictureListener;
+        mHaveNewActivePictureListener = false;
+    }
+
+    if (haveNewHdrInfoListeners || mHdrLayerInfoChanged) {
         for (auto& [compositionDisplay, listener] : hdrInfoListeners) {
             HdrLayerInfoReporter::HdrLayerInfo info;
             int32_t maxArea = 0;
@@ -3203,7 +3269,15 @@
                                             snapshot.desiredHdrSdrRatio < 1.f
                                             ? std::numeric_limits<float>::infinity()
                                             : snapshot.desiredHdrSdrRatio;
-                                    info.mergeDesiredRatio(desiredHdrSdrRatio);
+
+                                    float desiredRatio = desiredHdrSdrRatio;
+                                    if (FlagManager::getInstance().begone_bright_hlg() &&
+                                        desiredHdrSdrRatio ==
+                                                std::numeric_limits<float>::infinity()) {
+                                        desiredRatio = getIdealizedMaxHeadroom(snapshot.dataspace);
+                                    }
+
+                                    info.mergeDesiredRatio(desiredRatio);
                                     info.numberOfHdrLayers++;
                                     const auto displayFrame = outputLayer->getState().displayFrame;
                                     const int32_t area =
@@ -3235,9 +3309,19 @@
             listener->dispatchHdrLayerInfo(info);
         }
     }
-
     mHdrLayerInfoChanged = false;
 
+    if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+        // Track, update and notify changes to active pictures - layers that are undergoing picture
+        // processing
+        if (mActivePictureUpdater.updateAndHasChanged() || haveNewActivePictureListener) {
+            if (activePictureListener) {
+                activePictureListener->onActivePicturesChanged(
+                        mActivePictureUpdater.getActivePictures());
+            }
+        }
+    }
+
     mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
     mTransactionCallbackInvoker.clearCompletedTransactions();
 
@@ -3432,6 +3516,9 @@
     DisplayModes newModes;
     for (const auto& hwcMode : hwcModes) {
         const auto id = nextModeId++;
+        OutputType hdrOutputType = FlagManager::getInstance().connected_display_hdr()
+                ? hwcMode.hdrOutputType
+                : OutputType::INVALID;
         newModes.try_emplace(id,
                              DisplayMode::Builder(hwcMode.hwcId)
                                      .setId(id)
@@ -3442,6 +3529,7 @@
                                      .setDpiX(hwcMode.dpiX)
                                      .setDpiY(hwcMode.dpiY)
                                      .setGroup(hwcMode.configGroup)
+                                     .setHdrOutputType(hdrOutputType)
                                      .build());
     }
 
@@ -3483,10 +3571,8 @@
                         processHotplugConnect(displayId, hwcDisplayId, std::move(*info),
                                               displayString.c_str());
                 if (!activeModeIdOpt) {
-                    if (FlagManager::getInstance().hotplug2()) {
-                        mScheduler->dispatchHotplugError(
-                                static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
-                    }
+                    mScheduler->dispatchHotplugError(
+                            static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
                     getHwComposer().disconnectDisplay(displayId);
                     continue;
                 }
@@ -3734,7 +3820,7 @@
     if (const auto& physical = state.physical) {
         builder.setId(physical->id);
     } else {
-        builder.setId(acquireVirtualDisplay(resolution, pixelFormat));
+        builder.setId(acquireVirtualDisplay(resolution, pixelFormat, state.uniqueId));
     }
 
     builder.setPixels(resolution);
@@ -4324,7 +4410,7 @@
     scheduleNotifyExpectedPresentHint(displayId);
 }
 
-void SurfaceFlinger::onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) {
+void SurfaceFlinger::onCommitNotComposited() {
     if (FlagManager::getInstance().commit_not_composited()) {
         mFrameTimeline->onCommitNotComposited();
     }
@@ -5721,6 +5807,14 @@
             utils::Dumper::Section section(dumper,
                                            ftl::Concat("Virtual Display ", displayId.value).str());
             display->dump(dumper);
+
+            if (const auto virtualIdOpt = VirtualDisplayId::tryCast(displayId)) {
+                std::lock_guard lock(mVirtualDisplaysMutex);
+                const auto virtualSnapshotIt = mVirtualDisplays.find(virtualIdOpt.value());
+                if (virtualSnapshotIt != mVirtualDisplays.end()) {
+                    virtualSnapshotIt->second.dump(dumper);
+                }
+            }
         }
     }
 }
@@ -8077,6 +8171,14 @@
     }));
 }
 
+void SurfaceFlinger::setActivePictureListener(const sp<gui::IActivePictureListener>& listener) {
+    if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+        Mutex::Autolock lock(mStateLock);
+        mActivePictureListener = listener;
+        mHaveNewActivePictureListener = listener != nullptr;
+    }
+}
+
 std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData(
         BufferData& bufferData, const char* layerName, uint64_t transactionId) {
     if (bufferData.buffer &&
@@ -8579,6 +8681,11 @@
     gui::FrameRateCategoryRate& frameRateCategoryRate = outInfo->frameRateCategoryRate;
     frameRateCategoryRate.normal = info.frameRateCategoryRate.getNormal();
     frameRateCategoryRate.high = info.frameRateCategoryRate.getHigh();
+    outInfo->supportedRefreshRates.clear();
+    outInfo->supportedRefreshRates.reserve(info.supportedRefreshRates.size());
+    for (float supportedRefreshRate : info.supportedRefreshRates) {
+        outInfo->supportedRefreshRates.push_back(supportedRefreshRate);
+    }
 
     outInfo->supportedColorModes.clear();
     outInfo->supportedColorModes.reserve(info.supportedColorModes.size());
@@ -8734,6 +8841,16 @@
     return binder::Status::ok();
 }
 
+binder::Status SurfaceComposerAIDL::getMaxLayerPictureProfiles(const sp<IBinder>& display,
+                                                               int32_t* outMaxProfiles) {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binderStatusFromStatusT(status);
+    }
+    mFlinger->getMaxLayerPictureProfiles(display, outMaxProfiles);
+    return binder::Status::ok();
+}
+
 binder::Status SurfaceComposerAIDL::captureDisplay(
         const DisplayCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
     mFlinger->captureDisplay(args, captureListener);
@@ -9012,6 +9129,15 @@
     return binderStatusFromStatusT(status);
 }
 
+binder::Status SurfaceComposerAIDL::setActivePictureListener(
+        const sp<gui::IActivePictureListener>& listener) {
+    status_t status = checkObservePictureProfilesPermission();
+    if (status == OK) {
+        mFlinger->setActivePictureListener(listener);
+    }
+    return binderStatusFromStatusT(status);
+}
+
 binder::Status SurfaceComposerAIDL::notifyPowerBoost(int boostId) {
     status_t status = checkAccessPermission();
     if (status == OK) {
@@ -9082,26 +9208,46 @@
 }
 
 binder::Status SurfaceComposerAIDL::enableRefreshRateOverlay(bool active) {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binderStatusFromStatusT(status);
+    }
     mFlinger->sfdo_enableRefreshRateOverlay(active);
     return binder::Status::ok();
 }
 
 binder::Status SurfaceComposerAIDL::setDebugFlash(int delay) {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binderStatusFromStatusT(status);
+    }
     mFlinger->sfdo_setDebugFlash(delay);
     return binder::Status::ok();
 }
 
 binder::Status SurfaceComposerAIDL::scheduleComposite() {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binderStatusFromStatusT(status);
+    }
     mFlinger->sfdo_scheduleComposite();
     return binder::Status::ok();
 }
 
 binder::Status SurfaceComposerAIDL::scheduleCommit() {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binderStatusFromStatusT(status);
+    }
     mFlinger->sfdo_scheduleCommit();
     return binder::Status::ok();
 }
 
 binder::Status SurfaceComposerAIDL::forceClientComposition(bool enabled) {
+    status_t status = checkAccessPermission();
+    if (status != OK) {
+        return binderStatusFromStatusT(status);
+    }
     mFlinger->sfdo_forceClientComposition(enabled);
     return binder::Status::ok();
 }
@@ -9267,6 +9413,17 @@
     return OK;
 }
 
+status_t SurfaceComposerAIDL::checkObservePictureProfilesPermission() {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int pid = ipc->getCallingPid();
+    const int uid = ipc->getCallingUid();
+    if (!PermissionCache::checkPermission(sObservePictureProfiles, pid, uid)) {
+        ALOGE("Permission Denial: can't manage picture profiles pid=%d, uid=%d", pid, uid);
+        return PERMISSION_DENIED;
+    }
+    return OK;
+}
+
 void SurfaceFlinger::forceFutureUpdate(int delayInMs) {
     static_cast<void>(mScheduler->scheduleDelayed([&]() { scheduleRepaint(); }, ms2ns(delayInMs)));
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7e9d5b8..1e2c087 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -24,9 +24,11 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/thread_annotations.h>
+#include <android/gui/ActivePicture.h>
 #include <android/gui/BnSurfaceComposer.h>
 #include <android/gui/DisplayStatInfo.h>
 #include <android/gui/DisplayState.h>
+#include <android/gui/IActivePictureListener.h>
 #include <android/gui/IJankListener.h>
 #include <android/gui/ISurfaceComposerClient.h>
 #include <common/trace.h>
@@ -67,8 +69,11 @@
 #include <ui/FenceResult.h>
 
 #include <common/FlagManager.h>
+#include "ActivePictureUpdater.h"
+#include "BackgroundExecutor.h"
 #include "Display/DisplayModeController.h"
 #include "Display/PhysicalDisplay.h"
+#include "Display/VirtualDisplaySnapshot.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
 #include "DisplayIdGenerator.h"
@@ -93,6 +98,7 @@
 #include "TransactionState.h"
 #include "Utils/OnceFuture.h"
 
+#include <algorithm>
 #include <atomic>
 #include <cstdint>
 #include <functional>
@@ -591,6 +597,7 @@
     status_t getHdrOutputConversionSupport(bool* outSupport) const;
     void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on);
     void setGameContentType(const sp<IBinder>& displayToken, bool on);
+    status_t getMaxLayerPictureProfiles(const sp<IBinder>& displayToken, int32_t* outMaxProfiles);
     void setPowerMode(const sp<IBinder>& displayToken, int mode);
     status_t overrideHdrTypes(const sp<IBinder>& displayToken,
                               const std::vector<ui::Hdr>& hdrTypes);
@@ -660,6 +667,8 @@
 
     void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel);
 
+    void setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+
     // IBinder::DeathRecipient overrides:
     void binderDied(const wp<IBinder>& who) override;
 
@@ -692,7 +701,7 @@
     void onChoreographerAttached() override;
     void onExpectedPresentTimePosted(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
                                      Fps renderRate) override;
-    void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) override
+    void onCommitNotComposited() override
             REQUIRES(kMainThreadContext);
     void vrrDisplayIdle(bool idle) override;
 
@@ -1067,8 +1076,20 @@
     void enableHalVirtualDisplays(bool);
 
     // Virtual display lifecycle for ID generation and HAL allocation.
-    VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock);
+    VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat, const std::string& uniqueId)
+            REQUIRES(mStateLock);
+    template <typename ID>
+    void acquireVirtualDisplaySnapshot(ID displayId, const std::string& uniqueId) {
+        std::lock_guard lock(mVirtualDisplaysMutex);
+        const bool emplace_success =
+                mVirtualDisplays.try_emplace(displayId, displayId, uniqueId).second;
+        if (!emplace_success) {
+            ALOGW("%s: Virtual display snapshot with the same ID already exists", __func__);
+        }
+    }
+
     void releaseVirtualDisplay(VirtualDisplayId);
+    void releaseVirtualDisplaySnapshot(VirtualDisplayId displayId);
 
     // Returns a display other than `mActiveDisplayId` that can be activated, if any.
     sp<DisplayDevice> getActivatableDisplay() const REQUIRES(mStateLock, kMainThreadContext);
@@ -1269,6 +1290,10 @@
 
     display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock);
 
+    mutable std::mutex mVirtualDisplaysMutex;
+    ftl::SmallMap<VirtualDisplayId, const display::VirtualDisplaySnapshot, 2> mVirtualDisplays
+            GUARDED_BY(mVirtualDisplaysMutex);
+
     // The inner or outer display for foldables, while unfolded or folded, respectively.
     std::atomic<PhysicalDisplayId> mActiveDisplayId;
 
@@ -1377,6 +1402,10 @@
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
             GUARDED_BY(mStateLock);
 
+    sp<gui::IActivePictureListener> mActivePictureListener GUARDED_BY(mStateLock);
+    bool mHaveNewActivePictureListener GUARDED_BY(mStateLock);
+    ActivePictureUpdater mActivePictureUpdater GUARDED_BY(kMainThreadContext);
+
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
     // Must only be accessed on the main thread.
@@ -1522,6 +1551,8 @@
     binder::Status getHdrOutputConversionSupport(bool* outSupport) override;
     binder::Status setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override;
     binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
+    binder::Status getMaxLayerPictureProfiles(const sp<IBinder>& display,
+                                              int32_t* outMaxProfiles) override;
     binder::Status captureDisplay(const DisplayCaptureArgs&,
                                   const sp<IScreenCaptureListener>&) override;
     binder::Status captureDisplayById(int64_t, const CaptureArgs&,
@@ -1610,12 +1641,15 @@
     binder::Status flushJankData(int32_t layerId) override;
     binder::Status removeJankListener(int32_t layerId, const sp<gui::IJankListener>& listener,
                                       int64_t afterVsync) override;
+    binder::Status setActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+    binder::Status clearActivePictureListener();
 
 private:
     static const constexpr bool kUsePermissionCache = true;
     status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache);
     status_t checkControlDisplayBrightnessPermission();
     status_t checkReadFrameBufferPermission();
+    status_t checkObservePictureProfilesPermission();
     static void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo& info,
                                               gui::DynamicDisplayInfo*& outInfo);
 
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 12616e3..e28a0c1 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -89,9 +89,9 @@
     mBootCompleted = true;
 }
 
-void FlagManager::dumpFlag(std::string& result, bool readonly, const char* name,
+void FlagManager::dumpFlag(std::string& result, bool aconfig, const char* name,
                            std::function<bool()> getter) const {
-    if (readonly || mBootCompleted) {
+    if (aconfig || mBootCompleted) {
         base::StringAppendF(&result, "%s: %s\n", name, getter() ? "true" : "false");
     } else {
         base::StringAppendF(&result, "%s: in progress (still booting)\n", name);
@@ -99,69 +99,74 @@
 }
 
 void FlagManager::dump(std::string& result) const {
-#define DUMP_FLAG_INTERVAL(name, readonly) \
-    dumpFlag(result, (readonly), #name, std::bind(&FlagManager::name, this))
-#define DUMP_SERVER_FLAG(name) DUMP_FLAG_INTERVAL(name, false)
-#define DUMP_READ_ONLY_FLAG(name) DUMP_FLAG_INTERVAL(name, true)
+#define DUMP_FLAG_INTERNAL(name, aconfig) \
+    dumpFlag(result, (aconfig), #name, std::bind(&FlagManager::name, this))
+#define DUMP_LEGACY_SERVER_FLAG(name) DUMP_FLAG_INTERNAL(name, false)
+#define DUMP_ACONFIG_FLAG(name) DUMP_FLAG_INTERNAL(name, true)
 
     base::StringAppendF(&result, "FlagManager values: \n");
 
     /// Legacy server flags ///
-    DUMP_SERVER_FLAG(use_adpf_cpu_hint);
-    DUMP_SERVER_FLAG(use_skia_tracing);
+    DUMP_LEGACY_SERVER_FLAG(use_adpf_cpu_hint);
+    DUMP_LEGACY_SERVER_FLAG(use_skia_tracing);
 
-    /// Trunk stable server flags ///
-    DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
-    DUMP_SERVER_FLAG(adpf_gpu_sf);
-    DUMP_SERVER_FLAG(adpf_use_fmq_channel);
+    /// Trunk stable server (R/W) flags ///
+    DUMP_ACONFIG_FLAG(refresh_rate_overlay_on_external_display);
+    DUMP_ACONFIG_FLAG(adpf_gpu_sf);
+    DUMP_ACONFIG_FLAG(adpf_native_session_manager);
+    DUMP_ACONFIG_FLAG(adpf_use_fmq_channel);
+    DUMP_ACONFIG_FLAG(graphite_renderengine_preview_rollout);
 
     /// Trunk stable readonly flags ///
-    DUMP_READ_ONLY_FLAG(adpf_fmq_sf);
-    DUMP_READ_ONLY_FLAG(connected_display);
-    DUMP_READ_ONLY_FLAG(enable_small_area_detection);
-    DUMP_READ_ONLY_FLAG(frame_rate_category_mrr);
-    DUMP_READ_ONLY_FLAG(misc1);
-    DUMP_READ_ONLY_FLAG(vrr_config);
-    DUMP_READ_ONLY_FLAG(hotplug2);
-    DUMP_READ_ONLY_FLAG(hdcp_level_hal);
-    DUMP_READ_ONLY_FLAG(multithreaded_present);
-    DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace);
-    DUMP_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency);
-    DUMP_READ_ONLY_FLAG(cache_when_source_crop_layer_only_moved);
-    DUMP_READ_ONLY_FLAG(enable_fro_dependent_features);
-    DUMP_READ_ONLY_FLAG(display_protected);
-    DUMP_READ_ONLY_FLAG(fp16_client_target);
-    DUMP_READ_ONLY_FLAG(game_default_frame_rate);
-    DUMP_READ_ONLY_FLAG(enable_layer_command_batching);
-    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(no_vsyncs_on_screen_off);
-    DUMP_READ_ONLY_FLAG(protected_if_client);
-    DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout);
-    DUMP_READ_ONLY_FLAG(graphite_renderengine);
-    DUMP_READ_ONLY_FLAG(filter_frames_before_trace_starts);
-    DUMP_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed);
-    DUMP_READ_ONLY_FLAG(deprecate_vsync_sf);
-    DUMP_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter);
-    DUMP_READ_ONLY_FLAG(detached_mirror);
-    DUMP_READ_ONLY_FLAG(commit_not_composited);
-    DUMP_READ_ONLY_FLAG(correct_dpi_with_display_size);
-    DUMP_READ_ONLY_FLAG(local_tonemap_screenshots);
-    DUMP_READ_ONLY_FLAG(override_trusted_overlay);
-    DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache);
-    DUMP_READ_ONLY_FLAG(force_compile_graphite_renderengine);
-    DUMP_READ_ONLY_FLAG(trace_frame_rate_override);
-    DUMP_READ_ONLY_FLAG(true_hdr_screenshots);
-    DUMP_READ_ONLY_FLAG(display_config_error_hal);
-    DUMP_READ_ONLY_FLAG(connected_display_hdr);
-    DUMP_READ_ONLY_FLAG(deprecate_frame_tracker);
+    DUMP_ACONFIG_FLAG(adpf_fmq_sf);
+    DUMP_ACONFIG_FLAG(arr_setframerate_gte_enum);
+    DUMP_ACONFIG_FLAG(connected_display);
+    DUMP_ACONFIG_FLAG(enable_small_area_detection);
+    DUMP_ACONFIG_FLAG(stable_edid_ids);
+    DUMP_ACONFIG_FLAG(frame_rate_category_mrr);
+    DUMP_ACONFIG_FLAG(misc1);
+    DUMP_ACONFIG_FLAG(vrr_config);
+    DUMP_ACONFIG_FLAG(hdcp_level_hal);
+    DUMP_ACONFIG_FLAG(multithreaded_present);
+    DUMP_ACONFIG_FLAG(add_sf_skipped_frames_to_trace);
+    DUMP_ACONFIG_FLAG(use_known_refresh_rate_for_fps_consistency);
+    DUMP_ACONFIG_FLAG(cache_when_source_crop_layer_only_moved);
+    DUMP_ACONFIG_FLAG(enable_fro_dependent_features);
+    DUMP_ACONFIG_FLAG(display_protected);
+    DUMP_ACONFIG_FLAG(fp16_client_target);
+    DUMP_ACONFIG_FLAG(game_default_frame_rate);
+    DUMP_ACONFIG_FLAG(enable_layer_command_batching);
+    DUMP_ACONFIG_FLAG(vulkan_renderengine);
+    DUMP_ACONFIG_FLAG(renderable_buffer_usage);
+    DUMP_ACONFIG_FLAG(vrr_bugfix_24q4);
+    DUMP_ACONFIG_FLAG(vrr_bugfix_dropped_frame);
+    DUMP_ACONFIG_FLAG(restore_blur_step);
+    DUMP_ACONFIG_FLAG(dont_skip_on_early_ro);
+    DUMP_ACONFIG_FLAG(no_vsyncs_on_screen_off);
+    DUMP_ACONFIG_FLAG(protected_if_client);
+    DUMP_ACONFIG_FLAG(idle_screen_refresh_rate_timeout);
+    DUMP_ACONFIG_FLAG(graphite_renderengine);
+    DUMP_ACONFIG_FLAG(filter_frames_before_trace_starts);
+    DUMP_ACONFIG_FLAG(latch_unsignaled_with_auto_refresh_changed);
+    DUMP_ACONFIG_FLAG(deprecate_vsync_sf);
+    DUMP_ACONFIG_FLAG(allow_n_vsyncs_in_targeter);
+    DUMP_ACONFIG_FLAG(detached_mirror);
+    DUMP_ACONFIG_FLAG(commit_not_composited);
+    DUMP_ACONFIG_FLAG(correct_dpi_with_display_size);
+    DUMP_ACONFIG_FLAG(local_tonemap_screenshots);
+    DUMP_ACONFIG_FLAG(override_trusted_overlay);
+    DUMP_ACONFIG_FLAG(flush_buffer_slots_to_uncache);
+    DUMP_ACONFIG_FLAG(force_compile_graphite_renderengine);
+    DUMP_ACONFIG_FLAG(trace_frame_rate_override);
+    DUMP_ACONFIG_FLAG(true_hdr_screenshots);
+    DUMP_ACONFIG_FLAG(display_config_error_hal);
+    DUMP_ACONFIG_FLAG(connected_display_hdr);
+    DUMP_ACONFIG_FLAG(deprecate_frame_tracker);
+    DUMP_ACONFIG_FLAG(skip_invisible_windows_in_input);
+    DUMP_ACONFIG_FLAG(begone_bright_hlg);
 
-#undef DUMP_READ_ONLY_FLAG
-#undef DUMP_SERVER_FLAG
+#undef DUMP_ACONFIG_FLAG
+#undef DUMP_LEGACY_SERVER_FLAG
 #undef DUMP_FLAG_INTERVAL
 }
 
@@ -186,35 +191,24 @@
         return getServerConfigurableFlag(serverFlagName);                                   \
     }
 
-#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted, owner)         \
-    bool FlagManager::name() const {                                                            \
-        if (checkForBootCompleted) {                                                            \
-            LOG_ALWAYS_FATAL_IF(!mBootCompleted,                                                \
-                                "Can't read %s before boot completed as it is server writable", \
-                                __func__);                                                      \
-        }                                                                                       \
-        static const std::optional<bool> debugOverride = getBoolProperty(syspropOverride);      \
-        static const bool value = getFlagValue([] { return owner ::name(); }, debugOverride);   \
-        if (mUnitTestMode) {                                                                    \
-            /*                                                                                  \
-             * When testing, we don't want to rely on the cached `value` or the debugOverride.  \
-             */                                                                                 \
-            return owner ::name();                                                              \
-        }                                                                                       \
-        return value;                                                                           \
+#define FLAG_MANAGER_ACONFIG_INTERNAL(name, syspropOverride, owner)                            \
+    bool FlagManager::name() const {                                                           \
+        static const std::optional<bool> debugOverride = getBoolProperty(syspropOverride);     \
+        static const bool value = getFlagValue([] { return owner ::name(); }, debugOverride);  \
+        if (mUnitTestMode) {                                                                   \
+            /*                                                                                 \
+             * When testing, we don't want to rely on the cached `value` or the debugOverride. \
+             */                                                                                \
+            return owner ::name();                                                             \
+        }                                                                                      \
+        return value;                                                                          \
     }
 
-#define FLAG_MANAGER_SERVER_FLAG(name, syspropOverride) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, flags)
+#define FLAG_MANAGER_ACONFIG_FLAG(name, syspropOverride) \
+    FLAG_MANAGER_ACONFIG_INTERNAL(name, syspropOverride, flags)
 
-#define FLAG_MANAGER_READ_ONLY_FLAG(name, syspropOverride) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, flags)
-
-#define FLAG_MANAGER_SERVER_FLAG_IMPORTED(name, syspropOverride, owner) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, owner)
-
-#define FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(name, syspropOverride, owner) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, owner)
+#define FLAG_MANAGER_ACONFIG_FLAG_IMPORTED(name, syspropOverride, owner) \
+    FLAG_MANAGER_ACONFIG_INTERNAL(name, syspropOverride, owner)
 
 /// Legacy server flags ///
 FLAG_MANAGER_LEGACY_SERVER_FLAG(test_flag, "", "")
@@ -224,61 +218,66 @@
                                 "SkiaTracingFeature__use_skia_tracing")
 
 /// Trunk stable readonly flags ///
-FLAG_MANAGER_READ_ONLY_FLAG(adpf_fmq_sf, "")
-FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
-FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
-FLAG_MANAGER_READ_ONLY_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr")
-FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
-FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
-FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
-FLAG_MANAGER_READ_ONLY_FLAG(hdcp_level_hal, "")
-FLAG_MANAGER_READ_ONLY_FLAG(multithreaded_present, "debug.sf.multithreaded_present")
-FLAG_MANAGER_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace, "")
-FLAG_MANAGER_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency, "")
-FLAG_MANAGER_READ_ONLY_FLAG(cache_when_source_crop_layer_only_moved,
-                            "debug.sf.cache_source_crop_only_moved")
-FLAG_MANAGER_READ_ONLY_FLAG(enable_fro_dependent_features, "")
-FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
-FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
-FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
-FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "debug.sf.enable_layer_command_batching")
-FLAG_MANAGER_READ_ONLY_FLAG(vulkan_renderengine, "debug.renderengine.vulkan")
-FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "")
-FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step")
-FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "")
-FLAG_MANAGER_READ_ONLY_FLAG(no_vsyncs_on_screen_off, "debug.sf.no_vsyncs_on_screen_off")
-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(graphite_renderengine, "debug.renderengine.graphite")
-FLAG_MANAGER_READ_ONLY_FLAG(filter_frames_before_trace_starts, "")
-FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
-FLAG_MANAGER_READ_ONLY_FLAG(deprecate_vsync_sf, "");
-FLAG_MANAGER_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter, "");
-FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, "");
-FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, "");
-FLAG_MANAGER_READ_ONLY_FLAG(correct_dpi_with_display_size, "");
-FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots");
-FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, "");
-FLAG_MANAGER_READ_ONLY_FLAG(flush_buffer_slots_to_uncache, "");
-FLAG_MANAGER_READ_ONLY_FLAG(force_compile_graphite_renderengine, "");
-FLAG_MANAGER_READ_ONLY_FLAG(true_hdr_screenshots, "debug.sf.true_hdr_screenshots");
-FLAG_MANAGER_READ_ONLY_FLAG(display_config_error_hal, "");
-FLAG_MANAGER_READ_ONLY_FLAG(connected_display_hdr, "");
-FLAG_MANAGER_READ_ONLY_FLAG(deprecate_frame_tracker, "");
+FLAG_MANAGER_ACONFIG_FLAG(adpf_fmq_sf, "")
+FLAG_MANAGER_ACONFIG_FLAG(arr_setframerate_gte_enum, "debug.sf.arr_setframerate_gte_enum")
+FLAG_MANAGER_ACONFIG_FLAG(connected_display, "")
+FLAG_MANAGER_ACONFIG_FLAG(enable_small_area_detection, "")
+FLAG_MANAGER_ACONFIG_FLAG(stable_edid_ids, "debug.sf.stable_edid_ids")
+FLAG_MANAGER_ACONFIG_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr")
+FLAG_MANAGER_ACONFIG_FLAG(misc1, "")
+FLAG_MANAGER_ACONFIG_FLAG(vrr_config, "debug.sf.enable_vrr_config")
+FLAG_MANAGER_ACONFIG_FLAG(hdcp_level_hal, "")
+FLAG_MANAGER_ACONFIG_FLAG(multithreaded_present, "debug.sf.multithreaded_present")
+FLAG_MANAGER_ACONFIG_FLAG(add_sf_skipped_frames_to_trace, "")
+FLAG_MANAGER_ACONFIG_FLAG(use_known_refresh_rate_for_fps_consistency, "")
+FLAG_MANAGER_ACONFIG_FLAG(cache_when_source_crop_layer_only_moved,
+                          "debug.sf.cache_source_crop_only_moved")
+FLAG_MANAGER_ACONFIG_FLAG(enable_fro_dependent_features, "")
+FLAG_MANAGER_ACONFIG_FLAG(display_protected, "")
+FLAG_MANAGER_ACONFIG_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
+FLAG_MANAGER_ACONFIG_FLAG(game_default_frame_rate, "")
+FLAG_MANAGER_ACONFIG_FLAG(enable_layer_command_batching, "debug.sf.enable_layer_command_batching")
+FLAG_MANAGER_ACONFIG_FLAG(vulkan_renderengine, "debug.renderengine.vulkan")
+FLAG_MANAGER_ACONFIG_FLAG(renderable_buffer_usage, "")
+FLAG_MANAGER_ACONFIG_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step")
+FLAG_MANAGER_ACONFIG_FLAG(dont_skip_on_early_ro, "")
+FLAG_MANAGER_ACONFIG_FLAG(no_vsyncs_on_screen_off, "debug.sf.no_vsyncs_on_screen_off")
+FLAG_MANAGER_ACONFIG_FLAG(protected_if_client, "")
+FLAG_MANAGER_ACONFIG_FLAG(vrr_bugfix_24q4, "");
+FLAG_MANAGER_ACONFIG_FLAG(vrr_bugfix_dropped_frame, "")
+FLAG_MANAGER_ACONFIG_FLAG(graphite_renderengine, "debug.renderengine.graphite")
+FLAG_MANAGER_ACONFIG_FLAG(filter_frames_before_trace_starts, "")
+FLAG_MANAGER_ACONFIG_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
+FLAG_MANAGER_ACONFIG_FLAG(deprecate_vsync_sf, "");
+FLAG_MANAGER_ACONFIG_FLAG(allow_n_vsyncs_in_targeter, "");
+FLAG_MANAGER_ACONFIG_FLAG(detached_mirror, "");
+FLAG_MANAGER_ACONFIG_FLAG(commit_not_composited, "");
+FLAG_MANAGER_ACONFIG_FLAG(correct_dpi_with_display_size, "");
+FLAG_MANAGER_ACONFIG_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots");
+FLAG_MANAGER_ACONFIG_FLAG(override_trusted_overlay, "");
+FLAG_MANAGER_ACONFIG_FLAG(flush_buffer_slots_to_uncache, "");
+FLAG_MANAGER_ACONFIG_FLAG(force_compile_graphite_renderengine, "");
+FLAG_MANAGER_ACONFIG_FLAG(true_hdr_screenshots, "debug.sf.true_hdr_screenshots");
+FLAG_MANAGER_ACONFIG_FLAG(display_config_error_hal, "");
+FLAG_MANAGER_ACONFIG_FLAG(connected_display_hdr, "debug.sf.connected_display_hdr");
+FLAG_MANAGER_ACONFIG_FLAG(deprecate_frame_tracker, "");
+FLAG_MANAGER_ACONFIG_FLAG(skip_invisible_windows_in_input, "");
+FLAG_MANAGER_ACONFIG_FLAG(begone_bright_hlg, "debug.sf.begone_bright_hlg");
 
-/// Trunk stable server flags ///
-FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
-FLAG_MANAGER_SERVER_FLAG(adpf_gpu_sf, "")
+/// Trunk stable server (R/W) flags ///
+FLAG_MANAGER_ACONFIG_FLAG(refresh_rate_overlay_on_external_display, "")
+FLAG_MANAGER_ACONFIG_FLAG(adpf_gpu_sf, "")
+FLAG_MANAGER_ACONFIG_FLAG(adpf_native_session_manager, "");
+FLAG_MANAGER_ACONFIG_FLAG(graphite_renderengine_preview_rollout, "");
 
-/// Trunk stable server flags from outside SurfaceFlinger ///
-FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os)
+/// Trunk stable server (R/W) flags from outside SurfaceFlinger ///
+FLAG_MANAGER_ACONFIG_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os)
 
 /// Trunk stable readonly flags from outside SurfaceFlinger ///
-FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(idle_screen_refresh_rate_timeout, "",
-                                     com::android::server::display::feature::flags)
-FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(adpf_use_fmq_channel_fixed, "", android::os)
-FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(trace_frame_rate_override, "",
-                                     com::android::graphics::libgui::flags);
+FLAG_MANAGER_ACONFIG_FLAG_IMPORTED(idle_screen_refresh_rate_timeout, "",
+                                   com::android::server::display::feature::flags)
+FLAG_MANAGER_ACONFIG_FLAG_IMPORTED(adpf_use_fmq_channel_fixed, "", android::os)
+FLAG_MANAGER_ACONFIG_FLAG_IMPORTED(trace_frame_rate_override, "",
+                                   com::android::graphics::libgui::flags);
 
 } // namespace android
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index f5bea72..1a857c8 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -47,20 +47,23 @@
     bool use_adpf_cpu_hint() const;
     bool use_skia_tracing() const;
 
-    /// Trunk stable server flags ///
+    /// Trunk stable server (R/W) flags ///
     bool refresh_rate_overlay_on_external_display() const;
     bool adpf_gpu_sf() const;
     bool adpf_use_fmq_channel() const;
+    bool adpf_native_session_manager() const;
     bool adpf_use_fmq_channel_fixed() const;
+    bool graphite_renderengine_preview_rollout() const;
 
     /// Trunk stable readonly flags ///
+    bool arr_setframerate_gte_enum() const;
     bool adpf_fmq_sf() const;
     bool connected_display() const;
     bool frame_rate_category_mrr() const;
     bool enable_small_area_detection() const;
+    bool stable_edid_ids() const;
     bool misc1() const;
     bool vrr_config() const;
-    bool hotplug2() const;
     bool hdcp_level_hal() const;
     bool multithreaded_present() const;
     bool add_sf_skipped_frames_to_trace() const;
@@ -97,6 +100,8 @@
     bool display_config_error_hal() const;
     bool connected_display_hdr() const;
     bool deprecate_frame_tracker() const;
+    bool skip_invisible_windows_in_input() const;
+    bool begone_bright_hlg() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger.logtags b/services/surfaceflinger/surfaceflinger.logtags
index e68d9f5..6dbe0dd 100644
--- a/services/surfaceflinger/surfaceflinger.logtags
+++ b/services/surfaceflinger/surfaceflinger.logtags
@@ -31,7 +31,7 @@
 # 6: Percent
 # Default value for data of type int/long is 2 (bytes).
 #
-# See system/core/logcat/event.logtags for the master copy of the tags.
+# See system/logging/logcat/event.logtags for the master copy of the tags.
 
 # 60100 - 60199 reserved for surfaceflinger
 
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 014c736..d6a1ad4 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -19,6 +19,13 @@
 } # adpf_gpu_sf
 
 flag {
+  name: "adpf_native_session_manager"
+  namespace: "game"
+  description: "Controls ADPF SessionManager being enabled in SF"
+  bug: "367803904"
+} # adpf_sessionmanager
+
+flag {
   name: "arr_setframerate_api"
   namespace: "core_graphics"
   description: "New SDK Surface#setFrameRate API and Surface.FrameRateParams for Android 16"
@@ -27,6 +34,14 @@
 } # arr_setframerate_api
 
 flag {
+  name: "arr_setframerate_gte_enum"
+  namespace: "core_graphics"
+  description: "Exposes GTE (greater than or equal to) enum for Android 16"
+  bug: "380949716"
+  is_fixed_read_only: true
+} # arr_setframerate_gte_enum
+
+flag {
   name: "arr_surfacecontrol_setframerate_api"
   namespace: "core_graphics"
   description: "New SDK SurfaceControl.Transaction#setFrameRate API for Android 16"
@@ -35,6 +50,14 @@
 } # arr_surfacecontrol_setframerate_api
 
 flag {
+  name: "begone_bright_hlg"
+  namespace: "core_graphics"
+  description: "Caps HLG brightness relative to SDR"
+  bug: "362510107"
+  is_fixed_read_only: true
+} # begone_bright_hlg
+
+flag {
   name: "ce_fence_promise"
   namespace: "window_surfaces"
   description: "Moves logic for buffer release fences into LayerFE"
@@ -158,6 +181,13 @@
 } # frame_rate_category_mrr
 
 flag {
+  name: "graphite_renderengine_preview_rollout"
+  namespace: "core_graphics"
+  description: "R/W flag to enable Skia's Graphite Vulkan backend in RenderEngine, IF it is already compiled with force_compile_graphite_renderengine, AND the debug.renderengine.graphite_preview_optin sysprop is set to true."
+  bug: "293371537"
+} # graphite_renderengine_preview_rollout
+
+flag {
   name: "latch_unsignaled_with_auto_refresh_changed"
   namespace: "core_graphics"
   description: "Ignore eAutoRefreshChanged with latch unsignaled"
@@ -196,6 +226,25 @@
  } # single_hop_screenshot
 
 flag {
+  name: "skip_invisible_windows_in_input"
+  namespace: "window_surfaces"
+  description: "Only send visible windows to input list"
+  bug: "305254099"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+ } # skip_invisible_windows_in_input
+
+flag {
+  name: "stable_edid_ids"
+  namespace: "core_graphics"
+  description: "Guard use of the new stable EDID-based display IDs system."
+  bug: "352320847"
+  is_fixed_read_only: true
+} # stable_edid_ids
+
+flag {
   name: "true_hdr_screenshots"
   namespace: "core_graphics"
   description: "Enables screenshotting display content in HDR, sans tone mapping"
diff --git a/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp b/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp
new file mode 100644
index 0000000..b926d2f
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/ActivePictureUpdaterTest.cpp
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android/gui/ActivePicture.h>
+#include <android/gui/IActivePictureListener.h>
+#include <compositionengine/mock/CompositionEngine.h>
+#include <mock/DisplayHardware/MockComposer.h>
+#include <mock/MockLayer.h>
+#include <renderengine/mock/RenderEngine.h>
+
+#include "ActivePictureUpdater.h"
+#include "LayerFE.h"
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+using android::compositionengine::LayerFECompositionState;
+using android::gui::ActivePicture;
+using android::gui::IActivePictureListener;
+using android::mock::MockLayer;
+using surfaceflinger::frontend::LayerSnapshot;
+using testing::_;
+using testing::NiceMock;
+using testing::Return;
+
+class TestableLayerFE : public LayerFE {
+public:
+    TestableLayerFE() : LayerFE("TestableLayerFE"), snapshot(*(new LayerSnapshot)) {
+        mSnapshot = std::unique_ptr<LayerSnapshot>(&snapshot);
+    }
+
+    LayerSnapshot& snapshot;
+};
+
+class ActivePictureUpdaterTest : public testing::Test {
+protected:
+    SurfaceFlinger* flinger() {
+        if (!mFlingerSetup) {
+            mFlinger.setupMockScheduler();
+            mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+            mFlinger.setupRenderEngine(std::make_unique<renderengine::mock::RenderEngine>());
+            mFlingerSetup = true;
+        }
+        return mFlinger.flinger();
+    }
+
+private:
+    TestableSurfaceFlinger mFlinger;
+    bool mFlingerSetup = false;
+};
+
+// Hack to workaround initializer lists not working for parcelables because parcelables inherit from
+// Parcelable, which has a virtual destructor.
+auto UnorderedElementsAre(std::initializer_list<std::tuple<int32_t, int32_t, int64_t>> tuples) {
+    std::vector<ActivePicture> activePictures;
+    for (auto tuple : tuples) {
+        ActivePicture ap;
+        ap.layerId = std::get<0>(tuple);
+        ap.ownerUid = std::get<1>(tuple);
+        ap.pictureProfileId = std::get<2>(tuple);
+        activePictures.push_back(ap);
+    }
+    return testing::UnorderedElementsAreArray(activePictures);
+}
+
+// Parcelables don't define this for matchers, which is unfortunate
+void PrintTo(const ActivePicture& activePicture, std::ostream* os) {
+    *os << activePicture.toString();
+}
+
+TEST_F(ActivePictureUpdaterTest, notCalledWithNoProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_FALSE(updater.updateAndHasChanged());
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenLayerStartsUsingProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_FALSE(updater.updateAndHasChanged());
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, notCalledWhenLayerContinuesUsingProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_FALSE(updater.updateAndHasChanged());
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenLayerStopsUsingProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle::NONE;
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenLayerChangesProfile) {
+    sp<NiceMock<MockLayer>> layer = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE;
+    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+    {
+        layerFE.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer, layerFE, layerFE.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 2}}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, notCalledWhenUncommittedLayerChangesProfile) {
+    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE1;
+    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
+    TestableLayerFE layerFE2;
+    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(20)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_FALSE(updater.updateAndHasChanged());
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenDifferentLayerUsesSameProfile) {
+    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE1;
+    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
+    TestableLayerFE layerFE2;
+    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(20)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 1}, {200, 20, 2}}));
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 2}, {200, 20, 1}}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenSameUidUsesSameProfile) {
+    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE1;
+    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
+    TestableLayerFE layerFE2;
+    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 1}, {200, 10, 2}}));
+    }
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(2);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 2}, {200, 10, 1}}));
+    }
+}
+
+TEST_F(ActivePictureUpdaterTest, calledWhenNewLayerUsesSameProfile) {
+    sp<NiceMock<MockLayer>> layer1 = sp<NiceMock<MockLayer>>::make(flinger(), 100);
+    TestableLayerFE layerFE1;
+    EXPECT_CALL(*layer1, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    ActivePictureUpdater updater;
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(), UnorderedElementsAre({{100, 10, 1}}));
+    }
+
+    sp<NiceMock<MockLayer>> layer2 = sp<NiceMock<MockLayer>>::make(flinger(), 200);
+    TestableLayerFE layerFE2;
+    EXPECT_CALL(*layer2, getOwnerUid()).WillRepeatedly(Return(uid_t(10)));
+
+    {
+        layerFE1.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE1.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer1, layerFE1, layerFE1.stealCompositionResult());
+
+        layerFE2.snapshot.pictureProfileHandle = PictureProfileHandle(1);
+        layerFE2.onPictureProfileCommitted();
+        updater.onLayerComposed(*layer2, layerFE2, layerFE2.stealCompositionResult());
+
+        ASSERT_TRUE(updater.updateAndHasChanged());
+        EXPECT_THAT(updater.getActivePictures(),
+                    UnorderedElementsAre({{100, 10, 1}, {200, 10, 1}}));
+    }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
index 5413bae..72d1351 100644
--- a/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 #include <gtest/gtest.h>
 #include <condition_variable>
 
diff --git a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
index d971150..29a1fab 100644
--- a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
@@ -20,6 +20,7 @@
 #include "Display/DisplayModeController.h"
 #include "Display/DisplaySnapshot.h"
 #include "DisplayHardware/HWComposer.h"
+#include "DisplayHardware/Hal.h"
 #include "DisplayIdentificationTestHelpers.h"
 #include "FpsOps.h"
 #include "mock/DisplayHardware/MockComposer.h"
@@ -103,7 +104,7 @@
 
         EXPECT_CALL(*mComposerHal,
                     setActiveConfigWithConstraints(kHwcDisplayId, hwcModeId, constraints, _))
-                .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::V2_4::Error::NONE)));
+                .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::Error::NONE)));
 
         return constraints;
     }
@@ -183,7 +184,8 @@
     hal::VsyncPeriodChangeTimeline timeline;
     const auto constraints = expectModeSet(modeRequest, timeline);
 
-    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_EQ(DisplayModeController::ModeChangeResult::Changed,
+              mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
     EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
 
     mDmc.clearDesiredMode(mDisplayId);
@@ -210,7 +212,8 @@
     hal::VsyncPeriodChangeTimeline timeline;
     auto constraints = expectModeSet(modeRequest, timeline);
 
-    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_EQ(DisplayModeController::ModeChangeResult::Changed,
+              mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
     EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
 
     // No action since a mode switch has already been initiated.
@@ -223,7 +226,8 @@
     constexpr bool kSubsequent = true;
     constraints = expectModeSet(modeRequest, timeline, kSubsequent);
 
-    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_EQ(DisplayModeController::ModeChangeResult::Changed,
+              mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
     EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getPendingMode(mDisplayId));
 
     mDmc.clearDesiredMode(mDisplayId);
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index 51b5f40..a5b347a 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -85,12 +85,6 @@
     EXPECT_EQ(false, mFlagManager.test_flag());
 }
 
-TEST_F(FlagManagerTest, crashesIfQueriedBeforeBoot) {
-    mFlagManager.markBootIncomplete();
-    EXPECT_DEATH(FlagManager::getInstance()
-        .refresh_rate_overlay_on_external_display(), "");
-}
-
 TEST_F(FlagManagerTest, returnsOverrideTrue) {
     mFlagManager.markBootCompleted();
 
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index e0753a3..ba2d3e2 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -69,7 +69,7 @@
 using ::testing::StrictMock;
 
 struct HWComposerTest : testing::Test {
-    using HalError = hardware::graphics::composer::V2_1::Error;
+    using HalError = hal::Error;
 
     Hwc2::mock::Composer* const mHal = new StrictMock<Hwc2::mock::Composer>();
     impl::HWComposer mHwc{std::unique_ptr<Hwc2::Composer>(mHal)};
@@ -384,6 +384,7 @@
         const ui::Size size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
         const float expectedDpiX = (kWidth * kMmPerInch / size.width);
         const float expectedDpiY = (kHeight * kMmPerInch / size.height);
+        const OutputType hdrOutputType = OutputType::SYSTEM;
         const hal::VrrConfig vrrConfig =
                 hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
                                .notifyExpectedPresentConfig = hal::VrrConfig::
@@ -394,7 +395,8 @@
                                                        .height = kHeight,
                                                        .configGroup = kConfigGroup,
                                                        .vsyncPeriod = kVsyncPeriod,
-                                                       .vrrConfig = vrrConfig};
+                                                       .vrrConfig = vrrConfig,
+                                                       .hdrOutputType = hdrOutputType};
 
         EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
                 .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
@@ -410,6 +412,7 @@
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
+        EXPECT_EQ(modes.front().hdrOutputType, hdrOutputType);
         if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
             EXPECT_EQ(modes.front().dpiX, -1);
             EXPECT_EQ(modes.front().dpiY, -1);
@@ -435,6 +438,7 @@
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
+        EXPECT_EQ(modes.front().hdrOutputType, hdrOutputType);
         EXPECT_EQ(modes.front().dpiX, kDpi);
         EXPECT_EQ(modes.front().dpiY, kDpi);
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index de37b63..f9bfe97 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -584,7 +584,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_GTE,
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);
 
@@ -623,7 +623,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_GTE,
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);
 
@@ -715,6 +715,204 @@
     EXPECT_EQ(0, frequentLayerCount(time));
 }
 
+// Tests MRR NoPreference-only vote, no game default override. Expects vote reset.
+TEST_F(LayerHistoryIntegrationTest, oneLayerCategoryNoPreference_mrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const LayerHistory::LayerVoteType defaultVote = LayerHistory::LayerVoteType::Min;
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    setDefaultLayerVote(layer.get(), defaultVote);
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    EXPECT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(defaultVote, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+// Tests VRR NoPreference-only vote, no game default override. Expects NoPreference, *not* vote
+// reset.
+TEST_F(LayerHistoryIntegrationTest, oneLayerCategoryNoPreference_vrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    mSelector->setActiveMode(kVrrModeId, HI_FPS);
+
+    const LayerHistory::LayerVoteType defaultVote = LayerHistory::LayerVoteType::Min;
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    setDefaultLayerVote(layer.get(), defaultVote);
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    EXPECT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::NoPreference, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerCategoryNoPreferenceWithGameDefault_vrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    mSelector->setActiveMode(kVrrModeId, HI_FPS);
+
+    const Fps gameDefaultFrameRate = Fps::fromValue(30.0f);
+    const uid_t uid = 456;
+
+    history().updateGameDefaultFrameRateOverride(
+            FrameRateOverride({uid, gameDefaultFrameRate.getValue()}));
+
+    auto layer = createLegacyAndFrontedEndLayerWithUid(1, gui::Uid(uid));
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    EXPECT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(gameDefaultFrameRate, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerCategoryNoPreferenceWithGameDefault_mrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const Fps gameDefaultFrameRate = Fps::fromValue(30.0f);
+    const uid_t uid = 456;
+
+    history().updateGameDefaultFrameRateOverride(
+            FrameRateOverride({uid, gameDefaultFrameRate.getValue()}));
+
+    auto layer = createLegacyAndFrontedEndLayerWithUid(1, gui::Uid(uid));
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    EXPECT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(gameDefaultFrameRate, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerNoVoteWithGameDefault_vrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    mSelector->setActiveMode(kVrrModeId, HI_FPS);
+
+    const Fps gameDefaultFrameRate = Fps::fromValue(30.0f);
+    const uid_t uid = 456;
+
+    history().updateGameDefaultFrameRateOverride(
+            FrameRateOverride({uid, gameDefaultFrameRate.getValue()}));
+
+    auto layer = createLegacyAndFrontedEndLayerWithUid(1, gui::Uid(uid));
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // Expect NoVote to be skipped in summarize.
+    EXPECT_EQ(0u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerNoVoteWithGameDefault_mrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const Fps gameDefaultFrameRate = Fps::fromValue(30.0f);
+    const uid_t uid = 456;
+
+    history().updateGameDefaultFrameRateOverride(
+            FrameRateOverride({uid, gameDefaultFrameRate.getValue()}));
+
+    auto layer = createLegacyAndFrontedEndLayerWithUid(1, gui::Uid(uid));
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // Expect NoVote to be skipped in summarize.
+    EXPECT_EQ(0u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
 TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVoteWithCategory) {
     SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
 
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 6aec743..8c53eef 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1550,6 +1550,9 @@
 }
 
 TEST_F(LayerSnapshotTest, NonVisibleLayerWithInput) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              skip_invisible_windows_in_input,
+                      false);
     LayerHierarchyTestBase::createRootLayer(3);
     setColor(3, {-1._hf, -1._hf, -1._hf});
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
@@ -1575,6 +1578,39 @@
     EXPECT_TRUE(foundInputLayer);
 }
 
+TEST_F(LayerSnapshotTest, NonVisibleLayerWithInputShouldNotBeIncluded) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              skip_invisible_windows_in_input,
+                      true);
+    LayerHierarchyTestBase::createRootLayer(3);
+    setColor(3, {-1._hf, -1._hf, -1._hf});
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
+    transactions.back().states.front().layerId = 3;
+    transactions.back().states.front().state.windowInfoHandle = sp<gui::WindowInfoHandle>::make();
+    auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo();
+    inputInfo->token = sp<BBinder>::make();
+    hideLayer(3);
+    mLifecycleManager.applyTransactions(transactions);
+
+    update(mSnapshotBuilder);
+
+    bool foundInputLayer = false;
+    mSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        if (snapshot.uniqueSequence == 3) {
+            EXPECT_TRUE(
+                    snapshot.inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE));
+            EXPECT_FALSE(snapshot.isVisible);
+            foundInputLayer = true;
+        }
+    });
+    EXPECT_FALSE(foundInputLayer);
+}
+
 TEST_F(LayerSnapshotTest, ForEachSnapshotsWithPredicate) {
     std::vector<uint32_t> visitedUniqueSequences;
     mSnapshotBuilder.forEachSnapshot(
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 80b2b8d..9b3cba5 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -4667,5 +4667,47 @@
         EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
     }
 }
+
+TEST_P(RefreshRateSelectorTest, getSupportedFrameRates) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    auto selector = createSelector(kModes_60_90, kModeId90);
+    const FpsRange range60 = {0_Hz, 60_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId60, {range60, range60}, {range60, range60}}));
+
+    // Irrespective of the policy we get the full range of possible frame rates
+    const std::vector<float> expected = {90.0f, 60.0f, 45.0f, 30.0f, 22.5f, 20.0f};
+
+    const auto allSupportedFrameRates = selector.getSupportedFrameRates();
+    ASSERT_EQ(expected.size(), allSupportedFrameRates.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_EQ(expected[i], allSupportedFrameRates[i])
+                << "expected " << expected[i] << " received " << allSupportedFrameRates[i];
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getSupportedFrameRatesArr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    const auto selector = createSelector(kVrrMode_120, kModeId120);
+
+    const std::vector<float> expected = {120.0f, 80.0f,   60.0f, 48.0f,   40.0f, 34.285f,
+                                         30.0f,  26.666f, 24.0f, 21.818f, 20.0f};
+
+    const auto allSupportedFrameRates = selector.getSupportedFrameRates();
+    ASSERT_EQ(expected.size(), allSupportedFrameRates.size());
+    constexpr float kEpsilon = 0.001f;
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_TRUE(std::abs(expected[i] - allSupportedFrameRates[i]) <= kEpsilon)
+                << "expected " << expected[i] << " received " << allSupportedFrameRates[i];
+    }
+}
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 8699621..b0dd5c2 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -34,7 +34,7 @@
                                                static_cast<hal::HWConfigId>(        \
                                                        ftl::to_underlying(modeId)), \
                                                _, _))                               \
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::Error::NONE)))
 
 namespace android {
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
index 20a3315..6cc6322 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -18,12 +18,13 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
 #include <gui/SurfaceComposerClient.h>
+#include "DisplayHardware/Hal.h"
 #include "DisplayTransactionTestHelpers.h"
 
 namespace android {
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-using android::hardware::graphics::composer::V2_1::Error;
+using android::hardware::graphics::composer::hal::Error;
 
 class NotifyExpectedPresentTest : public DisplayTransactionTest {
 public:
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index c3934e6..3ae5ed9 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -21,6 +21,8 @@
 
 #include "CommitAndCompositeTest.h"
 
+#include "DisplayHardware/Hal.h"
+
 using namespace std::chrono_literals;
 using testing::_;
 using testing::Return;
@@ -40,7 +42,7 @@
     EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
         constexpr Duration kMockHwcRunTime = 20ms;
         std::this_thread::sleep_for(kMockHwcRunTime);
-        return hardware::graphics::composer::V2_1::Error::NONE;
+        return hardware::graphics::composer::hal::Error::NONE;
     });
     EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(1);
 
@@ -61,7 +63,7 @@
     EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
         constexpr Duration kMockHwcRunTime = 20ms;
         std::this_thread::sleep_for(kMockHwcRunTime);
-        return hardware::graphics::composer::V2_1::Error::NONE;
+        return hardware::graphics::composer::hal::Error::NONE;
     });
     EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(0);
 
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 88052db..0d5266e 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -20,6 +20,7 @@
 
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/HWC2.h"
+#include "DisplayHardware/Hal.h"
 
 namespace android {
 
@@ -34,9 +35,9 @@
 using android::hardware::graphics::common::V1_2::Dataspace;
 using android::hardware::graphics::common::V1_2::PixelFormat;
 
+using android::hardware::graphics::composer::hal::Error;
 using android::hardware::graphics::composer::V2_1::Config;
 using android::hardware::graphics::composer::V2_1::Display;
-using android::hardware::graphics::composer::V2_1::Error;
 using android::hardware::graphics::composer::V2_1::IComposer;
 using android::hardware::graphics::composer::V2_1::Layer;
 using android::hardware::graphics::composer::V2_4::IComposerCallback;
@@ -142,8 +143,8 @@
                  V2_4::Error(Display, Config, std::vector<VsyncPeriodNanos>*));
     MOCK_METHOD2(getDisplayVsyncPeriod, V2_4::Error(Display, VsyncPeriodNanos*));
     MOCK_METHOD4(setActiveConfigWithConstraints,
-                 V2_4::Error(Display, Config, const IComposerClient::VsyncPeriodChangeConstraints&,
-                             VsyncPeriodChangeTimeline*));
+                 Error(Display, Config, const IComposerClient::VsyncPeriodChangeConstraints&,
+                       VsyncPeriodChangeTimeline*));
     MOCK_METHOD2(setAutoLowLatencyMode, V2_4::Error(Display, bool));
     MOCK_METHOD2(setBootDisplayConfig, Error(Display, Config));
     MOCK_METHOD1(clearBootDisplayConfig, Error(Display));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index fa74492..ec065a7 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <gmock/gmock.h>
+#include <cstdint>
 
 #include "DisplayHardware/HWC2.h"
 
@@ -85,8 +86,8 @@
     MOCK_METHOD(ftl::Future<hal::Error>, setDisplayBrightness,
                 (float, float, const Hwc2::Composer::DisplayBrightnessOptions &), (override));
     MOCK_METHOD(hal::Error, setActiveConfigWithConstraints,
-                (hal::HWConfigId, const hal::VsyncPeriodChangeConstraints &,
-                 hal::VsyncPeriodChangeTimeline *),
+                (hal::HWConfigId, const hal::VsyncPeriodChangeConstraints&,
+                 hal::VsyncPeriodChangeTimeline*),
                 (override));
     MOCK_METHOD(hal::Error, setBootDisplayConfig, (hal::HWConfigId), (override));
     MOCK_METHOD(hal::Error, clearBootDisplayConfig, (), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index c976697..3036fec 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -34,6 +34,8 @@
     MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
     MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));
     MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override));
+    MOCK_METHOD(void, onModeRejected, (PhysicalDisplayId displayId, DisplayModeId modeId),
+                (override));
     MOCK_METHOD(void, onFrameRateOverridesChanged,
                 (PhysicalDisplayId, std::vector<FrameRateOverride>), (override));
     MOCK_METHOD(void, dump, (std::string&), (const, override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 45f86fa..59f0966 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -19,6 +19,8 @@
 #include <gmock/gmock.h>
 #include <optional>
 
+#include "Layer.h"
+
 namespace android::mock {
 
 class MockLayer : public Layer {
@@ -26,8 +28,11 @@
     MockLayer(SurfaceFlinger* flinger, std::string name)
           : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {}
 
-    MockLayer(SurfaceFlinger* flinger, std::string name, std::optional<uint32_t> uid)
-          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {}, uid)) {}
+    MockLayer(SurfaceFlinger* flinger, std::string name, std::optional<uint32_t> id)
+          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {}, id)) {}
+
+    MockLayer(SurfaceFlinger* flinger, std::optional<uint32_t> id)
+          : Layer(LayerCreationArgs(flinger, nullptr, "TestLayer", 0, {}, id)) {}
 
     explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {}
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index d45cc66..25dd68e 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -29,7 +29,7 @@
     MOCK_METHOD(void, onChoreographerAttached, (), (override));
     MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps),
                 (override));
-    MOCK_METHOD(void, onCommitNotComposited, (PhysicalDisplayId), (override));
+    MOCK_METHOD(void, onCommitNotComposited, (), (override));
     MOCK_METHOD(void, vrrDisplayIdle, (bool), (override));
 };
 
@@ -39,7 +39,7 @@
     void kernelTimerChanged(bool) override {}
     void onChoreographerAttached() override {}
     void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
-    void onCommitNotComposited(PhysicalDisplayId) override {}
+    void onCommitNotComposited() override {}
     void vrrDisplayIdle(bool) override {}
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerHalController.h
index fba4cd8..891f507 100644
--- a/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/PowerAdvisor/MockPowerHalController.h
@@ -54,6 +54,8 @@
     MOCK_METHOD(HalResult<aidl::android::hardware::power::ChannelConfig>, getSessionChannel,
                 (int tgid, int uid), (override));
     MOCK_METHOD(HalResult<void>, closeSessionChannel, (int tgid, int uid), (override));
+    MOCK_METHOD(HalResult<aidl::android::hardware::power::SupportInfo>, getSupportInfo, (),
+                (override));
 };
 
 } // namespace android::adpf::mock
\ No newline at end of file
diff --git a/vulkan/libvulkan/api.cpp b/vulkan/libvulkan/api.cpp
index c335e2a..8451ad1 100644
--- a/vulkan/libvulkan/api.cpp
+++ b/vulkan/libvulkan/api.cpp
@@ -45,6 +45,9 @@
 #include "driver.h"
 #include "layers_extensions.h"
 
+#include <com_android_graphics_libvulkan_flags.h>
+
+using namespace com::android::graphics::libvulkan;
 
 namespace vulkan {
 namespace api {
@@ -1473,7 +1476,7 @@
     if (!EnsureInitialized())
         return VK_ERROR_OUT_OF_HOST_MEMORY;
 
-    *pApiVersion = VK_API_VERSION_1_3;
+    *pApiVersion = flags::vulkan_1_4_instance_api() ? VK_API_VERSION_1_4 : VK_API_VERSION_1_3;
     return VK_SUCCESS;
 }
 
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index 9ff0b46..131c97c 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -266,6 +266,7 @@
     INIT_PROC(true, dev, CreateRenderPass);
     INIT_PROC(true, dev, DestroyRenderPass);
     INIT_PROC(true, dev, GetRenderAreaGranularity);
+    INIT_PROC(false, dev, GetRenderingAreaGranularity);
     INIT_PROC(true, dev, CreateCommandPool);
     INIT_PROC(true, dev, DestroyCommandPool);
     INIT_PROC(true, dev, ResetCommandPool);
@@ -323,6 +324,7 @@
     INIT_PROC_EXT(KHR_swapchain, true, dev, GetSwapchainImagesKHR);
     INIT_PROC_EXT(KHR_swapchain, true, dev, AcquireNextImageKHR);
     INIT_PROC_EXT(KHR_swapchain, true, dev, QueuePresentKHR);
+    INIT_PROC(false, dev, CmdPushDescriptorSet);
     INIT_PROC(false, dev, TrimCommandPool);
     INIT_PROC(false, dev, GetDeviceGroupPeerMemoryFeatures);
     INIT_PROC(false, dev, BindBufferMemory2);
@@ -335,6 +337,7 @@
     INIT_PROC(false, dev, CreateDescriptorUpdateTemplate);
     INIT_PROC(false, dev, DestroyDescriptorUpdateTemplate);
     INIT_PROC(false, dev, UpdateDescriptorSetWithTemplate);
+    INIT_PROC(false, dev, CmdPushDescriptorSetWithTemplate);
     INIT_PROC(false, dev, GetBufferMemoryRequirements2);
     INIT_PROC(false, dev, GetImageMemoryRequirements2);
     INIT_PROC(false, dev, GetImageSparseMemoryRequirements2);
@@ -359,11 +362,13 @@
     INIT_PROC(false, dev, GetBufferOpaqueCaptureAddress);
     INIT_PROC(false, dev, GetBufferDeviceAddress);
     INIT_PROC(false, dev, GetDeviceMemoryOpaqueCaptureAddress);
+    INIT_PROC(false, dev, CmdSetLineStipple);
     INIT_PROC(false, dev, CmdSetCullMode);
     INIT_PROC(false, dev, CmdSetFrontFace);
     INIT_PROC(false, dev, CmdSetPrimitiveTopology);
     INIT_PROC(false, dev, CmdSetViewportWithCount);
     INIT_PROC(false, dev, CmdSetScissorWithCount);
+    INIT_PROC(false, dev, CmdBindIndexBuffer2);
     INIT_PROC(false, dev, CmdBindVertexBuffers2);
     INIT_PROC(false, dev, CmdSetDepthTestEnable);
     INIT_PROC(false, dev, CmdSetDepthWriteEnable);
@@ -390,8 +395,22 @@
     INIT_PROC(false, dev, CmdPipelineBarrier2);
     INIT_PROC(false, dev, QueueSubmit2);
     INIT_PROC(false, dev, CmdWriteTimestamp2);
+    INIT_PROC(false, dev, CopyMemoryToImage);
+    INIT_PROC(false, dev, CopyImageToMemory);
+    INIT_PROC(false, dev, CopyImageToImage);
+    INIT_PROC(false, dev, TransitionImageLayout);
     INIT_PROC(false, dev, CmdBeginRendering);
     INIT_PROC(false, dev, CmdEndRendering);
+    INIT_PROC(false, dev, GetImageSubresourceLayout2);
+    INIT_PROC(false, dev, GetDeviceImageSubresourceLayout);
+    INIT_PROC(false, dev, MapMemory2);
+    INIT_PROC(false, dev, UnmapMemory2);
+    INIT_PROC(false, dev, CmdBindDescriptorSets2);
+    INIT_PROC(false, dev, CmdPushConstants2);
+    INIT_PROC(false, dev, CmdPushDescriptorSet2);
+    INIT_PROC(false, dev, CmdPushDescriptorSetWithTemplate2);
+    INIT_PROC(false, dev, CmdSetRenderingAttachmentLocations);
+    INIT_PROC(false, dev, CmdSetRenderingInputAttachmentIndices);
     // clang-format on
 
     return success;
@@ -480,6 +499,7 @@
 VKAPI_ATTR VkResult CreateRenderPass(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
 VKAPI_ATTR void DestroyRenderPass(VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR void GetRenderAreaGranularity(VkDevice device, VkRenderPass renderPass, VkExtent2D* pGranularity);
+VKAPI_ATTR void GetRenderingAreaGranularity(VkDevice device, const VkRenderingAreaInfo* pRenderingAreaInfo, VkExtent2D* pGranularity);
 VKAPI_ATTR VkResult CreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool);
 VKAPI_ATTR void DestroyCommandPool(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR VkResult ResetCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags);
@@ -550,6 +570,7 @@
 VKAPI_ATTR void GetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties);
 VKAPI_ATTR void GetPhysicalDeviceMemoryProperties2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties);
 VKAPI_ATTR void GetPhysicalDeviceSparseImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties);
+VKAPI_ATTR void CmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites);
 VKAPI_ATTR void TrimCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags);
 VKAPI_ATTR void GetPhysicalDeviceExternalBufferProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties);
 VKAPI_ATTR void GetPhysicalDeviceExternalSemaphoreProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties);
@@ -567,6 +588,7 @@
 VKAPI_ATTR VkResult CreateDescriptorUpdateTemplate(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate);
 VKAPI_ATTR void DestroyDescriptorUpdateTemplate(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR void UpdateDescriptorSetWithTemplate(VkDevice device, VkDescriptorSet descriptorSet, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const void* pData);
+VKAPI_ATTR void CmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void* pData);
 VKAPI_ATTR void GetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
@@ -591,12 +613,14 @@
 VKAPI_ATTR uint64_t GetBufferOpaqueCaptureAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR VkDeviceAddress GetBufferDeviceAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR uint64_t GetDeviceMemoryOpaqueCaptureAddress(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+VKAPI_ATTR void CmdSetLineStipple(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor, uint16_t lineStipplePattern);
 VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties);
 VKAPI_ATTR void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode);
 VKAPI_ATTR void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace);
 VKAPI_ATTR void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology);
 VKAPI_ATTR void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports);
 VKAPI_ATTR void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors);
+VKAPI_ATTR void CmdBindIndexBuffer2(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkIndexType indexType);
 VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides);
 VKAPI_ATTR void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable);
 VKAPI_ATTR void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable);
@@ -623,8 +647,22 @@
 VKAPI_ATTR void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo);
 VKAPI_ATTR VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence);
 VKAPI_ATTR void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query);
+VKAPI_ATTR VkResult CopyMemoryToImage(VkDevice device, const VkCopyMemoryToImageInfo* pCopyMemoryToImageInfo);
+VKAPI_ATTR VkResult CopyImageToMemory(VkDevice device, const VkCopyImageToMemoryInfo* pCopyImageToMemoryInfo);
+VKAPI_ATTR VkResult CopyImageToImage(VkDevice device, const VkCopyImageToImageInfo* pCopyImageToImageInfo);
+VKAPI_ATTR VkResult TransitionImageLayout(VkDevice device, uint32_t transitionCount, const VkHostImageLayoutTransitionInfo* pTransitions);
 VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo);
 VKAPI_ATTR void CmdEndRendering(VkCommandBuffer commandBuffer);
+VKAPI_ATTR void GetImageSubresourceLayout2(VkDevice device, VkImage image, const VkImageSubresource2* pSubresource, VkSubresourceLayout2* pLayout);
+VKAPI_ATTR void GetDeviceImageSubresourceLayout(VkDevice device, const VkDeviceImageSubresourceInfo* pInfo, VkSubresourceLayout2* pLayout);
+VKAPI_ATTR VkResult MapMemory2(VkDevice device, const VkMemoryMapInfo* pMemoryMapInfo, void** ppData);
+VKAPI_ATTR VkResult UnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* pMemoryUnmapInfo);
+VKAPI_ATTR void CmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const VkBindDescriptorSetsInfo* pBindDescriptorSetsInfo);
+VKAPI_ATTR void CmdPushConstants2(VkCommandBuffer commandBuffer, const VkPushConstantsInfo* pPushConstantsInfo);
+VKAPI_ATTR void CmdPushDescriptorSet2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetInfo* pPushDescriptorSetInfo);
+VKAPI_ATTR void CmdPushDescriptorSetWithTemplate2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfo* pPushDescriptorSetWithTemplateInfo);
+VKAPI_ATTR void CmdSetRenderingAttachmentLocations(VkCommandBuffer commandBuffer, const VkRenderingAttachmentLocationInfo* pLocationInfo);
+VKAPI_ATTR void CmdSetRenderingInputAttachmentIndices(VkCommandBuffer commandBuffer, const VkRenderingInputAttachmentIndexInfo* pInputAttachmentIndexInfo);
 
 VKAPI_ATTR VkResult EnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) {
     return GetData(instance).dispatch.EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices);
@@ -764,7 +802,9 @@
         { "vkCmdBeginRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginRenderPass2) },
         { "vkCmdBeginRendering", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginRendering) },
         { "vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(CmdBindDescriptorSets) },
+        { "vkCmdBindDescriptorSets2", reinterpret_cast<PFN_vkVoidFunction>(CmdBindDescriptorSets2) },
         { "vkCmdBindIndexBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdBindIndexBuffer) },
+        { "vkCmdBindIndexBuffer2", reinterpret_cast<PFN_vkVoidFunction>(CmdBindIndexBuffer2) },
         { "vkCmdBindPipeline", reinterpret_cast<PFN_vkVoidFunction>(CmdBindPipeline) },
         { "vkCmdBindVertexBuffers", reinterpret_cast<PFN_vkVoidFunction>(CmdBindVertexBuffers) },
         { "vkCmdBindVertexBuffers2", reinterpret_cast<PFN_vkVoidFunction>(CmdBindVertexBuffers2) },
@@ -802,6 +842,11 @@
         { "vkCmdPipelineBarrier", reinterpret_cast<PFN_vkVoidFunction>(CmdPipelineBarrier) },
         { "vkCmdPipelineBarrier2", reinterpret_cast<PFN_vkVoidFunction>(CmdPipelineBarrier2) },
         { "vkCmdPushConstants", reinterpret_cast<PFN_vkVoidFunction>(CmdPushConstants) },
+        { "vkCmdPushConstants2", reinterpret_cast<PFN_vkVoidFunction>(CmdPushConstants2) },
+        { "vkCmdPushDescriptorSet", reinterpret_cast<PFN_vkVoidFunction>(CmdPushDescriptorSet) },
+        { "vkCmdPushDescriptorSet2", reinterpret_cast<PFN_vkVoidFunction>(CmdPushDescriptorSet2) },
+        { "vkCmdPushDescriptorSetWithTemplate", reinterpret_cast<PFN_vkVoidFunction>(CmdPushDescriptorSetWithTemplate) },
+        { "vkCmdPushDescriptorSetWithTemplate2", reinterpret_cast<PFN_vkVoidFunction>(CmdPushDescriptorSetWithTemplate2) },
         { "vkCmdResetEvent", reinterpret_cast<PFN_vkVoidFunction>(CmdResetEvent) },
         { "vkCmdResetEvent2", reinterpret_cast<PFN_vkVoidFunction>(CmdResetEvent2) },
         { "vkCmdResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(CmdResetQueryPool) },
@@ -820,10 +865,13 @@
         { "vkCmdSetEvent", reinterpret_cast<PFN_vkVoidFunction>(CmdSetEvent) },
         { "vkCmdSetEvent2", reinterpret_cast<PFN_vkVoidFunction>(CmdSetEvent2) },
         { "vkCmdSetFrontFace", reinterpret_cast<PFN_vkVoidFunction>(CmdSetFrontFace) },
+        { "vkCmdSetLineStipple", reinterpret_cast<PFN_vkVoidFunction>(CmdSetLineStipple) },
         { "vkCmdSetLineWidth", reinterpret_cast<PFN_vkVoidFunction>(CmdSetLineWidth) },
         { "vkCmdSetPrimitiveRestartEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetPrimitiveRestartEnable) },
         { "vkCmdSetPrimitiveTopology", reinterpret_cast<PFN_vkVoidFunction>(CmdSetPrimitiveTopology) },
         { "vkCmdSetRasterizerDiscardEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetRasterizerDiscardEnable) },
+        { "vkCmdSetRenderingAttachmentLocations", reinterpret_cast<PFN_vkVoidFunction>(CmdSetRenderingAttachmentLocations) },
+        { "vkCmdSetRenderingInputAttachmentIndices", reinterpret_cast<PFN_vkVoidFunction>(CmdSetRenderingInputAttachmentIndices) },
         { "vkCmdSetScissor", reinterpret_cast<PFN_vkVoidFunction>(CmdSetScissor) },
         { "vkCmdSetScissorWithCount", reinterpret_cast<PFN_vkVoidFunction>(CmdSetScissorWithCount) },
         { "vkCmdSetStencilCompareMask", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilCompareMask) },
@@ -838,6 +886,9 @@
         { "vkCmdWaitEvents2", reinterpret_cast<PFN_vkVoidFunction>(CmdWaitEvents2) },
         { "vkCmdWriteTimestamp", reinterpret_cast<PFN_vkVoidFunction>(CmdWriteTimestamp) },
         { "vkCmdWriteTimestamp2", reinterpret_cast<PFN_vkVoidFunction>(CmdWriteTimestamp2) },
+        { "vkCopyImageToImage", reinterpret_cast<PFN_vkVoidFunction>(CopyImageToImage) },
+        { "vkCopyImageToMemory", reinterpret_cast<PFN_vkVoidFunction>(CopyImageToMemory) },
+        { "vkCopyMemoryToImage", reinterpret_cast<PFN_vkVoidFunction>(CopyMemoryToImage) },
         { "vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(CreateBuffer) },
         { "vkCreateBufferView", reinterpret_cast<PFN_vkVoidFunction>(CreateBufferView) },
         { "vkCreateCommandPool", reinterpret_cast<PFN_vkVoidFunction>(CreateCommandPool) },
@@ -911,6 +962,7 @@
         { "vkGetDeviceGroupSurfacePresentModesKHR", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceGroupSurfacePresentModesKHR) },
         { "vkGetDeviceImageMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceImageMemoryRequirements) },
         { "vkGetDeviceImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceImageSparseMemoryRequirements) },
+        { "vkGetDeviceImageSubresourceLayout", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceImageSubresourceLayout) },
         { "vkGetDeviceMemoryCommitment", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceMemoryCommitment) },
         { "vkGetDeviceMemoryOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceMemoryOpaqueCaptureAddress) },
         { "vkGetDeviceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceProcAddr) },
@@ -923,16 +975,19 @@
         { "vkGetImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetImageSparseMemoryRequirements) },
         { "vkGetImageSparseMemoryRequirements2", reinterpret_cast<PFN_vkVoidFunction>(GetImageSparseMemoryRequirements2) },
         { "vkGetImageSubresourceLayout", reinterpret_cast<PFN_vkVoidFunction>(GetImageSubresourceLayout) },
+        { "vkGetImageSubresourceLayout2", reinterpret_cast<PFN_vkVoidFunction>(GetImageSubresourceLayout2) },
         { "vkGetInstanceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(GetInstanceProcAddr) },
         { "vkGetMemoryAndroidHardwareBufferANDROID", reinterpret_cast<PFN_vkVoidFunction>(GetMemoryAndroidHardwareBufferANDROID) },
         { "vkGetPipelineCacheData", reinterpret_cast<PFN_vkVoidFunction>(GetPipelineCacheData) },
         { "vkGetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(GetPrivateData) },
         { "vkGetQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(GetQueryPoolResults) },
         { "vkGetRenderAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(GetRenderAreaGranularity) },
+        { "vkGetRenderingAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(GetRenderingAreaGranularity) },
         { "vkGetSemaphoreCounterValue", reinterpret_cast<PFN_vkVoidFunction>(GetSemaphoreCounterValue) },
         { "vkGetSwapchainImagesKHR", reinterpret_cast<PFN_vkVoidFunction>(GetSwapchainImagesKHR) },
         { "vkInvalidateMappedMemoryRanges", reinterpret_cast<PFN_vkVoidFunction>(InvalidateMappedMemoryRanges) },
         { "vkMapMemory", reinterpret_cast<PFN_vkVoidFunction>(MapMemory) },
+        { "vkMapMemory2", reinterpret_cast<PFN_vkVoidFunction>(MapMemory2) },
         { "vkMergePipelineCaches", reinterpret_cast<PFN_vkVoidFunction>(MergePipelineCaches) },
         { "vkQueueBindSparse", reinterpret_cast<PFN_vkVoidFunction>(QueueBindSparse) },
         { "vkQueuePresentKHR", reinterpret_cast<PFN_vkVoidFunction>(QueuePresentKHR) },
@@ -948,8 +1003,10 @@
         { "vkSetEvent", reinterpret_cast<PFN_vkVoidFunction>(SetEvent) },
         { "vkSetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(SetPrivateData) },
         { "vkSignalSemaphore", reinterpret_cast<PFN_vkVoidFunction>(SignalSemaphore) },
+        { "vkTransitionImageLayout", reinterpret_cast<PFN_vkVoidFunction>(TransitionImageLayout) },
         { "vkTrimCommandPool", reinterpret_cast<PFN_vkVoidFunction>(TrimCommandPool) },
         { "vkUnmapMemory", reinterpret_cast<PFN_vkVoidFunction>(UnmapMemory) },
+        { "vkUnmapMemory2", reinterpret_cast<PFN_vkVoidFunction>(UnmapMemory2) },
         { "vkUpdateDescriptorSetWithTemplate", reinterpret_cast<PFN_vkVoidFunction>(UpdateDescriptorSetWithTemplate) },
         { "vkUpdateDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(UpdateDescriptorSets) },
         { "vkWaitForFences", reinterpret_cast<PFN_vkVoidFunction>(WaitForFences) },
@@ -1273,6 +1330,10 @@
     GetData(device).dispatch.GetRenderAreaGranularity(device, renderPass, pGranularity);
 }
 
+VKAPI_ATTR void GetRenderingAreaGranularity(VkDevice device, const VkRenderingAreaInfo* pRenderingAreaInfo, VkExtent2D* pGranularity) {
+    GetData(device).dispatch.GetRenderingAreaGranularity(device, pRenderingAreaInfo, pGranularity);
+}
+
 VKAPI_ATTR VkResult CreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool) {
     return GetData(device).dispatch.CreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool);
 }
@@ -1553,6 +1614,10 @@
     GetData(physicalDevice).dispatch.GetPhysicalDeviceSparseImageFormatProperties2(physicalDevice, pFormatInfo, pPropertyCount, pProperties);
 }
 
+VKAPI_ATTR void CmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites) {
+    GetData(commandBuffer).dispatch.CmdPushDescriptorSet(commandBuffer, pipelineBindPoint, layout, set, descriptorWriteCount, pDescriptorWrites);
+}
+
 VKAPI_ATTR void TrimCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags) {
     GetData(device).dispatch.TrimCommandPool(device, commandPool, flags);
 }
@@ -1621,6 +1686,10 @@
     GetData(device).dispatch.UpdateDescriptorSetWithTemplate(device, descriptorSet, descriptorUpdateTemplate, pData);
 }
 
+VKAPI_ATTR void CmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void* pData) {
+    GetData(commandBuffer).dispatch.CmdPushDescriptorSetWithTemplate(commandBuffer, descriptorUpdateTemplate, layout, set, pData);
+}
+
 VKAPI_ATTR void GetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
     GetData(device).dispatch.GetBufferMemoryRequirements2(device, pInfo, pMemoryRequirements);
 }
@@ -1717,6 +1786,10 @@
     return GetData(device).dispatch.GetDeviceMemoryOpaqueCaptureAddress(device, pInfo);
 }
 
+VKAPI_ATTR void CmdSetLineStipple(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor, uint16_t lineStipplePattern) {
+    GetData(commandBuffer).dispatch.CmdSetLineStipple(commandBuffer, lineStippleFactor, lineStipplePattern);
+}
+
 VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties) {
     return GetData(physicalDevice).dispatch.GetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties);
 }
@@ -1741,6 +1814,10 @@
     GetData(commandBuffer).dispatch.CmdSetScissorWithCount(commandBuffer, scissorCount, pScissors);
 }
 
+VKAPI_ATTR void CmdBindIndexBuffer2(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkIndexType indexType) {
+    GetData(commandBuffer).dispatch.CmdBindIndexBuffer2(commandBuffer, buffer, offset, size, indexType);
+}
+
 VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides) {
     GetData(commandBuffer).dispatch.CmdBindVertexBuffers2(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets, pSizes, pStrides);
 }
@@ -1845,6 +1922,22 @@
     GetData(commandBuffer).dispatch.CmdWriteTimestamp2(commandBuffer, stage, queryPool, query);
 }
 
+VKAPI_ATTR VkResult CopyMemoryToImage(VkDevice device, const VkCopyMemoryToImageInfo* pCopyMemoryToImageInfo) {
+    return GetData(device).dispatch.CopyMemoryToImage(device, pCopyMemoryToImageInfo);
+}
+
+VKAPI_ATTR VkResult CopyImageToMemory(VkDevice device, const VkCopyImageToMemoryInfo* pCopyImageToMemoryInfo) {
+    return GetData(device).dispatch.CopyImageToMemory(device, pCopyImageToMemoryInfo);
+}
+
+VKAPI_ATTR VkResult CopyImageToImage(VkDevice device, const VkCopyImageToImageInfo* pCopyImageToImageInfo) {
+    return GetData(device).dispatch.CopyImageToImage(device, pCopyImageToImageInfo);
+}
+
+VKAPI_ATTR VkResult TransitionImageLayout(VkDevice device, uint32_t transitionCount, const VkHostImageLayoutTransitionInfo* pTransitions) {
+    return GetData(device).dispatch.TransitionImageLayout(device, transitionCount, pTransitions);
+}
+
 VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
     GetData(commandBuffer).dispatch.CmdBeginRendering(commandBuffer, pRenderingInfo);
 }
@@ -1853,6 +1946,46 @@
     GetData(commandBuffer).dispatch.CmdEndRendering(commandBuffer);
 }
 
+VKAPI_ATTR void GetImageSubresourceLayout2(VkDevice device, VkImage image, const VkImageSubresource2* pSubresource, VkSubresourceLayout2* pLayout) {
+    GetData(device).dispatch.GetImageSubresourceLayout2(device, image, pSubresource, pLayout);
+}
+
+VKAPI_ATTR void GetDeviceImageSubresourceLayout(VkDevice device, const VkDeviceImageSubresourceInfo* pInfo, VkSubresourceLayout2* pLayout) {
+    GetData(device).dispatch.GetDeviceImageSubresourceLayout(device, pInfo, pLayout);
+}
+
+VKAPI_ATTR VkResult MapMemory2(VkDevice device, const VkMemoryMapInfo* pMemoryMapInfo, void** ppData) {
+    return GetData(device).dispatch.MapMemory2(device, pMemoryMapInfo, ppData);
+}
+
+VKAPI_ATTR VkResult UnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* pMemoryUnmapInfo) {
+    return GetData(device).dispatch.UnmapMemory2(device, pMemoryUnmapInfo);
+}
+
+VKAPI_ATTR void CmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const VkBindDescriptorSetsInfo* pBindDescriptorSetsInfo) {
+    GetData(commandBuffer).dispatch.CmdBindDescriptorSets2(commandBuffer, pBindDescriptorSetsInfo);
+}
+
+VKAPI_ATTR void CmdPushConstants2(VkCommandBuffer commandBuffer, const VkPushConstantsInfo* pPushConstantsInfo) {
+    GetData(commandBuffer).dispatch.CmdPushConstants2(commandBuffer, pPushConstantsInfo);
+}
+
+VKAPI_ATTR void CmdPushDescriptorSet2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetInfo* pPushDescriptorSetInfo) {
+    GetData(commandBuffer).dispatch.CmdPushDescriptorSet2(commandBuffer, pPushDescriptorSetInfo);
+}
+
+VKAPI_ATTR void CmdPushDescriptorSetWithTemplate2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfo* pPushDescriptorSetWithTemplateInfo) {
+    GetData(commandBuffer).dispatch.CmdPushDescriptorSetWithTemplate2(commandBuffer, pPushDescriptorSetWithTemplateInfo);
+}
+
+VKAPI_ATTR void CmdSetRenderingAttachmentLocations(VkCommandBuffer commandBuffer, const VkRenderingAttachmentLocationInfo* pLocationInfo) {
+    GetData(commandBuffer).dispatch.CmdSetRenderingAttachmentLocations(commandBuffer, pLocationInfo);
+}
+
+VKAPI_ATTR void CmdSetRenderingInputAttachmentIndices(VkCommandBuffer commandBuffer, const VkRenderingInputAttachmentIndexInfo* pInputAttachmentIndexInfo) {
+    GetData(commandBuffer).dispatch.CmdSetRenderingInputAttachmentIndices(commandBuffer, pInputAttachmentIndexInfo);
+}
+
 
 }  // anonymous namespace
 
@@ -2299,6 +2432,11 @@
 }
 
 __attribute__((visibility("default")))
+VKAPI_ATTR void vkGetRenderingAreaGranularity(VkDevice device, const VkRenderingAreaInfo* pRenderingAreaInfo, VkExtent2D* pGranularity) {
+    vulkan::api::GetRenderingAreaGranularity(device, pRenderingAreaInfo, pGranularity);
+}
+
+__attribute__((visibility("default")))
 VKAPI_ATTR VkResult vkCreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool) {
     return vulkan::api::CreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool);
 }
@@ -2649,6 +2787,11 @@
 }
 
 __attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites) {
+    vulkan::api::CmdPushDescriptorSet(commandBuffer, pipelineBindPoint, layout, set, descriptorWriteCount, pDescriptorWrites);
+}
+
+__attribute__((visibility("default")))
 VKAPI_ATTR void vkTrimCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags) {
     vulkan::api::TrimCommandPool(device, commandPool, flags);
 }
@@ -2734,6 +2877,11 @@
 }
 
 __attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void* pData) {
+    vulkan::api::CmdPushDescriptorSetWithTemplate(commandBuffer, descriptorUpdateTemplate, layout, set, pData);
+}
+
+__attribute__((visibility("default")))
 VKAPI_ATTR void vkGetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
     vulkan::api::GetBufferMemoryRequirements2(device, pInfo, pMemoryRequirements);
 }
@@ -2854,6 +3002,11 @@
 }
 
 __attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetLineStipple(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor, uint16_t lineStipplePattern) {
+    vulkan::api::CmdSetLineStipple(commandBuffer, lineStippleFactor, lineStipplePattern);
+}
+
+__attribute__((visibility("default")))
 VKAPI_ATTR VkResult vkGetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties) {
     return vulkan::api::GetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties);
 }
@@ -2884,6 +3037,11 @@
 }
 
 __attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdBindIndexBuffer2(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkIndexType indexType) {
+    vulkan::api::CmdBindIndexBuffer2(commandBuffer, buffer, offset, size, indexType);
+}
+
+__attribute__((visibility("default")))
 VKAPI_ATTR void vkCmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides) {
     vulkan::api::CmdBindVertexBuffers2(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets, pSizes, pStrides);
 }
@@ -3014,6 +3172,26 @@
 }
 
 __attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkCopyMemoryToImage(VkDevice device, const VkCopyMemoryToImageInfo* pCopyMemoryToImageInfo) {
+    return vulkan::api::CopyMemoryToImage(device, pCopyMemoryToImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkCopyImageToMemory(VkDevice device, const VkCopyImageToMemoryInfo* pCopyImageToMemoryInfo) {
+    return vulkan::api::CopyImageToMemory(device, pCopyImageToMemoryInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkCopyImageToImage(VkDevice device, const VkCopyImageToImageInfo* pCopyImageToImageInfo) {
+    return vulkan::api::CopyImageToImage(device, pCopyImageToImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkTransitionImageLayout(VkDevice device, uint32_t transitionCount, const VkHostImageLayoutTransitionInfo* pTransitions) {
+    return vulkan::api::TransitionImageLayout(device, transitionCount, pTransitions);
+}
+
+__attribute__((visibility("default")))
 VKAPI_ATTR void vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
     vulkan::api::CmdBeginRendering(commandBuffer, pRenderingInfo);
 }
@@ -3023,4 +3201,54 @@
     vulkan::api::CmdEndRendering(commandBuffer);
 }
 
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkGetImageSubresourceLayout2(VkDevice device, VkImage image, const VkImageSubresource2* pSubresource, VkSubresourceLayout2* pLayout) {
+    vulkan::api::GetImageSubresourceLayout2(device, image, pSubresource, pLayout);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkGetDeviceImageSubresourceLayout(VkDevice device, const VkDeviceImageSubresourceInfo* pInfo, VkSubresourceLayout2* pLayout) {
+    vulkan::api::GetDeviceImageSubresourceLayout(device, pInfo, pLayout);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkMapMemory2(VkDevice device, const VkMemoryMapInfo* pMemoryMapInfo, void** ppData) {
+    return vulkan::api::MapMemory2(device, pMemoryMapInfo, ppData);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkUnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* pMemoryUnmapInfo) {
+    return vulkan::api::UnmapMemory2(device, pMemoryUnmapInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const VkBindDescriptorSetsInfo* pBindDescriptorSetsInfo) {
+    vulkan::api::CmdBindDescriptorSets2(commandBuffer, pBindDescriptorSetsInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdPushConstants2(VkCommandBuffer commandBuffer, const VkPushConstantsInfo* pPushConstantsInfo) {
+    vulkan::api::CmdPushConstants2(commandBuffer, pPushConstantsInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdPushDescriptorSet2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetInfo* pPushDescriptorSetInfo) {
+    vulkan::api::CmdPushDescriptorSet2(commandBuffer, pPushDescriptorSetInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdPushDescriptorSetWithTemplate2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfo* pPushDescriptorSetWithTemplateInfo) {
+    vulkan::api::CmdPushDescriptorSetWithTemplate2(commandBuffer, pPushDescriptorSetWithTemplateInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetRenderingAttachmentLocations(VkCommandBuffer commandBuffer, const VkRenderingAttachmentLocationInfo* pLocationInfo) {
+    vulkan::api::CmdSetRenderingAttachmentLocations(commandBuffer, pLocationInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetRenderingInputAttachmentIndices(VkCommandBuffer commandBuffer, const VkRenderingInputAttachmentIndexInfo* pInputAttachmentIndexInfo) {
+    vulkan::api::CmdSetRenderingInputAttachmentIndices(commandBuffer, pInputAttachmentIndexInfo);
+}
+
 // clang-format on
diff --git a/vulkan/libvulkan/api_gen.h b/vulkan/libvulkan/api_gen.h
index b468a89..17dc62f 100644
--- a/vulkan/libvulkan/api_gen.h
+++ b/vulkan/libvulkan/api_gen.h
@@ -139,6 +139,7 @@
     PFN_vkCreateRenderPass CreateRenderPass;
     PFN_vkDestroyRenderPass DestroyRenderPass;
     PFN_vkGetRenderAreaGranularity GetRenderAreaGranularity;
+    PFN_vkGetRenderingAreaGranularity GetRenderingAreaGranularity;
     PFN_vkCreateCommandPool CreateCommandPool;
     PFN_vkDestroyCommandPool DestroyCommandPool;
     PFN_vkResetCommandPool ResetCommandPool;
@@ -196,6 +197,7 @@
     PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR;
     PFN_vkAcquireNextImageKHR AcquireNextImageKHR;
     PFN_vkQueuePresentKHR QueuePresentKHR;
+    PFN_vkCmdPushDescriptorSet CmdPushDescriptorSet;
     PFN_vkTrimCommandPool TrimCommandPool;
     PFN_vkGetDeviceGroupPeerMemoryFeatures GetDeviceGroupPeerMemoryFeatures;
     PFN_vkBindBufferMemory2 BindBufferMemory2;
@@ -208,6 +210,7 @@
     PFN_vkCreateDescriptorUpdateTemplate CreateDescriptorUpdateTemplate;
     PFN_vkDestroyDescriptorUpdateTemplate DestroyDescriptorUpdateTemplate;
     PFN_vkUpdateDescriptorSetWithTemplate UpdateDescriptorSetWithTemplate;
+    PFN_vkCmdPushDescriptorSetWithTemplate CmdPushDescriptorSetWithTemplate;
     PFN_vkGetBufferMemoryRequirements2 GetBufferMemoryRequirements2;
     PFN_vkGetImageMemoryRequirements2 GetImageMemoryRequirements2;
     PFN_vkGetImageSparseMemoryRequirements2 GetImageSparseMemoryRequirements2;
@@ -232,11 +235,13 @@
     PFN_vkGetBufferOpaqueCaptureAddress GetBufferOpaqueCaptureAddress;
     PFN_vkGetBufferDeviceAddress GetBufferDeviceAddress;
     PFN_vkGetDeviceMemoryOpaqueCaptureAddress GetDeviceMemoryOpaqueCaptureAddress;
+    PFN_vkCmdSetLineStipple CmdSetLineStipple;
     PFN_vkCmdSetCullMode CmdSetCullMode;
     PFN_vkCmdSetFrontFace CmdSetFrontFace;
     PFN_vkCmdSetPrimitiveTopology CmdSetPrimitiveTopology;
     PFN_vkCmdSetViewportWithCount CmdSetViewportWithCount;
     PFN_vkCmdSetScissorWithCount CmdSetScissorWithCount;
+    PFN_vkCmdBindIndexBuffer2 CmdBindIndexBuffer2;
     PFN_vkCmdBindVertexBuffers2 CmdBindVertexBuffers2;
     PFN_vkCmdSetDepthTestEnable CmdSetDepthTestEnable;
     PFN_vkCmdSetDepthWriteEnable CmdSetDepthWriteEnable;
@@ -263,8 +268,22 @@
     PFN_vkCmdPipelineBarrier2 CmdPipelineBarrier2;
     PFN_vkQueueSubmit2 QueueSubmit2;
     PFN_vkCmdWriteTimestamp2 CmdWriteTimestamp2;
+    PFN_vkCopyMemoryToImage CopyMemoryToImage;
+    PFN_vkCopyImageToMemory CopyImageToMemory;
+    PFN_vkCopyImageToImage CopyImageToImage;
+    PFN_vkTransitionImageLayout TransitionImageLayout;
     PFN_vkCmdBeginRendering CmdBeginRendering;
     PFN_vkCmdEndRendering CmdEndRendering;
+    PFN_vkGetImageSubresourceLayout2 GetImageSubresourceLayout2;
+    PFN_vkGetDeviceImageSubresourceLayout GetDeviceImageSubresourceLayout;
+    PFN_vkMapMemory2 MapMemory2;
+    PFN_vkUnmapMemory2 UnmapMemory2;
+    PFN_vkCmdBindDescriptorSets2 CmdBindDescriptorSets2;
+    PFN_vkCmdPushConstants2 CmdPushConstants2;
+    PFN_vkCmdPushDescriptorSet2 CmdPushDescriptorSet2;
+    PFN_vkCmdPushDescriptorSetWithTemplate2 CmdPushDescriptorSetWithTemplate2;
+    PFN_vkCmdSetRenderingAttachmentLocations CmdSetRenderingAttachmentLocations;
+    PFN_vkCmdSetRenderingInputAttachmentIndices CmdSetRenderingInputAttachmentIndices;
     // clang-format on
 };
 
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 01436db..7d0f545 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -398,7 +398,7 @@
                                      const VkAllocationCallbacks& allocator)
     : is_instance_(true),
       allocator_(allocator),
-      loader_api_version_(VK_API_VERSION_1_3),
+      loader_api_version_(flags::vulkan_1_4_instance_api() ? VK_API_VERSION_1_4 : VK_API_VERSION_1_3),
       icd_api_version_(icd_api_version),
       physical_dev_(VK_NULL_HANDLE),
       instance_info_(create_info),
@@ -410,7 +410,7 @@
                                      const VkAllocationCallbacks& allocator)
     : is_instance_(false),
       allocator_(allocator),
-      loader_api_version_(VK_API_VERSION_1_3),
+      loader_api_version_(flags::vulkan_1_4_instance_api() ? VK_API_VERSION_1_4 : VK_API_VERSION_1_3),
       icd_api_version_(icd_api_version),
       physical_dev_(physical_dev),
       dev_info_(create_info),
@@ -552,6 +552,10 @@
         is_instance_ ? loader_api_version_
                      : std::min(icd_api_version_, loader_api_version_);
     switch (api_version) {
+        case VK_API_VERSION_1_4:
+            hook_extensions_.set(ProcHook::EXTENSION_CORE_1_4);
+            hal_extensions_.set(ProcHook::EXTENSION_CORE_1_4);
+            [[clang::fallthrough]];
         case VK_API_VERSION_1_3:
             hook_extensions_.set(ProcHook::EXTENSION_CORE_1_3);
             hal_extensions_.set(ProcHook::EXTENSION_CORE_1_3);
@@ -701,6 +705,7 @@
             case ProcHook::EXTENSION_CORE_1_1:
             case ProcHook::EXTENSION_CORE_1_2:
             case ProcHook::EXTENSION_CORE_1_3:
+            case ProcHook::EXTENSION_CORE_1_4:
             case ProcHook::EXTENSION_COUNT:
                 // Device and meta extensions. If we ever get here it's a bug in
                 // our code. But enumerating them lets us avoid having a default
@@ -766,6 +771,7 @@
             case ProcHook::EXTENSION_CORE_1_1:
             case ProcHook::EXTENSION_CORE_1_2:
             case ProcHook::EXTENSION_CORE_1_3:
+            case ProcHook::EXTENSION_CORE_1_4:
             case ProcHook::EXTENSION_COUNT:
                 // Instance and meta extensions. If we ever get here it's a bug
                 // in our code. But enumerating them lets us avoid having a
diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h
index 649c0f1..204b16f 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -68,6 +68,7 @@
         EXTENSION_CORE_1_1,
         EXTENSION_CORE_1_2,
         EXTENSION_CORE_1_3,
+        EXTENSION_CORE_1_4,
         EXTENSION_COUNT,
         EXTENSION_UNKNOWN,
     };
diff --git a/vulkan/libvulkan/libvulkan.map.txt b/vulkan/libvulkan/libvulkan.map.txt
index b189c68..ffe46f7 100644
--- a/vulkan/libvulkan/libvulkan.map.txt
+++ b/vulkan/libvulkan/libvulkan.map.txt
@@ -6,32 +6,34 @@
     vkAllocateDescriptorSets;
     vkAllocateMemory;
     vkBeginCommandBuffer;
-    vkBindBufferMemory;
     vkBindBufferMemory2; # introduced=28
-    vkBindImageMemory;
+    vkBindBufferMemory;
     vkBindImageMemory2; # introduced=28
+    vkBindImageMemory;
     vkCmdBeginQuery;
-    vkCmdBeginRendering; # introduced=33
-    vkCmdBeginRenderPass;
     vkCmdBeginRenderPass2; # introduced=31
+    vkCmdBeginRenderPass;
+    vkCmdBeginRendering; # introduced=33
+    vkCmdBindDescriptorSets2; #introduced=36
     vkCmdBindDescriptorSets;
+    vkCmdBindIndexBuffer2; #introduced=36
     vkCmdBindIndexBuffer;
     vkCmdBindPipeline;
-    vkCmdBindVertexBuffers;
     vkCmdBindVertexBuffers2; #introduced=33
-    vkCmdBlitImage;
+    vkCmdBindVertexBuffers;
     vkCmdBlitImage2; #introduced=33
+    vkCmdBlitImage;
     vkCmdClearAttachments;
     vkCmdClearColorImage;
     vkCmdClearDepthStencilImage;
-    vkCmdCopyBuffer;
     vkCmdCopyBuffer2; #introduced=33
-    vkCmdCopyBufferToImage;
+    vkCmdCopyBuffer;
     vkCmdCopyBufferToImage2; #introduced=33
-    vkCmdCopyImage;
+    vkCmdCopyBufferToImage;
     vkCmdCopyImage2; #introduced=33
-    vkCmdCopyImageToBuffer;
+    vkCmdCopyImage;
     vkCmdCopyImageToBuffer2; #introduced=33
+    vkCmdCopyImageToBuffer;
     vkCmdCopyQueryPoolResults;
     vkCmdDispatch;
     vkCmdDispatchBase; # introduced=28
@@ -43,21 +45,26 @@
     vkCmdDrawIndirect;
     vkCmdDrawIndirectCount; # introduced=31
     vkCmdEndQuery;
-    vkCmdEndRendering; #introduced=33
-    vkCmdEndRenderPass;
     vkCmdEndRenderPass2; # introduced=31
+    vkCmdEndRenderPass;
+    vkCmdEndRendering; #introduced=33
     vkCmdExecuteCommands;
     vkCmdFillBuffer;
-    vkCmdNextSubpass;
     vkCmdNextSubpass2; # introduced=31
-    vkCmdPipelineBarrier;
+    vkCmdNextSubpass;
     vkCmdPipelineBarrier2; #introduced=33
+    vkCmdPipelineBarrier;
+    vkCmdPushConstants2; #introduced=36
     vkCmdPushConstants;
-    vkCmdResetEvent;
+    vkCmdPushDescriptorSet2; #introduced=36
+    vkCmdPushDescriptorSet; #introduced=36
+    vkCmdPushDescriptorSetWithTemplate2; #introduced=36
+    vkCmdPushDescriptorSetWithTemplate; #introduced=36
     vkCmdResetEvent2; #introduced=33
+    vkCmdResetEvent;
     vkCmdResetQueryPool;
-    vkCmdResolveImage;
     vkCmdResolveImage2; #introduced=33
+    vkCmdResolveImage;
     vkCmdSetBlendConstants;
     vkCmdSetCullMode; #introduced=33
     vkCmdSetDepthBias;
@@ -68,13 +75,16 @@
     vkCmdSetDepthTestEnable; #introduced=33
     vkCmdSetDepthWriteEnable; #introduced=33
     vkCmdSetDeviceMask; # introduced=28
-    vkCmdSetEvent;
     vkCmdSetEvent2; #introduced=33
+    vkCmdSetEvent;
     vkCmdSetFrontFace; #introduced=33
+    vkCmdSetLineStipple; #introduced=36
     vkCmdSetLineWidth;
     vkCmdSetPrimitiveRestartEnable; #introduced=33
     vkCmdSetPrimitiveTopology; #introduced=33
     vkCmdSetRasterizerDiscardEnable; #introduced=33
+    vkCmdSetRenderingAttachmentLocations; #introduced=36
+    vkCmdSetRenderingInputAttachmentIndices; #introduced=36
     vkCmdSetScissor;
     vkCmdSetScissorWithCount; #introduced=33
     vkCmdSetStencilCompareMask;
@@ -85,10 +95,12 @@
     vkCmdSetViewport;
     vkCmdSetViewportWithCount; #introduced=33
     vkCmdUpdateBuffer;
-    vkCmdWaitEvents;
     vkCmdWaitEvents2; #introduced=33
-    vkCmdWriteTimestamp;
+    vkCmdWaitEvents;
     vkCmdWriteTimestamp2; #introduced=33
+    vkCmdWriteTimestamp;
+    vkCopyImageToMemory; #introduced=36
+    vkCopyMemoryToImage; #introduced=36
     vkCreateAndroidSurfaceKHR;
     vkCreateBuffer;
     vkCreateBufferView;
@@ -109,8 +121,8 @@
     vkCreatePipelineLayout;
     vkCreatePrivateDataSlot; #introduced=33
     vkCreateQueryPool;
-    vkCreateRenderPass;
     vkCreateRenderPass2; # introduced=31
+    vkCreateRenderPass;
     vkCreateSampler;
     vkCreateSamplerYcbcrConversion; # introduced=28
     vkCreateSemaphore;
@@ -156,8 +168,8 @@
     vkFreeMemory;
     vkGetAndroidHardwareBufferPropertiesANDROID; # introduced=28
     vkGetBufferDeviceAddress; # introduced=31
-    vkGetBufferMemoryRequirements;
     vkGetBufferMemoryRequirements2; # introduced=28
+    vkGetBufferMemoryRequirements;
     vkGetBufferOpaqueCaptureAddress; # introduced=31
     vkGetDescriptorSetLayoutSupport; # introduced=28
     vkGetDeviceBufferMemoryRequirements; #introduced=33
@@ -166,39 +178,41 @@
     vkGetDeviceGroupSurfacePresentModesKHR; # introduced=28
     vkGetDeviceImageMemoryRequirements; #introduced=33
     vkGetDeviceImageSparseMemoryRequirements; #introduced=33
+    vkGetDeviceImageSubresourceLayout; #introduced=36
     vkGetDeviceMemoryCommitment;
     vkGetDeviceMemoryOpaqueCaptureAddress; # introduced=31
     vkGetDeviceProcAddr;
-    vkGetDeviceQueue;
     vkGetDeviceQueue2; # introduced=28
+    vkGetDeviceQueue;
     vkGetEventStatus;
     vkGetFenceStatus;
-    vkGetImageMemoryRequirements;
     vkGetImageMemoryRequirements2; # introduced=28
-    vkGetImageSparseMemoryRequirements;
+    vkGetImageMemoryRequirements;
     vkGetImageSparseMemoryRequirements2; # introduced=28
-    vkGetImageSubresourceLayout;
+    vkGetImageSparseMemoryRequirements;
+    vkGetImageSubresourceLayout2; #introduced=36
     vkGetImageSubresourceLayout2EXT; # introduced=UpsideDownCake
+    vkGetImageSubresourceLayout;
     vkGetInstanceProcAddr;
     vkGetMemoryAndroidHardwareBufferANDROID; # introduced=28
     vkGetPhysicalDeviceExternalBufferProperties; # introduced=28
     vkGetPhysicalDeviceExternalFenceProperties; # introduced=28
     vkGetPhysicalDeviceExternalSemaphoreProperties; # introduced=28
-    vkGetPhysicalDeviceFeatures;
     vkGetPhysicalDeviceFeatures2; # introduced=28
-    vkGetPhysicalDeviceFormatProperties;
+    vkGetPhysicalDeviceFeatures;
     vkGetPhysicalDeviceFormatProperties2; # introduced=28
-    vkGetPhysicalDeviceImageFormatProperties;
+    vkGetPhysicalDeviceFormatProperties;
     vkGetPhysicalDeviceImageFormatProperties2; # introduced=28
-    vkGetPhysicalDeviceMemoryProperties;
+    vkGetPhysicalDeviceImageFormatProperties;
     vkGetPhysicalDeviceMemoryProperties2; # introduced=28
+    vkGetPhysicalDeviceMemoryProperties;
     vkGetPhysicalDevicePresentRectanglesKHR; # introduced=28
-    vkGetPhysicalDeviceProperties;
     vkGetPhysicalDeviceProperties2; # introduced=28
-    vkGetPhysicalDeviceQueueFamilyProperties;
+    vkGetPhysicalDeviceProperties;
     vkGetPhysicalDeviceQueueFamilyProperties2; # introduced=28
-    vkGetPhysicalDeviceSparseImageFormatProperties;
+    vkGetPhysicalDeviceQueueFamilyProperties;
     vkGetPhysicalDeviceSparseImageFormatProperties2; # introduced=28
+    vkGetPhysicalDeviceSparseImageFormatProperties;
     vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
     vkGetPhysicalDeviceSurfaceFormatsKHR;
     vkGetPhysicalDeviceSurfacePresentModesKHR;
@@ -208,15 +222,17 @@
     vkGetPrivateData; #introduced=33
     vkGetQueryPoolResults;
     vkGetRenderAreaGranularity;
+    vkGetRenderingAreaGranularity; #introduced=36
     vkGetSemaphoreCounterValue; # introduced=31
     vkGetSwapchainImagesKHR;
     vkInvalidateMappedMemoryRanges;
+    vkMapMemory2; #introduced=36
     vkMapMemory;
     vkMergePipelineCaches;
     vkQueueBindSparse;
     vkQueuePresentKHR;
-    vkQueueSubmit;
     vkQueueSubmit2; #introduced=33
+    vkQueueSubmit;
     vkQueueWaitIdle;
     vkResetCommandBuffer;
     vkResetCommandPool;
@@ -227,10 +243,12 @@
     vkSetEvent;
     vkSetPrivateData; # introduced=33
     vkSignalSemaphore; # introduced=31
+    vkTransitionImageLayout; #introduced=36
     vkTrimCommandPool; # introduced=28
+    vkUnmapMemory2; #introduced=36
     vkUnmapMemory;
-    vkUpdateDescriptorSets;
     vkUpdateDescriptorSetWithTemplate; # introduced=28
+    vkUpdateDescriptorSets;
     vkWaitForFences;
     vkWaitSemaphores; # introduced=31
   local:
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index 973e71c..48de3d6 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -1767,6 +1767,69 @@
     return VK_SUCCESS;
 }
 
+void GetRenderingAreaGranularity(VkDevice device, const VkRenderingAreaInfo* pRenderingAreaInfo, VkExtent2D* pGranularity) {
+}
+
+void CmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites) {
+}
+
+void CmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void* pData) {
+}
+
+void CmdSetLineStipple(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor, uint16_t lineStipplePattern) {
+}
+
+void CmdBindIndexBuffer2(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkIndexType indexType) {
+}
+
+VkResult CopyMemoryToImage(VkDevice device, const VkCopyMemoryToImageInfo* pCopyMemoryToImageInfo) {
+    return VK_SUCCESS;
+}
+
+VkResult CopyImageToMemory(VkDevice device, const VkCopyImageToMemoryInfo* pCopyImageToMemoryInfo) {
+    return VK_SUCCESS;
+}
+
+VkResult CopyImageToImage(VkDevice device, const VkCopyImageToImageInfo* pCopyImageToImageInfo) {
+    return VK_SUCCESS;
+}
+
+VkResult TransitionImageLayout(VkDevice device, uint32_t transitionCount, const VkHostImageLayoutTransitionInfo* pTransitions) {
+    return VK_SUCCESS;
+}
+
+void GetImageSubresourceLayout2(VkDevice device, VkImage image, const VkImageSubresource2* pSubresource, VkSubresourceLayout2* pLayout) {
+}
+
+void GetDeviceImageSubresourceLayout(VkDevice device, const VkDeviceImageSubresourceInfo* pInfo, VkSubresourceLayout2* pLayout) {
+}
+
+VkResult MapMemory2(VkDevice device, const VkMemoryMapInfo* pMemoryMapInfo, void** ppData) {
+    return VK_SUCCESS;
+}
+
+VkResult UnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* pMemoryUnmapInfo) {
+    return VK_SUCCESS;
+}
+
+void CmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const VkBindDescriptorSetsInfo* pBindDescriptorSetsInfo) {
+}
+
+void CmdPushConstants2(VkCommandBuffer commandBuffer, const VkPushConstantsInfo* pPushConstantsInfo) {
+}
+
+void CmdPushDescriptorSet2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetInfo* pPushDescriptorSetInfo) {
+}
+
+void CmdPushDescriptorSetWithTemplate2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfo* pPushDescriptorSetWithTemplateInfo) {
+}
+
+void CmdSetRenderingAttachmentLocations(VkCommandBuffer commandBuffer, const VkRenderingAttachmentLocationInfo* pLocationInfo) {
+}
+
+void CmdSetRenderingInputAttachmentIndices(VkCommandBuffer commandBuffer, const VkRenderingInputAttachmentIndexInfo* pInputAttachmentIndexInfo) {
+}
+
 #pragma clang diagnostic pop
 // clang-format on
 
diff --git a/vulkan/nulldrv/null_driver_gen.cpp b/vulkan/nulldrv/null_driver_gen.cpp
index 40a45af..30967c2 100644
--- a/vulkan/nulldrv/null_driver_gen.cpp
+++ b/vulkan/nulldrv/null_driver_gen.cpp
@@ -75,7 +75,9 @@
     {"vkCmdBeginRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginRenderPass2>(CmdBeginRenderPass2))},
     {"vkCmdBeginRendering", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginRendering>(CmdBeginRendering))},
     {"vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindDescriptorSets>(CmdBindDescriptorSets))},
+    {"vkCmdBindDescriptorSets2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindDescriptorSets2>(CmdBindDescriptorSets2))},
     {"vkCmdBindIndexBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindIndexBuffer>(CmdBindIndexBuffer))},
+    {"vkCmdBindIndexBuffer2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindIndexBuffer2>(CmdBindIndexBuffer2))},
     {"vkCmdBindPipeline", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindPipeline>(CmdBindPipeline))},
     {"vkCmdBindVertexBuffers", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindVertexBuffers>(CmdBindVertexBuffers))},
     {"vkCmdBindVertexBuffers2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindVertexBuffers2>(CmdBindVertexBuffers2))},
@@ -113,6 +115,11 @@
     {"vkCmdPipelineBarrier", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPipelineBarrier>(CmdPipelineBarrier))},
     {"vkCmdPipelineBarrier2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPipelineBarrier2>(CmdPipelineBarrier2))},
     {"vkCmdPushConstants", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPushConstants>(CmdPushConstants))},
+    {"vkCmdPushConstants2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPushConstants2>(CmdPushConstants2))},
+    {"vkCmdPushDescriptorSet", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPushDescriptorSet>(CmdPushDescriptorSet))},
+    {"vkCmdPushDescriptorSet2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPushDescriptorSet2>(CmdPushDescriptorSet2))},
+    {"vkCmdPushDescriptorSetWithTemplate", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPushDescriptorSetWithTemplate>(CmdPushDescriptorSetWithTemplate))},
+    {"vkCmdPushDescriptorSetWithTemplate2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPushDescriptorSetWithTemplate2>(CmdPushDescriptorSetWithTemplate2))},
     {"vkCmdResetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetEvent>(CmdResetEvent))},
     {"vkCmdResetEvent2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetEvent2>(CmdResetEvent2))},
     {"vkCmdResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetQueryPool>(CmdResetQueryPool))},
@@ -131,10 +138,13 @@
     {"vkCmdSetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetEvent>(CmdSetEvent))},
     {"vkCmdSetEvent2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetEvent2>(CmdSetEvent2))},
     {"vkCmdSetFrontFace", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetFrontFace>(CmdSetFrontFace))},
+    {"vkCmdSetLineStipple", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetLineStipple>(CmdSetLineStipple))},
     {"vkCmdSetLineWidth", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetLineWidth>(CmdSetLineWidth))},
     {"vkCmdSetPrimitiveRestartEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetPrimitiveRestartEnable>(CmdSetPrimitiveRestartEnable))},
     {"vkCmdSetPrimitiveTopology", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetPrimitiveTopology>(CmdSetPrimitiveTopology))},
     {"vkCmdSetRasterizerDiscardEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetRasterizerDiscardEnable>(CmdSetRasterizerDiscardEnable))},
+    {"vkCmdSetRenderingAttachmentLocations", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetRenderingAttachmentLocations>(CmdSetRenderingAttachmentLocations))},
+    {"vkCmdSetRenderingInputAttachmentIndices", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetRenderingInputAttachmentIndices>(CmdSetRenderingInputAttachmentIndices))},
     {"vkCmdSetScissor", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetScissor>(CmdSetScissor))},
     {"vkCmdSetScissorWithCount", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetScissorWithCount>(CmdSetScissorWithCount))},
     {"vkCmdSetStencilCompareMask", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilCompareMask>(CmdSetStencilCompareMask))},
@@ -149,6 +159,9 @@
     {"vkCmdWaitEvents2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWaitEvents2>(CmdWaitEvents2))},
     {"vkCmdWriteTimestamp", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWriteTimestamp>(CmdWriteTimestamp))},
     {"vkCmdWriteTimestamp2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWriteTimestamp2>(CmdWriteTimestamp2))},
+    {"vkCopyImageToImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCopyImageToImage>(CopyImageToImage))},
+    {"vkCopyImageToMemory", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCopyImageToMemory>(CopyImageToMemory))},
+    {"vkCopyMemoryToImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCopyMemoryToImage>(CopyMemoryToImage))},
     {"vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateBuffer>(CreateBuffer))},
     {"vkCreateBufferView", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateBufferView>(CreateBufferView))},
     {"vkCreateCommandPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateCommandPool>(CreateCommandPool))},
@@ -222,6 +235,7 @@
     {"vkGetDeviceGroupPeerMemoryFeatures", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceGroupPeerMemoryFeatures>(GetDeviceGroupPeerMemoryFeatures))},
     {"vkGetDeviceImageMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceImageMemoryRequirements>(GetDeviceImageMemoryRequirements))},
     {"vkGetDeviceImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceImageSparseMemoryRequirements>(GetDeviceImageSparseMemoryRequirements))},
+    {"vkGetDeviceImageSubresourceLayout", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceImageSubresourceLayout>(GetDeviceImageSubresourceLayout))},
     {"vkGetDeviceMemoryCommitment", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceMemoryCommitment>(GetDeviceMemoryCommitment))},
     {"vkGetDeviceMemoryOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceMemoryOpaqueCaptureAddress>(GetDeviceMemoryOpaqueCaptureAddress))},
     {"vkGetDeviceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceProcAddr>(GetDeviceProcAddr))},
@@ -234,6 +248,7 @@
     {"vkGetImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetImageSparseMemoryRequirements>(GetImageSparseMemoryRequirements))},
     {"vkGetImageSparseMemoryRequirements2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetImageSparseMemoryRequirements2>(GetImageSparseMemoryRequirements2))},
     {"vkGetImageSubresourceLayout", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetImageSubresourceLayout>(GetImageSubresourceLayout))},
+    {"vkGetImageSubresourceLayout2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetImageSubresourceLayout2>(GetImageSubresourceLayout2))},
     {"vkGetInstanceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetInstanceProcAddr>(GetInstanceProcAddr))},
     {"vkGetPhysicalDeviceExternalBufferProperties", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceExternalBufferProperties>(GetPhysicalDeviceExternalBufferProperties))},
     {"vkGetPhysicalDeviceExternalFenceProperties", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceExternalFenceProperties>(GetPhysicalDeviceExternalFenceProperties))},
@@ -264,6 +279,7 @@
     {"vkGetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPrivateData>(GetPrivateData))},
     {"vkGetQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetQueryPoolResults>(GetQueryPoolResults))},
     {"vkGetRenderAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetRenderAreaGranularity>(GetRenderAreaGranularity))},
+    {"vkGetRenderingAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetRenderingAreaGranularity>(GetRenderingAreaGranularity))},
     {"vkGetSemaphoreCounterValue", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSemaphoreCounterValue>(GetSemaphoreCounterValue))},
     {"vkGetSwapchainGrallocUsage2ANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsage2ANDROID>(GetSwapchainGrallocUsage2ANDROID))},
     {"vkGetSwapchainGrallocUsage3ANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsage3ANDROID>(GetSwapchainGrallocUsage3ANDROID))},
@@ -271,6 +287,7 @@
     {"vkGetSwapchainGrallocUsageANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsageANDROID>(GetSwapchainGrallocUsageANDROID))},
     {"vkInvalidateMappedMemoryRanges", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkInvalidateMappedMemoryRanges>(InvalidateMappedMemoryRanges))},
     {"vkMapMemory", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkMapMemory>(MapMemory))},
+    {"vkMapMemory2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkMapMemory2>(MapMemory2))},
     {"vkMergePipelineCaches", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkMergePipelineCaches>(MergePipelineCaches))},
     {"vkQueueBindSparse", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueBindSparse>(QueueBindSparse))},
     {"vkQueueSignalReleaseImageANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueSignalReleaseImageANDROID>(QueueSignalReleaseImageANDROID))},
@@ -286,8 +303,10 @@
     {"vkSetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSetEvent>(SetEvent))},
     {"vkSetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSetPrivateData>(SetPrivateData))},
     {"vkSignalSemaphore", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSignalSemaphore>(SignalSemaphore))},
+    {"vkTransitionImageLayout", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkTransitionImageLayout>(TransitionImageLayout))},
     {"vkTrimCommandPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkTrimCommandPool>(TrimCommandPool))},
     {"vkUnmapMemory", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkUnmapMemory>(UnmapMemory))},
+    {"vkUnmapMemory2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkUnmapMemory2>(UnmapMemory2))},
     {"vkUpdateDescriptorSetWithTemplate", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkUpdateDescriptorSetWithTemplate>(UpdateDescriptorSetWithTemplate))},
     {"vkUpdateDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkUpdateDescriptorSets>(UpdateDescriptorSets))},
     {"vkWaitForFences", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkWaitForFences>(WaitForFences))},
diff --git a/vulkan/nulldrv/null_driver_gen.h b/vulkan/nulldrv/null_driver_gen.h
index 0d1e223..f609e7e 100644
--- a/vulkan/nulldrv/null_driver_gen.h
+++ b/vulkan/nulldrv/null_driver_gen.h
@@ -118,6 +118,7 @@
 VKAPI_ATTR VkResult CreateRenderPass(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
 VKAPI_ATTR void DestroyRenderPass(VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR void GetRenderAreaGranularity(VkDevice device, VkRenderPass renderPass, VkExtent2D* pGranularity);
+VKAPI_ATTR void GetRenderingAreaGranularity(VkDevice device, const VkRenderingAreaInfo* pRenderingAreaInfo, VkExtent2D* pGranularity);
 VKAPI_ATTR VkResult CreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool);
 VKAPI_ATTR void DestroyCommandPool(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR VkResult ResetCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags);
@@ -187,6 +188,7 @@
 VKAPI_ATTR void GetPhysicalDeviceMemoryProperties2KHR(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties);
 VKAPI_ATTR void GetPhysicalDeviceSparseImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties);
 VKAPI_ATTR void GetPhysicalDeviceSparseImageFormatProperties2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties);
+VKAPI_ATTR void CmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites);
 VKAPI_ATTR void TrimCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags);
 VKAPI_ATTR void GetPhysicalDeviceExternalBufferProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties);
 VKAPI_ATTR void GetPhysicalDeviceExternalSemaphoreProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties);
@@ -200,6 +202,7 @@
 VKAPI_ATTR VkResult CreateDescriptorUpdateTemplate(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate);
 VKAPI_ATTR void DestroyDescriptorUpdateTemplate(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR void UpdateDescriptorSetWithTemplate(VkDevice device, VkDescriptorSet descriptorSet, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const void* pData);
+VKAPI_ATTR void CmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void* pData);
 VKAPI_ATTR void GetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
@@ -228,12 +231,14 @@
 VKAPI_ATTR uint64_t GetBufferOpaqueCaptureAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR VkDeviceAddress GetBufferDeviceAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR uint64_t GetDeviceMemoryOpaqueCaptureAddress(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+VKAPI_ATTR void CmdSetLineStipple(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor, uint16_t lineStipplePattern);
 VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties);
 VKAPI_ATTR void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode);
 VKAPI_ATTR void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace);
 VKAPI_ATTR void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology);
 VKAPI_ATTR void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports);
 VKAPI_ATTR void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors);
+VKAPI_ATTR void CmdBindIndexBuffer2(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkIndexType indexType);
 VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides);
 VKAPI_ATTR void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable);
 VKAPI_ATTR void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable);
@@ -260,8 +265,22 @@
 VKAPI_ATTR void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo);
 VKAPI_ATTR VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence);
 VKAPI_ATTR void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query);
+VKAPI_ATTR VkResult CopyMemoryToImage(VkDevice device, const VkCopyMemoryToImageInfo* pCopyMemoryToImageInfo);
+VKAPI_ATTR VkResult CopyImageToMemory(VkDevice device, const VkCopyImageToMemoryInfo* pCopyImageToMemoryInfo);
+VKAPI_ATTR VkResult CopyImageToImage(VkDevice device, const VkCopyImageToImageInfo* pCopyImageToImageInfo);
+VKAPI_ATTR VkResult TransitionImageLayout(VkDevice device, uint32_t transitionCount, const VkHostImageLayoutTransitionInfo* pTransitions);
 VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo);
 VKAPI_ATTR void CmdEndRendering(VkCommandBuffer commandBuffer);
+VKAPI_ATTR void GetImageSubresourceLayout2(VkDevice device, VkImage image, const VkImageSubresource2* pSubresource, VkSubresourceLayout2* pLayout);
+VKAPI_ATTR void GetDeviceImageSubresourceLayout(VkDevice device, const VkDeviceImageSubresourceInfo* pInfo, VkSubresourceLayout2* pLayout);
+VKAPI_ATTR VkResult MapMemory2(VkDevice device, const VkMemoryMapInfo* pMemoryMapInfo, void** ppData);
+VKAPI_ATTR VkResult UnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* pMemoryUnmapInfo);
+VKAPI_ATTR void CmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const VkBindDescriptorSetsInfo* pBindDescriptorSetsInfo);
+VKAPI_ATTR void CmdPushConstants2(VkCommandBuffer commandBuffer, const VkPushConstantsInfo* pPushConstantsInfo);
+VKAPI_ATTR void CmdPushDescriptorSet2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetInfo* pPushDescriptorSetInfo);
+VKAPI_ATTR void CmdPushDescriptorSetWithTemplate2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfo* pPushDescriptorSetWithTemplateInfo);
+VKAPI_ATTR void CmdSetRenderingAttachmentLocations(VkCommandBuffer commandBuffer, const VkRenderingAttachmentLocationInfo* pLocationInfo);
+VKAPI_ATTR void CmdSetRenderingInputAttachmentIndices(VkCommandBuffer commandBuffer, const VkRenderingInputAttachmentIndexInfo* pInputAttachmentIndexInfo);
 // clang-format on
 
 }  // namespace null_driver
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index 6b4cbad..aa1e7f2 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -379,6 +379,9 @@
                 version_dict[cmd_name] = apiversion
 
   for feature in root.iter('feature'):
+    # hack, 'feature' element has multiple meanings.. should be more precise with path match
+    if feature.get('api') is None:
+      continue
     if 'vulkan' not in feature.get('api').split(','):
       continue
 
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index bfb7bd6..9b508aa 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -1050,6 +1050,9 @@
   bool ret = true;
   switch (device->properties.apiVersion ^
           VK_API_VERSION_PATCH(device->properties.apiVersion)) {
+    case VK_API_VERSION_1_4:
+      // TODO: real 1.4 support here
+      FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_3:
       ret &= visitor->Visit("core13", &device->core13);
       FALLTHROUGH_INTENDED;
@@ -1110,6 +1113,8 @@
 inline bool Iterate(Visitor* visitor, VkJsonInstance* instance) {
   bool ret = true;
   switch (instance->api_version ^ VK_API_VERSION_PATCH(instance->api_version)) {
+    case VK_API_VERSION_1_4:
+      FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_3:
       FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_2: