Merge "libinput: avoid null segmentation fault" into main
diff --git a/include/android/system_health.h b/include/android/system_health.h
index 69620df..352eb72 100644
--- a/include/android/system_health.h
+++ b/include/android/system_health.h
@@ -81,10 +81,10 @@
  *
  * @return A new instance of ACpuHeadroomParams.
  */
-ACpuHeadroomParams *_Nonnull ACpuHeadroomParams_create()
+ACpuHeadroomParams* _Nonnull ACpuHeadroomParams_create(void)
 __INTRODUCED_IN(36);
 
-enum ACpuHeadroomCalculationType {
+typedef enum ACpuHeadroomCalculationType : int32_t {
     /**
      * Use the minimum headroom value within the calculation window.
      * Introduced in API level 36.
@@ -95,10 +95,9 @@
      * Introduced in API level 36.
      */
     ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1,
-};
-typedef enum ACpuHeadroomCalculationType ACpuHeadroomCalculationType;
+} ACpuHeadroomCalculationType;
 
-enum AGpuHeadroomCalculationType {
+typedef enum AGpuHeadroomCalculationType : int32_t {
     /**
      * Use the minimum headroom value within the calculation window.
      * Introduced in API level 36.
@@ -109,8 +108,7 @@
      * Introduced in API level 36.
      */
     AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1,
-};
-typedef enum AGpuHeadroomCalculationType AGpuHeadroomCalculationType;
+} AGpuHeadroomCalculationType;
 
 /**
  * Sets the headroom calculation window size in ACpuHeadroomParams.
@@ -124,7 +122,7 @@
  *                     {@link #ACpuHeadroomParams_getCalculationWindowMillis} if not set. The device
  *                     will try to use the closest feasible window size to this param.
  */
-void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams *_Nonnull params,
+void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params,
                                                    int windowMillis)
 __INTRODUCED_IN(36);
 
@@ -136,7 +134,7 @@
  * @param params The params to be set.
  * @return This will return the default value chosen by the device if the params is not set.
  */
-int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams *_Nonnull params)
+int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -151,7 +149,7 @@
  *                     {@link #AGpuHeadroomParams_getCalculationWindowMillis} if not set. The device
  *                     will try to use the closest feasible window size to this param.
  */
-void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams *_Nonnull params,
+void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params,
                                                    int windowMillis)
 __INTRODUCED_IN(36);
 
@@ -163,7 +161,7 @@
  * @param params The params to be set.
  * @return This will return the default value chosen by the device if the params is not set.
  */
-int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams *_Nonnull params)
+int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -174,7 +172,7 @@
  * @param params The params to be set.
  * @param calculationType The headroom calculation type.
  */
-void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams *_Nonnull params,
+void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams* _Nonnull params,
                                            ACpuHeadroomCalculationType calculationType)
 __INTRODUCED_IN(36);
 
@@ -187,7 +185,7 @@
  * @return The headroom calculation type.
  */
 ACpuHeadroomCalculationType
-ACpuHeadroomParams_getCalculationType(ACpuHeadroomParams *_Nonnull params)
+ACpuHeadroomParams_getCalculationType(ACpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -198,7 +196,7 @@
  * @param params The params to be set.
  * @param calculationType The headroom calculation type.
  */
-void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams *_Nonnull params,
+void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams* _Nonnull params,
                                            AGpuHeadroomCalculationType calculationType)
 __INTRODUCED_IN(36);
 
@@ -211,7 +209,7 @@
  * @return The headroom calculation type.
  */
 AGpuHeadroomCalculationType
-AGpuHeadroomParams_getCalculationType(AGpuHeadroomParams *_Nonnull params)
+AGpuHeadroomParams_getCalculationType(AGpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -223,7 +221,7 @@
  * @param tids Non-null array of TIDs, maximum 5.
  * @param tidsSize The size of the tids array.
  */
-void ACpuHeadroomParams_setTids(ACpuHeadroomParams *_Nonnull params, const int *_Nonnull tids,
+void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids,
                                 int tidsSize)
 __INTRODUCED_IN(36);
 
@@ -238,7 +236,7 @@
  *
  * @return A new instance of AGpuHeadroomParams.
  */
-AGpuHeadroomParams *_Nonnull AGpuHeadroomParams_create()
+AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create(void)
 __INTRODUCED_IN(36);
 
 /**
@@ -248,7 +246,7 @@
  *
  * @param params The params to be deleted.
  */
-void ACpuHeadroomParams_destroy(ACpuHeadroomParams *_Nonnull params)
+void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -258,7 +256,7 @@
  *
  * @param params The params to be deleted.
  */
-void AGpuHeadroomParams_destroy(AGpuHeadroomParams *_Nonnull params)
+void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nonnull params)
 __INTRODUCED_IN(36);
 
 /**
@@ -282,8 +280,8 @@
  *         EPERM if the TIDs do not belong to the same process.
  *         ENOTSUP if API or requested params is unsupported.
  */
-int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams *_Nullable params,
-                                 float *_Nonnull outHeadroom)
+int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params,
+                                 float* _Nonnull outHeadroom)
 __INTRODUCED_IN(36);
 
 /**
@@ -306,8 +304,8 @@
  *         EPIPE if failed to get the GPU headroom.
  *         ENOTSUP if API or requested params is unsupported.
  */
-int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams *_Nullable params,
-                                 float *_Nonnull outHeadroom)
+int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams* _Nullable params,
+                                 float* _Nonnull outHeadroom)
 __INTRODUCED_IN(36);
 
 /**
@@ -323,7 +321,7 @@
  *         EPIPE if failed to get the minimum polling interval.
  *         ENOTSUP if API is unsupported.
  */
-int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t *_Nonnull outMinIntervalMillis)
+int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis)
 __INTRODUCED_IN(36);
 
 /**
@@ -339,7 +337,7 @@
  *         EPIPE if failed to get the minimum polling interval.
  *         ENOTSUP if API is unsupported.
  */
-int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t *_Nonnull outMinIntervalMillis)
+int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis)
 __INTRODUCED_IN(36);
 
 #ifdef __cplusplus
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index b0f6e69..f1374e2 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -108,6 +108,15 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void BufferQueue::ProxyConsumerListener::onSlotCountChanged(int slotCount) {
+    sp<ConsumerListener> listener(mConsumerListener.promote());
+    if (listener != nullptr) {
+        listener->onSlotCountChanged(slotCount);
+    }
+}
+#endif
+
 void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
         sp<IGraphicBufferConsumer>* outConsumer,
         bool consumerIsSurfaceFlinger) {
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 9855b5b..f012586 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -341,9 +341,9 @@
             return BAD_VALUE;
         }
 
-        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
-                    slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount) {
+            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
             return BAD_VALUE;
         } else if (!mSlots[slot].mBufferState.isAcquired()) {
             BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
@@ -483,10 +483,13 @@
     ATRACE_CALL();
     ATRACE_BUFFER_INDEX(slot);
 
-    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
-            releaseFence == nullptr) {
-        BQ_LOGE("releaseBuffer: slot %d out of range or fence %p NULL", slot,
-                releaseFence.get());
+    const int totalSlotCount = mCore->getTotalSlotCountLocked();
+    if (slot < 0 || slot >= totalSlotCount) {
+        BQ_LOGE("releaseBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
+        return BAD_VALUE;
+    }
+    if (releaseFence == nullptr) {
+        BQ_LOGE("releaseBuffer: slot %d fence %p NULL", slot, releaseFence.get());
         return BAD_VALUE;
     }
 
@@ -515,6 +518,13 @@
     { // Autolock scope
         std::lock_guard<std::mutex> lock(mCore->mMutex);
 
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount || releaseFence == nullptr) {
+            BQ_LOGE("releaseBuffer: slot %d out of range [0, %d) or fence %p NULL", slot,
+                    totalSlotCount, releaseFence.get());
+            return BAD_VALUE;
+        }
+
         // If the frame number has changed because the buffer has been reallocated,
         // we can ignore this releaseBuffer for the old buffer.
         // Ignore this for the shared buffer where the frame number can easily
@@ -661,6 +671,43 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueConsumer::getReleasedBuffersExtended(std::vector<bool>* outSlotMask) {
+    ATRACE_CALL();
+
+    if (outSlotMask == nullptr) {
+        BQ_LOGE("getReleasedBuffersExtended: outSlotMask may not be NULL");
+        return BAD_VALUE;
+    }
+
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+    if (mCore->mIsAbandoned) {
+        BQ_LOGE("getReleasedBuffersExtended: BufferQueue has been abandoned");
+        return NO_INIT;
+    }
+
+    const int totalSlotCount = mCore->getTotalSlotCountLocked();
+    outSlotMask->resize(totalSlotCount);
+    for (int s = 0; s < totalSlotCount; ++s) {
+        (*outSlotMask)[s] = !mSlots[s].mAcquireCalled;
+    }
+
+    // Remove from the mask queued buffers for which acquire has been called,
+    // since the consumer will not receive their buffer addresses and so must
+    // retain their cached information
+    BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+    while (current != mCore->mQueue.end()) {
+        if (current->mAcquireCalled) {
+            (*outSlotMask)[current->mSlot] = false;
+        }
+        ++current;
+    }
+
+    return NO_ERROR;
+}
+#endif
+
 status_t BufferQueueConsumer::setDefaultBufferSize(uint32_t width,
         uint32_t height) {
     ATRACE_CALL();
@@ -679,6 +726,28 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueConsumer::allowUnlimitedSlots(bool allowUnlimitedSlots) {
+    ATRACE_CALL();
+    BQ_LOGV("allowUnlimitedSlots: %d", allowUnlimitedSlots);
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+    if (mCore->mIsAbandoned) {
+        BQ_LOGE("allowUnlimitedSlots: BufferQueue has been abandoned");
+        return NO_INIT;
+    }
+
+    if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) {
+        BQ_LOGE("allowUnlimitedSlots: BufferQueue already connected");
+        return INVALID_OPERATION;
+    }
+
+    mCore->mAllowExtendedSlotCount = allowUnlimitedSlots;
+
+    return OK;
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+
 status_t BufferQueueConsumer::setMaxBufferCount(int bufferCount) {
     ATRACE_CALL();
 
@@ -718,16 +787,23 @@
         int maxAcquiredBuffers) {
     ATRACE_FORMAT("%s(%d)", __func__, maxAcquiredBuffers);
 
-    if (maxAcquiredBuffers < 1 ||
-            maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) {
-        BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d",
-                maxAcquiredBuffers);
-        return BAD_VALUE;
-    }
-
     sp<IConsumerListener> listener;
     { // Autolock scope
         std::unique_lock<std::mutex> lock(mCore->mMutex);
+
+        // We reserve two slots in order to guarantee that the producer and
+        // consumer can run asynchronously.
+        int maxMaxAcquiredBuffers =
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+                mCore->getTotalSlotCountLocked() - 2;
+#else
+                BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS;
+#endif
+        if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > maxMaxAcquiredBuffers) {
+            BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d", maxAcquiredBuffers);
+            return BAD_VALUE;
+        }
+
         mCore->waitWhileAllocatingLocked(lock);
 
         if (mCore->mIsAbandoned) {
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index 5a09399..6c79904 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -38,6 +38,8 @@
 
 #include <system/window.h>
 
+#include <ui/BufferQueueDefs.h>
+
 namespace android {
 
 // Macros for include BufferQueueCore information in log messages
@@ -97,7 +99,11 @@
         mConnectedProducerListener(),
         mBufferReleasedCbEnabled(false),
         mBufferAttachedCbEnabled(false),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#else
         mSlots(),
+#endif
         mQueue(),
         mFreeSlots(),
         mFreeBuffers(),
@@ -111,6 +117,9 @@
         mDefaultWidth(1),
         mDefaultHeight(1),
         mDefaultBufferDataSpace(HAL_DATASPACE_UNKNOWN),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mAllowExtendedSlotCount(false),
+#endif
         mMaxBufferCount(BufferQueueDefs::NUM_BUFFER_SLOTS),
         mMaxAcquiredBufferCount(1),
         mMaxDequeuedBufferCount(1),
@@ -221,6 +230,14 @@
     }
 }
 
+int BufferQueueCore::getTotalSlotCountLocked() const {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    return mAllowExtendedSlotCount ? mMaxBufferCount : BufferQueueDefs::NUM_BUFFER_SLOTS;
+#else
+    return BufferQueueDefs::NUM_BUFFER_SLOTS;
+#endif
+}
+
 int BufferQueueCore::getMinUndequeuedBufferCountLocked() const {
     // If dequeueBuffer is allowed to error out, we don't have to add an
     // extra buffer.
@@ -253,6 +270,26 @@
     return maxBufferCount;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueCore::extendSlotCountLocked(int size) {
+    int previousSize = (int)mSlots.size();
+    if (previousSize > size) {
+        return BAD_VALUE;
+    }
+    if (previousSize == size) {
+        return NO_ERROR;
+    }
+
+    mSlots.resize(size);
+    for (int i = previousSize; i < size; i++) {
+        mUnusedSlots.push_back(i);
+    }
+
+    mMaxBufferCount = size;
+    return NO_ERROR;
+}
+#endif
+
 void BufferQueueCore::clearBufferSlotLocked(int slot) {
     BQ_LOGV("clearBufferSlotLocked: slot %d", slot);
 
@@ -383,7 +420,7 @@
 void BufferQueueCore::validateConsistencyLocked() const {
     static const useconds_t PAUSE_TIME = 0;
     int allocatedSlots = 0;
-    for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
+    for (int slot = 0; slot < getTotalSlotCountLocked(); ++slot) {
         bool isInFreeSlots = mFreeSlots.count(slot) != 0;
         bool isInFreeBuffers =
                 std::find(mFreeBuffers.cbegin(), mFreeBuffers.cend(), slot) !=
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 2e7cef0..c241482 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -40,6 +40,7 @@
 #include <gui/TraceUtils.h>
 #include <private/gui/BufferQueueThreadState.h>
 
+#include <utils/Errors.h>
 #include <utils/Log.h>
 #include <utils/Trace.h>
 
@@ -108,9 +109,9 @@
         return NO_INIT;
     }
 
-    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-        BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)",
-                slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+    int maxSlot = mCore->getTotalSlotCountLocked();
+    if (slot < 0 || slot >= maxSlot) {
+        BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)", slot, maxSlot);
         return BAD_VALUE;
     } else if (!mSlots[slot].mBufferState.isDequeued()) {
         BQ_LOGE("requestBuffer: slot %d is not owned by the producer "
@@ -123,6 +124,49 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueProducer::extendSlotCount(int size) {
+    ATRACE_CALL();
+
+    sp<IConsumerListener> listener;
+    {
+        std::lock_guard<std::mutex> lock(mCore->mMutex);
+        BQ_LOGV("extendSlotCount: size %d", size);
+
+        if (mCore->mIsAbandoned) {
+            BQ_LOGE("extendSlotCount: BufferQueue has been abandoned");
+            return NO_INIT;
+        }
+
+        if (!mCore->mAllowExtendedSlotCount) {
+            BQ_LOGE("extendSlotCount: Consumer did not allow unlimited slots");
+            return INVALID_OPERATION;
+        }
+
+        int maxBeforeExtension = mCore->mMaxBufferCount;
+
+        if (size == maxBeforeExtension) {
+            return NO_ERROR;
+        }
+
+        if (size < maxBeforeExtension) {
+            return BAD_VALUE;
+        }
+
+        if (status_t ret = mCore->extendSlotCountLocked(size); ret != OK) {
+            return ret;
+        }
+        listener = mCore->mConsumerListener;
+    }
+
+    if (listener) {
+        listener->onSlotCountChanged(size);
+    }
+
+    return NO_ERROR;
+}
+#endif
+
 status_t BufferQueueProducer::setMaxDequeuedBufferCount(
         int maxDequeuedBuffers) {
     int maxBufferCount;
@@ -170,9 +214,10 @@
         int bufferCount = mCore->getMinUndequeuedBufferCountLocked();
         bufferCount += maxDequeuedBuffers;
 
-        if (bufferCount > BufferQueueDefs::NUM_BUFFER_SLOTS) {
+        if (bufferCount > mCore->getTotalSlotCountLocked()) {
             BQ_LOGE("setMaxDequeuedBufferCount: bufferCount %d too large "
-                    "(max %d)", bufferCount, BufferQueueDefs::NUM_BUFFER_SLOTS);
+                    "(max %d)",
+                    bufferCount, mCore->getTotalSlotCountLocked());
             return BAD_VALUE;
         }
 
@@ -756,9 +801,9 @@
             return BAD_VALUE;
         }
 
-        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
-                    slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount) {
+            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
             return BAD_VALUE;
         } else if (!mSlots[slot].mBufferState.isDequeued()) {
             // TODO(http://b/140581935): This message is BQ_LOGW because it
@@ -993,9 +1038,9 @@
             return NO_INIT;
         }
 
-        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-            BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)",
-                    slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount) {
+            BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
             return BAD_VALUE;
         } else if (!mSlots[slot].mBufferState.isDequeued()) {
             BQ_LOGE("queueBuffer: slot %d is not owned by the producer "
@@ -1239,9 +1284,9 @@
             return BAD_VALUE;
         }
 
-        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-            BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)", slot,
-                    BufferQueueDefs::NUM_BUFFER_SLOTS);
+        const int totalSlotCount = mCore->getTotalSlotCountLocked();
+        if (slot < 0 || slot >= totalSlotCount) {
+            BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
             return BAD_VALUE;
         } else if (!mSlots[slot].mBufferState.isDequeued()) {
             BQ_LOGE("cancelBuffer: slot %d is not owned by the producer "
@@ -1409,6 +1454,9 @@
             output->nextFrameNumber = mCore->mFrameCounter + 1;
             output->bufferReplaced = false;
             output->maxBufferCount = mCore->mMaxBufferCount;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            output->isSlotExpansionAllowed = mCore->mAllowExtendedSlotCount;
+#endif
 
             if (listener != nullptr) {
                 // Set up a death notification so that we can disconnect
diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index 602bba8..504509d 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -37,6 +37,8 @@
 
 #include <private/gui/ComposerService.h>
 
+#include <ui/BufferQueueDefs.h>
+
 #include <log/log.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
@@ -59,7 +61,11 @@
     return android_atomic_inc(&globalCounter);
 }
 
-ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :
+ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp)
+      :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
         mAbandoned(false),
         mConsumer(bufferQueue),
         mPrevFinalReleaseFence(Fence::NO_FENCE) {
@@ -68,7 +74,12 @@
 
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 ConsumerBase::ConsumerBase(bool controlledByApp, bool consumerIsSurfaceFlinger)
-      : mAbandoned(false), mPrevFinalReleaseFence(Fence::NO_FENCE) {
+      :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+        mAbandoned(false),
+        mPrevFinalReleaseFence(Fence::NO_FENCE) {
     sp<IGraphicBufferProducer> producer;
     BufferQueue::createBufferQueue(&producer, &mConsumer, consumerIsSurfaceFlinger);
     mSurface = sp<Surface>::make(producer, controlledByApp);
@@ -77,7 +88,11 @@
 
 ConsumerBase::ConsumerBase(const sp<IGraphicBufferProducer>& producer,
                            const sp<IGraphicBufferConsumer>& consumer, bool controlledByApp)
-      : mAbandoned(false),
+      :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+        mAbandoned(false),
         mConsumer(consumer),
         mSurface(sp<Surface>::make(producer, controlledByApp)),
         mPrevFinalReleaseFence(Fence::NO_FENCE) {
@@ -101,9 +116,16 @@
     if (err != NO_ERROR) {
         CB_LOGE("ConsumerBase: error connecting to BufferQueue: %s (%d)",
                 strerror(-err), err);
-    } else {
-        mConsumer->setConsumerName(mName);
+        return;
     }
+
+    mConsumer->setConsumerName(mName);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (err = mConsumer->allowUnlimitedSlots(true); err != NO_ERROR) {
+        CB_LOGE("ConsumerBase: error marking as allowed to have unlimited slots: %s (%d)",
+                strerror(-err), err);
+    }
+#endif
 }
 
 ConsumerBase::~ConsumerBase() {
@@ -130,7 +152,11 @@
     }
 
     uint64_t id = buffer->getId();
-    for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); ++i) {
+#else
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+#endif
         auto& slot = mSlots[i];
         if (slot.mGraphicBuffer && slot.mGraphicBuffer->getId() == id) {
             return i;
@@ -242,6 +268,15 @@
         return;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<bool> mask;
+    mConsumer->getReleasedBuffersExtended(&mask);
+    for (size_t i = 0; i < mSlots.size(); i++) {
+        if (mask[i]) {
+            freeBufferLocked(i);
+        }
+    }
+#else
     uint64_t mask = 0;
     mConsumer->getReleasedBuffers(&mask);
     for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
@@ -249,11 +284,23 @@
             freeBufferLocked(i);
         }
     }
+#endif
 }
 
 void ConsumerBase::onSidebandStreamChanged() {
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void ConsumerBase::onSlotCountChanged(int slotCount) {
+    CB_LOGV("onSlotCountChanged: %d", slotCount);
+    Mutex::Autolock lock(mMutex);
+
+    if (slotCount > (int)mSlots.size()) {
+        mSlots.resize(slotCount);
+    }
+}
+#endif
+
 void ConsumerBase::abandon() {
     CB_LOGV("abandon");
     Mutex::Autolock lock(mMutex);
@@ -270,7 +317,11 @@
         CB_LOGE("abandonLocked: ConsumerBase is abandoned!");
         return;
     }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); ++i) {
+#else
     for (int i =0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+#endif
         freeBufferLocked(i);
     }
     // disconnect from the BufferQueue
@@ -387,6 +438,15 @@
         CB_LOGE("setMaxBufferCount: ConsumerBase is abandoned!");
         return NO_INIT;
     }
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (status_t err = mConsumer->allowUnlimitedSlots(false); err != NO_ERROR) {
+        CB_LOGE("ConsumerBase: error marking as not allowed to have unlimited slots: %s (%d)",
+                strerror(-err), err);
+        return err;
+    }
+#endif
+
     return mConsumer->setMaxBufferCount(bufferCount);
 }
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
@@ -448,6 +508,15 @@
     if (err != OK) {
         return err;
     }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<bool> mask;
+    mConsumer->getReleasedBuffersExtended(&mask);
+    for (int i = 0; i < (int)mSlots.size(); i++) {
+        if (mask[i]) {
+            freeBufferLocked(i);
+        }
+    }
+#else
     uint64_t mask;
     mConsumer->getReleasedBuffers(&mask);
     for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
@@ -455,6 +524,8 @@
             freeBufferLocked(i);
         }
     }
+#endif
+
     return OK;
 }
 
@@ -596,6 +667,9 @@
     // buffer on the same slot), the buffer producer is definitely no longer
     // tracking it.
     if (!stillTracking(slot, graphicBuffer)) {
+        CB_LOGV("releaseBufferLocked: Not tracking, exiting without calling releaseBuffer for "
+                "slot=%d/%" PRIu64,
+                slot, mSlots[slot].mFrameNumber);
         return OK;
     }
 
@@ -615,7 +689,11 @@
 
 bool ConsumerBase::stillTracking(int slot,
         const sp<GraphicBuffer> graphicBuffer) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (slot < 0 || slot >= (int)mSlots.size()) {
+#else
     if (slot < 0 || slot >= BufferQueue::NUM_BUFFER_SLOTS) {
+#endif
         return false;
     }
     return (mSlots[slot].mGraphicBuffer != nullptr &&
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
index 5c4879c..1b2354e 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_COMPATIBILITY_GTE &&
+        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST &&
         (!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 f2173cd..168129b 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -119,6 +119,9 @@
         mTexTarget(texTarget),
         mEglDisplay(EGL_NO_DISPLAY),
         mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
         mAttached(true) {
     GLC_LOGV("GLConsumer");
@@ -129,27 +132,29 @@
 }
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
-GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex,
-        uint32_t texTarget, bool useFenceSync, bool isControlledByApp) :
-    ConsumerBase(bq, isControlledByApp),
-    mCurrentCrop(Rect::EMPTY_RECT),
-    mCurrentTransform(0),
-    mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
-    mCurrentFence(Fence::NO_FENCE),
-    mCurrentTimestamp(0),
-    mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
-    mCurrentFrameNumber(0),
-    mDefaultWidth(1),
-    mDefaultHeight(1),
-    mFilteringEnabled(true),
-    mTexName(tex),
-    mUseFenceSync(useFenceSync),
-    mTexTarget(texTarget),
-    mEglDisplay(EGL_NO_DISPLAY),
-    mEglContext(EGL_NO_CONTEXT),
-    mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
-    mAttached(true)
-{
+GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, uint32_t texTarget,
+                       bool useFenceSync, bool isControlledByApp)
+      : ConsumerBase(bq, isControlledByApp),
+        mCurrentCrop(Rect::EMPTY_RECT),
+        mCurrentTransform(0),
+        mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+        mCurrentFence(Fence::NO_FENCE),
+        mCurrentTimestamp(0),
+        mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
+        mCurrentFrameNumber(0),
+        mDefaultWidth(1),
+        mDefaultHeight(1),
+        mFilteringEnabled(true),
+        mTexName(tex),
+        mUseFenceSync(useFenceSync),
+        mTexTarget(texTarget),
+        mEglDisplay(EGL_NO_DISPLAY),
+        mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+        mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+        mAttached(true) {
     GLC_LOGV("GLConsumer");
 
     memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(),
@@ -176,6 +181,9 @@
         mTexTarget(texTarget),
         mEglDisplay(EGL_NO_DISPLAY),
         mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
         mAttached(false) {
     GLC_LOGV("GLConsumer");
@@ -204,6 +212,9 @@
         mTexTarget(texTarget),
         mEglDisplay(EGL_NO_DISPLAY),
         mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
         mAttached(false) {
     GLC_LOGV("GLConsumer");
@@ -395,6 +406,17 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void GLConsumer::onSlotCountChanged(int slotCount) {
+    ConsumerBase::onSlotCountChanged(slotCount);
+
+    Mutex::Autolock lock(mMutex);
+    if (slotCount > (int)mEglSlots.size()) {
+        mEglSlots.resize(slotCount);
+    }
+}
+#endif
+
 status_t GLConsumer::releaseBufferLocked(int buf,
         sp<GraphicBuffer> graphicBuffer,
         EGLDisplay display, EGLSyncKHR eglFence) {
diff --git a/libs/gui/IConsumerListener.cpp b/libs/gui/IConsumerListener.cpp
index f3bd90c..939db59 100644
--- a/libs/gui/IConsumerListener.cpp
+++ b/libs/gui/IConsumerListener.cpp
@@ -31,7 +31,8 @@
     ON_FRAME_DEQUEUED,
     ON_FRAME_CANCELLED,
     ON_FRAME_DETACHED,
-    LAST = ON_FRAME_DETACHED,
+    ON_SLOT_COUNT_CHANGED,
+    LAST = ON_SLOT_COUNT_CHANGED,
 };
 
 } // Anonymous namespace
@@ -85,6 +86,14 @@
                                   FrameEventHistoryDelta* /*outDelta*/) override {
         LOG_ALWAYS_FATAL("IConsumerListener::addAndGetFrameTimestamps cannot be proxied");
     }
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    void onSlotCountChanged(int slotCount) override {
+        callRemoteAsync<
+                decltype(&IConsumerListener::onSlotCountChanged)>(Tag::ON_SLOT_COUNT_CHANGED,
+                                                                  slotCount);
+    }
+#endif
 };
 
 // Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
@@ -116,6 +125,13 @@
             return callLocalAsync(data, reply, &IConsumerListener::onFrameCancelled);
         case Tag::ON_FRAME_DETACHED:
             return callLocalAsync(data, reply, &IConsumerListener::onFrameDetached);
+        case Tag::ON_SLOT_COUNT_CHANGED: {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            return callLocalAsync(data, reply, &IConsumerListener::onSlotCountChanged);
+#else
+            return INVALID_OPERATION;
+#endif
+        }
     }
 }
 
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
index 282957b..c1b6568 100644
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ b/libs/gui/IGraphicBufferConsumer.cpp
@@ -16,6 +16,7 @@
 
 #include <gui/IGraphicBufferConsumer.h>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferItem.h>
 #include <gui/IConsumerListener.h>
 
@@ -24,6 +25,7 @@
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
 
+#include <utils/Errors.h>
 #include <utils/NativeHandle.h>
 #include <utils/String8.h>
 #include <cstdint>
@@ -53,7 +55,9 @@
     GET_OCCUPANCY_HISTORY,
     DISCARD_FREE_BUFFERS,
     DUMP_STATE,
-    LAST = DUMP_STATE,
+    ALLOW_UNLIMITED_SLOTS,
+    GET_RELEASED_BUFFERS_EXTENDED,
+    LAST = GET_RELEASED_BUFFERS_EXTENDED,
 };
 
 } // Anonymous namespace
@@ -104,11 +108,25 @@
         return callRemote<Signature>(Tag::GET_RELEASED_BUFFERS, slotMask);
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    status_t getReleasedBuffersExtended(std::vector<bool>* slotMask) override {
+        using Signature = decltype(&IGraphicBufferConsumer::getReleasedBuffersExtended);
+        return callRemote<Signature>(Tag::GET_RELEASED_BUFFERS_EXTENDED, slotMask);
+    }
+#endif
+
     status_t setDefaultBufferSize(uint32_t width, uint32_t height) override {
         using Signature = decltype(&IGraphicBufferConsumer::setDefaultBufferSize);
         return callRemote<Signature>(Tag::SET_DEFAULT_BUFFER_SIZE, width, height);
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    status_t allowUnlimitedSlots(bool allowUnlimitedSlots) override {
+        using Signature = decltype(&IGraphicBufferConsumer::allowUnlimitedSlots);
+        return callRemote<Signature>(Tag::ALLOW_UNLIMITED_SLOTS, allowUnlimitedSlots);
+    }
+#endif
+
     status_t setMaxBufferCount(int bufferCount) override {
         using Signature = decltype(&IGraphicBufferConsumer::setMaxBufferCount);
         return callRemote<Signature>(Tag::SET_MAX_BUFFER_COUNT, bufferCount);
@@ -228,6 +246,20 @@
             using Signature = status_t (IGraphicBufferConsumer::*)(const String8&, String8*) const;
             return callLocal<Signature>(data, reply, &IGraphicBufferConsumer::dumpState);
         }
+        case Tag::GET_RELEASED_BUFFERS_EXTENDED: {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            return callLocal(data, reply, &IGraphicBufferConsumer::getReleasedBuffersExtended);
+#else
+            return INVALID_OPERATION;
+#endif
+        }
+        case Tag::ALLOW_UNLIMITED_SLOTS: {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            return callLocal(data, reply, &IGraphicBufferConsumer::allowUnlimitedSlots);
+#else
+            return INVALID_OPERATION;
+#endif
+        }
     }
 }
 
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 0914480..9f71eb1 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -81,6 +81,7 @@
     GET_LAST_QUEUED_BUFFER2,
     SET_FRAME_RATE,
     SET_ADDITIONAL_OPTIONS,
+    SET_MAX_BUFER_COUNT_EXTENDED,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -149,6 +150,20 @@
         return result;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    status_t extendSlotCount(int size) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeInt32(size);
+        status_t result = remote()->transact(SET_MAX_BUFER_COUNT_EXTENDED, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.readInt32();
+        return result;
+    }
+#endif
+
     virtual status_t setAsyncMode(bool async) {
         Parcel data, reply;
         data.writeInterfaceToken(
@@ -981,6 +996,14 @@
 
 // ----------------------------------------------------------------------
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t IGraphicBufferProducer::extendSlotCount(int size) {
+    // No-op for IGBP other than BufferQueue.
+    (void)size;
+    return INVALID_OPERATION;
+}
+#endif
+
 status_t IGraphicBufferProducer::setLegacyBufferDrop(bool drop) {
     // No-op for IGBP other than BufferQueue.
     (void) drop;
@@ -1582,6 +1605,15 @@
             return NO_ERROR;
         }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        case SET_MAX_BUFER_COUNT_EXTENDED: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            int size = data.readInt32();
+            status_t result = extendSlotCount(size);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
+#endif
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/IGraphicBufferProducerFlattenables.cpp b/libs/gui/IGraphicBufferProducerFlattenables.cpp
index 4e92a39..8b2e2dd 100644
--- a/libs/gui/IGraphicBufferProducerFlattenables.cpp
+++ b/libs/gui/IGraphicBufferProducerFlattenables.cpp
@@ -128,7 +128,7 @@
 constexpr size_t IGraphicBufferProducer::QueueBufferOutput::minFlattenedSize() {
     return sizeof(width) + sizeof(height) + sizeof(transformHint) + sizeof(numPendingBuffers) +
             sizeof(nextFrameNumber) + sizeof(bufferReplaced) + sizeof(maxBufferCount) +
-            sizeof(result);
+            sizeof(result) + sizeof(isSlotExpansionAllowed);
 }
 size_t IGraphicBufferProducer::QueueBufferOutput::getFlattenedSize() const {
     return minFlattenedSize() + frameTimestamps.getFlattenedSize();
@@ -152,6 +152,7 @@
     FlattenableUtils::write(buffer, size, nextFrameNumber);
     FlattenableUtils::write(buffer, size, bufferReplaced);
     FlattenableUtils::write(buffer, size, maxBufferCount);
+    FlattenableUtils::write(buffer, size, isSlotExpansionAllowed);
 
     status_t result = frameTimestamps.flatten(buffer, size, fds, count);
     if (result != NO_ERROR) {
@@ -175,6 +176,7 @@
     FlattenableUtils::read(buffer, size, nextFrameNumber);
     FlattenableUtils::read(buffer, size, bufferReplaced);
     FlattenableUtils::read(buffer, size, maxBufferCount);
+    FlattenableUtils::read(buffer, size, isSlotExpansionAllowed);
 
     status_t result = frameTimestamps.unflatten(buffer, size, fds, count);
     if (result != NO_ERROR) {
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index e41f9bb..ec23365 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -38,6 +38,7 @@
 #include <utils/NativeHandle.h>
 #include <utils/Trace.h>
 
+#include <ui/BufferQueueDefs.h>
 #include <ui/DynamicDisplayInfo.h>
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
@@ -98,7 +99,10 @@
       : mGraphicBufferProducer(bufferProducer),
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
         mSurfaceDeathListener(nullptr),
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+#endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mSlots(NUM_BUFFER_SLOTS),
+#endif
         mCrop(Rect::EMPTY_RECT),
         mBufferAge(0),
         mGenerationNumber(0),
@@ -192,7 +196,7 @@
 status_t Surface::allowAllocation(bool allowAllocation) {
     return mGraphicBufferProducer->allowAllocation(allowAllocation);
 }
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+#endif
 
 status_t Surface::setGenerationNumber(uint32_t generation) {
     status_t result = mGraphicBufferProducer->setGenerationNumber(generation);
@@ -658,7 +662,11 @@
         return result;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (buf < 0 || buf >= (int)mSlots.size()) {
+#else
     if (buf < 0 || buf >= NUM_BUFFER_SLOTS) {
+#endif
         ALOGE("dequeueBuffer: IGraphicBufferProducer returned invalid slot number %d", buf);
         android_errorWriteLog(0x534e4554, "36991414"); // SafetyNet logging
         return FAILED_TRANSACTION;
@@ -757,7 +765,11 @@
     Mutex::Autolock lock(mMutex);
 
     uint64_t bufferId = buffer->getId();
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int slot = 0; slot < (int)mSlots.size(); ++slot) {
+#else
     for (int slot = 0; slot < Surface::NUM_BUFFER_SLOTS; ++slot) {
+#endif
         auto& bufferSlot = mSlots[slot];
         if (bufferSlot.buffer != nullptr && bufferSlot.buffer->getId() == bufferId) {
             bufferSlot.buffer = nullptr;
@@ -840,7 +852,11 @@
             return output.result;
         }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        if (output.slot < 0 || output.slot >= (int)mSlots.size()) {
+#else
         if (output.slot < 0 || output.slot >= NUM_BUFFER_SLOTS) {
+#endif
             mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
             ALOGE("%s: IGraphicBufferProducer returned invalid slot number %d",
                     __FUNCTION__, output.slot);
@@ -1027,7 +1043,11 @@
         return BAD_VALUE;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
         if (mSlots[i].buffer != nullptr &&
                 mSlots[i].buffer->handle == buffer->handle) {
             return i;
@@ -2094,6 +2114,9 @@
         mDefaultHeight = output.height;
         mNextFrameNumber = output.nextFrameNumber;
         mMaxBufferCount = output.maxBufferCount;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        mIsSlotExpansionAllowed = output.isSlotExpansionAllowed;
+#endif
 
         // Ignore transform hint if sticky transform is set or transform to display inverse flag is
         // set. Transform hint should be ignored if the client is expected to always submit buffers
@@ -2190,7 +2213,11 @@
         *outFence = Fence::NO_FENCE;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
         if (mSlots[i].buffer != nullptr &&
                 mSlots[i].buffer->getId() == buffer->getId()) {
             if (mReportRemovedBuffers) {
@@ -2292,8 +2319,35 @@
     ALOGV("Surface::setMaxDequeuedBufferCount");
     Mutex::Autolock lock(mMutex);
 
-    status_t err = mGraphicBufferProducer->setMaxDequeuedBufferCount(
-            maxDequeuedBuffers);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    if (maxDequeuedBuffers > BufferQueueDefs::NUM_BUFFER_SLOTS && !mIsSlotExpansionAllowed) {
+        return BAD_VALUE;
+    }
+
+    int minUndequeuedBuffers = 0;
+    status_t err = mGraphicBufferProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+                                                 &minUndequeuedBuffers);
+    if (err != OK) {
+        ALOGE("IGraphicBufferProducer::query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS) returned %s",
+              strerror(-err));
+        return err;
+    }
+
+    if (maxDequeuedBuffers > (int)mSlots.size()) {
+        int newSlotCount = minUndequeuedBuffers + maxDequeuedBuffers;
+        err = mGraphicBufferProducer->extendSlotCount(newSlotCount);
+        if (err != OK) {
+            ALOGE("IGraphicBufferProducer::extendSlotCount(%d) returned %s", newSlotCount,
+                  strerror(-err));
+            return err;
+        }
+
+        mSlots.resize(newSlotCount);
+    }
+    err = mGraphicBufferProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+#else
+    status_t err = mGraphicBufferProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+#endif
     ALOGE_IF(err, "IGraphicBufferProducer::setMaxDequeuedBufferCount(%d) "
             "returned %s", maxDequeuedBuffers, strerror(-err));
 
@@ -2501,7 +2555,11 @@
         ALOGE("%s: %zu buffers were freed while being dequeued!",
                 __FUNCTION__, mDequeuedSlots.size());
     }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
         mSlots[i].buffer = nullptr;
     }
 }
@@ -2510,7 +2568,11 @@
         std::vector<sp<GraphicBuffer>>* outBuffers) {
     ALOGV("Surface::getAndFlushBuffersFromSlots");
     for (int32_t i : slots) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        if (i < 0 || i >= (int)mSlots.size()) {
+#else
         if (i < 0 || i >= NUM_BUFFER_SLOTS) {
+#endif
             ALOGE("%s: Invalid slotIndex: %d", __FUNCTION__, i);
             return BAD_VALUE;
         }
@@ -2670,7 +2732,11 @@
             newDirtyRegion.set(bounds);
             mDirtyRegion.clear();
             Mutex::Autolock lock(mMutex);
-            for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+            for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
+            for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
                 mSlots[i].dirtyRegion.clear();
             }
         }
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 0948c4d0..f1c75d3 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -76,6 +76,9 @@
         void onSetFrameRate(float frameRate, int8_t compatibility,
                             int8_t changeFrameRateStrategy) override;
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        void onSlotCountChanged(int slotCount) override;
+#endif
     private:
         // mConsumerListener is a weak reference to the IConsumerListener.  This is
         // the raison d'etre of ProxyConsumerListener.
diff --git a/libs/gui/include/gui/BufferQueueConsumer.h b/libs/gui/include/gui/BufferQueueConsumer.h
index 6aa801a..e00c44e 100644
--- a/libs/gui/include/gui/BufferQueueConsumer.h
+++ b/libs/gui/include/gui/BufferQueueConsumer.h
@@ -96,11 +96,26 @@
     // This should be called from the onBuffersReleased() callback.
     virtual status_t getReleasedBuffers(uint64_t* outSlotMask);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // getReleasedBuffers sets the values pointed to by outSlotMask to the bits
+    // indicating which buffer slots have been released by the BufferQueue
+    // but have not yet been released by the consumer.
+    //
+    // This should be called from the onBuffersReleased() callback when
+    // allowUnlimitedSlots has been called.
+    virtual status_t getReleasedBuffersExtended(std::vector<bool>* outSlotMask) override;
+#endif
+
     // setDefaultBufferSize is used to set the size of buffers returned by
     // dequeueBuffer when a width and height of zero is requested.  Default
     // is 1x1.
     virtual status_t setDefaultBufferSize(uint32_t width, uint32_t height);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // see IGraphicBufferConsumer::allowUnlimitedSlots
+    virtual status_t allowUnlimitedSlots(bool allowUnlimitedSlots) override;
+#endif
+
     // see IGraphicBufferConsumer::setMaxBufferCount
     virtual status_t setMaxBufferCount(int bufferCount);
 
diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h
index 77cdf2c..7f92a46 100644
--- a/libs/gui/include/gui/BufferQueueCore.h
+++ b/libs/gui/include/gui/BufferQueueCore.h
@@ -32,10 +32,11 @@
 #include <utils/Trace.h>
 #include <utils/Vector.h>
 
-#include <list>
-#include <set>
-#include <mutex>
 #include <condition_variable>
+#include <list>
+#include <mutex>
+#include <set>
+#include <vector>
 
 #define ATRACE_BUFFER_INDEX(index)                                                        \
     do {                                                                                  \
@@ -91,6 +92,10 @@
     // Dump our state in a string
     void dumpState(const String8& prefix, String8* outResult) const;
 
+    // getTotalSlotCountLocked returns the total number of slots in use by the
+    // buffer queue at this time.
+    int getTotalSlotCountLocked() const;
+
     // getMinUndequeuedBufferCountLocked returns the minimum number of buffers
     // that must remain in a state other than DEQUEUED. The async parameter
     // tells whether we're in asynchronous mode.
@@ -120,6 +125,10 @@
     int getMaxBufferCountLocked(bool asyncMode,
             bool dequeueBufferCannotBlock, int maxBufferCount) const;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // This resizes mSlots to the given size, but only if it's increasing.
+    status_t extendSlotCountLocked(int size);
+#endif
     // clearBufferSlotLocked frees the GraphicBuffer and sync resources for the
     // given slot.
     void clearBufferSlotLocked(int slot);
@@ -204,7 +213,7 @@
     // mConnectedProducerListener will not trigger onBufferAttached() callback.
     bool mBufferAttachedCbEnabled;
 
-    // mSlots is an array of buffer slots that must be mirrored on the producer
+    // mSlots is a collection of buffer slots that must be mirrored on the producer
     // side. This allows buffer ownership to be transferred between the producer
     // and consumer without sending a GraphicBuffer over Binder. The entire
     // array is initialized to NULL at construction time, and buffers are
@@ -266,8 +275,14 @@
     // is specified.
     android_dataspace mDefaultBufferDataSpace;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // mAllowExtendedSlotCount is set by the consumer to permit the producer to
+    // request an unlimited number of slots.
+    bool mAllowExtendedSlotCount;
+#endif
+
     // mMaxBufferCount is the limit on the number of buffers that will be
-    // allocated at one time. This limit can be set by the consumer.
+    // allocated at one time.
     int mMaxBufferCount;
 
     // mMaxAcquiredBufferCount is the number of buffers that the consumer may
diff --git a/libs/gui/include/gui/BufferQueueDefs.h b/libs/gui/include/gui/BufferQueueDefs.h
index ffafb49..42cf439 100644
--- a/libs/gui/include/gui/BufferQueueDefs.h
+++ b/libs/gui/include/gui/BufferQueueDefs.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_GUI_BUFFERQUEUECOREDEFS_H
 #define ANDROID_GUI_BUFFERQUEUECOREDEFS_H
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferSlot.h>
 #include <ui/BufferQueueDefs.h>
 
@@ -24,7 +25,11 @@
     class BufferQueueCore;
 
     namespace BufferQueueDefs {
-        typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    typedef std::vector<BufferSlot> SlotsType;
+#else
+    typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
+#endif
     } // namespace BufferQueueDefs
 } // namespace android
 
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 086ce7c..50abadb 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -47,6 +47,11 @@
     // flags indicating that previously-returned buffers are no longer valid.
     virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // see IGraphicsBufferProducer::extendSlotCount
+    virtual status_t extendSlotCount(int size) override;
+#endif
+
     // see IGraphicsBufferProducer::setMaxDequeuedBufferCount
     virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers);
 
diff --git a/libs/gui/include/gui/ConsumerBase.h b/libs/gui/include/gui/ConsumerBase.h
index e976aa4..5cd19c1 100644
--- a/libs/gui/include/gui/ConsumerBase.h
+++ b/libs/gui/include/gui/ConsumerBase.h
@@ -185,7 +185,9 @@
     virtual void onFrameDetached(const uint64_t bufferId) override;
     virtual void onBuffersReleased() override;
     virtual void onSidebandStreamChanged() override;
-
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    virtual void onSlotCountChanged(int slotCount) override;
+#endif
     virtual int getSlotForBufferLocked(const sp<GraphicBuffer>& buffer);
 
     virtual status_t detachBufferLocked(int slotIndex);
@@ -284,7 +286,11 @@
     // slot that has not yet been used. The buffer allocated to a slot will also
     // be replaced if the requested buffer usage or geometry differs from that
     // of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<Slot> mSlots;
+#else
     Slot mSlots[BufferQueueDefs::NUM_BUFFER_SLOTS];
+#endif
 
     // mAbandoned indicates that the BufferQueue will no longer be used to
     // consume images buffers pushed to it using the IGraphicBufferProducer
diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h
index 8a66dc0..30cbfa2 100644
--- a/libs/gui/include/gui/GLConsumer.h
+++ b/libs/gui/include/gui/GLConsumer.h
@@ -266,6 +266,9 @@
     virtual status_t acquireBufferLocked(BufferItem *item, nsecs_t presentWhen,
             uint64_t maxFrameNumber = 0) override;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    virtual void onSlotCountChanged(int slotCount) override;
+#endif
     // 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,
@@ -496,8 +499,11 @@
     // slot that has not yet been used. The buffer allocated to a slot will also
     // be replaced if the requested buffer usage or geometry differs from that
     // of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<EglSlot> mEglSlots;
+#else
     EglSlot mEglSlots[BufferQueueDefs::NUM_BUFFER_SLOTS];
-
+#endif
     // mCurrentTexture is the buffer slot index of the buffer that is currently
     // bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT,
     // indicating that no buffer slot is currently bound to the texture. Note,
diff --git a/libs/gui/include/gui/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h
index 51d3959..1695aae 100644
--- a/libs/gui/include/gui/IConsumerListener.h
+++ b/libs/gui/include/gui/IConsumerListener.h
@@ -98,6 +98,16 @@
     virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
                                 int8_t /*changeFrameRateStrategy*/) {}
 #endif
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // Notifies the consumer that IGraphicBufferProducer::extendSlotCount has
+    // been called and the total slot count has increased.
+    //
+    // This will only ever be called if
+    // IGraphicBufferConsumer::allowUnlimitedSlots has been called on the
+    // consumer.
+    virtual void onSlotCountChanged(int /* slotCount */) {}
+#endif
 };
 
 #ifndef NO_BINDER
diff --git a/libs/gui/include/gui/IGraphicBufferConsumer.h b/libs/gui/include/gui/IGraphicBufferConsumer.h
index 18f5488..56eb291 100644
--- a/libs/gui/include/gui/IGraphicBufferConsumer.h
+++ b/libs/gui/include/gui/IGraphicBufferConsumer.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/OccupancyTracker.h>
 
 #include <binder/IInterface.h>
@@ -35,6 +36,10 @@
 class GraphicBuffer;
 class IConsumerListener;
 class NativeHandle;
+
+/*
+ * See IGraphicBufferProducer for details on SLOT_COUNT.
+ */
 #ifndef NO_BINDER
 class IGraphicBufferConsumer : public IInterface {
 public:
@@ -92,7 +97,7 @@
     //
     // Return of a value other than NO_ERROR means an error has occurred:
     // * BAD_VALUE - the given slot number is invalid, either because it is out of the range
-    //               [0, NUM_BUFFER_SLOTS) or because the slot it refers to is not
+    //               [0, SLOT_COUNT) or because the slot it refers to is not
     //               currently acquired.
     virtual status_t detachBuffer(int slot) = 0;
 
@@ -173,6 +178,19 @@
     // * NO_INIT - the BufferQueue has been abandoned.
     virtual status_t getReleasedBuffers(uint64_t* slotMask) = 0;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // getReleasedBuffersExtended for each slot, sets slotMask[slot] to 1 if it
+    // corresponds to a released buffer slot. In particular, a released buffer
+    // is one that has been released by the BufferQueue but has not yet been
+    // released by the consumer.
+    //
+    // This should be called from the onBuffersReleased() callback.
+    //
+    // Return of a value other than NO_ERROR means an error has occurred:
+    // * NO_INIT - the BufferQueue has been abandoned.
+    virtual status_t getReleasedBuffersExtended(std::vector<bool>* slotMask) = 0;
+#endif
+
     // setDefaultBufferSize is used to set the size of buffers returned by dequeueBuffer when a
     // width and height of zero is requested. Default is 1x1.
     //
@@ -180,6 +198,26 @@
     // * BAD_VALUE - either w or h was zero
     virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) = 0;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // allowUnlimitedSlots allows the producer to set the upper bound on slots.
+    //
+    // Must be called before the producer is connected. If the producer
+    // increases the slot count, an IConsumerListener::onSlotCountChanged
+    // update is sent.
+    //
+    // This can not be used with setMaxBufferCount. Calls after
+    // setMaxBufferCount will fail and calls to setMaxBufferCount after setting
+    // this to true will fail.
+    //
+    // Return of a value other than NO_ERROR means an error has occurred:
+    // * NO_INIT - the BufferQueue has been abandoned
+    // * INVALID_OPERATION - one of the following errors has occurred:
+    //                       * Producer has been connected
+    //                       * setMaxBufferCount has been called and shrunk the
+    //                         BufferQueue.
+    virtual status_t allowUnlimitedSlots(bool allowUnlimitedSlots) = 0;
+#endif
+
     // setMaxBufferCount sets the maximum value for the number of buffers used in the BufferQueue
     // (the initial default is NUM_BUFFER_SLOTS). If a call to setMaxAcquiredBufferCount (by the
     // consumer), or a call to setAsyncMode or setMaxDequeuedBufferCount (by the producer), would
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index a42ddc4..7accca6 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -72,6 +72,14 @@
  * dequeueBuffer() to get an empty buffer, fills it with data, then
  * calls queueBuffer() to make it available to the consumer.
  *
+ * BufferQueues have a size, which we'll refer to in other comments as
+ * SLOT_COUNT. Its default is 64 (NUM_BUFFER_SLOTS). It can be adjusted by
+ * the IGraphicBufferConsumer::setMaxBufferCount, or when
+ * IGraphicBufferConsumer::allowUnlimitedSlots is set to true, by
+ * IGraphicBufferProducer::extendSlotCount. The actual number of buffers in use
+ * is a function of various configurations, including whether we're in single
+ * buffer mode, the maximum dequeuable/aquirable buffers, and SLOT_COUNT.
+ *
  * This class was previously called ISurfaceTexture.
  */
 #ifndef NO_BINDER
@@ -106,7 +114,7 @@
     // slot->buffer mapping so that it's not necessary to transfer a
     // GraphicBuffer for every dequeue operation.
     //
-    // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+    // The slot must be in the range of [0, SLOT_COUNT).
     //
     // Return of a value other than NO_ERROR means an error has occurred:
     // * NO_INIT - the buffer queue has been abandoned or the producer is not
@@ -116,6 +124,30 @@
     //              * buffer specified by the slot is not dequeued
     virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) = 0;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    // extendSlotCount sets the maximum slot count (SLOT_COUNT) to the given
+    //  size. This feature must be enabled by the consumer to function via
+    // IGraphicBufferConsumer::allowUnlimitedSlots. This must be called before
+    // the producer connects.
+    //
+    // After calling this, any slot can be returned in the [0, size) range.
+    // Callers are responsible for the allocation of the appropriate slots
+    // array for their own buffer cache.
+    //
+    // On success, the consumer is notified (so that it can increase its own
+    // slot cache).
+    //
+    // Return of a value other than NO_ERROR means that an error has occurred:
+    // * NO_INIT - the buffer queue has been abandoned
+    // * INVALID_OPERATION - one of the following conditions has occurred:
+    //                     *  The producer is connected already
+    //                     *  The consumer didn't call allowUnlimitedSlots
+    // * BAD_VALUE - The value is smaller than the previous max size
+    //               (initialized to 64, then whatever the last call to this
+    //               was)
+    virtual status_t extendSlotCount(int size);
+#endif
+
     // setMaxDequeuedBufferCount sets the maximum number of buffers that can be
     // dequeued by the producer at one time. If this method succeeds, any new
     // buffer slots will be both unallocated and owned by the BufferQueue object
@@ -129,7 +161,7 @@
     // will result in a BAD_VALUE error.
     //
     // The buffer count should be at least 1 (inclusive), but at most
-    // (NUM_BUFFER_SLOTS - the minimum undequeued buffer count) (exclusive). The
+    // (SLOT_COUNT - the minimum undequeued buffer count) (exclusive). The
     // minimum undequeued buffer count can be obtained by calling
     // query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS).
     //
@@ -239,8 +271,8 @@
     // * NO_INIT - the buffer queue has been abandoned or the producer is not
     //             connected.
     // * BAD_VALUE - the given slot number is invalid, either because it is
-    //               out of the range [0, NUM_BUFFER_SLOTS), or because the slot
-    //               it refers to is not currently dequeued and requested.
+    //               out of the range [0, SLOT_COUNT), or because the slot it
+    //               refers to is not currently dequeued and requested.
     virtual status_t detachBuffer(int slot) = 0;
 
     // detachNextBuffer is equivalent to calling dequeueBuffer, requestBuffer,
@@ -415,6 +447,7 @@
         FrameEventHistoryDelta frameTimestamps;
         bool bufferReplaced{false};
         int maxBufferCount{BufferQueueDefs::NUM_BUFFER_SLOTS};
+        bool isSlotExpansionAllowed{false};
         status_t result{NO_ERROR};
     };
 
@@ -430,7 +463,7 @@
     // below). Any other properties (zero point, etc)
     // are client-dependent, and should be documented by the client.
     //
-    // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+    // The slot must be in the range of [0, SLOT_COUNT).
     //
     // Upon success, the output will be filled with meaningful values
     // (refer to the documentation below).
@@ -460,7 +493,7 @@
     //
     // The buffer is not queued for use by the consumer.
     //
-    // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+    // The slot must be in the range of [0, SLOT_COUNT).
     //
     // The buffer will not be overwritten until the fence signals.  The fence
     // will usually be the one obtained from dequeueBuffer.
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 14a3513..755674d 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -558,7 +558,11 @@
     // slot that has not yet been used. The buffer allocated to a slot will also
     // be replaced if the requested buffer usage or geometry differs from that
     // of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    std::vector<BufferSlot> mSlots;
+#else
     BufferSlot mSlots[NUM_BUFFER_SLOTS];
+#endif
 
     // mReqWidth is the buffer width that will be requested at the next dequeue
     // operation. It is initialized to 1.
@@ -732,6 +736,10 @@
     std::vector<sp<GraphicBuffer>> mRemovedBuffers;
     int mMaxBufferCount;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+    bool mIsSlotExpansionAllowed;
+#endif
+
     sp<IProducerListener> mListenerProxy;
 
     // Get and flush the buffers of given slots, if the buffer in the slot
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index f07747f..87051a7 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -55,6 +55,7 @@
         "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_EXTENDEDALLOCATE=true",
         "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_CONSUMER_BASE_OWNS_BQ=true",
         "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_PLATFORM_API_IMPROVEMENTS=true",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_UNLIMITED_SLOTS=true",
     ],
 
     srcs: [
diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp
index 3b6a66e..6453885 100644
--- a/libs/gui/tests/BufferItemConsumer_test.cpp
+++ b/libs/gui/tests/BufferItemConsumer_test.cpp
@@ -22,8 +22,11 @@
 #include <gui/BufferItemConsumer.h>
 #include <gui/IProducerListener.h>
 #include <gui/Surface.h>
+#include <ui/BufferQueueDefs.h>
 #include <ui/GraphicBuffer.h>
 
+#include <unordered_set>
+
 namespace android {
 
 static constexpr int kWidth = 100;
@@ -57,6 +60,8 @@
     };
 
     void SetUp() override {
+        mBuffers.resize(BufferQueueDefs::NUM_BUFFER_SLOTS);
+
         mBIC = new BufferItemConsumer(kUsage, kMaxLockedBuffers, true);
         String8 name("BufferItemConsumer_Under_Test");
         mBIC->setName(name);
@@ -137,6 +142,11 @@
         ASSERT_EQ(NO_ERROR, ret);
     }
 
+    void DetachBuffer(int slot) {
+        ALOGD("detachBuffer: slot=%d", slot);
+        status_t ret = mBIC->detachBuffer(mBuffers[slot]);
+        ASSERT_EQ(NO_ERROR, ret);
+    }
 
     std::mutex mMutex;
     int mFreedBufferCount{0};
@@ -146,7 +156,7 @@
     sp<BufferFreedListener> mBFL;
     sp<IGraphicBufferProducer> mProducer;
     sp<IGraphicBufferConsumer> mConsumer;
-    sp<GraphicBuffer> mBuffers[BufferQueueDefs::NUM_BUFFER_SLOTS];
+    std::vector<sp<GraphicBuffer>> mBuffers;
 };
 
 // Test that detaching buffer from consumer side triggers onBufferFreed.
@@ -239,4 +249,52 @@
 }
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST_F(BufferItemConsumerTest, UnlimitedSlots_AcquireReleaseAll) {
+    ASSERT_EQ(OK, mProducer->extendSlotCount(256));
+    mBuffers.resize(256);
+
+    ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(100));
+
+    std::unordered_set<int> slots;
+    for (int i = 0; i < 100; i++) {
+        int slot;
+        DequeueBuffer(&slot);
+        slots.insert(slot);
+    }
+    EXPECT_EQ(100u, slots.size());
+
+    for (int dequeuedSlot : slots) {
+        QueueBuffer(dequeuedSlot);
+
+        int slot;
+        AcquireBuffer(&slot);
+        ReleaseBuffer(slot);
+    }
+}
+
+TEST_F(BufferItemConsumerTest, UnlimitedSlots_AcquireDetachAll) {
+    ASSERT_EQ(OK, mProducer->extendSlotCount(256));
+    mBuffers.resize(256);
+
+    ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(100));
+
+    std::unordered_set<int> slots;
+    for (int i = 0; i < 100; i++) {
+        int slot;
+        DequeueBuffer(&slot);
+        slots.insert(slot);
+    }
+    EXPECT_EQ(100u, slots.size());
+
+    for (int dequeuedSlot : slots) {
+        QueueBuffer(dequeuedSlot);
+
+        int slot;
+        AcquireBuffer(&slot);
+        DetachBuffer(slot);
+    }
+}
+#endif
+
 }  // namespace android
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 1606099..77b4ae8 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -20,6 +20,8 @@
 #include "Constants.h"
 #include "MockConsumer.h"
 
+#include <EGL/egl.h>
+
 #include <gui/BufferItem.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/BufferQueue.h>
@@ -44,7 +46,9 @@
 #include <gtest/gtest.h>
 
 #include <future>
+#include <optional>
 #include <thread>
+#include <unordered_map>
 
 #include <com_android_graphics_libgui_flags.h>
 
@@ -1612,4 +1616,221 @@
     }
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+struct MockUnlimitedSlotConsumer : public MockConsumer {
+    virtual void onSlotCountChanged(int size) override { mSize = size; }
+
+    std::optional<int> mSize;
+};
+
+TEST_F(BufferQueueTest, UnlimitedSlots_FailsWhenNotAllowed) {
+    createBufferQueue();
+
+    sp<MockUnlimitedSlotConsumer> mc = sp<MockUnlimitedSlotConsumer>::make();
+    EXPECT_EQ(OK, mConsumer->consumerConnect(mc, false));
+
+    EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(64));
+    EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(32));
+    EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(128));
+
+    EXPECT_EQ(std::nullopt, mc->mSize);
+}
+
+TEST_F(BufferQueueTest, UnlimitedSlots_OnlyAllowedForExtensions) {
+    createBufferQueue();
+
+    sp<MockUnlimitedSlotConsumer> consumerListener = sp<MockUnlimitedSlotConsumer>::make();
+    EXPECT_EQ(OK, mConsumer->consumerConnect(consumerListener, false));
+    EXPECT_EQ(OK, mConsumer->allowUnlimitedSlots(true));
+
+    EXPECT_EQ(BAD_VALUE, mProducer->extendSlotCount(32));
+    EXPECT_EQ(OK, mProducer->extendSlotCount(64));
+    EXPECT_EQ(OK, mProducer->extendSlotCount(128));
+    EXPECT_EQ(128, *consumerListener->mSize);
+
+    EXPECT_EQ(OK, mProducer->extendSlotCount(128));
+    EXPECT_EQ(BAD_VALUE, mProducer->extendSlotCount(127));
+}
+
+class BufferQueueUnlimitedTest : public BufferQueueTest {
+protected:
+    static constexpr auto kMaxBufferCount = 128;
+    static constexpr auto kAcquirableBufferCount = 2;
+    static constexpr auto kDequeableBufferCount = kMaxBufferCount - kAcquirableBufferCount;
+
+    virtual void SetUp() override {
+        BufferQueueTest::SetUp();
+
+        createBufferQueue();
+        setUpConsumer();
+        setUpProducer();
+    }
+
+    void setUpConsumer() {
+        EXPECT_EQ(OK, mConsumer->consumerConnect(mConsumerListener, false));
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        EXPECT_EQ(OK, mConsumer->allowUnlimitedSlots(true));
+#endif
+        EXPECT_EQ(OK, mConsumer->setConsumerUsageBits(GraphicBuffer::USAGE_SW_READ_OFTEN));
+        EXPECT_EQ(OK, mConsumer->setDefaultBufferSize(10, 10));
+        EXPECT_EQ(OK, mConsumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+        EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(kAcquirableBufferCount));
+    }
+
+    void setUpProducer() {
+        EXPECT_EQ(OK, mProducer->extendSlotCount(kMaxBufferCount));
+
+        IGraphicBufferProducer::QueueBufferOutput output;
+        EXPECT_EQ(OK,
+                  mProducer->connect(mProducerListener, NATIVE_WINDOW_API_CPU,
+                                     /*producerControlledByApp*/ true, &output));
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+        ASSERT_TRUE(output.isSlotExpansionAllowed);
+#endif
+        ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(kDequeableBufferCount));
+        ASSERT_EQ(OK, mProducer->allowAllocation(true));
+    }
+
+    std::unordered_map<int, sp<Fence>> dequeueAll() {
+        std::unordered_map<int, sp<Fence>> slotsToFences;
+
+        for (int i = 0; i < kDequeableBufferCount; ++i) {
+            int slot;
+            sp<Fence> fence;
+            sp<GraphicBuffer> buffer;
+
+            status_t ret =
+                    mProducer->dequeueBuffer(&slot, &fence, /*w*/ 0, /*h*/ 0, /*format*/ 0,
+                                             /*uint64_t*/ 0,
+                                             /*outBufferAge*/ nullptr, /*outTimestamps*/ nullptr);
+            if (ret & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+                EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buffer))
+                        << "Unable to request buffer for slot " << slot;
+            }
+            EXPECT_FALSE(slotsToFences.contains(slot));
+            slotsToFences.emplace(slot, fence);
+        }
+        EXPECT_EQ(kDequeableBufferCount, (int)slotsToFences.size());
+        return slotsToFences;
+    }
+
+    sp<MockUnlimitedSlotConsumer> mConsumerListener = sp<MockUnlimitedSlotConsumer>::make();
+    sp<StubProducerListener> mProducerListener = sp<StubProducerListener>::make();
+};
+
+TEST_F(BufferQueueUnlimitedTest, ExpandOverridesConsumerMaxBuffers) {
+    createBufferQueue();
+    setUpConsumer();
+    EXPECT_EQ(OK, mConsumer->setMaxBufferCount(10));
+
+    setUpProducer();
+
+    EXPECT_EQ(kDequeableBufferCount, (int)dequeueAll().size());
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanDetachAll) {
+    auto slotsToFences = dequeueAll();
+    for (auto& [slot, fence] : slotsToFences) {
+        EXPECT_EQ(OK, mProducer->detachBuffer(slot));
+    }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanCancelAll) {
+    auto slotsToFences = dequeueAll();
+    for (auto& [slot, fence] : slotsToFences) {
+        EXPECT_EQ(OK, mProducer->cancelBuffer(slot, fence));
+    }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanAcquireAndReleaseAll) {
+    auto slotsToFences = dequeueAll();
+    for (auto& [slot, fence] : slotsToFences) {
+        IGraphicBufferProducer::QueueBufferInput input;
+        input.fence = fence;
+
+        IGraphicBufferProducer::QueueBufferOutput output;
+        EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+        BufferItem buffer;
+        EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+        EXPECT_EQ(OK,
+                  mConsumer->releaseBuffer(buffer.mSlot, buffer.mFrameNumber, EGL_NO_DISPLAY,
+                                           EGL_NO_SYNC, buffer.mFence));
+    }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanAcquireAndDetachAll) {
+    auto slotsToFences = dequeueAll();
+    for (auto& [slot, fence] : slotsToFences) {
+        IGraphicBufferProducer::QueueBufferInput input;
+        input.fence = fence;
+
+        IGraphicBufferProducer::QueueBufferOutput output;
+        EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+        BufferItem buffer;
+        EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+        EXPECT_EQ(OK, mConsumer->detachBuffer(buffer.mSlot));
+    }
+}
+
+TEST_F(BufferQueueUnlimitedTest, GetReleasedBuffersExtended) {
+    // First, acquire and release all the buffers so the consumer "knows" about
+    // them
+    auto slotsToFences = dequeueAll();
+
+    std::vector<bool> releasedSlots;
+    EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+    for (auto& [slot, _] : slotsToFences) {
+        EXPECT_TRUE(releasedSlots[slot])
+                << "Slots that haven't been acquired will show up as released.";
+    }
+    for (auto& [slot, fence] : slotsToFences) {
+        IGraphicBufferProducer::QueueBufferInput input;
+        input.fence = fence;
+
+        IGraphicBufferProducer::QueueBufferOutput output;
+        EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+        BufferItem buffer;
+        EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+        EXPECT_EQ(OK,
+                  mConsumer->releaseBuffer(buffer.mSlot, buffer.mFrameNumber, EGL_NO_DISPLAY,
+                                           EGL_NO_SYNC_KHR, buffer.mFence));
+    }
+
+    EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+    for (auto& [slot, _] : slotsToFences) {
+        EXPECT_FALSE(releasedSlots[slot])
+                << "Slots that have been acquired will show up as not released.";
+    }
+
+    // Then, alternatively cancel and detach (release) buffers. Only detached
+    // buffers should be returned by getReleasedBuffersExtended
+    slotsToFences = dequeueAll();
+    std::set<int> cancelledSlots;
+    std::set<int> detachedSlots;
+    bool cancel;
+    for (auto& [slot, fence] : slotsToFences) {
+        if (cancel) {
+            EXPECT_EQ(OK, mProducer->cancelBuffer(slot, fence));
+            cancelledSlots.insert(slot);
+        } else {
+            EXPECT_EQ(OK, mProducer->detachBuffer(slot));
+            detachedSlots.insert(slot);
+        }
+        cancel = !cancel;
+    }
+
+    EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+    for (int slot : detachedSlots) {
+        EXPECT_TRUE(releasedSlots[slot]) << "Slots that are detached are released.";
+    }
+    for (int slot : cancelledSlots) {
+        EXPECT_FALSE(releasedSlots[slot])
+                << "Slots that are still held in the queue are not released.";
+    }
+}
+#endif //  COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
 } // namespace android
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index f4239cb..9476930 100644
--- a/libs/gui/tests/CpuConsumer_test.cpp
+++ b/libs/gui/tests/CpuConsumer_test.cpp
@@ -803,6 +803,27 @@
         ::testing::ValuesIn(rgba8888TestSets));
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST(CpuConsumerSlotTest, UnlimitedSlots_AcquireReleaseAll) {
+    sp<CpuConsumer> cpuConsumer = sp<CpuConsumer>::make(3);
+    sp<Surface> surface = cpuConsumer->getSurface();
+    sp<SurfaceListener> listener = sp<StubSurfaceListener>::make();
 
+    ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, listener));
+    ASSERT_EQ(OK, surface->setMaxDequeuedBufferCount(256));
 
+    std::vector<Surface::BatchBuffer> buffers(256);
+    EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+
+    for (auto& buffer : buffers) {
+        sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(buffer.buffer);
+        sp<Fence> fence = sp<Fence>::make(buffer.fenceFd);
+        EXPECT_EQ(OK, surface->queueBuffer(graphicBuffer, fence));
+
+        CpuConsumer::LockedBuffer nativeBuffer;
+        EXPECT_EQ(OK, cpuConsumer->lockNextBuffer(&nativeBuffer));
+        EXPECT_EQ(OK, cpuConsumer->unlockBuffer(nativeBuffer));
+    }
+}
+#endif
 } // namespace android
diff --git a/libs/gui/tests/FillBuffer.cpp b/libs/gui/tests/FillBuffer.cpp
index b60995a..11383d9 100644
--- a/libs/gui/tests/FillBuffer.cpp
+++ b/libs/gui/tests/FillBuffer.cpp
@@ -76,7 +76,7 @@
 }
 
 void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
-    const size_t PIXEL_SIZE = 4;
+    constexpr size_t PIXEL_SIZE = 4;
     for (int x = 0; x < w; x++) {
         for (int y = 0; y < h; y++) {
             off_t offset = (y * stride + x) * PIXEL_SIZE;
@@ -89,6 +89,21 @@
     }
 }
 
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride, uint8_t r, uint8_t g, uint8_t b,
+                     uint8_t a) {
+    constexpr size_t PIXEL_SIZE = 4;
+
+    for (int x = 0; x < w; x++) {
+        for (int y = 0; y < h; y++) {
+            off_t offset = (y * stride + x) * PIXEL_SIZE;
+            buf[offset] = r;
+            buf[offset + 1] = g;
+            buf[offset + 2] = b;
+            buf[offset + 3] = a;
+        }
+    }
+}
+
 void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) {
     android_native_buffer_t* anb;
     ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
diff --git a/libs/gui/tests/FillBuffer.h b/libs/gui/tests/FillBuffer.h
index b584179..f5d6b8b 100644
--- a/libs/gui/tests/FillBuffer.h
+++ b/libs/gui/tests/FillBuffer.h
@@ -30,6 +30,8 @@
         const android_native_rect_t& rect);
 
 void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride);
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride, uint8_t r, uint8_t g, uint8_t b,
+                     uint8_t a);
 
 // Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern
 // using the CPU.  This assumes that the ANativeWindow is already configured to
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
index 9ffe91f..c502533 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_COMPATIBILITY_GTE,
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
     // Privileged APIs.
diff --git a/libs/gui/tests/SurfaceTextureGL_test.cpp b/libs/gui/tests/SurfaceTextureGL_test.cpp
index 449533a..b22b853 100644
--- a/libs/gui/tests/SurfaceTextureGL_test.cpp
+++ b/libs/gui/tests/SurfaceTextureGL_test.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "SurfaceTextureGL_test"
 //#define LOG_NDEBUG 0
 
+#include <gmock/gmock.h>
+
 #include "SurfaceTextureGL.h"
 
 #include "DisconnectWaiter.h"
@@ -735,4 +737,30 @@
     ASSERT_NE(NO_ERROR, mST->updateTexImage());
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+TEST_F(SurfaceTextureGLTest, TestUnlimitedSlots) {
+    ASSERT_EQ(OK, mSTC->connect(NATIVE_WINDOW_API_CPU, sp<StubSurfaceListener>::make()));
+    ASSERT_EQ(OK, mSTC->setMaxDequeuedBufferCount(256));
+
+    std::vector<Surface::BatchBuffer> buffers(256);
+    ASSERT_EQ(OK, mSTC->dequeueBuffers(&buffers));
+    ASSERT_EQ(256u, buffers.size());
+    ASSERT_THAT(buffers, Each(Field(&Surface::BatchBuffer::buffer, ::testing::NotNull())));
+
+    for (size_t i = 0; i < buffers.size(); ++i) {
+        sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(buffers[i].buffer);
+        sp<Fence> fence = sp<Fence>::make(buffers[i].fenceFd);
+
+        void* buf;
+        ASSERT_EQ(OK, graphicBuffer->lock(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, &buf));
+        fillRGBA8Buffer((uint8_t*)buf, graphicBuffer->getWidth(), graphicBuffer->getHeight(),
+                        graphicBuffer->getStride(), i, i, i, i);
+        graphicBuffer->unlock();
+
+        ASSERT_EQ(OK, mSTC->queueBuffer(graphicBuffer, fence));
+        ASSERT_EQ(OK, mST->updateTexImage());
+        checkPixel(0, 0, i, i, i, i);
+    }
+}
+#endif
 } // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 3185778..646e30e 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-#include "gui/view/Surface.h"
-#include "Constants.h"
-#include "MockConsumer.h"
-
 #include <gtest/gtest.h>
 
 #include <SurfaceFlingerProperties.h>
@@ -36,10 +32,13 @@
 #include <gui/IConsumerListener.h>
 #include <gui/IGraphicBufferConsumer.h>
 #include <gui/IGraphicBufferProducer.h>
+#include <gui/IProducerListener.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
+#include <gui/view/Surface.h>
+#include <nativebase/nativebase.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <sys/types.h>
@@ -47,6 +46,7 @@
 #include <ui/BufferQueueDefs.h>
 #include <ui/DisplayMode.h>
 #include <ui/GraphicBuffer.h>
+#include <ui/PixelFormat.h>
 #include <ui/Rect.h>
 #include <utils/Errors.h>
 #include <utils/String8.h>
@@ -55,9 +55,12 @@
 #include <cstddef>
 #include <cstdint>
 #include <future>
+#include <iterator>
 #include <limits>
 #include <thread>
 
+#include "Constants.h"
+#include "MockConsumer.h"
 #include "testserver/TestServerClient.h"
 
 namespace android {
@@ -2533,4 +2536,128 @@
 }
 #endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST_F(SurfaceTest, UnlimitedSlots_FailsOnIncompatibleConsumer) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+    EXPECT_EQ(OK, consumer->allowUnlimitedSlots(false));
+    EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+    EXPECT_NE(OK, surface->setMaxDequeuedBufferCount(128))
+            << "We shouldn't be able to set high max buffer counts if the consumer doesn't allow "
+               "it";
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_CanDequeueAndQueueMoreThanOldMaximum) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+    EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+    EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+    EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+    EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+    EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+            << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+               "count arbitrarily";
+
+    std::vector<std::tuple<sp<GraphicBuffer>, sp<Fence>, int>> buffers;
+    for (int i = 0; i < 128; i++) {
+        sp<GraphicBuffer> buffer;
+        sp<Fence> fence;
+        ASSERT_EQ(OK, surface->dequeueBuffer(&buffer, &fence)) << "Unable to dequeue buffer #" << i;
+        buffers.push_back({buffer, fence, i});
+    }
+
+    for (auto& [buffer, fence, idx] : buffers) {
+        ASSERT_EQ(OK, surface->queueBuffer(buffer, fence)) << "Unable to queue buffer #" << idx;
+    }
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_CanDequeueAndDetachMoreThanOldMaximum) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+    EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+    EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+    EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+    EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+    EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+            << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+               "count arbitrarily";
+
+    std::vector<std::tuple<sp<GraphicBuffer>, sp<Fence>, int>> buffers;
+    for (int i = 0; i < 128; i++) {
+        sp<GraphicBuffer> buffer;
+        sp<Fence> fence;
+        ASSERT_EQ(OK, surface->dequeueBuffer(&buffer, &fence)) << "Unable to dequeue buffer #" << i;
+        buffers.push_back({buffer, fence, i});
+    }
+
+    for (auto& [buffer, _, idx] : buffers) {
+        ASSERT_EQ(OK, surface->detachBuffer(buffer)) << "Unable to detach buffer #" << idx;
+    }
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_BatchOperations) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+    EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+    EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+    EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+    EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+    EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+            << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+               "count arbitrarily";
+
+    std::vector<Surface::BatchBuffer> buffers(128);
+    EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+    EXPECT_EQ(128u, buffers.size());
+
+    std::vector<Surface::BatchQueuedBuffer> queuedBuffers;
+    std::transform(buffers.begin(), buffers.end(), std::back_inserter(queuedBuffers),
+                   [](Surface::BatchBuffer& buffer) {
+                       Surface::BatchQueuedBuffer out;
+                       out.buffer = buffer.buffer;
+                       out.fenceFd = buffer.fenceFd;
+                       return out;
+                   });
+
+    std::vector<SurfaceQueueBufferOutput> outputs;
+    EXPECT_EQ(OK, surface->queueBuffers(queuedBuffers, &outputs));
+    EXPECT_EQ(128u, outputs.size());
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
 } // namespace android
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index ed3e8c1..10abb7c 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -258,11 +258,11 @@
     ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1,
 
     /**
-     * The window requests a frame rate that is greater than or equal to the specified frame rate.
+     * The window requests a frame rate that is at least 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
+    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST = 2
 };
 
 /**
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index 243532b..7e179d6 100644
--- a/libs/permission/include/binder/AppOpsManager.h
+++ b/libs/permission/include/binder/AppOpsManager.h
@@ -148,7 +148,10 @@
         OP_BLUETOOTH_ADVERTISE = 114,
         OP_RECORD_INCOMING_PHONE_AUDIO = 115,
         OP_NEARBY_WIFI_DEVICES = 116,
-        _NUM_OP = 117
+        // 116 - 154 omitted due to lack of use in native
+        OP_CONTROL_AUDIO = 154,
+        OP_CONTROL_AUDIO_PARTIAL = 155,
+        _NUM_OP = 156,
     };
 
     enum {
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 811692f..5bf6ebb 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -98,6 +98,22 @@
     return privacySensitiveDisplays;
 }
 
+vec2 calculatePositionOnDestinationViewport(const DisplayViewport& destinationViewport,
+                                            float pointerOffset,
+                                            DisplayTopologyPosition sourceBoundary) {
+    // destination is opposite of the source boundary
+    switch (sourceBoundary) {
+        case DisplayTopologyPosition::RIGHT:
+            return {0, pointerOffset}; // left edge
+        case DisplayTopologyPosition::TOP:
+            return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
+        case DisplayTopologyPosition::LEFT:
+            return {destinationViewport.logicalRight, pointerOffset}; // right edge
+        case DisplayTopologyPosition::BOTTOM:
+            return {pointerOffset, 0}; // top edge
+    }
+}
+
 } // namespace
 
 // --- PointerChoreographer ---
@@ -327,19 +343,19 @@
     // 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;
+    DisplayTopologyPosition sourceBoundary;
     float cursorOffset = 0.0f;
     if (rotatedUnconsumedDelta.x > 0) {
-        sourceBoundary = DisplayPosition::RIGHT;
+        sourceBoundary = DisplayTopologyPosition::RIGHT;
         cursorOffset = rotatedCursorPosition.y;
     } else if (rotatedUnconsumedDelta.x < 0) {
-        sourceBoundary = DisplayPosition::LEFT;
+        sourceBoundary = DisplayTopologyPosition::LEFT;
         cursorOffset = rotatedCursorPosition.y;
     } else if (rotatedUnconsumedDelta.y > 0) {
-        sourceBoundary = DisplayPosition::BOTTOM;
+        sourceBoundary = DisplayTopologyPosition::BOTTOM;
         cursorOffset = rotatedCursorPosition.x;
     } else {
-        sourceBoundary = DisplayPosition::TOP;
+        sourceBoundary = DisplayTopologyPosition::TOP;
         cursorOffset = rotatedCursorPosition.x;
     }
 
@@ -369,8 +385,9 @@
     pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
     pc.setDisplayViewport(destinationViewport);
     vec2 destinationPosition =
-            calculateDestinationPosition(destinationViewport, cursorOffset - destinationOffset,
-                                         sourceBoundary);
+            calculatePositionOnDestinationViewport(destinationViewport,
+                                                   cursorOffset - destinationOffset,
+                                                   sourceBoundary);
 
     // Transform position back to un-rotated coordinate space before sending it to controller
     destinationPosition = pc.getDisplayTransform().inverse().transform(destinationPosition.x,
@@ -379,22 +396,6 @@
     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;
@@ -540,8 +541,7 @@
 }
 
 void PointerChoreographer::onControllerAddedOrRemovedLocked() {
-    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
-        !com::android::input::flags::connected_displays_cursor()) {
+    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
         return;
     }
     bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
@@ -607,11 +607,16 @@
     mNextListener.notify(args);
 }
 
-void PointerChoreographer::setDisplayTopology(
-        const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
-                displayTopology) {
+void PointerChoreographer::setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph) {
     std::scoped_lock _l(getLock());
-    mTopology = displayTopology;
+    mTopology = displayTopologyGraph;
+
+    // make primary display default mouse display, if it was not set
+    // or the existing display was removed
+    if (mDefaultMouseDisplayId == ui::LogicalDisplayId::INVALID ||
+        mTopology.graph.find(mDefaultMouseDisplayId) != mTopology.graph.end()) {
+        mDefaultMouseDisplayId = mTopology.primaryDisplayId;
+    }
 }
 
 void PointerChoreographer::dump(std::string& dump) {
@@ -985,73 +990,17 @@
     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,
+                                                   const DisplayTopologyPosition sourceBoundary,
                                                    float cursorOffset) const {
-    const auto& sourceNode = mTopology.find(sourceDisplayId);
-    if (sourceNode == mTopology.end()) {
+    const auto& sourceNode = mTopology.graph.find(sourceDisplayId);
+    if (sourceNode == mTopology.graph.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) {
+    for (const DisplayTopologyAdjacentDisplay& adjacentDisplay : sourceNode->second) {
         if (adjacentDisplay.position != sourceBoundary) {
             continue;
         }
@@ -1064,8 +1013,8 @@
             continue;
         }
         // target position must be within target display boundary
-        const int32_t edgeSize =
-                sourceBoundary == DisplayPosition::TOP || sourceBoundary == DisplayPosition::BOTTOM
+        const int32_t edgeSize = sourceBoundary == DisplayTopologyPosition::TOP ||
+                        sourceBoundary == DisplayTopologyPosition::BOTTOM
                 ? (destinationViewport->logicalRight - destinationViewport->logicalLeft)
                 : (destinationViewport->logicalBottom - destinationViewport->logicalTop);
         if (cursorOffset >= adjacentDisplay.offsetPx &&
@@ -1093,7 +1042,6 @@
         mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
         mPointerChoreographer->onPrivacySensitiveDisplaysChangedLocked(mPrivacySensitiveDisplays);
     }
-    mPointerChoreographer->populateFakeDisplayTopologyLocked(windowInfosUpdate.displayInfos);
 }
 
 void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked(
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 939529f..c2f5ec0 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -22,6 +22,7 @@
 
 #include <android-base/thread_annotations.h>
 #include <gui/WindowInfosListener.h>
+#include <input/DisplayTopologyGraph.h>
 #include <type_traits>
 #include <unordered_set>
 
@@ -80,6 +81,11 @@
      */
     virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0;
 
+    /*
+     * Used by InputManager to notify changes in the DisplayTopology
+     */
+    virtual void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph) = 0;
+
     /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
@@ -103,6 +109,7 @@
                         ui::LogicalDisplayId displayId, DeviceId deviceId) override;
     void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
     void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
+    void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph);
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyKey(const NotifyKeyArgs& args) override;
@@ -113,24 +120,6 @@
     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:
@@ -174,18 +163,16 @@
     void handleUnconsumedDeltaLocked(PointerControllerInterface& pc, const vec2& unconsumedDelta)
             REQUIRES(getLock());
 
-    void populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo>& displayInfos)
+    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> findDestinationDisplayLocked(
+            const ui::LogicalDisplayId sourceDisplayId,
+            const DisplayTopologyPosition sourceBoundary, float cursorOffset) const
             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());
+    /* Topology is initialized with default-constructed value, which is an empty topology. Till we
+     * receive setDisplayTopology call.
+     * Meanwhile Choreographer will treat every display as independent disconnected display.
+     */
+    DisplayTopologyGraph mTopology GUARDED_BY(getLock());
 
     /* This listener keeps tracks of visible privacy sensitive displays and updates the
      * choreographer if there are any changes.
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 27da3d3..1ca2998 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -2633,16 +2633,14 @@
                            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}}},
-            };
+    DisplayTopologyGraph mTopology{DISPLAY_CENTER_ID,
+                                   {{DISPLAY_CENTER_ID,
+                                     {{DISPLAY_TOP_ID, DisplayTopologyPosition::TOP, 10.0f},
+                                      {DISPLAY_RIGHT_ID, DisplayTopologyPosition::RIGHT, 10.0f},
+                                      {DISPLAY_BOTTOM_ID, DisplayTopologyPosition::BOTTOM, 10.0f},
+                                      {DISPLAY_LEFT_ID, DisplayTopologyPosition::LEFT, 10.0f},
+                                      {DISPLAY_TOP_RIGHT_CORNER_ID, DisplayTopologyPosition::RIGHT,
+                                       -90.0f}}}}};
 
 private:
     DisplayViewport createViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
@@ -2706,6 +2704,11 @@
         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("PrimaryDisplayIsDefault", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(50, 50) /* initial x/y */, vec2(0, 0) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(50, 50) /* destination x/y */),
                 std::make_tuple("UnchangedDisplay", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE,
                                 ToolType::MOUSE, vec2(50, 50) /* initial x/y */,
                                 vec2(25, 25) /* delta x/y */,
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 6e2b943..8c22de1 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_COMPATIBILITY_GTE:
+        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST:
             return FrameRateCompatibility::Gte;
         case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
             return FrameRateCompatibility::NoVote;
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 53a9062..f3d6dcc 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_COMPATIBILITY_GTE,
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                  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_COMPATIBILITY_GTE,
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);
 
@@ -662,7 +662,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
 
     EXPECT_EQ(1u, layerCount());
@@ -694,7 +694,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);