libgui: Add getLastQueuedBuffer to BufferQueue

Adds the ability to get the last buffer queued to a BufferQueue plus
its acquire fence.

Bug: 27708453
Change-Id: Iee39475740b40c854a5f46178b2934fd930e61b8
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index cdece73..052de3d 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -200,6 +200,10 @@
     }
     mSlots[slot].mFence = Fence::NO_FENCE;
     mSlots[slot].mEglDisplay = EGL_NO_DISPLAY;
+
+    if (mLastQueuedSlot == slot) {
+        mLastQueuedSlot = INVALID_BUFFER_SLOT;
+    }
 }
 
 void BufferQueueCore::freeAllBuffersLocked() {
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index bb8d39b..4c3bcd9 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -867,6 +867,7 @@
 
         mCore->mBufferHasBeenQueued = true;
         mCore->mDequeueCondition.broadcast();
+        mCore->mLastQueuedSlot = slot;
 
         output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight,
                 mCore->mTransformHint,
@@ -909,8 +910,8 @@
         // third. In the event that frames take varying time, this makes a
         // small trade-off in favor of latency rather than throughput.
         mLastQueueBufferFence->waitForever("Throttling EGL Production");
-        mLastQueueBufferFence = fence;
     }
+    mLastQueueBufferFence = fence;
 
     return NO_ERROR;
 }
@@ -1356,6 +1357,24 @@
     return NO_ERROR;
 }
 
+status_t BufferQueueProducer::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer,
+        sp<Fence>* outFence) {
+    ATRACE_CALL();
+    BQ_LOGV("getLastQueuedBuffer");
+
+    Mutex::Autolock lock(mCore->mMutex);
+    if (mCore->mLastQueuedSlot == BufferItem::INVALID_BUFFER_SLOT) {
+        *outBuffer = nullptr;
+        *outFence = Fence::NO_FENCE;
+        return NO_ERROR;
+    }
+
+    *outBuffer = mSlots[mCore->mLastQueuedSlot].mGraphicBuffer;
+    *outFence = mLastQueueBufferFence;
+
+    return NO_ERROR;
+}
+
 void BufferQueueProducer::binderDied(const wp<android::IBinder>& /* who */) {
     // If we're here, it means that a producer we were connected to died.
     // We're guaranteed that we are still connected to it because we remove
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 7cdb8f4..c36fcad 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -54,6 +54,7 @@
     SET_SHARED_BUFFER_MODE,
     SET_AUTO_REFRESH,
     SET_DEQUEUE_TIMEOUT,
+    GET_LAST_QUEUED_BUFFER,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -379,6 +380,37 @@
         }
         return reply.readInt32();
     }
+
+    virtual status_t getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer,
+            sp<Fence>* outFence) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        status_t result = remote()->transact(GET_LAST_QUEUED_BUFFER, data,
+                &reply);
+        if (result != NO_ERROR) {
+            ALOGE("getLastQueuedBuffer failed to transact: %d", result);
+            return result;
+        }
+        result = reply.readInt32();
+        if (result != NO_ERROR) {
+            return result;
+        }
+        sp<GraphicBuffer> buffer(new GraphicBuffer);
+        result = reply.read(*buffer);
+        if (result != NO_ERROR) {
+            ALOGE("getLastQueuedBuffer failed to read buffer: %d", result);
+            return result;
+        }
+        sp<Fence> fence(new Fence);
+        result = reply.read(*fence);
+        if (result != NO_ERROR) {
+            ALOGE("getLastQueuedBuffer failed to read fence: %d", result);
+            return result;
+        }
+        *outBuffer = buffer;
+        *outFence = fence;
+        return result;
+    }
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -590,6 +622,27 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+        case GET_LAST_QUEUED_BUFFER: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            sp<GraphicBuffer> buffer(nullptr);
+            sp<Fence> fence(Fence::NO_FENCE);
+            status_t result = getLastQueuedBuffer(&buffer, &fence);
+            reply->writeInt32(result);
+            if (result != NO_ERROR) {
+                return result;
+            }
+            result = reply->write(*buffer);
+            if (result != NO_ERROR) {
+                ALOGE("getLastQueuedBuffer failed to write buffer: %d", result);
+                return result;
+            }
+            result = reply->write(*fence);
+            if (result != NO_ERROR) {
+                ALOGE("getLastQueuedBuffer failed to write fence: %d", result);
+                return result;
+            }
+            return NO_ERROR;
+        }
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 5efc333..b304633 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -127,6 +127,11 @@
     return mGraphicBufferProducer->setDequeueTimeout(timeout);
 }
 
+status_t Surface::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer,
+        sp<Fence>* outFence) {
+    return mGraphicBufferProducer->getLastQueuedBuffer(outBuffer, outFence);
+}
+
 int Surface::hook_setSwapInterval(ANativeWindow* window, int interval) {
     Surface* c = getSelf(window);
     return c->setSwapInterval(interval);
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 82df9a9..9876d94 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -796,4 +796,56 @@
     ASSERT_EQ(OK, mProducer->attachBuffer(&slot, buffer));
 }
 
+TEST_F(BufferQueueTest, CanRetrieveLastQueuedBuffer) {
+    createBufferQueue();
+    sp<DummyConsumer> dc(new DummyConsumer);
+    ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false));
+    IGraphicBufferProducer::QueueBufferOutput output;
+    ASSERT_EQ(OK, mProducer->connect(new DummyProducerListener,
+            NATIVE_WINDOW_API_CPU, false, &output));
+
+    // Dequeue and queue the first buffer, storing the handle
+    int slot = BufferQueue::INVALID_BUFFER_SLOT;
+    sp<Fence> fence;
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+            mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+    sp<GraphicBuffer> firstBuffer;
+    ASSERT_EQ(OK, mProducer->requestBuffer(slot, &firstBuffer));
+
+    IGraphicBufferProducer::QueueBufferInput input(0ull, true,
+        HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
+        NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
+    ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+    // Dequeue a second buffer
+    slot = BufferQueue::INVALID_BUFFER_SLOT;
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+            mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+    sp<GraphicBuffer> secondBuffer;
+    ASSERT_EQ(OK, mProducer->requestBuffer(slot, &secondBuffer));
+
+    // Ensure it's a new buffer
+    ASSERT_NE(firstBuffer->getNativeBuffer()->handle,
+            secondBuffer->getNativeBuffer()->handle);
+
+    // Queue the second buffer
+    ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+    // Acquire and release both buffers
+    for (size_t i = 0; i < 2; ++i) {
+        BufferItem item;
+        ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+                    EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    }
+
+    // Make sure we got the second buffer back
+    sp<GraphicBuffer> returnedBuffer;
+    sp<Fence> returnedFence;
+    ASSERT_EQ(OK,
+            mProducer->getLastQueuedBuffer(&returnedBuffer, &returnedFence));
+    ASSERT_EQ(secondBuffer->getNativeBuffer()->handle,
+            returnedBuffer->getNativeBuffer()->handle);
+}
+
 } // namespace android