Merge "Move blast sync handling to BBQ"
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 36de581..2d2b6b2 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -46,6 +46,8 @@
 namespace android {
 
 // Macros to include adapter info in log messages
+#define BQA_LOGD(x, ...) \
+    ALOGD("[%s](f:%u,a:%u) " x, mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__)
 #define BQA_LOGV(x, ...) \
     ALOGV("[%s](f:%u,a:%u) " x, mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__)
 // enable logs for a single layer
@@ -244,6 +246,67 @@
     }
 }
 
+static std::optional<SurfaceControlStats> findMatchingStat(
+        const std::vector<SurfaceControlStats>& stats, const sp<SurfaceControl>& sc) {
+    for (auto stat : stats) {
+        if (SurfaceControl::isSameSurface(sc, stat.surfaceControl)) {
+            return stat;
+        }
+    }
+    return std::nullopt;
+}
+
+static void transactionCommittedCallbackThunk(void* context, nsecs_t latchTime,
+                                              const sp<Fence>& presentFence,
+                                              const std::vector<SurfaceControlStats>& stats) {
+    if (context == nullptr) {
+        return;
+    }
+    sp<BLASTBufferQueue> bq = static_cast<BLASTBufferQueue*>(context);
+    bq->transactionCommittedCallback(latchTime, presentFence, stats);
+}
+
+void BLASTBufferQueue::transactionCommittedCallback(nsecs_t /*latchTime*/,
+                                                    const sp<Fence>& /*presentFence*/,
+                                                    const std::vector<SurfaceControlStats>& stats) {
+    {
+        std::unique_lock _lock{mMutex};
+        ATRACE_CALL();
+        BQA_LOGV("transactionCommittedCallback");
+        if (!mSurfaceControlsWithPendingCallback.empty()) {
+            sp<SurfaceControl> pendingSC = mSurfaceControlsWithPendingCallback.front();
+            std::optional<SurfaceControlStats> stat = findMatchingStat(stats, pendingSC);
+            if (stat) {
+                uint64_t currFrameNumber = stat->frameEventStats.frameNumber;
+
+                // We need to check if we were waiting for a transaction callback in order to
+                // process any pending buffers and unblock. It's possible to get transaction
+                // callbacks for previous requests so we need to ensure the frame from this
+                // transaction callback matches the last acquired buffer. Since acquireNextBuffer
+                // will stop processing buffers when mWaitForTransactionCallback is set, we know
+                // that mLastAcquiredFrameNumber is the frame we're waiting on.
+                // We also want to check if mNextTransaction is null because it's possible another
+                // sync request came in while waiting, but it hasn't started processing yet. In that
+                // case, we don't actually want to flush the frames in between since they will get
+                // processed and merged with the sync transaction and released earlier than if they
+                // were sent to SF
+                if (mWaitForTransactionCallback && mNextTransaction == nullptr &&
+                    currFrameNumber >= mLastAcquiredFrameNumber) {
+                    mWaitForTransactionCallback = false;
+                    flushShadowQueue();
+                }
+            } else {
+                BQA_LOGE("Failed to find matching SurfaceControl in transaction callback");
+            }
+        } else {
+            BQA_LOGE("No matching SurfaceControls found: mSurfaceControlsWithPendingCallback was "
+                     "empty.");
+        }
+
+        decStrong((void*)transactionCommittedCallbackThunk);
+    }
+}
+
 static void transactionCallbackThunk(void* context, nsecs_t latchTime,
                                      const sp<Fence>& presentFence,
                                      const std::vector<SurfaceControlStats>& stats) {
@@ -267,12 +330,9 @@
         if (!mSurfaceControlsWithPendingCallback.empty()) {
             sp<SurfaceControl> pendingSC = mSurfaceControlsWithPendingCallback.front();
             mSurfaceControlsWithPendingCallback.pop();
-            bool found = false;
-            for (auto stat : stats) {
-                if (!SurfaceControl::isSameSurface(pendingSC, stat.surfaceControl)) {
-                    continue;
-                }
-
+            std::optional<SurfaceControlStats> statsOptional = findMatchingStat(stats, pendingSC);
+            if (statsOptional) {
+                SurfaceControlStats stat = *statsOptional;
                 mTransformHint = stat.transformHint;
                 mBufferItemConsumer->setTransformHint(mTransformHint);
                 BQA_LOGV("updated mTransformHint=%d", mTransformHint);
@@ -300,12 +360,7 @@
                     transactionCompleteCallback = std::move(mTransactionCompleteCallback);
                     mTransactionCompleteFrameNumber = 0;
                 }
-
-                found = true;
-                break;
-            }
-
-            if (!found) {
+            } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transaction callback");
             }
         } else {
@@ -336,6 +391,15 @@
     }
 }
 
+void BLASTBufferQueue::flushShadowQueue() {
+    BQA_LOGV("flushShadowQueue");
+    int numFramesToFlush = mNumFrameAvailable;
+    while (numFramesToFlush > 0) {
+        acquireNextBufferLocked(std::nullopt);
+        numFramesToFlush--;
+    }
+}
+
 void BLASTBufferQueue::releaseBufferCallback(
         const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
         std::optional<uint32_t> currentMaxAcquiredBufferCount) {
@@ -374,7 +438,11 @@
         BQA_LOGV("released %s", releaseBuffer.callbackId.to_string().c_str());
         mBufferItemConsumer->releaseBuffer(it->second, releaseBuffer.releaseFence);
         mSubmitted.erase(it);
-        processNextBufferLocked(false /* useNextTransaction */);
+        // Don't process the transactions here if mWaitForTransactionCallback is set. Instead, let
+        // onFrameAvailable handle processing them since it will merge with the nextTransaction.
+        if (!mWaitForTransactionCallback) {
+            acquireNextBufferLocked(std::nullopt);
+        }
     }
 
     ATRACE_INT("PendingRelease", mPendingRelease.size());
@@ -383,13 +451,15 @@
     mCallbackCV.notify_all();
 }
 
-void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {
+void BLASTBufferQueue::acquireNextBufferLocked(
+        const std::optional<SurfaceComposerClient::Transaction*> transaction) {
     ATRACE_CALL();
     // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
     // include the extra buffer when checking if we can acquire the next buffer.
-    const bool includeExtraAcquire = !useNextTransaction;
-    if (mNumFrameAvailable == 0 || maxBuffersAcquired(includeExtraAcquire)) {
-        mCallbackCV.notify_all();
+    const bool includeExtraAcquire = !transaction;
+    const bool maxAcquired = maxBuffersAcquired(includeExtraAcquire);
+    if (mNumFrameAvailable == 0 || maxAcquired) {
+        BQA_LOGV("Can't process next buffer maxBuffersAcquired=%s", boolToString(maxAcquired));
         return;
     }
 
@@ -401,9 +471,8 @@
     SurfaceComposerClient::Transaction localTransaction;
     bool applyTransaction = true;
     SurfaceComposerClient::Transaction* t = &localTransaction;
-    if (mNextTransaction != nullptr && useNextTransaction) {
-        t = mNextTransaction;
-        mNextTransaction = nullptr;
+    if (transaction) {
+        t = *transaction;
         applyTransaction = false;
     }
 
@@ -433,7 +502,7 @@
                  mSize.width, mSize.height, mRequestedSize.width, mRequestedSize.height,
                  buffer->getWidth(), buffer->getHeight(), bufferItem.mTransform);
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
-        processNextBufferLocked(useNextTransaction);
+        acquireNextBufferLocked(transaction);
         return;
     }
 
@@ -452,6 +521,7 @@
 
     // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
     incStrong((void*)transactionCallbackThunk);
+    incStrong((void*)transactionCommittedCallbackThunk);
 
     const bool sizeHasChanged = mRequestedSize != mSize;
     mSize = mRequestedSize;
@@ -471,6 +541,7 @@
     t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
     t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
     t->addTransactionCompletedCallback(transactionCallbackThunk, static_cast<void*>(this));
+    t->addTransactionCommittedCallback(transactionCommittedCallbackThunk, static_cast<void*>(this));
     mSurfaceControlsWithPendingCallback.push(mSurfaceControl);
 
     if (updateDestinationFrame) {
@@ -508,7 +579,7 @@
         t->setApplyToken(mApplyToken).apply();
     }
 
-    BQA_LOGV("processNextBufferLocked size=%dx%d mFrameNumber=%" PRIu64
+    BQA_LOGV("acquireNextBufferLocked size=%dx%d mFrameNumber=%" PRIu64
              " applyTransaction=%s mTimestamp=%" PRId64 "%s mPendingTransactions.size=%d"
              " graphicBufferId=%" PRIu64 "%s transform=%d",
              mSize.width, mSize.height, bufferItem.mFrameNumber, boolToString(applyTransaction),
@@ -524,17 +595,44 @@
     return item.mCrop;
 }
 
+void BLASTBufferQueue::acquireAndReleaseBuffer() {
+    BufferItem bufferItem;
+    mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
+    mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
+    mNumFrameAvailable--;
+}
+
 void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
     ATRACE_CALL();
     std::unique_lock _lock{mMutex};
 
     const bool nextTransactionSet = mNextTransaction != nullptr;
+    BQA_LOGV("onFrameAvailable-start nextTransactionSet=%s", boolToString(nextTransactionSet));
     if (nextTransactionSet) {
-        while (mNumFrameAvailable > 0 || maxBuffersAcquired(false /* includeExtraAcquire */)) {
-            BQA_LOGV("waiting in onFrameAvailable...");
+        if (mWaitForTransactionCallback) {
+            // We are waiting on a previous sync's transaction callback so allow another sync
+            // transaction to proceed.
+            //
+            // We need to first flush out the transactions that were in between the two syncs.
+            // We do this by merging them into mNextTransaction so any buffer merging will get
+            // a release callback invoked. The release callback will be async so we need to wait
+            // on max acquired to make sure we have the capacity to acquire another buffer.
+            if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
+                BQA_LOGD("waiting to flush shadow queue...");
+                mCallbackCV.wait(_lock);
+            }
+            while (mNumFrameAvailable > 0) {
+                // flush out the shadow queue
+                acquireAndReleaseBuffer();
+            }
+        }
+
+        while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
+            BQA_LOGD("waiting for free buffer.");
             mCallbackCV.wait(_lock);
         }
     }
+
     // add to shadow queue
     mNumFrameAvailable++;
     ATRACE_INT(mQueuedBufferTrace.c_str(),
@@ -542,7 +640,14 @@
 
     BQA_LOGV("onFrameAvailable framenumber=%" PRIu64 " nextTransactionSet=%s", item.mFrameNumber,
              boolToString(nextTransactionSet));
-    processNextBufferLocked(nextTransactionSet /* useNextTransaction */);
+
+    if (nextTransactionSet) {
+        acquireNextBufferLocked(std::move(mNextTransaction));
+        mNextTransaction = nullptr;
+        mWaitForTransactionCallback = true;
+    } else if (!mWaitForTransactionCallback) {
+        acquireNextBufferLocked(std::nullopt);
+    }
 }
 
 void BLASTBufferQueue::onFrameReplaced(const BufferItem& item) {
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 49ece6e..4a1d475 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -88,6 +88,8 @@
     void onFrameDequeued(const uint64_t) override;
     void onFrameCancelled(const uint64_t) override;
 
+    void transactionCommittedCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
+                                      const std::vector<SurfaceControlStats>& stats);
     void transactionCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
             const std::vector<SurfaceControlStats>& stats);
     void releaseBufferCallback(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
@@ -120,7 +122,8 @@
     void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
                            sp<IGraphicBufferConsumer>* outConsumer);
 
-    void processNextBufferLocked(bool useNextTransaction) REQUIRES(mMutex);
+    void acquireNextBufferLocked(
+            const std::optional<SurfaceComposerClient::Transaction*> transaction) REQUIRES(mMutex);
     Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
     // Return true if we need to reject the buffer based on the scaling mode and the buffer size.
     bool rejectBuffer(const BufferItem& item) REQUIRES(mMutex);
@@ -129,6 +132,9 @@
     void mergePendingTransactions(SurfaceComposerClient::Transaction* t, uint64_t frameNumber)
             REQUIRES(mMutex);
 
+    void flushShadowQueue() REQUIRES(mMutex);
+    void acquireAndReleaseBuffer() REQUIRES(mMutex);
+
     std::string mName;
     // Represents the queued buffer count from buffer queue,
     // pre-BLAST. This is mNumFrameAvailable (buffers that queued to blast) +
@@ -238,6 +244,7 @@
     std::queue<sp<SurfaceControl>> mSurfaceControlsWithPendingCallback GUARDED_BY(mMutex);
 
     uint32_t mCurrentMaxAcquiredBufferCount;
+    bool mWaitForTransactionCallback = false;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/utils/CallbackUtils.h b/libs/gui/include/gui/test/CallbackUtils.h
similarity index 96%
rename from services/surfaceflinger/tests/utils/CallbackUtils.h
rename to libs/gui/include/gui/test/CallbackUtils.h
index f4a3425..6403208 100644
--- a/services/surfaceflinger/tests/utils/CallbackUtils.h
+++ b/libs/gui/include/gui/test/CallbackUtils.h
@@ -134,10 +134,8 @@
 
         void verifySurfaceControlStats(const SurfaceControlStats& surfaceControlStats,
                                        nsecs_t latchTime) const {
-            const auto&
-                    [surfaceControl, latch, acquireTime, presentFence, previousReleaseFence,
-                     transformHint,
-                     frameEvents] = surfaceControlStats;
+            const auto& [surfaceControl, latch, acquireTime, presentFence, previousReleaseFence,
+                         transformHint, frameEvents] = surfaceControlStats;
 
             ASSERT_EQ(acquireTime > 0, mBufferResult == ExpectedResult::Buffer::ACQUIRED)
                     << "bad acquire time";
@@ -199,7 +197,7 @@
         std::this_thread::sleep_for(500ms);
 
         std::lock_guard lock(mMutex);
-        EXPECT_EQ(mCallbackDataQueue.size(), 0) << "extra callbacks received";
+        EXPECT_EQ(mCallbackDataQueue.size(), 0U) << "extra callbacks received";
         mCallbackDataQueue = {};
     }
 
@@ -209,5 +207,5 @@
     std::condition_variable mConditionVariable;
     std::queue<CallbackData> mCallbackDataQueue;
 };
-}
+} // namespace
 } // namespace android
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index b474086..8e4898d 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -27,6 +27,7 @@
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
+#include <gui/test/CallbackUtils.h>
 #include <private/gui/ComposerService.h>
 #include <ui/DisplayMode.h>
 #include <ui/GraphicBuffer.h>
@@ -42,6 +43,29 @@
 using Transaction = SurfaceComposerClient::Transaction;
 using android::hardware::graphics::common::V1_2::BufferUsage;
 
+class CountProducerListener : public BnProducerListener {
+public:
+    void onBufferReleased() override {
+        std::scoped_lock<std::mutex> lock(mMutex);
+        mNumReleased++;
+        mReleaseCallback.notify_one();
+    }
+
+    void waitOnNumberReleased(int32_t expectedNumReleased) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        while (mNumReleased < expectedNumReleased) {
+            ASSERT_NE(mReleaseCallback.wait_for(lock, std::chrono::seconds(3)),
+                      std::cv_status::timeout)
+                    << "did not receive release";
+        }
+    }
+
+private:
+    std::mutex mMutex;
+    std::condition_variable mReleaseCallback;
+    int32_t mNumReleased GUARDED_BY(mMutex) = 0;
+};
+
 class BLASTBufferQueueHelper {
 public:
     BLASTBufferQueueHelper(const sp<SurfaceControl>& sc, int width, int height) {
@@ -152,18 +176,19 @@
         mCaptureArgs.dataspace = ui::Dataspace::V0_SRGB;
     }
 
-    void setUpProducer(BLASTBufferQueueHelper& adapter, sp<IGraphicBufferProducer>& producer) {
+    void setUpProducer(BLASTBufferQueueHelper& adapter, sp<IGraphicBufferProducer>& producer,
+                       int32_t maxBufferCount = 2) {
         producer = adapter.getIGraphicBufferProducer();
-        setUpProducer(producer);
+        setUpProducer(producer, maxBufferCount);
     }
 
-    void setUpProducer(sp<IGraphicBufferProducer>& igbProducer) {
+    void setUpProducer(sp<IGraphicBufferProducer>& igbProducer, int32_t maxBufferCount) {
         ASSERT_NE(nullptr, igbProducer.get());
-        ASSERT_EQ(NO_ERROR, igbProducer->setMaxDequeuedBufferCount(2));
+        ASSERT_EQ(NO_ERROR, igbProducer->setMaxDequeuedBufferCount(maxBufferCount));
         IGraphicBufferProducer::QueueBufferOutput qbOutput;
+        mProducerListener = new CountProducerListener();
         ASSERT_EQ(NO_ERROR,
-                  igbProducer->connect(new StubProducerListener, NATIVE_WINDOW_API_CPU, false,
-                                       &qbOutput));
+                  igbProducer->connect(mProducerListener, NATIVE_WINDOW_API_CPU, false, &qbOutput));
         ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
     }
 
@@ -287,6 +312,7 @@
 
     DisplayCaptureArgs mCaptureArgs;
     ScreenCaptureResults mCaptureResults;
+    sp<CountProducerListener> mProducerListener;
 };
 
 TEST_F(BLASTBufferQueueTest, CreateBLASTBufferQueue) {
@@ -749,6 +775,240 @@
                                {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight / 2}));
 }
 
+TEST_F(BLASTBufferQueueTest, SyncThenNoSync) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+
+    Transaction next;
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    // queue non sync buffer, so this one should get blocked
+    // Add a present delay to allow the first screenshot to get taken.
+    nsecs_t presentTimeDelay = std::chrono::nanoseconds(500ms).count();
+    queueBuffer(igbProducer, r, g, b, presentTimeDelay);
+
+    CallbackHelper transactionCallback;
+    next.addTransactionCompletedCallback(transactionCallback.function,
+                                         transactionCallback.getContext())
+            .apply();
+
+    CallbackData callbackData;
+    transactionCallback.getCallbackData(&callbackData);
+
+    // capture screen and verify that it is red
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(0, 255, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+
+    mProducerListener->waitOnNumberReleased(1);
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
+TEST_F(BLASTBufferQueueTest, MultipleSyncTransactions) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+
+    Transaction mainTransaction;
+
+    Transaction next;
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    mainTransaction.merge(std::move(next));
+
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, r, g, b, 0);
+
+    mainTransaction.merge(std::move(next));
+    // Expect 1 buffer to be released even before sending to SurfaceFlinger
+    mProducerListener->waitOnNumberReleased(1);
+
+    CallbackHelper transactionCallback;
+    mainTransaction
+            .addTransactionCompletedCallback(transactionCallback.function,
+                                             transactionCallback.getContext())
+            .apply();
+
+    CallbackData callbackData;
+    transactionCallback.getCallbackData(&callbackData);
+
+    // capture screen and verify that it is red
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
+TEST_F(BLASTBufferQueueTest, MultipleSyncTransactionWithNonSync) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+
+    Transaction mainTransaction;
+
+    Transaction next;
+    // queue a sync transaction
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    mainTransaction.merge(std::move(next));
+
+    // queue another buffer without setting next transaction
+    queueBuffer(igbProducer, 0, 0, 255, 0);
+
+    // queue another sync transaction
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, r, g, b, 0);
+    // Expect 1 buffer to be released because the non sync transaction should merge
+    // with the sync
+    mProducerListener->waitOnNumberReleased(1);
+
+    mainTransaction.merge(std::move(next));
+    // Expect 2 buffers to be released due to merging the two syncs.
+    mProducerListener->waitOnNumberReleased(2);
+
+    CallbackHelper transactionCallback;
+    mainTransaction
+            .addTransactionCompletedCallback(transactionCallback.function,
+                                             transactionCallback.getContext())
+            .apply();
+
+    CallbackData callbackData;
+    transactionCallback.getCallbackData(&callbackData);
+
+    // capture screen and verify that it is red
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
+TEST_F(BLASTBufferQueueTest, MultipleSyncRunOutOfBuffers) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer, 3);
+
+    Transaction mainTransaction;
+
+    Transaction next;
+    // queue a sync transaction
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    mainTransaction.merge(std::move(next));
+
+    // queue a few buffers without setting next transaction
+    queueBuffer(igbProducer, 0, 0, 255, 0);
+    queueBuffer(igbProducer, 0, 0, 255, 0);
+    queueBuffer(igbProducer, 0, 0, 255, 0);
+
+    // queue another sync transaction
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, r, g, b, 0);
+    // Expect 3 buffers to be released because the non sync transactions should merge
+    // with the sync
+    mProducerListener->waitOnNumberReleased(3);
+
+    mainTransaction.merge(std::move(next));
+    // Expect 4 buffers to be released due to merging the two syncs.
+    mProducerListener->waitOnNumberReleased(4);
+
+    CallbackHelper transactionCallback;
+    mainTransaction
+            .addTransactionCompletedCallback(transactionCallback.function,
+                                             transactionCallback.getContext())
+            .apply();
+
+    CallbackData callbackData;
+    transactionCallback.getCallbackData(&callbackData);
+
+    // capture screen and verify that it is red
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
+// Tests BBQ with a sync transaction when the buffers acquired reaches max and the only way to
+// continue processing is for a release callback from SurfaceFlinger.
+// This is done by sending a buffer to SF so it can release the previous one and allow BBQ to
+// continue acquiring buffers.
+TEST_F(BLASTBufferQueueTest, RunOutOfBuffersWaitingOnSF) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer, 4);
+
+    Transaction mainTransaction;
+
+    // Send a buffer to SF
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    Transaction next;
+    // queue a sync transaction
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+
+    mainTransaction.merge(std::move(next));
+
+    // queue a few buffers without setting next transaction
+    queueBuffer(igbProducer, 0, 0, 255, 0);
+    queueBuffer(igbProducer, 0, 0, 255, 0);
+    queueBuffer(igbProducer, 0, 0, 255, 0);
+
+    // apply the first synced buffer to ensure we have to wait on SF
+    mainTransaction.apply();
+
+    // queue another sync transaction
+    adapter.setNextTransaction(&next);
+    queueBuffer(igbProducer, r, g, b, 0);
+    // Expect 2 buffers to be released because the non sync transactions should merge
+    // with the sync
+    mProducerListener->waitOnNumberReleased(3);
+
+    mainTransaction.merge(std::move(next));
+
+    CallbackHelper transactionCallback;
+    mainTransaction
+            .addTransactionCompletedCallback(transactionCallback.function,
+                                             transactionCallback.getContext())
+            .apply();
+
+    CallbackData callbackData;
+    transactionCallback.getCallbackData(&callbackData);
+
+    // capture screen and verify that it is red
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
 class TestProducerListener : public BnProducerListener {
 public:
     sp<IGraphicBufferProducer> mIgbp;
diff --git a/services/surfaceflinger/tests/IPC_test.cpp b/services/surfaceflinger/tests/IPC_test.cpp
index 94e1e0c..ce94dab 100644
--- a/services/surfaceflinger/tests/IPC_test.cpp
+++ b/services/surfaceflinger/tests/IPC_test.cpp
@@ -28,8 +28,8 @@
 
 #include <limits>
 
+#include <gui/test/CallbackUtils.h>
 #include "BufferGenerator.h"
-#include "utils/CallbackUtils.h"
 #include "utils/ColorUtils.h"
 #include "utils/TransactionUtils.h"
 
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index e8759e5..9ddbed2 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -18,8 +18,8 @@
 
 #include <gui/DisplayEventReceiver.h>
 
+#include <gui/test/CallbackUtils.h>
 #include "LayerTransactionTest.h"
-#include "utils/CallbackUtils.h"
 
 using namespace std::chrono_literals;
 
diff --git a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
index e50c2fc..f6b0def 100644
--- a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
+++ b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
+#include <gui/test/CallbackUtils.h>
 #include "LayerTransactionTest.h"
-#include "utils/CallbackUtils.h"
 
 using namespace std::chrono_literals;
 
@@ -61,7 +61,7 @@
         std::this_thread::sleep_for(300ms);
 
         std::lock_guard lock(mMutex);
-        EXPECT_EQ(mCallbackDataQueue.size(), 0) << "extra callbacks received";
+        EXPECT_EQ(mCallbackDataQueue.size(), 0U) << "extra callbacks received";
         mCallbackDataQueue = {};
     }