libgui: Allow an IGBProducer to disable allocation

Adds a new method IGBP::allowAllocation, which controls whether
dequeueBuffer is permitted to allocate a new buffer. If allocation is
disallowed, dequeueBuffer will block or return an error as it
normally would (as controlled by *ControlledByApp).

If there are free buffers, but they are not of the correct dimensions,
format, or usage, they may be freed if a more suitable buffer is not
found first.

Bug: 19801715
Change-Id: I0d604958b78b2fd775c2547690301423f9a52165
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index 29415c9..bc75ca7 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -69,7 +69,8 @@
     mFrameCounter(0),
     mTransformHint(0),
     mIsAllocating(false),
-    mIsAllocatingCondition()
+    mIsAllocatingCondition(),
+    mAllowAllocation(true)
 {
     if (allocator == NULL) {
         sp<ISurfaceComposer> composer(ComposerService::getComposerService());
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index a27d5f0..86e45c8 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -219,7 +219,7 @@
                 auto slot = mCore->mFreeBuffers.begin();
                 *found = *slot;
                 mCore->mFreeBuffers.erase(slot);
-            } else if (!mCore->mFreeSlots.empty()) {
+            } else if (mCore->mAllowAllocation && !mCore->mFreeSlots.empty()) {
                 auto slot = mCore->mFreeSlots.begin();
                 // Only return free slots up to the max buffer count
                 if (*slot < maxBufferCount) {
@@ -285,17 +285,39 @@
         // Enable the usage bits the consumer requested
         usage |= mCore->mConsumerUsageBits;
 
-        int found;
-        status_t status = waitForFreeSlotThenRelock("dequeueBuffer", async,
-                &found, &returnFlags);
-        if (status != NO_ERROR) {
-            return status;
+        const bool useDefaultSize = !width && !height;
+        if (useDefaultSize) {
+            width = mCore->mDefaultWidth;
+            height = mCore->mDefaultHeight;
         }
 
-        // This should not happen
-        if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
-            BQ_LOGE("dequeueBuffer: no available buffer slots");
-            return -EBUSY;
+        int found = BufferItem::INVALID_BUFFER_SLOT;
+        while (found == BufferItem::INVALID_BUFFER_SLOT) {
+            status_t status = waitForFreeSlotThenRelock("dequeueBuffer", async,
+                    &found, &returnFlags);
+            if (status != NO_ERROR) {
+                return status;
+            }
+
+            // This should not happen
+            if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+                BQ_LOGE("dequeueBuffer: no available buffer slots");
+                return -EBUSY;
+            }
+
+            const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
+
+            // If we are not allowed to allocate new buffers,
+            // waitForFreeSlotThenRelock must have returned a slot containing a
+            // buffer. If this buffer would require reallocation to meet the
+            // requested attributes, we free it and attempt to get another one.
+            if (!mCore->mAllowAllocation) {
+                if (buffer->needsReallocation(width, height, format, usage)) {
+                    mCore->freeBufferLocked(found);
+                    found = BufferItem::INVALID_BUFFER_SLOT;
+                    continue;
+                }
+            }
         }
 
         *outSlot = found;
@@ -303,20 +325,11 @@
 
         attachedByConsumer = mSlots[found].mAttachedByConsumer;
 
-        const bool useDefaultSize = !width && !height;
-        if (useDefaultSize) {
-            width = mCore->mDefaultWidth;
-            height = mCore->mDefaultHeight;
-        }
-
         mSlots[found].mBufferState = BufferSlot::DEQUEUED;
 
         const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
         if ((buffer == NULL) ||
-                (static_cast<uint32_t>(buffer->width) != width) ||
-                (static_cast<uint32_t>(buffer->height) != height) ||
-                (buffer->format != format) ||
-                ((static_cast<uint32_t>(buffer->usage) & usage) != usage))
+                buffer->needsReallocation(width, height, format, usage))
         {
             mSlots[found].mAcquireCalled = false;
             mSlots[found].mGraphicBuffer = NULL;
@@ -933,6 +946,12 @@
             Mutex::Autolock lock(mCore->mMutex);
             mCore->waitWhileAllocatingLocked();
 
+            if (!mCore->mAllowAllocation) {
+                BQ_LOGE("allocateBuffers: allocation is not allowed for this "
+                        "BufferQueue");
+                return;
+            }
+
             int currentBufferCount = 0;
             for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
                 if (mSlots[slot].mGraphicBuffer != NULL) {
@@ -1027,6 +1046,15 @@
     }
 }
 
+status_t BufferQueueProducer::allowAllocation(bool allow) {
+    ATRACE_CALL();
+    BQ_LOGV("allowAllocation: %s", allow ? "true" : "false");
+
+    Mutex::Autolock lock(mCore->mMutex);
+    mCore->mAllowAllocation = allow;
+    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 b7982a9..7093ffa 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -46,6 +46,7 @@
     DISCONNECT,
     SET_SIDEBAND_STREAM,
     ALLOCATE_BUFFERS,
+    ALLOW_ALLOCATION,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -271,6 +272,18 @@
             ALOGE("allocateBuffers failed to transact: %d", result);
         }
     }
+
+    virtual status_t allowAllocation(bool allow) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeInt32(static_cast<int32_t>(allow));
+        status_t result = remote()->transact(ALLOW_ALLOCATION, data, &reply);
+        if (result != NO_ERROR) {
+            return result;
+        }
+        result = reply.readInt32();
+        return result;
+    }
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -418,7 +431,7 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
-        case ALLOCATE_BUFFERS:
+        case ALLOCATE_BUFFERS: {
             CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
             bool async = static_cast<bool>(data.readInt32());
             uint32_t width = data.readUint32();
@@ -427,6 +440,14 @@
             uint32_t usage = data.readUint32();
             allocateBuffers(async, width, height, format, usage);
             return NO_ERROR;
+        }
+        case ALLOW_ALLOCATION: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            bool allow = static_cast<bool>(data.readInt32());
+            status_t result = allowAllocation(allow);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index c38c797..112dcb9 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -364,4 +364,40 @@
     ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
 }
 
+TEST_F(BufferQueueTest, TestDisallowingAllocation) {
+    createBufferQueue();
+    sp<DummyConsumer> dc(new DummyConsumer);
+    ASSERT_EQ(OK, mConsumer->consumerConnect(dc, true));
+    IGraphicBufferProducer::QueueBufferOutput output;
+    ASSERT_EQ(OK, mProducer->connect(new DummyProducerListener,
+            NATIVE_WINDOW_API_CPU, true, &output));
+
+    static const uint32_t WIDTH = 320;
+    static const uint32_t HEIGHT = 240;
+
+    ASSERT_EQ(OK, mConsumer->setDefaultBufferSize(WIDTH, HEIGHT));
+
+    int slot;
+    sp<Fence> fence;
+    sp<GraphicBuffer> buffer;
+    // This should return an error since it would require an allocation
+    ASSERT_EQ(OK, mProducer->allowAllocation(false));
+    ASSERT_EQ(WOULD_BLOCK, mProducer->dequeueBuffer(&slot, &fence, false, 0, 0,
+            0, GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+    // This should succeed, now that we've lifted the prohibition
+    ASSERT_EQ(OK, mProducer->allowAllocation(true));
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+            mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+            GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+    // Release the previous buffer back to the BufferQueue
+    mProducer->cancelBuffer(slot, fence);
+
+    // This should fail since we're requesting a different size
+    ASSERT_EQ(OK, mProducer->allowAllocation(false));
+    ASSERT_EQ(WOULD_BLOCK, mProducer->dequeueBuffer(&slot, &fence, false,
+            WIDTH * 2, HEIGHT * 2, 0, GRALLOC_USAGE_SW_WRITE_OFTEN));
+}
+
 } // namespace android