Merge "Remove stray character." into main
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index 2e346bb..70d00d1 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -141,7 +141,7 @@
         }
 
     private:
-        std::function<int(int events)> mCallback;
+        const std::function<int(int events)> mCallback;
     };
     sp<LooperEventCallback> mCallback;
     /**
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 6a248ef..6b45dd3 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -131,8 +131,9 @@
     PLAYER_ID = 1,
     KEYBOARD_BACKLIGHT = 2,
     KEYBOARD_MIC_MUTE = 3,
+    KEYBOARD_VOLUME_MUTE = 4,
 
-    ftl_last = KEYBOARD_MIC_MUTE
+    ftl_last = KEYBOARD_VOLUME_MUTE
 };
 
 enum class InputDeviceLightCapability : uint32_t {
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index a7423b3..5710bbf 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -82,7 +82,6 @@
 
     llndk: {
         symbol_file: "libbinder_ndk.map.txt",
-        export_llndk_headers: ["libvendorsupport_llndk_headers"],
     },
 
     cflags: [
@@ -110,11 +109,9 @@
     ],
 
     header_libs: [
-        "libvendorsupport_llndk_headers",
         "jni_headers",
     ],
     export_header_lib_headers: [
-        "libvendorsupport_llndk_headers",
         "jni_headers",
     ],
 
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index 0ad110e..c6518d8 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -30,16 +30,14 @@
 #include <android/binder_auto_utils.h>
 #include <android/binder_ibinder.h>
 
-#if defined(__ANDROID_VENDOR_API__)
-#include <android/llndk-versioning.h>
-#elif !defined(API_LEVEL_AT_LEAST)
 #if defined(__BIONIC__)
-#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
-    (__builtin_available(android sdk_api_level, *))
+#define API_LEVEL_AT_LEAST(sdk_api_level) __builtin_available(android sdk_api_level, *)
+#elif defined(TRUSTY_USERSPACE)
+// TODO(b/349936395): set to true for Trusty
+#define API_LEVEL_AT_LEAST(sdk_api_level) (false)
 #else
-#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) (true)
+#define API_LEVEL_AT_LEAST(sdk_api_level) (true)
 #endif  // __BIONIC__
-#endif  // __ANDROID_VENDOR_API__
 
 #if __has_include(<android/binder_shell.h>)
 #include <android/binder_shell.h>
@@ -298,9 +296,8 @@
 #endif
 
 #if defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 36
-    if API_LEVEL_AT_LEAST (36, 202504) {
-        if (codeToFunction != nullptr &&
-            (&AIBinder_Class_setTransactionCodeToFunctionNameMap != nullptr)) {
+    if (API_LEVEL_AT_LEAST(36)) {
+        if (codeToFunction != nullptr) {
             AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, codeToFunction,
                                                                functionCount);
         }
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
index 83976b3..f3f3c38 100644
--- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -22,17 +22,14 @@
 #include <set>
 #include <sstream>
 
-// Include llndk-versioning.h only for non-system build as it is not available for NDK headers.
-#if defined(__ANDROID_VENDOR_API__)
-#include <android/llndk-versioning.h>
-#elif !defined(API_LEVEL_AT_LEAST)
 #if defined(__BIONIC__)
-#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
-    (__builtin_available(android sdk_api_level, *))
+#define API_LEVEL_AT_LEAST(sdk_api_level) __builtin_available(android sdk_api_level, *)
+#elif defined(TRUSTY_USERSPACE)
+// TODO(b/349936395): set to true for Trusty
+#define API_LEVEL_AT_LEAST(sdk_api_level) (false)
 #else
-#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) (true)
+#define API_LEVEL_AT_LEAST(sdk_api_level) (true)
 #endif  // __BIONIC__
-#endif  // __ANDROID_VENDOR_API__
 
 namespace aidl::android::os {
 
@@ -44,7 +41,7 @@
 class PersistableBundle {
    public:
     PersistableBundle() noexcept {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             mPBundle = APersistableBundle_new();
         }
     }
@@ -54,13 +51,13 @@
     PersistableBundle(PersistableBundle&& other) noexcept : mPBundle(other.release()) {}
     // duplicates, does not take ownership of the APersistableBundle*
     PersistableBundle(const PersistableBundle& other) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             mPBundle = APersistableBundle_dup(other.mPBundle);
         }
     }
     // duplicates, does not take ownership of the APersistableBundle*
     PersistableBundle& operator=(const PersistableBundle& other) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             mPBundle = APersistableBundle_dup(other.mPBundle);
         }
         return *this;
@@ -70,7 +67,7 @@
 
     binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
         reset();
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_readFromParcel(parcel, &mPBundle);
         } else {
             return STATUS_INVALID_OPERATION;
@@ -81,7 +78,7 @@
         if (!mPBundle) {
             return STATUS_BAD_VALUE;
         }
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_writeToParcel(mPBundle, parcel);
         } else {
             return STATUS_INVALID_OPERATION;
@@ -96,7 +93,7 @@
      */
     void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept {
         if (mPBundle) {
-            if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+            if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
                 APersistableBundle_delete(mPBundle);
             }
             mPBundle = nullptr;
@@ -109,7 +106,7 @@
      * what should be used to check for equality.
      */
     bool deepEquals(const PersistableBundle& rhs) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_isEqual(get(), rhs.get());
         } else {
             return false;
@@ -148,7 +145,7 @@
     inline std::string toString() const {
         if (!mPBundle) {
             return "<PersistableBundle: null>";
-        } else if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        } else if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             std::ostringstream os;
             os << "<PersistableBundle: ";
             os << "size: " << std::to_string(APersistableBundle_size(mPBundle));
@@ -159,7 +156,7 @@
     }
 
     int32_t size() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_size(mPBundle);
         } else {
             return 0;
@@ -167,7 +164,7 @@
     }
 
     int32_t erase(const std::string& key) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_erase(mPBundle, key.c_str());
         } else {
             return 0;
@@ -175,37 +172,37 @@
     }
 
     void putBoolean(const std::string& key, bool val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putBoolean(mPBundle, key.c_str(), val);
         }
     }
 
     void putInt(const std::string& key, int32_t val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putInt(mPBundle, key.c_str(), val);
         }
     }
 
     void putLong(const std::string& key, int64_t val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putLong(mPBundle, key.c_str(), val);
         }
     }
 
     void putDouble(const std::string& key, double val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putDouble(mPBundle, key.c_str(), val);
         }
     }
 
     void putString(const std::string& key, const std::string& val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putString(mPBundle, key.c_str(), val.c_str());
         }
     }
 
     void putBooleanVector(const std::string& key, const std::vector<bool>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             // std::vector<bool> has no ::data().
             int32_t num = vec.size();
             if (num > 0) {
@@ -222,7 +219,7 @@
     }
 
     void putIntVector(const std::string& key, const std::vector<int32_t>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num);
@@ -230,7 +227,7 @@
         }
     }
     void putLongVector(const std::string& key, const std::vector<int64_t>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num);
@@ -238,7 +235,7 @@
         }
     }
     void putDoubleVector(const std::string& key, const std::vector<double>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num);
@@ -246,7 +243,7 @@
         }
     }
     void putStringVector(const std::string& key, const std::vector<std::string>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t num = vec.size();
             if (num > 0) {
                 char** inVec = (char**)malloc(num * sizeof(char*));
@@ -261,13 +258,13 @@
         }
     }
     void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putPersistableBundle(mPBundle, key.c_str(), pBundle.mPBundle);
         }
     }
 
     bool getBoolean(const std::string& key, bool* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_getBoolean(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -275,7 +272,7 @@
     }
 
     bool getInt(const std::string& key, int32_t* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_getInt(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -283,7 +280,7 @@
     }
 
     bool getLong(const std::string& key, int64_t* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_getLong(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -291,7 +288,7 @@
     }
 
     bool getDouble(const std::string& key, double* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_getDouble(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -303,7 +300,7 @@
     }
 
     bool getString(const std::string& key, std::string* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             char* outString = nullptr;
             bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString,
                                                     &stringAllocator, nullptr);
@@ -321,7 +318,7 @@
                                                    const char* _Nonnull, T* _Nullable, int32_t),
                         const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
                         std::vector<T>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t bytes = 0;
             // call first with nullptr to get required size in bytes
             bytes = getVec(pBundle, key, nullptr, 0);
@@ -343,28 +340,28 @@
     }
 
     bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
                                         vec);
         }
         return false;
     }
     bool getIntVector(const std::string& key, std::vector<int32_t>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
                                            vec);
         }
         return false;
     }
     bool getLongVector(const std::string& key, std::vector<int64_t>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
                                            vec);
         }
         return false;
     }
     bool getDoubleVector(const std::string& key, std::vector<double>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle,
                                           key.c_str(), vec);
         }
@@ -389,7 +386,7 @@
     }
 
     bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
                                                                &stringAllocator, nullptr);
             if (bytes > 0) {
@@ -406,7 +403,7 @@
     }
 
     bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle* bundle = nullptr;
             bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle);
             if (ret) {
@@ -438,77 +435,77 @@
     }
 
     std::set<std::string> getBooleanKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getBooleanKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getIntKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getIntKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getLongKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getLongKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getDoubleKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getDoubleKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getStringKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getStringKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getBooleanVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getIntVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getLongVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getDoubleVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getStringVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getPersistableBundleKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getPersistableBundleKeys, mPBundle);
         } else {
             return {};
diff --git a/libs/binder/trusty/ndk/include/android/llndk-versioning.h b/libs/binder/trusty/ndk/include/android/llndk-versioning.h
deleted file mode 100644
index e955a34..0000000
--- a/libs/binder/trusty/ndk/include/android/llndk-versioning.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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
-
-// TODO(b/349936395): set to true for Trusty
-#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) (false)
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 495418b..7aee903 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -286,18 +286,23 @@
     if (surfaceControlChanged && mSurfaceControl != nullptr) {
         BQA_LOGD("Updating SurfaceControl without recreating BBQ");
     }
-    bool applyTransaction = false;
 
     // Always update the native object even though they might have the same layer handle, so we can
     // get the updated transform hint from WM.
     mSurfaceControl = surface;
     SurfaceComposerClient::Transaction t;
+    bool applyTransaction = false;
     if (surfaceControlChanged) {
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
         updateBufferReleaseProducer();
 #endif
         t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure,
                    layer_state_t::eEnableBackpressure);
+        // Migrate the picture profile handle to the new surface control.
+        if (com_android_graphics_libgui_flags_apply_picture_profiles() &&
+            mPictureProfileHandle.has_value()) {
+            t.setPictureProfileHandle(mSurfaceControl, *mPictureProfileHandle);
+        }
         applyTransaction = true;
     }
     mTransformHint = mSurfaceControl->getTransformHint();
@@ -679,6 +684,17 @@
     if (!bufferItem.mIsAutoTimestamp) {
         t->setDesiredPresentTime(bufferItem.mTimestamp);
     }
+    if (com_android_graphics_libgui_flags_apply_picture_profiles() &&
+        bufferItem.mPictureProfileHandle.has_value()) {
+        t->setPictureProfileHandle(mSurfaceControl, *bufferItem.mPictureProfileHandle);
+        // The current picture profile must be maintained in case the BBQ gets its
+        // SurfaceControl switched out.
+        mPictureProfileHandle = bufferItem.mPictureProfileHandle;
+        // Clear out the picture profile if the requestor has asked for it to be cleared
+        if (mPictureProfileHandle == PictureProfileHandle::NONE) {
+            mPictureProfileHandle = std::nullopt;
+        }
+    }
 
     // Drop stale frame timeline infos
     while (!mPendingFrameTimelines.empty() &&
diff --git a/libs/gui/BufferItem.cpp b/libs/gui/BufferItem.cpp
index 5beba02..3b2d337 100644
--- a/libs/gui/BufferItem.cpp
+++ b/libs/gui/BufferItem.cpp
@@ -38,26 +38,25 @@
     return static_cast<T>(static_cast<uint64_t>(hi)<<32 | lo);
 }
 
-BufferItem::BufferItem() :
-    mGraphicBuffer(nullptr),
-    mFence(nullptr),
-    mCrop(Rect::INVALID_RECT),
-    mTransform(0),
-    mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
-    mTimestamp(0),
-    mIsAutoTimestamp(false),
-    mDataSpace(HAL_DATASPACE_UNKNOWN),
-    mFrameNumber(0),
-    mSlot(INVALID_BUFFER_SLOT),
-    mIsDroppable(false),
-    mAcquireCalled(false),
-    mTransformToDisplayInverse(false),
-    mSurfaceDamage(),
-    mAutoRefresh(false),
-    mQueuedBuffer(true),
-    mIsStale(false),
-    mApi(0) {
-}
+BufferItem::BufferItem()
+      : mGraphicBuffer(nullptr),
+        mFence(nullptr),
+        mCrop(Rect::INVALID_RECT),
+        mTransform(0),
+        mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+        mTimestamp(0),
+        mIsAutoTimestamp(false),
+        mDataSpace(HAL_DATASPACE_UNKNOWN),
+        mFrameNumber(0),
+        mSlot(INVALID_BUFFER_SLOT),
+        mIsDroppable(false),
+        mAcquireCalled(false),
+        mTransformToDisplayInverse(false),
+        mSurfaceDamage(),
+        mAutoRefresh(false),
+        mQueuedBuffer(true),
+        mIsStale(false),
+        mApi(0) {}
 
 BufferItem::~BufferItem() {}
 
@@ -76,6 +75,11 @@
     addAligned(size, high32(mTimestamp));
     addAligned(size, mIsAutoTimestamp);
     addAligned(size, mDataSpace);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+    addAligned(size, mPictureProfileHandle.has_value());
+    addAligned(size, low32(PictureProfileHandle::NONE.getId()));
+    addAligned(size, high32(PictureProfileHandle::NONE.getId()));
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
     addAligned(size, low32(mFrameNumber));
     addAligned(size, high32(mFrameNumber));
     addAligned(size, mSlot);
@@ -170,6 +174,16 @@
     writeAligned(buffer, size, high32(mTimestamp));
     writeAligned(buffer, size, mIsAutoTimestamp);
     writeAligned(buffer, size, mDataSpace);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+    writeAligned(buffer, size, mPictureProfileHandle.has_value());
+    if (mPictureProfileHandle.has_value()) {
+        writeAligned(buffer, size, low32(mPictureProfileHandle->getId()));
+        writeAligned(buffer, size, high32(mPictureProfileHandle->getId()));
+    } else {
+        writeAligned(buffer, size, low32(PictureProfileHandle::NONE.getId()));
+        writeAligned(buffer, size, high32(PictureProfileHandle::NONE.getId()));
+    }
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
     writeAligned(buffer, size, low32(mFrameNumber));
     writeAligned(buffer, size, high32(mFrameNumber));
     writeAligned(buffer, size, mSlot);
@@ -231,6 +245,7 @@
 
     uint32_t timestampLo = 0, timestampHi = 0;
     uint32_t frameNumberLo = 0, frameNumberHi = 0;
+    int32_t pictureProfileIdLo = 0, pictureProfileIdHi = 0;
 
     readAligned(buffer, size, mCrop);
     readAligned(buffer, size, mTransform);
@@ -240,6 +255,16 @@
     mTimestamp = to64<int64_t>(timestampLo, timestampHi);
     readAligned(buffer, size, mIsAutoTimestamp);
     readAligned(buffer, size, mDataSpace);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+    bool hasPictureProfileHandle;
+    readAligned(buffer, size, hasPictureProfileHandle);
+    readAligned(buffer, size, pictureProfileIdLo);
+    readAligned(buffer, size, pictureProfileIdHi);
+    mPictureProfileHandle = hasPictureProfileHandle
+            ? std::optional(PictureProfileHandle(
+                      to64<PictureProfileId>(pictureProfileIdLo, pictureProfileIdHi)))
+            : std::nullopt;
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
     readAligned(buffer, size, frameNumberLo);
     readAligned(buffer, size, frameNumberHi);
     mFrameNumber = to64<uint64_t>(frameNumberLo, frameNumberHi);
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 473a374..39209f9 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -938,6 +938,8 @@
             &getFrameTimestamps);
     const Region& surfaceDamage = input.getSurfaceDamage();
     const HdrMetadata& hdrMetadata = input.getHdrMetadata();
+    const std::optional<PictureProfileHandle>& pictureProfileHandle =
+            input.getPictureProfileHandle();
 
     if (acquireFence == nullptr) {
         BQ_LOGE("queueBuffer: fence is NULL");
@@ -1044,6 +1046,7 @@
         item.mIsAutoTimestamp = isAutoTimestamp;
         item.mDataSpace = dataSpace;
         item.mHdrMetadata = hdrMetadata;
+        item.mPictureProfileHandle = pictureProfileHandle;
         item.mFrameNumber = currentFrameNumber;
         item.mSlot = slot;
         item.mFence = acquireFence;
diff --git a/libs/gui/BufferStuffing.md b/libs/gui/BufferStuffing.md
new file mode 100644
index 0000000..6ed8ad9
--- /dev/null
+++ b/libs/gui/BufferStuffing.md
@@ -0,0 +1,7 @@
+### Buffer Stuffing and Recovery ###
+
+Buffer stuffing happens on the client side when SurfaceFlinger misses a frame, but the client continues producing buffers at the same rate. This could occur anytime when SurfaceFlinger does not meet the expected timeline’s deadline to finish composing a frame. As a result, SurfaceFlinger cannot yet release the buffer for the frame that it missed and the client has one less buffer to render into. The client may then run out of buffers or have to wait for buffer release callbacks, increasing the chances of janking when clients render multiple windows.
+
+Recovery is implemented by first detecting when buffer stuffing occurs and ensuring that the elevated buffer counts in the server are from a relevant SurfaceControl (is a ViewRootImpl). Other SurfaceControl buffer producers such as games, media, and camera have other reasons for expectedly increased buffer counts, which do not need buffer stuffing recovery.
+
+The actual recovery adjusts the animation timeline in the Choreographer so that the client deadlines for subsequent frames are moved forward in time by one frame. This approach adjusts the client buffer production timeline such that SurfaceFlinger does not fall behind when it misses a frame because the client will simply match its frame production rate with SurfaceFlinger. Ordinarily, buffer stuffing is problematic because the client continues producing buffers when SurfaceFlinger is behind. However, if the client delays producing its buffers to match SurfaceFlinger’s rate, the animation has new frame deadlines that can be reasonably met. The animation is effectively paused for one frame longer than originally intended, and continues the remainder of the animation normally.
\ No newline at end of file
diff --git a/libs/gui/IGraphicBufferProducerFlattenables.cpp b/libs/gui/IGraphicBufferProducerFlattenables.cpp
index c8b9b67..4e92a39 100644
--- a/libs/gui/IGraphicBufferProducerFlattenables.cpp
+++ b/libs/gui/IGraphicBufferProducerFlattenables.cpp
@@ -20,21 +20,19 @@
 namespace android {
 
 constexpr size_t IGraphicBufferProducer::QueueBufferInput::minFlattenedSize() {
-    return sizeof(timestamp) +
-            sizeof(isAutoTimestamp) +
-            sizeof(dataSpace) +
-            sizeof(crop) +
-            sizeof(scalingMode) +
-            sizeof(transform) +
-            sizeof(stickyTransform) +
-            sizeof(getFrameTimestamps) +
-            sizeof(slot);
+    return sizeof(timestamp) + sizeof(isAutoTimestamp) + sizeof(dataSpace) + sizeof(crop) +
+            sizeof(scalingMode) + sizeof(transform) + sizeof(stickyTransform) +
+            sizeof(getFrameTimestamps) + sizeof(slot) +
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+            sizeof(decltype(pictureProfileHandle.has_value())) +
+            sizeof(decltype(pictureProfileHandle.getId()));
+#else
+            0;
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
 }
 
 size_t IGraphicBufferProducer::QueueBufferInput::getFlattenedSize() const {
-    return minFlattenedSize() +
-            fence->getFlattenedSize() +
-            surfaceDamage.getFlattenedSize() +
+    return minFlattenedSize() + fence->getFlattenedSize() + surfaceDamage.getFlattenedSize() +
             hdrMetadata.getFlattenedSize();
 }
 
@@ -57,6 +55,12 @@
     FlattenableUtils::write(buffer, size, transform);
     FlattenableUtils::write(buffer, size, stickyTransform);
     FlattenableUtils::write(buffer, size, getFrameTimestamps);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+    FlattenableUtils::write(buffer, size, pictureProfileHandle.has_value());
+    FlattenableUtils::write(buffer, size,
+                            pictureProfileHandle.has_value() ? pictureProfileHandle->getId()
+                                                             : PictureProfileHandle::NONE.getId());
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
 
     status_t result = fence->flatten(buffer, size, fds, count);
     if (result != NO_ERROR) {
@@ -91,6 +95,15 @@
     FlattenableUtils::read(buffer, size, transform);
     FlattenableUtils::read(buffer, size, stickyTransform);
     FlattenableUtils::read(buffer, size, getFrameTimestamps);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+    bool hasPictureProfileHandle;
+    FlattenableUtils::read(buffer, size, hasPictureProfileHandle);
+    PictureProfileId pictureProfileId;
+    FlattenableUtils::read(buffer, size, pictureProfileId);
+    pictureProfileHandle = hasPictureProfileHandle
+            ? std::optional(PictureProfileHandle(pictureProfileId))
+            : std::nullopt;
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
 
     fence = new Fence();
     status_t result = fence->unflatten(buffer, size, fds, count);
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 8894b66..07558aa 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -17,7 +17,9 @@
 #ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H
 #define ANDROID_GUI_BLAST_BUFFER_QUEUE_H
 
-#include <com_android_graphics_libgui_flags.h>
+#include <optional>
+#include <queue>
+
 #include <gui/BufferItem.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/IGraphicBufferConsumer.h>
@@ -29,7 +31,6 @@
 #include <utils/RefBase.h>
 
 #include <system/window.h>
-#include <queue>
 
 #include <com_android_graphics_libgui_flags.h>
 
@@ -222,6 +223,10 @@
     ui::Size mRequestedSize GUARDED_BY(mMutex);
     int32_t mFormat GUARDED_BY(mMutex);
 
+    // Keep a copy of the current picture profile handle, so it can be moved to a new
+    // SurfaceControl when BBQ migrates via ::update.
+    std::optional<PictureProfileHandle> mPictureProfileHandle;
+
     struct BufferInfo {
         bool hasBuffer = false;
         uint32_t width;
diff --git a/libs/gui/include/gui/BufferItem.h b/libs/gui/include/gui/BufferItem.h
index 218bb42..2f85c62 100644
--- a/libs/gui/include/gui/BufferItem.h
+++ b/libs/gui/include/gui/BufferItem.h
@@ -17,9 +17,12 @@
 #ifndef ANDROID_GUI_BUFFERITEM_H
 #define ANDROID_GUI_BUFFERITEM_H
 
+#include <optional>
+
 #include <gui/HdrMetadata.h>
 
 #include <ui/FenceTime.h>
+#include <ui/PictureProfileHandle.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
@@ -91,6 +94,10 @@
     // mHdrMetadata is the HDR metadata associated with this buffer slot.
     HdrMetadata mHdrMetadata;
 
+    // mPictureProfileHandle is a handle that points to a set of parameters that configure picture
+    // processing hardware to enhance the quality of buffer contents.
+    std::optional<PictureProfileHandle> mPictureProfileHandle;
+
     // mFrameNumber is the number of the queued frame for this slot.
     uint64_t mFrameNumber;
 
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 4dbf9e1..40a6e79 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -119,7 +119,7 @@
             HdcpLevelsChange hdcpLevelsChange;
         };
     };
-    static_assert(sizeof(Event) == 216);
+    static_assert(sizeof(Event) == 224);
 
 public:
     /*
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 3aac457..a42ddc4 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <optional>
 
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
@@ -28,6 +29,7 @@
 #include <ui/BufferQueueDefs.h>
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
+#include <ui/PictureProfileHandle.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
@@ -365,6 +367,14 @@
         const HdrMetadata& getHdrMetadata() const { return hdrMetadata; }
         void setHdrMetadata(const HdrMetadata& metadata) { hdrMetadata = metadata; }
 
+        const std::optional<PictureProfileHandle>& getPictureProfileHandle() const {
+            return pictureProfileHandle;
+        }
+        void setPictureProfileHandle(const PictureProfileHandle& profile) {
+            pictureProfileHandle = profile;
+        }
+        void clearPictureProfileHandle() { pictureProfileHandle = std::nullopt; }
+
         int64_t timestamp{0};
         int isAutoTimestamp{0};
         android_dataspace dataSpace{HAL_DATASPACE_UNKNOWN};
@@ -377,6 +387,7 @@
         bool getFrameTimestamps{false};
         int slot{-1};
         HdrMetadata hdrMetadata;
+        std::optional<PictureProfileHandle> pictureProfileHandle;
     };
 
     struct QueueBufferOutput : public Flattenable<QueueBufferOutput> {
@@ -403,7 +414,7 @@
         uint64_t nextFrameNumber{0};
         FrameEventHistoryDelta frameTimestamps;
         bool bufferReplaced{false};
-        int maxBufferCount{0};
+        int maxBufferCount{BufferQueueDefs::NUM_BUFFER_SLOTS};
         status_t result{NO_ERROR};
     };
 
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 9098dff..1c31e46 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -171,6 +171,10 @@
         // Sets a property on this layer indicating that its visible region should be considered
         // when computing TrustedPresentation Thresholds.
         eCanOccludePresentation = 0x1000,
+        // Indicates that the SurfaceControl should recover from buffer stuffing when
+        // possible. This is the case when the SurfaceControl is the root SurfaceControl
+        // owned by ViewRootImpl.
+        eRecoverableFromBufferStuffing = 0x2000,
     };
 
     enum {
diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h
index b40a840..ced5130 100644
--- a/libs/gui/include/gui/VsyncEventData.h
+++ b/libs/gui/include/gui/VsyncEventData.h
@@ -36,6 +36,9 @@
     // Size of frame timelines provided by the platform; max is kFrameTimelinesCapacity.
     uint32_t frameTimelinesLength;
 
+    // Number of queued buffers to indicate if buffer stuffing mode is detected.
+    uint32_t numberQueuedBuffers;
+
     struct alignas(8) FrameTimeline {
         // The Vsync Id corresponsing to this vsync event. This will be used to
         // populate ISurfaceComposer::setFrameTimelineVsync and
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 2e6ffcb..b026e64 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -27,6 +27,7 @@
 #include <gui/Surface.h>
 
 #include <ui/GraphicBuffer.h>
+#include <ui/PictureProfileHandle.h>
 
 #include <android-base/properties.h>
 
@@ -1569,4 +1570,61 @@
     EXPECT_EQ(ADATASPACE_UNKNOWN, dataSpace);
 }
 
+TEST_F(BufferQueueTest, PassesThroughPictureProfileHandle) {
+    createBufferQueue();
+    sp<MockConsumer> mc(new MockConsumer);
+    mConsumer->consumerConnect(mc, false);
+
+    IGraphicBufferProducer::QueueBufferOutput qbo;
+    mProducer->connect(new StubProducerListener, NATIVE_WINDOW_API_CPU, false, &qbo);
+    mProducer->setMaxDequeuedBufferCount(2);
+    mConsumer->setMaxAcquiredBufferCount(2);
+
+    // First try to pass a valid picture profile handle
+    {
+        int slot;
+        sp<Fence> fence;
+        sp<GraphicBuffer> buf;
+        IGraphicBufferProducer::QueueBufferInput qbi(0, false, HAL_DATASPACE_UNKNOWN,
+                                                     Rect(0, 0, 1, 1),
+                                                     NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+                                                     Fence::NO_FENCE);
+        qbi.setPictureProfileHandle(PictureProfileHandle(1));
+
+        EXPECT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+                  mProducer->dequeueBuffer(&slot, &fence, 1, 1, 0, GRALLOC_USAGE_SW_READ_OFTEN,
+                                           nullptr, nullptr));
+        EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buf));
+        EXPECT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo));
+
+        BufferItem item;
+        EXPECT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+
+        ASSERT_TRUE(item.mPictureProfileHandle.has_value());
+        ASSERT_EQ(item.mPictureProfileHandle, PictureProfileHandle(1));
+    }
+
+    // Then validate that the picture profile handle isn't sticky and is reset for the next buffer
+    {
+        int slot;
+        sp<Fence> fence;
+        sp<GraphicBuffer> buf;
+        IGraphicBufferProducer::QueueBufferInput qbi(0, false, HAL_DATASPACE_UNKNOWN,
+                                                     Rect(0, 0, 1, 1),
+                                                     NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+                                                     Fence::NO_FENCE);
+
+        EXPECT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+                  mProducer->dequeueBuffer(&slot, &fence, 1, 1, 0, GRALLOC_USAGE_SW_READ_OFTEN,
+                                           nullptr, nullptr));
+        EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buf));
+        EXPECT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo));
+
+        BufferItem item;
+        EXPECT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+
+        ASSERT_FALSE(item.mPictureProfileHandle.has_value());
+    }
+}
+
 } // namespace android
diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp
index 29eeaa8..791f471 100644
--- a/libs/gui/tests/DisplayEventStructLayout_test.cpp
+++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp
@@ -36,15 +36,16 @@
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameInterval, 8);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.preferredFrameTimelineIndex, 16);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelinesLength, 20);
-    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 24);
-    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 24);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.numberQueuedBuffers, 24);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 32);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 32);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].deadlineTimestamp,
-                 32);
+                 40);
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
-                 vsyncData.frameTimelines[0].expectedPresentationTime, 40);
+                 vsyncData.frameTimelines[0].expectedPresentationTime, 48);
     // Also test the offsets of the last frame timeline. A loop is not used because the non-const
     // index cannot be used in static_assert.
-    const int lastFrameTimelineOffset = /* Start of array */ 24 +
+    const int lastFrameTimelineOffset = /* Start of array */ 32 +
             (VsyncEventData::kFrameTimelinesCapacity - 1) * /* Size of FrameTimeline */ 24;
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
                  vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1].vsyncId,
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index d3653cf..2c0f77a 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -193,13 +193,6 @@
 
 InputConsumerNoResampling::~InputConsumerNoResampling() {
     ensureCalledOnLooperThread(__func__);
-    while (!mOutboundQueue.empty()) {
-        processOutboundEvents();
-        // This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
-        // so keep trying to send the events as long as they are present in the queue.
-    }
-
-    setFdEvents(0);
     // If there are any remaining unread batches, send an ack for them and don't deliver
     // them to callbacks.
     for (auto& [_, batches] : mBatches) {
@@ -208,6 +201,12 @@
             batches.pop();
         }
     }
+
+    while (!mOutboundQueue.empty()) {
+        processOutboundEvents();
+        // This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
+        // so keep trying to send the events as long as they are present in the queue.
+    }
     // However, it is still up to the app to finish any events that have already been delivered
     // to the callbacks. If we wanted to change that behaviour and auto-finish all unfinished events
     // that were already sent to callbacks, we could potentially loop through "mConsumeTimes"
@@ -216,6 +215,10 @@
     const size_t unfinishedEvents = mConsumeTimes.size();
     LOG_IF(INFO, unfinishedEvents != 0)
             << getName() << " has " << unfinishedEvents << " unfinished event(s)";
+    // Remove the fd from epoll, so that Looper does not call 'handleReceiveCallback' anymore.
+    // This must be done at the end of the destructor; otherwise, some of the other functions may
+    // call 'setFdEvents' as a side-effect, thus adding the fd back to the epoll set of the looper.
+    setFdEvents(0);
 }
 
 int InputConsumerNoResampling::handleReceiveCallback(int events) {
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 77dcaa9..6a55726 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -583,15 +583,6 @@
                    StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)",
                                 mChannel->getName().c_str(),
                                 MotionEvent::actionToString(action).c_str()));
-    if (verifyEvents()) {
-        Result<void> result =
-                mInputVerifier.processMovement(deviceId, source, action, pointerCount,
-                                               pointerProperties, pointerCoords, flags);
-        if (!result.ok()) {
-            LOG(ERROR) << "Bad stream: " << result.error();
-            return BAD_VALUE;
-        }
-    }
     if (debugTransportPublisher()) {
         std::string transformString;
         transform.dump(transformString, "transform", "        ");
@@ -657,8 +648,18 @@
         msg.body.motion.pointers[i].properties = pointerProperties[i];
         msg.body.motion.pointers[i].coords = pointerCoords[i];
     }
+    const status_t status = mChannel->sendMessage(&msg);
 
-    return mChannel->sendMessage(&msg);
+    if (status == OK && verifyEvents()) {
+        Result<void> result =
+                mInputVerifier.processMovement(deviceId, source, action, pointerCount,
+                                               pointerProperties, pointerCoords, flags);
+        if (!result.ok()) {
+            LOG(ERROR) << "Bad stream: " << result.error();
+            return BAD_VALUE;
+        }
+    }
+    return status;
 }
 
 status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus) {
diff --git a/libs/input/tests/InputConsumer_test.cpp b/libs/input/tests/InputConsumer_test.cpp
index 06e19bb..226b892 100644
--- a/libs/input/tests/InputConsumer_test.cpp
+++ b/libs/input/tests/InputConsumer_test.cpp
@@ -70,11 +70,20 @@
                                            []() { return std::make_unique<LegacyResampler>(); });
     }
 
-    void invokeLooperCallback() const {
+    bool invokeLooperCallback() const {
         sp<LooperCallback> callback;
-        ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
-                                             /*events=*/nullptr, &callback, /*data=*/nullptr));
+        const bool found =
+                mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
+                                         /*events=*/nullptr, &callback, /*data=*/nullptr);
+        if (!found) {
+            return false;
+        }
+        if (callback == nullptr) {
+            LOG(FATAL) << "Looper has the fd of interest, but the callback is null!";
+            return false;
+        }
         callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr);
+        return true;
     }
 
     void assertOnBatchedInputEventPendingWasCalled() {
@@ -271,6 +280,27 @@
 }
 
 /**
+ * Check what happens when looper invokes callback after consumer has been destroyed.
+ * This reproduces a crash where the LooperEventCallback was added back to the Looper during
+ * destructor, thus allowing the looper callback to be invoked onto a null consumer object.
+ */
+TEST_F(InputConsumerTest, LooperCallbackInvokedAfterConsumerDestroyed) {
+    mClientTestChannel->enqueueMessage(
+            InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}.action(ACTION_DOWN).build());
+    mClientTestChannel->enqueueMessage(
+            InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}.action(ACTION_MOVE).build());
+    ASSERT_TRUE(invokeLooperCallback());
+    assertOnBatchedInputEventPendingWasCalled();
+    assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN));
+    mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
+
+    // Now, destroy the consumer and invoke the looper callback again after it's been destroyed.
+    mConsumer.reset();
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/false);
+    ASSERT_FALSE(invokeLooperCallback());
+}
+
+/**
  * Send an event to the InputConsumer, but do not invoke "consumeBatchedInputEvents", thus leaving
  * the input event unconsumed by the callbacks. Ensure that no crash occurs when the consumer is
  * destroyed.
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index ac3a832..5ce4076 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -222,6 +222,8 @@
         static_cast<int>(HAL_DATASPACE_BT2020_ITU_HLG));
     static_assert(static_cast<int>(ADATASPACE_DEPTH) == static_cast<int>(HAL_DATASPACE_DEPTH));
     static_assert(static_cast<int>(ADATASPACE_DYNAMIC_DEPTH) == static_cast<int>(HAL_DATASPACE_DYNAMIC_DEPTH));
+    static_assert(static_cast<int>(ADATASPACE_DISPLAY_BT2020) ==
+                  static_cast<int>(HAL_DATASPACE_DISPLAY_BT2020));
 
     if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
         return -EINVAL;
diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h
index 8056d9a..295a307 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -578,6 +578,13 @@
      */
     ADATASPACE_BT2020_ITU_HLG = 302383104, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG |
                                            // ADATASPACE_RANGE_LIMITED
+    /**
+     * sRGB-encoded BT. 2020
+     *
+     * Uses full range, sRGB transfer and BT2020 standard.
+     */
+    ADATASPACE_DISPLAY_BT2020 = 142999552, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_SRGB
+                                           // | ADATASPACE_RANGE_FULL
 
     /**
      * Depth
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 5c46c91..a93f6c3 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -544,7 +544,8 @@
     }
 
     if (graphicBuffer && parameters.layer.luts) {
-        shader = mLutShader.lutShader(shader, parameters.layer.luts);
+        shader = mLutShader.lutShader(shader, parameters.layer.luts,
+                                      toSkColorSpace(parameters.outputDataSpace));
     }
 
     if (parameters.requiresLinearEffect) {
diff --git a/libs/renderengine/skia/filters/LutShader.cpp b/libs/renderengine/skia/filters/LutShader.cpp
index cea46ef..1e43ff3 100644
--- a/libs/renderengine/skia/filters/LutShader.cpp
+++ b/libs/renderengine/skia/filters/LutShader.cpp
@@ -169,7 +169,8 @@
 }
 
 sk_sp<SkShader> LutShader::lutShader(sk_sp<SkShader>& input,
-                                     std::shared_ptr<gui::DisplayLuts> displayLuts) {
+                                     std::shared_ptr<gui::DisplayLuts> displayLuts,
+                                     sk_sp<SkColorSpace> outColorSpace) {
     if (mBuilder == nullptr) {
         const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(kShader);
         mBuilder = std::make_unique<SkRuntimeShaderBuilder>(instance.effect);
@@ -179,14 +180,11 @@
     if (fd.ok()) {
         // de-gamma the image without changing the primaries
         SkImage* baseImage = input->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
-        if (baseImage) {
-            sk_sp<SkColorSpace> baseColorSpace =
-                    baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
-            sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
-            auto colorXformSdrToGainmap =
-                    SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
-            input = input->makeWithColorFilter(colorXformSdrToGainmap);
-        }
+        sk_sp<SkColorSpace> baseColorSpace = baseImage && baseImage->colorSpace()
+                ? baseImage->refColorSpace()
+                : SkColorSpace::MakeSRGB();
+        sk_sp<SkColorSpace> lutMathColorSpace = baseColorSpace->makeLinearGamma();
+        input = input->makeWithWorkingColorSpace(lutMathColorSpace);
 
         auto& offsets = displayLuts->offsets;
         auto& lutProperties = displayLuts->lutProperties;
@@ -223,16 +221,9 @@
                                       lutProperties[i].samplingKey);
         }
 
-        // re-gamma
-        baseImage = input->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
-        if (baseImage) {
-            sk_sp<SkColorSpace> baseColorSpace =
-                    baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
-            sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
-            auto colorXformGainmapToDst =
-                    SkColorFilterPriv::MakeColorSpaceXform(gainmapMathColorSpace, baseColorSpace);
-            input = input->makeWithColorFilter(colorXformGainmapToDst);
-        }
+        auto colorXformLutToDst =
+                SkColorFilterPriv::MakeColorSpaceXform(lutMathColorSpace, outColorSpace);
+        input = input->makeWithColorFilter(colorXformLutToDst);
     }
     return input;
 }
diff --git a/libs/renderengine/skia/filters/LutShader.h b/libs/renderengine/skia/filters/LutShader.h
index c157904..ce3e059 100644
--- a/libs/renderengine/skia/filters/LutShader.h
+++ b/libs/renderengine/skia/filters/LutShader.h
@@ -28,8 +28,8 @@
 
 class LutShader {
 public:
-    sk_sp<SkShader> lutShader(sk_sp<SkShader>& input,
-                              std::shared_ptr<gui::DisplayLuts> displayLuts);
+    sk_sp<SkShader> lutShader(sk_sp<SkShader>& input, std::shared_ptr<gui::DisplayLuts> displayLuts,
+                              sk_sp<SkColorSpace> outColorSpace);
 
 private:
     sk_sp<SkShader> generateLutShader(sk_sp<SkShader> input, const std::vector<float>& buffers,
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index c9ec036..2143f79 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -23,7 +23,6 @@
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android/binder_manager.h>
 #include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
-#include <android/llndk-versioning.h>
 #include <binder/IPCThreadState.h>
 #include <dlfcn.h>
 #include <ui/FatVector.h>
@@ -91,7 +90,7 @@
         }
 
         void* so = nullptr;
-        if API_LEVEL_AT_LEAST (__ANDROID_API_V__, 202404) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
             so = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.c_str(),
                                                             RTLD_LOCAL | RTLD_NOW);
         } else {
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 35ba48f..013ef86 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -129,7 +129,8 @@
          {"multi_intensity", InputLightClass::MULTI_INTENSITY},
          {"max_brightness", InputLightClass::MAX_BRIGHTNESS},
          {"kbd_backlight", InputLightClass::KEYBOARD_BACKLIGHT},
-         {"mic_mute", InputLightClass::KEYBOARD_MIC_MUTE}};
+         {"mic_mute", InputLightClass::KEYBOARD_MIC_MUTE},
+         {"mute", InputLightClass::KEYBOARD_VOLUME_MUTE}};
 
 // Mapping for input multicolor led class node names.
 // https://www.kernel.org/doc/html/latest/leds/leds-class-multicolor.html
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index 49ad8b5..9eeb2b2 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -514,6 +514,8 @@
             type = InputDeviceLightType::KEYBOARD_BACKLIGHT;
         } else if (rawInfo.flags.test(InputLightClass::KEYBOARD_MIC_MUTE)) {
             type = InputDeviceLightType::KEYBOARD_MIC_MUTE;
+        } else if (rawInfo.flags.test(InputLightClass::KEYBOARD_VOLUME_MUTE)) {
+            type = InputDeviceLightType::KEYBOARD_VOLUME_MUTE;
         } else {
             type = InputDeviceLightType::INPUT;
         }
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 4336945..5839b4c 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -179,6 +179,8 @@
     KEYBOARD_BACKLIGHT = 0x00000100,
     /* The input light has mic_mute name */
     KEYBOARD_MIC_MUTE = 0x00000200,
+    /* The input light has mute name */
+    KEYBOARD_VOLUME_MUTE = 0x00000400,
 };
 
 enum class InputBatteryClass : uint32_t {
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 1959423..54270eb 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -99,6 +99,8 @@
     out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n";
     out << "Is hovering: " << mIsHovering << "\n";
     out << "Enable Tap Timestamp: " << mWhenToEnableTapToClick << "\n";
+    out << "Three finger tap shortcut enabled: "
+        << (mThreeFingerTapShortcutEnabled ? "enabled" : "disabled") << "\n";
     return out.str();
 }
 
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index f5c4fc5..ae84d7b 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -30,6 +30,8 @@
 
 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;
@@ -71,6 +73,14 @@
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
     MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
     MOCK_METHOD(bool, isRemote, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getCpuHeadroom,
+                (const CpuHeadroomParams& params, std::vector<float>* 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));
 };
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index c13e444..86d7388 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -29,6 +29,7 @@
 #include <cinttypes>
 #include <numeric>
 #include <unordered_set>
+#include <vector>
 
 #include "../Jank/JankTracker.h"
 
@@ -1002,6 +1003,11 @@
     finalizeCurrentDisplayFrame();
 }
 
+const std::vector<std::shared_ptr<frametimeline::SurfaceFrame>>& FrameTimeline::getPresentFrames()
+        const {
+    return mPresentFrames;
+}
+
 void FrameTimeline::onCommitNotComposited() {
     SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
@@ -1521,6 +1527,7 @@
         mPendingPresentFences.erase(mPendingPresentFences.begin());
     }
 
+    mPresentFrames.clear();
     for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
         const auto& pendingPresentFence = mPendingPresentFences[i];
         nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
@@ -1533,6 +1540,13 @@
 
         auto& displayFrame = pendingPresentFence.second;
         displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
+
+        // Surface frames have been jank classified and can be provided to caller
+        // to detect if buffer stuffing is occurring.
+        for (const auto& frame : displayFrame->getSurfaceFrames()) {
+            mPresentFrames.push_back(frame);
+        }
+
         mPreviousPredictionPresentTime =
                 displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
                                     mPreviousPredictionPresentTime, mFilterFramesBeforeTraceStarts);
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 6cda309..a47bd57 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -328,6 +328,11 @@
     virtual void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                               const std::shared_ptr<FenceTime>& gpuFence) = 0;
 
+    // Provides surface frames that have already been jank classified in the most recent
+    // flush of pending present fences. This allows buffer stuffing detection from SF.
+    virtual const std::vector<std::shared_ptr<frametimeline::SurfaceFrame>>& getPresentFrames()
+            const = 0;
+
     // Tells FrameTimeline that a frame was committed but not composited. This is used to flush
     // all the associated surface frames.
     virtual void onCommitNotComposited() = 0;
@@ -505,6 +510,8 @@
     void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate, Fps renderRate) override;
     void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                       const std::shared_ptr<FenceTime>& gpuFence = FenceTime::NO_FENCE) override;
+    const std::vector<std::shared_ptr<frametimeline::SurfaceFrame>>& getPresentFrames()
+            const override;
     void onCommitNotComposited() override;
     void parseArgs(const Vector<String16>& args, std::string& result) override;
     void setMaxDisplayFrames(uint32_t size) override;
@@ -552,6 +559,9 @@
     // display frame, this is a good starting size for the vector so that we can avoid the
     // internal vector resizing that happens with push_back.
     static constexpr uint32_t kNumSurfaceFramesInitial = 10;
+    // Presented surface frames that have been jank classified and can
+    // indicate of potential buffer stuffing.
+    std::vector<std::shared_ptr<frametimeline::SurfaceFrame>> mPresentFrames;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 7729671..fff4284 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -44,10 +44,8 @@
 
 #include <common/FlagManager.h>
 #include <scheduler/VsyncConfig.h>
-#include "DisplayHardware/DisplayMode.h"
 #include "FrameTimeline.h"
 #include "VSyncDispatch.h"
-#include "VSyncTracker.h"
 
 #include "EventThread.h"
 
@@ -482,6 +480,14 @@
     mCondition.notify_all();
 }
 
+// Merge lists of buffer stuffed Uids
+void EventThread::addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    for (auto& [uid, count] : bufferStuffedUids) {
+        mBufferStuffedUids.emplace_or_replace(uid, count);
+    }
+}
+
 void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
     DisplayEventConsumers consumers;
 
@@ -721,6 +727,10 @@
 
 void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event,
                                 const DisplayEventConsumers& consumers) {
+    // List of Uids that have been sent vsync data with queued buffer count.
+    // Used to keep track of which Uids can be removed from the map of
+    // buffer stuffed clients.
+    ftl::SmallVector<uid_t, 10> uidsPostedQueuedBuffers;
     for (const auto& consumer : consumers) {
         DisplayEventReceiver::Event copy = event;
         if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
@@ -730,6 +740,13 @@
                                   event.vsync.vsyncData.preferredExpectedPresentationTime(),
                                   event.vsync.vsyncData.preferredDeadlineTimestamp());
         }
+        auto it = mBufferStuffedUids.find(consumer->mOwnerUid);
+        if (it != mBufferStuffedUids.end()) {
+            copy.vsync.vsyncData.numberQueuedBuffers = it->second;
+            uidsPostedQueuedBuffers.emplace_back(consumer->mOwnerUid);
+        } else {
+            copy.vsync.vsyncData.numberQueuedBuffers = 0;
+        }
         switch (consumer->postEvent(copy)) {
             case NO_ERROR:
                 break;
@@ -745,6 +762,12 @@
                 removeDisplayEventConnectionLocked(consumer);
         }
     }
+    // The clients that have already received the queued buffer count
+    // can be removed from the buffer stuffed Uid list to avoid
+    // being sent duplicate messages.
+    for (auto uid : uidsPostedQueuedBuffers) {
+        mBufferStuffedUids.erase(uid);
+    }
     if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC &&
         FlagManager::getInstance().vrr_config()) {
         mLastCommittedVsyncTime =
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 2daf126..95632c7 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -32,7 +32,6 @@
 #include <thread>
 #include <vector>
 
-#include "DisplayHardware/DisplayMode.h"
 #include "TracedOrdinal.h"
 #include "VSyncDispatch.h"
 #include "VsyncSchedule.h"
@@ -55,6 +54,7 @@
 // ---------------------------------------------------------------------------
 
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
+using BufferStuffingMap = ftl::SmallMap<uid_t, uint32_t, 10>;
 
 enum class VSyncRequest {
     None = -2,
@@ -136,6 +136,10 @@
 
     virtual void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                                      int32_t maxLevel) = 0;
+
+    // An elevated number of queued buffers in the server is detected. This propagates a
+    // flag to Choreographer indicating that buffer stuffing recovery should begin.
+    virtual void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids);
 };
 
 struct IEventThreadCallback {
@@ -188,6 +192,8 @@
     void onHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                              int32_t maxLevel) override;
 
+    void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) override;
+
 private:
     friend EventThreadTest;
 
@@ -228,6 +234,10 @@
     scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
     frametimeline::TokenManager* const mTokenManager;
 
+    // All consumers that need to recover from buffer stuffing and the number
+    // of their queued buffers.
+    BufferStuffingMap mBufferStuffedUids GUARDED_BY(mMutex);
+
     IEventThreadCallback& mCallback;
 
     std::thread mThread;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 274e121..8c587a9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -951,6 +951,11 @@
     return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides);
 }
 
+void Scheduler::addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) {
+    if (!mRenderEventThread) return;
+    mRenderEventThread->addBufferStuffedUids(std::move(bufferStuffedUids));
+}
+
 void Scheduler::promotePacesetterDisplay(PhysicalDisplayId pacesetterId, PromotionParams params) {
     std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
     {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index e77af60..8340880 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -42,7 +42,6 @@
 #include <ui/DisplayId.h>
 #include <ui/DisplayMap.h>
 
-#include "Display/DisplayModeRequest.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "ISchedulerCallback.h"
@@ -334,6 +333,10 @@
         mPacesetterFrameDurationFractionToSkip = frameDurationFraction;
     }
 
+    // Propagates a flag to the EventThread indicating that buffer stuffing
+    // recovery should begin.
+    void addBufferStuffedUids(BufferStuffingMap bufferStuffedUids);
+
 private:
     friend class TestableScheduler;
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 0d03a6c..9854174 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -64,11 +64,13 @@
 #include <ftl/concat.h>
 #include <ftl/fake_guard.h>
 #include <ftl/future.h>
+#include <ftl/small_map.h>
 #include <ftl/unit.h>
 #include <gui/AidlUtil.h>
 #include <gui/BufferQueue.h>
 #include <gui/DebugEGLImageTracker.h>
 #include <gui/IProducerListener.h>
+#include <gui/JankInfo.h>
 #include <gui/LayerMetadata.h>
 #include <gui/LayerState.h>
 #include <gui/Surface.h>
@@ -3072,12 +3074,40 @@
 
     const TimePoint presentTime = TimePoint::now();
 
+    // The Uids of layer owners that are in buffer stuffing mode, and their elevated
+    // buffer counts. Messages to start recovery are sent exclusively to these Uids.
+    BufferStuffingMap bufferStuffedUids;
+
     // Set presentation information before calling Layer::releasePendingBuffer, such that jank
     // information from previous' frame classification is already available when sending jank info
     // to clients, so they get jank classification as early as possible.
     mFrameTimeline->setSfPresent(presentTime.ns(), pacesetterPresentFenceTime,
                                  pacesetterGpuCompositionDoneFenceTime);
 
+    // Find and register any layers that are in buffer stuffing mode
+    const auto& presentFrames = mFrameTimeline->getPresentFrames();
+
+    for (const auto& frame : presentFrames) {
+        const auto& layer = mLayerLifecycleManager.getLayerFromId(frame->getLayerId());
+        if (!layer) continue;
+        uint32_t numberQueuedBuffers = layer->pendingBuffers ? layer->pendingBuffers->load() : 0;
+        int32_t jankType = frame->getJankType().value_or(JankType::None);
+        if (jankType & JankType::BufferStuffing &&
+            layer->flags & layer_state_t::eRecoverableFromBufferStuffing) {
+            auto [it, wasEmplaced] =
+                    bufferStuffedUids.try_emplace(layer->ownerUid.val(), numberQueuedBuffers);
+            // Update with maximum number of queued buffers, allows clients drawing
+            // multiple windows to account for the most severely stuffed window
+            if (!wasEmplaced && it->second < numberQueuedBuffers) {
+                it->second = numberQueuedBuffers;
+            }
+        }
+    }
+
+    if (!bufferStuffedUids.empty()) {
+        mScheduler->addBufferStuffedUids(std::move(bufferStuffedUids));
+    }
+
     // We use the CompositionEngine::getLastFrameRefreshTimestamp() which might
     // be sampled a little later than when we started doing work for this frame,
     // but that should be okay since CompositorTiming has snapping logic.
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index d3479b7..7e9d5b8 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1370,6 +1370,8 @@
     // Flag used to set override desired display mode from backdoor
     bool mDebugDisplayModeSetByBackdoor = false;
 
+    // Tracks the number of maximum queued buffers by layer owner Uid.
+    using BufferStuffingMap = ftl::SmallMap<uid_t, uint32_t, 10>;
     BufferCountTracker mBufferCountTracker;
 
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 625d2e6..268a6c4 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -23,6 +23,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <gui/DisplayEventReceiver.h>
 #include <log/log.h>
 #include <scheduler/VsyncConfig.h>
 #include <utils/Errors.h>
@@ -111,6 +112,8 @@
     void expectOnExpectedPresentTimePosted(nsecs_t expectedPresentTime);
     void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                             std::vector<FrameRateOverride>);
+    void expectQueuedBufferCountReceivedByConnection(
+            ConnectionEventRecorder& connectionEventRecorder, uint32_t expectedBufferCount);
 
     void onVSyncEvent(nsecs_t timestamp, nsecs_t expectedPresentationTime,
                       nsecs_t deadlineTimestamp) {
@@ -144,6 +147,7 @@
     sp<MockEventThreadConnection> mConnection;
     sp<MockEventThreadConnection> mThrottledConnection;
     std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
+    std::vector<ConnectionEventRecorder*> mBufferStuffedConnectionRecorders;
 
     std::chrono::nanoseconds mVsyncPeriod;
 
@@ -376,6 +380,14 @@
     EXPECT_EQ(expectedDisplayId, event.header.displayId);
 }
 
+void EventThreadTest::expectQueuedBufferCountReceivedByConnection(
+        ConnectionEventRecorder& connectionEventRecorder, uint32_t expectedBufferCount) {
+    auto args = connectionEventRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    const auto& event = std::get<0>(args.value());
+    EXPECT_EQ(expectedBufferCount, event.vsync.vsyncData.numberQueuedBuffers);
+}
+
 namespace {
 
 using namespace testing;
@@ -868,6 +880,63 @@
     EXPECT_EQ(HDCP_V2, event.hdcpLevelsChange.maxLevel);
 }
 
+TEST_F(EventThreadTest, connectionReceivesBufferStuffing) {
+    setupEventThread();
+
+    // Create a connection that will experience buffer stuffing.
+    ConnectionEventRecorder stuffedConnectionEventRecorder{0};
+    sp<MockEventThreadConnection> stuffedConnection =
+            createConnection(stuffedConnectionEventRecorder,
+                             gui::ISurfaceComposer::EventRegistration::modeChanged |
+                                     gui::ISurfaceComposer::EventRegistration::frameRateOverride,
+                             111);
+
+    // Add a connection and buffer count to the list of stuffed Uids that will receive
+    // data in the next vsync event.
+    BufferStuffingMap bufferStuffedUids;
+    bufferStuffedUids.try_emplace(stuffedConnection->mOwnerUid, 3);
+    mThread->addBufferStuffedUids(bufferStuffedUids);
+    mBufferStuffedConnectionRecorders.emplace_back(&stuffedConnectionEventRecorder);
+
+    // Signal that we want the next vsync event to be posted to two connections.
+    mThread->requestNextVsync(mConnection);
+    mThread->requestNextVsync(stuffedConnection);
+    onVSyncEvent(123, 456, 789);
+
+    // Vsync event data contains number of queued buffers.
+    expectQueuedBufferCountReceivedByConnection(mConnectionEventCallRecorder, 0);
+    expectQueuedBufferCountReceivedByConnection(stuffedConnectionEventRecorder, 3);
+}
+
+TEST_F(EventThreadTest, connectionsWithSameUidReceiveBufferStuffing) {
+    setupEventThread();
+
+    // Create a connection with the same Uid as another connection.
+    ConnectionEventRecorder secondConnectionEventRecorder{0};
+    sp<MockEventThreadConnection> secondConnection =
+            createConnection(secondConnectionEventRecorder,
+                             gui::ISurfaceComposer::EventRegistration::modeChanged |
+                                     gui::ISurfaceComposer::EventRegistration::frameRateOverride,
+                             mConnectionUid);
+
+    // Add connection Uid and buffer count to the list of stuffed Uids that will receive
+    // data in the next vsync event.
+    BufferStuffingMap bufferStuffedUids;
+    bufferStuffedUids.try_emplace(mConnectionUid, 3);
+    mThread->addBufferStuffedUids(bufferStuffedUids);
+    mBufferStuffedConnectionRecorders.emplace_back(&mConnectionEventCallRecorder);
+    mBufferStuffedConnectionRecorders.emplace_back(&secondConnectionEventRecorder);
+
+    // Signal that we want the next vsync event to be posted to two connections.
+    mThread->requestNextVsync(mConnection);
+    mThread->requestNextVsync(secondConnection);
+    onVSyncEvent(123, 456, 789);
+
+    // Vsync event data contains number of queued buffers.
+    expectQueuedBufferCountReceivedByConnection(mConnectionEventCallRecorder, 3);
+    expectQueuedBufferCountReceivedByConnection(secondConnectionEventRecorder, 3);
+}
+
 } // namespace
 } // namespace android
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 82500fe..c976697 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -53,6 +53,7 @@
     MOCK_METHOD(void, onHdcpLevelsChanged,
                 (PhysicalDisplayId displayId, int32_t connectedLevel, int32_t maxLevel),
                 (override));
+    MOCK_METHOD(void, addBufferStuffedUids, (BufferStuffingMap), (override));
 };
 
 } // namespace android::mock