Merge changes from topic 'single_buffer_mode'

* changes:
  SF: Force refresh when in single buffer mode
  BQ: Add support for single buffer mode
diff --git a/include/gui/BufferItem.h b/include/gui/BufferItem.h
index 504ac64..370f5d5 100644
--- a/include/gui/BufferItem.h
+++ b/include/gui/BufferItem.h
@@ -118,6 +118,13 @@
     // Describes the portion of the surface that has been modified since the
     // previous frame
     Region mSurfaceDamage;
+
+    // Indicates that the BufferQueue is in single buffer mode
+    bool mSingleBufferMode;
+
+    // Indicates that this buffer was queued by the producer. When in single
+    // buffer mode acquire() can return a BufferItem that wasn't in the queue.
+    bool mQueuedBuffer;
 };
 
 } // namespace android
diff --git a/include/gui/BufferQueueCore.h b/include/gui/BufferQueueCore.h
index 36cd238..fbd5114 100644
--- a/include/gui/BufferQueueCore.h
+++ b/include/gui/BufferQueueCore.h
@@ -107,10 +107,10 @@
 
     // freeBufferLocked frees the GraphicBuffer and sync resources for the
     // given slot.
-    void freeBufferLocked(int slot);
+    void freeBufferLocked(int slot, bool validate = true);
 
     // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
-    // all slots.
+    // all slots, even if they're currently dequeued, queued, or acquired.
     void freeAllBuffersLocked();
 
     // stillTracking returns true iff the buffer item is still being tracked
@@ -271,6 +271,32 @@
     // enqueue buffers without blocking.
     bool mAsyncMode;
 
+    // mSingleBufferMode indicates whether or not single buffer mode is enabled.
+    // In single buffer mode, the last buffer that was dequeued is cached and
+    // returned to all calls to dequeueBuffer and acquireBuffer. This allows the
+    // consumer and producer to access the same buffer simultaneously.
+    bool mSingleBufferMode;
+
+    // When single buffer mode is enabled, this tracks which slot contains the
+    // shared buffer.
+    int mSingleBufferSlot;
+
+    // Cached data about the shared buffer in single buffer mode
+    struct SingleBufferCache {
+        SingleBufferCache(Rect _crop, uint32_t _transform, int _scalingMode,
+                android_dataspace _dataspace)
+        : crop(_crop),
+          transform(_transform),
+          scalingMode(_scalingMode),
+          dataspace(_dataspace) {
+        };
+
+        Rect crop;
+        uint32_t transform;
+        uint32_t scalingMode;
+        android_dataspace dataspace;
+    } mSingleBufferCache;
+
 }; // class BufferQueueCore
 
 } // namespace android
diff --git a/include/gui/BufferQueueProducer.h b/include/gui/BufferQueueProducer.h
index 322f2ec..5fe5ce0 100644
--- a/include/gui/BufferQueueProducer.h
+++ b/include/gui/BufferQueueProducer.h
@@ -173,6 +173,9 @@
     // See IGraphicBufferProducer::getNextFrameNumber
     virtual uint64_t getNextFrameNumber() const override;
 
+    // See IGraphicBufferProducer::setSingleBufferMode
+    virtual status_t setSingleBufferMode(bool singleBufferMode);
+
 private:
     // This is required by the IBinder::DeathRecipient interface
     virtual void binderDied(const wp<IBinder>& who);
diff --git a/include/gui/BufferSlot.h b/include/gui/BufferSlot.h
index 6085e11..17a654a 100644
--- a/include/gui/BufferSlot.h
+++ b/include/gui/BufferSlot.h
@@ -29,11 +29,153 @@
 
 class Fence;
 
+// BufferState tracks the states in which a buffer slot can be.
+struct BufferState {
+
+    // All slots are initially FREE (not dequeued, queued, acquired, or shared).
+    BufferState()
+    : mDequeueCount(0),
+      mQueueCount(0),
+      mAcquireCount(0),
+      mShared(false) {
+    }
+
+    uint32_t mDequeueCount;
+    uint32_t mQueueCount;
+    uint32_t mAcquireCount;
+    bool mShared;
+
+    // A buffer can be in one of five states, represented as below:
+    //
+    //         | mShared | mDequeueCount | mQueueCount | mAcquireCount |
+    // --------|---------|---------------|-------------|---------------|
+    // FREE    |  false  |       0       |      0      |       0       |
+    // DEQUEUED|  false  |       1       |      0      |       0       |
+    // QUEUED  |  false  |       0       |      1      |       0       |
+    // ACQUIRED|  false  |       0       |      0      |       1       |
+    // SHARED  |  true   |      any      |     any     |      any      |
+    //
+    // FREE indicates that the buffer is available to be dequeued by the
+    // producer. The slot is "owned" by BufferQueue. It transitions to DEQUEUED
+    // when dequeueBuffer is called.
+    //
+    // DEQUEUED indicates that the buffer has been dequeued by the producer, but
+    // has not yet been queued or canceled. The producer may modify the
+    // buffer's contents as soon as the associated release fence is signaled.
+    // The slot is "owned" by the producer. It can transition to QUEUED (via
+    // queueBuffer or attachBuffer) or back to FREE (via cancelBuffer or
+    // detachBuffer).
+    //
+    // QUEUED indicates that the buffer has been filled by the producer and
+    // queued for use by the consumer. The buffer contents may continue to be
+    // modified for a finite time, so the contents must not be accessed until
+    // the associated fence is signaled. The slot is "owned" by BufferQueue. It
+    // can transition to ACQUIRED (via acquireBuffer) or to FREE (if another
+    // buffer is queued in asynchronous mode).
+    //
+    // ACQUIRED indicates that the buffer has been acquired by the consumer. As
+    // with QUEUED, the contents must not be accessed by the consumer until the
+    // acquire fence is signaled. The slot is "owned" by the consumer. It
+    // transitions to FREE when releaseBuffer (or detachBuffer) is called. A
+    // detached buffer can also enter the ACQUIRED state via attachBuffer.
+    //
+    // SHARED indicates that this buffer is being used in single-buffer
+    // mode. It can be in any combination of the other states at the same time,
+    // except for FREE (since that excludes being in any other state). It can
+    // also be dequeued, queued, or acquired multiple times.
+
+    inline bool isFree() const {
+        return !isAcquired() && !isDequeued() && !isQueued();
+    }
+
+    inline bool isDequeued() const {
+        return mDequeueCount > 0;
+    }
+
+    inline bool isQueued() const {
+        return mQueueCount > 0;
+    }
+
+    inline bool isAcquired() const {
+        return mAcquireCount > 0;
+    }
+
+    inline bool isShared() const {
+        return mShared;
+    }
+
+    inline void reset() {
+        *this = BufferState();
+    }
+
+    const char* string() const;
+
+    inline void dequeue() {
+        mDequeueCount++;
+    }
+
+    inline void detachProducer() {
+        if (mDequeueCount > 0) {
+            mDequeueCount--;
+        }
+    }
+
+    inline void attachProducer() {
+        mDequeueCount++;
+    }
+
+    inline void queue() {
+        if (mDequeueCount > 0) {
+            mDequeueCount--;
+        }
+        mQueueCount++;
+    }
+
+    inline void cancel() {
+        if (mDequeueCount > 0) {
+            mDequeueCount--;
+        }
+    }
+
+    inline void freeQueued() {
+        if (mQueueCount > 0) {
+            mQueueCount--;
+        }
+    }
+
+    inline void acquire() {
+        if (mQueueCount > 0) {
+            mQueueCount--;
+        }
+        mAcquireCount++;
+    }
+
+    inline void acquireNotInQueue() {
+        mAcquireCount++;
+    }
+
+    inline void release() {
+        if (mAcquireCount > 0) {
+            mAcquireCount--;
+        }
+    }
+
+    inline void detachConsumer() {
+        if (mAcquireCount > 0) {
+            mAcquireCount--;
+        }
+    }
+
+    inline void attachConsumer() {
+        mAcquireCount++;
+    }
+};
+
 struct BufferSlot {
 
     BufferSlot()
     : mEglDisplay(EGL_NO_DISPLAY),
-      mBufferState(BufferSlot::FREE),
+      mBufferState(),
       mRequestBufferCalled(false),
       mFrameNumber(0),
       mEglFence(EGL_NO_SYNC_KHR),
@@ -49,47 +191,6 @@
     // mEglDisplay is the EGLDisplay used to create EGLSyncKHR objects.
     EGLDisplay mEglDisplay;
 
-    // BufferState represents the different states in which a buffer slot
-    // can be.  All slots are initially FREE.
-    enum BufferState {
-        // FREE indicates that the buffer is available to be dequeued
-        // by the producer.  The buffer may be in use by the consumer for
-        // a finite time, so the buffer must not be modified until the
-        // associated fence is signaled.
-        //
-        // The slot is "owned" by BufferQueue.  It transitions to DEQUEUED
-        // when dequeueBuffer is called.
-        FREE = 0,
-
-        // DEQUEUED indicates that the buffer has been dequeued by the
-        // producer, but has not yet been queued or canceled.  The
-        // producer may modify the buffer's contents as soon as the
-        // associated ready fence is signaled.
-        //
-        // The slot is "owned" by the producer.  It can transition to
-        // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
-        DEQUEUED = 1,
-
-        // QUEUED indicates that the buffer has been filled by the
-        // producer and queued for use by the consumer.  The buffer
-        // contents may continue to be modified for a finite time, so
-        // the contents must not be accessed until the associated fence
-        // is signaled.
-        //
-        // The slot is "owned" by BufferQueue.  It can transition to
-        // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
-        // queued in asynchronous mode).
-        QUEUED = 2,
-
-        // ACQUIRED indicates that the buffer has been acquired by the
-        // consumer.  As with QUEUED, the contents must not be accessed
-        // by the consumer until the fence is signaled.
-        //
-        // The slot is "owned" by the consumer.  It transitions to FREE
-        // when releaseBuffer is called.
-        ACQUIRED = 3
-    };
-
     static const char* bufferStateName(BufferState state);
 
     // mBufferState is the current state of this buffer slot.
diff --git a/include/gui/IGraphicBufferProducer.h b/include/gui/IGraphicBufferProducer.h
index 4586839..d6daca7 100644
--- a/include/gui/IGraphicBufferProducer.h
+++ b/include/gui/IGraphicBufferProducer.h
@@ -511,6 +511,13 @@
 
     // Returns the number of the next frame which will be dequeued.
     virtual uint64_t getNextFrameNumber() const = 0;
+
+    // Used to enable/disable single buffer mode.
+    //
+    // In single buffer mode the last buffer that was dequeued will be cached
+    // and returned to all calls to dequeueBuffer and acquireBuffer. This allows
+    // the producer and consumer to simultaneously access the same buffer.
+    virtual status_t setSingleBufferMode(bool singleBufferMode) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/gui/Surface.h b/include/gui/Surface.h
index 3f46460..f9fc6df 100644
--- a/include/gui/Surface.h
+++ b/include/gui/Surface.h
@@ -159,6 +159,7 @@
     int dispatchSetSidebandStream(va_list args);
     int dispatchSetBuffersDataSpace(va_list args);
     int dispatchSetSurfaceDamage(va_list args);
+    int dispatchSetSingleBufferMode(va_list args);
 
 protected:
     virtual int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd);
@@ -188,6 +189,7 @@
 public:
     virtual int setMaxDequeuedBufferCount(int maxDequeuedBuffers);
     virtual int setAsyncMode(bool async);
+    virtual int setSingleBufferMode(bool singleBufferMode);
     virtual int lock(ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds);
     virtual int unlockAndPost();
 
diff --git a/include/private/gui/LayerState.h b/include/private/gui/LayerState.h
index 1711071..5cf316f 100644
--- a/include/private/gui/LayerState.h
+++ b/include/private/gui/LayerState.h
@@ -59,7 +59,7 @@
         :   what(0),
             x(0), y(0), z(0), w(0), h(0), layerStack(0),
             alpha(0), flags(0), mask(0),
-            reserved(0), crop(Rect::INVALID_RECT)
+            reserved(0), crop(Rect::INVALID_RECT), frameNumber(0)
     {
         matrix.dsdx = matrix.dtdy = 1.0f;
         matrix.dsdy = matrix.dtdx = 0.0f;
diff --git a/libs/gui/BufferItem.cpp b/libs/gui/BufferItem.cpp
index 6a883cf..de8ff70 100644
--- a/libs/gui/BufferItem.cpp
+++ b/libs/gui/BufferItem.cpp
@@ -24,6 +24,8 @@
 namespace android {
 
 BufferItem::BufferItem() :
+    mGraphicBuffer(NULL),
+    mFence(NULL),
     mCrop(Rect::INVALID_RECT),
     mTransform(0),
     mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
@@ -34,7 +36,10 @@
     mSlot(INVALID_BUFFER_SLOT),
     mIsDroppable(false),
     mAcquireCalled(false),
-    mTransformToDisplayInverse(false) {
+    mTransformToDisplayInverse(false),
+    mSurfaceDamage(),
+    mSingleBufferMode(false),
+    mQueuedBuffer(true) {
 }
 
 BufferItem::~BufferItem() {}
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index d52b47f..6f9f21f 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -50,7 +50,7 @@
         // buffer before releasing the old one.
         int numAcquiredBuffers = 0;
         for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
-            if (mSlots[s].mBufferState == BufferSlot::ACQUIRED) {
+            if (mSlots[s].mBufferState.isAcquired()) {
                 ++numAcquiredBuffers;
             }
         }
@@ -60,10 +60,13 @@
             return INVALID_OPERATION;
         }
 
-        // Check if the queue is empty.
+        bool sharedBufferAvailable = mCore->mSingleBufferMode &&
+                mCore->mSingleBufferSlot !=
+                BufferQueueCore::INVALID_BUFFER_SLOT;
+
         // In asynchronous mode the list is guaranteed to be one buffer deep,
         // while in synchronous mode we use the oldest buffer.
-        if (mCore->mQueue.empty()) {
+        if (mCore->mQueue.empty() && !sharedBufferAvailable) {
             return NO_BUFFER_AVAILABLE;
         }
 
@@ -72,7 +75,9 @@
         // If expectedPresent is specified, we may not want to return a buffer yet.
         // If it's specified and there's more than one buffer queued, we may want
         // to drop a buffer.
-        if (expectedPresent != 0) {
+        // Skip this if we're in single buffer mode and the queue is empty,
+        // since in that case we'll just return the shared buffer.
+        if (expectedPresent != 0 && !mCore->mQueue.empty()) {
             const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
 
             // The 'expectedPresent' argument indicates when the buffer is expected
@@ -130,8 +135,19 @@
                         desiredPresent, expectedPresent, mCore->mQueue.size());
                 if (mCore->stillTracking(front)) {
                     // Front buffer is still in mSlots, so mark the slot as free
-                    mSlots[front->mSlot].mBufferState = BufferSlot::FREE;
-                    mCore->mFreeBuffers.push_back(front->mSlot);
+                    mSlots[front->mSlot].mBufferState.freeQueued();
+
+                    // After leaving single buffer mode, the shared buffer will
+                    // still be around. Mark it as no longer shared if this
+                    // operation causes it to be free.
+                    if (!mCore->mSingleBufferMode &&
+                            mSlots[front->mSlot].mBufferState.isFree()) {
+                        mSlots[front->mSlot].mBufferState.mShared = false;
+                    }
+                    // Don't put the shared buffer on the free list.
+                    if (!mSlots[front->mSlot].mBufferState.isShared()) {
+                        mCore->mFreeBuffers.push_back(front->mSlot);
+                    }
                     listener = mCore->mConnectedProducerListener;
                     ++numDroppedBuffers;
                 }
@@ -162,17 +178,56 @@
                     systemTime(CLOCK_MONOTONIC));
         }
 
-        int slot = front->mSlot;
-        *outBuffer = *front;
+        int slot = BufferQueueCore::INVALID_BUFFER_SLOT;
+
+        if (sharedBufferAvailable && mCore->mQueue.empty()) {
+            // make sure the buffer has finished allocating before acquiring it
+            mCore->waitWhileAllocatingLocked();
+
+            slot = mCore->mSingleBufferSlot;
+
+            // Recreate the BufferItem for the shared buffer from the data that
+            // was cached when it was last queued.
+            outBuffer->mGraphicBuffer = mSlots[slot].mGraphicBuffer;
+            outBuffer->mFence = Fence::NO_FENCE;
+            outBuffer->mCrop = mCore->mSingleBufferCache.crop;
+            outBuffer->mTransform = mCore->mSingleBufferCache.transform &
+                    ~static_cast<uint32_t>(
+                    NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
+            outBuffer->mScalingMode = mCore->mSingleBufferCache.scalingMode;
+            outBuffer->mDataSpace = mCore->mSingleBufferCache.dataspace;
+            outBuffer->mFrameNumber = mCore->mFrameCounter;
+            outBuffer->mSlot = slot;
+            outBuffer->mAcquireCalled = mSlots[slot].mAcquireCalled;
+            outBuffer->mTransformToDisplayInverse =
+                    (mCore->mSingleBufferCache.transform &
+                    NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) != 0;
+            outBuffer->mSurfaceDamage = Region::INVALID_REGION;
+            outBuffer->mSingleBufferMode = true;
+            outBuffer->mQueuedBuffer = false;
+        } else {
+            slot = front->mSlot;
+            *outBuffer = *front;
+        }
+
+        outBuffer->mSingleBufferMode = mCore->mSingleBufferMode;
+
         ATRACE_BUFFER_INDEX(slot);
 
         BQ_LOGV("acquireBuffer: acquiring { slot=%d/%" PRIu64 " buffer=%p }",
-                slot, front->mFrameNumber, front->mGraphicBuffer->handle);
+                slot, outBuffer->mFrameNumber, outBuffer->mGraphicBuffer->handle);
         // If the front buffer is still being tracked, update its slot state
-        if (mCore->stillTracking(front)) {
+        if (mCore->stillTracking(outBuffer)) {
             mSlots[slot].mAcquireCalled = true;
             mSlots[slot].mNeedsCleanupOnRelease = false;
-            mSlots[slot].mBufferState = BufferSlot::ACQUIRED;
+            // Don't decrease the queue count if the BufferItem wasn't
+            // previously in the queue. This happens in single buffer mode when
+            // the queue is empty and the BufferItem is created above.
+            if (mCore->mQueue.empty()) {
+                mSlots[slot].mBufferState.acquireNotInQueue();
+            } else {
+                mSlots[slot].mBufferState.acquire();
+            }
             mSlots[slot].mFence = Fence::NO_FENCE;
         }
 
@@ -207,24 +262,31 @@
 status_t BufferQueueConsumer::detachBuffer(int slot) {
     ATRACE_CALL();
     ATRACE_BUFFER_INDEX(slot);
-    BQ_LOGV("detachBuffer(C): slot %d", slot);
+    BQ_LOGV("detachBuffer: slot %d", slot);
     Mutex::Autolock lock(mCore->mMutex);
 
     if (mCore->mIsAbandoned) {
-        BQ_LOGE("detachBuffer(C): BufferQueue has been abandoned");
+        BQ_LOGE("detachBuffer: BufferQueue has been abandoned");
         return NO_INIT;
     }
 
-    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-        BQ_LOGE("detachBuffer(C): slot index %d out of range [0, %d)",
-                slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
-        return BAD_VALUE;
-    } else if (mSlots[slot].mBufferState != BufferSlot::ACQUIRED) {
-        BQ_LOGE("detachBuffer(C): slot %d is not owned by the consumer "
-                "(state = %d)", slot, mSlots[slot].mBufferState);
+    if (mCore->mSingleBufferMode) {
+        BQ_LOGE("detachBuffer: detachBuffer not allowed in single buffer"
+                "mode");
         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);
+        return BAD_VALUE;
+    } else if (!mSlots[slot].mBufferState.isAcquired()) {
+        BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
+                "(state = %s)", slot, mSlots[slot].mBufferState.string());
+        return BAD_VALUE;
+    }
+
+    mSlots[slot].mBufferState.detachConsumer();
     mCore->freeBufferLocked(slot);
     mCore->mDequeueCondition.broadcast();
     mCore->validateConsistencyLocked();
@@ -237,25 +299,31 @@
     ATRACE_CALL();
 
     if (outSlot == NULL) {
-        BQ_LOGE("attachBuffer(P): outSlot must not be NULL");
+        BQ_LOGE("attachBuffer: outSlot must not be NULL");
         return BAD_VALUE;
     } else if (buffer == NULL) {
-        BQ_LOGE("attachBuffer(P): cannot attach NULL buffer");
+        BQ_LOGE("attachBuffer: cannot attach NULL buffer");
         return BAD_VALUE;
     }
 
     Mutex::Autolock lock(mCore->mMutex);
 
+    if (mCore->mSingleBufferMode) {
+        BQ_LOGE("attachBuffer: cannot attach a buffer in single buffer"
+                "mode");
+        return BAD_VALUE;
+    }
+
     // Make sure we don't have too many acquired buffers
     int numAcquiredBuffers = 0;
     for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
-        if (mSlots[s].mBufferState == BufferSlot::ACQUIRED) {
+        if (mSlots[s].mBufferState.isAcquired()) {
             ++numAcquiredBuffers;
         }
     }
 
     if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
-        BQ_LOGE("attachBuffer(P): max acquired buffer count reached: %d "
+        BQ_LOGE("attachBuffer: max acquired buffer count reached: %d "
                 "(max %d)", numAcquiredBuffers,
                 mCore->mMaxAcquiredBufferCount);
         return INVALID_OPERATION;
@@ -279,16 +347,16 @@
         mCore->mFreeBuffers.remove(found);
     }
     if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
-        BQ_LOGE("attachBuffer(P): could not find free buffer slot");
+        BQ_LOGE("attachBuffer: could not find free buffer slot");
         return NO_MEMORY;
     }
 
     *outSlot = found;
     ATRACE_BUFFER_INDEX(*outSlot);
-    BQ_LOGV("attachBuffer(C): returning slot %d", *outSlot);
+    BQ_LOGV("attachBuffer: returning slot %d", *outSlot);
 
     mSlots[*outSlot].mGraphicBuffer = buffer;
-    mSlots[*outSlot].mBufferState = BufferSlot::ACQUIRED;
+    mSlots[*outSlot].mBufferState.attachConsumer();
     mSlots[*outSlot].mAttachedByConsumer = true;
     mSlots[*outSlot].mNeedsCleanupOnRelease = false;
     mSlots[*outSlot].mFence = Fence::NO_FENCE;
@@ -334,38 +402,45 @@
         Mutex::Autolock lock(mCore->mMutex);
 
         // If the frame number has changed because the buffer has been reallocated,
-        // we can ignore this releaseBuffer for the old buffer
-        if (frameNumber != mSlots[slot].mFrameNumber) {
+        // we can ignore this releaseBuffer for the old buffer.
+        // Ignore this for the shared buffer where the frame number can easily
+        // get out of sync due to the buffer being queued and acquired at the
+        // same time.
+        if (frameNumber != mSlots[slot].mFrameNumber &&
+                !mSlots[slot].mBufferState.isShared()) {
             return STALE_BUFFER_SLOT;
         }
 
-        // Make sure this buffer hasn't been queued while acquired by the consumer
-        BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
-        while (current != mCore->mQueue.end()) {
-            if (current->mSlot == slot) {
-                BQ_LOGE("releaseBuffer: buffer slot %d pending release is "
-                        "currently queued", slot);
-                return BAD_VALUE;
-            }
-            ++current;
-        }
 
-        if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) {
+        if (mSlots[slot].mBufferState.isAcquired()) {
             mSlots[slot].mEglDisplay = eglDisplay;
             mSlots[slot].mEglFence = eglFence;
             mSlots[slot].mFence = releaseFence;
-            mSlots[slot].mBufferState = BufferSlot::FREE;
-            mCore->mFreeBuffers.push_back(slot);
+            mSlots[slot].mBufferState.release();
+
+            // After leaving single buffer mode, the shared buffer will
+            // still be around. Mark it as no longer shared if this
+            // operation causes it to be free.
+            if (!mCore->mSingleBufferMode &&
+                    mSlots[slot].mBufferState.isFree()) {
+                mSlots[slot].mBufferState.mShared = false;
+            }
+            // Don't put the shared buffer on the free list.
+            if (!mSlots[slot].mBufferState.isShared()) {
+                mCore->mFreeBuffers.push_back(slot);
+            }
+
             listener = mCore->mConnectedProducerListener;
             BQ_LOGV("releaseBuffer: releasing slot %d", slot);
         } else if (mSlots[slot].mNeedsCleanupOnRelease) {
             BQ_LOGV("releaseBuffer: releasing a stale buffer slot %d "
-                    "(state = %d)", slot, mSlots[slot].mBufferState);
+                    "(state = %s)", slot, mSlots[slot].mBufferState.string());
             mSlots[slot].mNeedsCleanupOnRelease = false;
             return STALE_BUFFER_SLOT;
         } else {
             BQ_LOGE("releaseBuffer: attempted to release buffer slot %d "
-                    "but its state was %d", slot, mSlots[slot].mBufferState);
+                    "but its state was %s", slot,
+                    mSlots[slot].mBufferState.string());
             return BAD_VALUE;
         }
 
@@ -386,17 +461,17 @@
     ATRACE_CALL();
 
     if (consumerListener == NULL) {
-        BQ_LOGE("connect(C): consumerListener may not be NULL");
+        BQ_LOGE("connect: consumerListener may not be NULL");
         return BAD_VALUE;
     }
 
-    BQ_LOGV("connect(C): controlledByApp=%s",
+    BQ_LOGV("connect: controlledByApp=%s",
             controlledByApp ? "true" : "false");
 
     Mutex::Autolock lock(mCore->mMutex);
 
     if (mCore->mIsAbandoned) {
-        BQ_LOGE("connect(C): BufferQueue has been abandoned");
+        BQ_LOGE("connect: BufferQueue has been abandoned");
         return NO_INIT;
     }
 
@@ -409,12 +484,12 @@
 status_t BufferQueueConsumer::disconnect() {
     ATRACE_CALL();
 
-    BQ_LOGV("disconnect(C)");
+    BQ_LOGV("disconnect");
 
     Mutex::Autolock lock(mCore->mMutex);
 
     if (mCore->mConsumerListener == NULL) {
-        BQ_LOGE("disconnect(C): no consumer is connected");
+        BQ_LOGE("disconnect: no consumer is connected");
         return BAD_VALUE;
     }
 
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index b1cbc86..c24ad19 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -69,7 +69,11 @@
     mAllowAllocation(true),
     mBufferAge(0),
     mGenerationNumber(0),
-    mAsyncMode(false)
+    mAsyncMode(false),
+    mSingleBufferMode(false),
+    mSingleBufferSlot(INVALID_BUFFER_SLOT),
+    mSingleBufferCache(Rect::INVALID_RECT, 0, NATIVE_WINDOW_SCALING_MODE_FREEZE,
+            HAL_DATASPACE_UNKNOWN)
 {
     if (allocator == NULL) {
         sp<ISurfaceComposer> composer(ComposerService::getComposerService());
@@ -113,7 +117,7 @@
     int maxBufferCount = 0;
     for (int s = BufferQueueDefs::NUM_BUFFER_SLOTS - 1; s >= 0; --s) {
         const BufferSlot& slot(mSlots[s]);
-        if (slot.mBufferState != BufferSlot::FREE ||
+        if (!slot.mBufferState.isFree() ||
                 slot.mGraphicBuffer != NULL) {
             maxBufferCount = s + 1;
             break;
@@ -124,9 +128,9 @@
         const BufferSlot& slot(mSlots[s]);
         const sp<GraphicBuffer>& buffer(slot.mGraphicBuffer);
         result.appendFormat("%s%s[%02d:%p] state=%-8s", prefix,
-                (slot.mBufferState == BufferSlot::ACQUIRED) ? ">" : " ",
+                (slot.mBufferState.isAcquired()) ? ">" : " ",
                 s, buffer.get(),
-                BufferSlot::bufferStateName(slot.mBufferState));
+                slot.mBufferState.string());
 
         if (buffer != NULL) {
             result.appendFormat(", %p [%4ux%4u:%4u,%3X]", buffer->handle,
@@ -164,8 +168,8 @@
     // will temporarily keep the max buffer count up until the slots no longer
     // need to be preserved.
     for (int s = maxBufferCount; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
-        BufferSlot::BufferState state = mSlots[s].mBufferState;
-        if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) {
+        BufferState state = mSlots[s].mBufferState;
+        if (state.isQueued() || state.isDequeued()) {
             maxBufferCount = s + 1;
         }
     }
@@ -173,14 +177,14 @@
     return maxBufferCount;
 }
 
-void BufferQueueCore::freeBufferLocked(int slot) {
+void BufferQueueCore::freeBufferLocked(int slot, bool validate) {
     BQ_LOGV("freeBufferLocked: slot %d", slot);
     bool hadBuffer = mSlots[slot].mGraphicBuffer != NULL;
     mSlots[slot].mGraphicBuffer.clear();
-    if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) {
+    if (mSlots[slot].mBufferState.isAcquired()) {
         mSlots[slot].mNeedsCleanupOnRelease = true;
     }
-    if (mSlots[slot].mBufferState != BufferSlot::FREE) {
+    if (!mSlots[slot].mBufferState.isFree()) {
         mFreeSlots.insert(slot);
     } else if (hadBuffer) {
         // If the slot was FREE, but we had a buffer, we need to move this slot
@@ -188,7 +192,6 @@
         mFreeBuffers.remove(slot);
         mFreeSlots.insert(slot);
     }
-    mSlots[slot].mBufferState = BufferSlot::FREE;
     mSlots[slot].mAcquireCalled = false;
     mSlots[slot].mFrameNumber = 0;
 
@@ -198,14 +201,19 @@
         mSlots[slot].mEglFence = EGL_NO_SYNC_KHR;
     }
     mSlots[slot].mFence = Fence::NO_FENCE;
-    validateConsistencyLocked();
+    if (validate) {
+        validateConsistencyLocked();
+    }
 }
 
 void BufferQueueCore::freeAllBuffersLocked() {
     mBufferHasBeenQueued = false;
     for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
-        freeBufferLocked(s);
+        freeBufferLocked(s, false);
+        mSlots[s].mBufferState.reset();
     }
+    mSingleBufferSlot = INVALID_BUFFER_SLOT;
+    validateConsistencyLocked();
 }
 
 bool BufferQueueCore::stillTracking(const BufferItem* item) const {
@@ -238,7 +246,8 @@
         bool isInFreeBuffers =
                 std::find(mFreeBuffers.cbegin(), mFreeBuffers.cend(), slot) !=
                 mFreeBuffers.cend();
-        if (mSlots[slot].mBufferState == BufferSlot::FREE) {
+        if (mSlots[slot].mBufferState.isFree() &&
+                !mSlots[slot].mBufferState.isShared()) {
             if (mSlots[slot].mGraphicBuffer == NULL) {
                 if (!isInFreeSlots) {
                     BQ_LOGE("Slot %d is FREE but is not in mFreeSlots", slot);
@@ -262,13 +271,13 @@
             }
         } else {
             if (isInFreeSlots) {
-                BQ_LOGE("Slot %d is in mFreeSlots but is not FREE (%d)",
-                        slot, mSlots[slot].mBufferState);
+                BQ_LOGE("Slot %d is in mFreeSlots but is not FREE (%s)",
+                        slot, mSlots[slot].mBufferState.string());
                 usleep(PAUSE_TIME);
             }
             if (isInFreeBuffers) {
-                BQ_LOGE("Slot %d is in mFreeBuffers but is not FREE (%d)",
-                        slot, mSlots[slot].mBufferState);
+                BQ_LOGE("Slot %d is in mFreeBuffers but is not FREE (%s)",
+                        slot, mSlots[slot].mBufferState.string());
                 usleep(PAUSE_TIME);
             }
         }
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 9ef8ff7..ef01745 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -66,9 +66,9 @@
         BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)",
                 slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
         return BAD_VALUE;
-    } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+    } else if (!mSlots[slot].mBufferState.isDequeued()) {
         BQ_LOGE("requestBuffer: slot %d is not owned by the producer "
-                "(state = %d)", slot, mSlots[slot].mBufferState);
+                "(state = %s)", slot, mSlots[slot].mBufferState.string());
         return BAD_VALUE;
     }
 
@@ -96,7 +96,7 @@
 
         // There must be no dequeued buffers when changing the buffer count.
         for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
-            if (mSlots[s].mBufferState == BufferSlot::DEQUEUED) {
+            if (mSlots[s].mBufferState.isDequeued()) {
                 BQ_LOGE("setMaxDequeuedBufferCount: buffer owned by producer");
                 return BAD_VALUE;
             }
@@ -196,7 +196,7 @@
 
         // Free up any buffers that are in slots beyond the max buffer count
         for (int s = maxBufferCount; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
-            assert(mSlots[s].mBufferState == BufferSlot::FREE);
+            assert(mSlots[s].mBufferState.isFree());
             if (mSlots[s].mGraphicBuffer != NULL) {
                 mCore->freeBufferLocked(s);
                 *returnFlags |= RELEASE_ALL_BUFFERS;
@@ -206,15 +206,11 @@
         int dequeuedCount = 0;
         int acquiredCount = 0;
         for (int s = 0; s < maxBufferCount; ++s) {
-            switch (mSlots[s].mBufferState) {
-                case BufferSlot::DEQUEUED:
-                    ++dequeuedCount;
-                    break;
-                case BufferSlot::ACQUIRED:
-                    ++acquiredCount;
-                    break;
-                default:
-                    break;
+            if (mSlots[s].mBufferState.isDequeued()) {
+                ++dequeuedCount;
+            }
+            if (mSlots[s].mBufferState.isAcquired()) {
+                ++acquiredCount;
             }
         }
 
@@ -240,7 +236,12 @@
             BQ_LOGV("%s: queue size is %zu, waiting", caller,
                     mCore->mQueue.size());
         } else {
-            if (!mCore->mFreeBuffers.empty()) {
+            // If in single buffer mode and a shared buffer exists, always
+            // return it.
+            if (mCore->mSingleBufferMode && mCore->mSingleBufferSlot !=
+                    BufferQueueCore::INVALID_BUFFER_SLOT) {
+                *found = mCore->mSingleBufferSlot;
+            } else if (!mCore->mFreeBuffers.empty()) {
                 auto slot = mCore->mFreeBuffers.begin();
                 *found = *slot;
                 mCore->mFreeBuffers.erase(slot);
@@ -348,6 +349,13 @@
             // requested attributes, we free it and attempt to get another one.
             if (!mCore->mAllowAllocation) {
                 if (buffer->needsReallocation(width, height, format, usage)) {
+                    if (mCore->mSingleBufferMode &&
+                            mCore->mSingleBufferSlot == found) {
+                        BQ_LOGE("dequeueBuffer: cannot re-allocate a shared"
+                                "buffer");
+                        return BAD_VALUE;
+                    }
+
                     mCore->freeBufferLocked(found);
                     found = BufferItem::INVALID_BUFFER_SLOT;
                     continue;
@@ -360,7 +368,15 @@
 
         attachedByConsumer = mSlots[found].mAttachedByConsumer;
 
-        mSlots[found].mBufferState = BufferSlot::DEQUEUED;
+        mSlots[found].mBufferState.dequeue();
+
+        // If single buffer mode has just been enabled, cache the slot of the
+        // first buffer that is dequeued and mark it as the shared buffer.
+        if (mCore->mSingleBufferMode && mCore->mSingleBufferSlot ==
+                BufferQueueCore::INVALID_BUFFER_SLOT) {
+            mCore->mSingleBufferSlot = found;
+            mSlots[found].mBufferState.mShared = true;
+        }
 
         const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
         if ((buffer == NULL) ||
@@ -373,6 +389,7 @@
             mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
             mSlots[found].mFence = Fence::NO_FENCE;
             mCore->mBufferAge = 0;
+            mCore->mIsAllocating = true;
 
             returnFlags |= BUFFER_NEEDS_REALLOCATION;
         } else {
@@ -405,21 +422,26 @@
         BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
         sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer(
                 width, height, format, usage, &error));
-        if (graphicBuffer == NULL) {
-            BQ_LOGE("dequeueBuffer: createGraphicBuffer failed");
-            return error;
-        }
-
         { // Autolock scope
             Mutex::Autolock lock(mCore->mMutex);
 
+            if (graphicBuffer != NULL && !mCore->mIsAbandoned) {
+                graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
+                mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
+            }
+
+            mCore->mIsAllocating = false;
+            mCore->mIsAllocatingCondition.broadcast();
+
+            if (graphicBuffer == NULL) {
+                BQ_LOGE("dequeueBuffer: createGraphicBuffer failed");
+                return error;
+            }
+
             if (mCore->mIsAbandoned) {
                 BQ_LOGE("dequeueBuffer: BufferQueue has been abandoned");
                 return NO_INIT;
             }
-
-            graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
-            mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
         } // Autolock scope
     }
 
@@ -453,33 +475,40 @@
 status_t BufferQueueProducer::detachBuffer(int slot) {
     ATRACE_CALL();
     ATRACE_BUFFER_INDEX(slot);
-    BQ_LOGV("detachBuffer(P): slot %d", slot);
+    BQ_LOGV("detachBuffer: slot %d", slot);
     Mutex::Autolock lock(mCore->mMutex);
 
     if (mCore->mIsAbandoned) {
-        BQ_LOGE("detachBuffer(P): BufferQueue has been abandoned");
+        BQ_LOGE("detachBuffer: BufferQueue has been abandoned");
         return NO_INIT;
     }
 
     if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
-        BQ_LOGE("detachBuffer(P): BufferQueue has no connected producer");
+        BQ_LOGE("detachBuffer: BufferQueue has no connected producer");
         return NO_INIT;
     }
 
+    if (mCore->mSingleBufferMode) {
+        BQ_LOGE("detachBuffer: cannot detach a buffer in single buffer"
+                "mode");
+        return BAD_VALUE;
+    }
+
     if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-        BQ_LOGE("detachBuffer(P): slot index %d out of range [0, %d)",
+        BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
                 slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
         return BAD_VALUE;
-    } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
-        BQ_LOGE("detachBuffer(P): slot %d is not owned by the producer "
-                "(state = %d)", slot, mSlots[slot].mBufferState);
+    } else if (!mSlots[slot].mBufferState.isDequeued()) {
+        BQ_LOGE("detachBuffer: slot %d is not owned by the producer "
+                "(state = %s)", slot, mSlots[slot].mBufferState.string());
         return BAD_VALUE;
     } else if (!mSlots[slot].mRequestBufferCalled) {
-        BQ_LOGE("detachBuffer(P): buffer in slot %d has not been requested",
+        BQ_LOGE("detachBuffer: buffer in slot %d has not been requested",
                 slot);
         return BAD_VALUE;
     }
 
+    mSlots[slot].mBufferState.detachProducer();
     mCore->freeBufferLocked(slot);
     mCore->mDequeueCondition.broadcast();
     mCore->validateConsistencyLocked();
@@ -511,6 +540,12 @@
         return NO_INIT;
     }
 
+    if (mCore->mSingleBufferMode) {
+        BQ_LOGE("detachNextBuffer: cannot detach a buffer in single buffer"
+                "mode");
+        return BAD_VALUE;
+    }
+
     mCore->waitWhileAllocatingLocked();
 
     if (mCore->mFreeBuffers.empty()) {
@@ -535,25 +570,30 @@
     ATRACE_CALL();
 
     if (outSlot == NULL) {
-        BQ_LOGE("attachBuffer(P): outSlot must not be NULL");
+        BQ_LOGE("attachBuffer: outSlot must not be NULL");
         return BAD_VALUE;
     } else if (buffer == NULL) {
-        BQ_LOGE("attachBuffer(P): cannot attach NULL buffer");
+        BQ_LOGE("attachBuffer: cannot attach NULL buffer");
         return BAD_VALUE;
     }
 
     Mutex::Autolock lock(mCore->mMutex);
 
     if (mCore->mIsAbandoned) {
-        BQ_LOGE("attachBuffer(P): BufferQueue has been abandoned");
+        BQ_LOGE("attachBuffer: BufferQueue has been abandoned");
         return NO_INIT;
     }
 
     if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
-        BQ_LOGE("attachBuffer(P): BufferQueue has no connected producer");
+        BQ_LOGE("attachBuffer: BufferQueue has no connected producer");
         return NO_INIT;
     }
 
+    if (mCore->mSingleBufferMode) {
+        BQ_LOGE("attachBuffer: cannot atach a buffer in single buffer mode");
+        return BAD_VALUE;
+    }
+
     if (buffer->getGenerationNumber() != mCore->mGenerationNumber) {
         BQ_LOGE("attachBuffer: generation number mismatch [buffer %u] "
                 "[queue %u]", buffer->getGenerationNumber(),
@@ -565,7 +605,7 @@
 
     status_t returnFlags = NO_ERROR;
     int found;
-    status_t status = waitForFreeSlotThenRelock("attachBuffer(P)", &found,
+    status_t status = waitForFreeSlotThenRelock("attachBuffer", &found,
             &returnFlags);
     if (status != NO_ERROR) {
         return status;
@@ -573,17 +613,17 @@
 
     // This should not happen
     if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
-        BQ_LOGE("attachBuffer(P): no available buffer slots");
+        BQ_LOGE("attachBuffer: no available buffer slots");
         return -EBUSY;
     }
 
     *outSlot = found;
     ATRACE_BUFFER_INDEX(*outSlot);
-    BQ_LOGV("attachBuffer(P): returning slot %d flags=%#x",
+    BQ_LOGV("attachBuffer: returning slot %d flags=%#x",
             *outSlot, returnFlags);
 
     mSlots[*outSlot].mGraphicBuffer = buffer;
-    mSlots[*outSlot].mBufferState = BufferSlot::DEQUEUED;
+    mSlots[*outSlot].mBufferState.attachProducer();
     mSlots[*outSlot].mEglFence = EGL_NO_SYNC_KHR;
     mSlots[*outSlot].mFence = Fence::NO_FENCE;
     mSlots[*outSlot].mRequestBufferCalled = true;
@@ -649,9 +689,9 @@
             BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)",
                     slot, maxBufferCount);
             return BAD_VALUE;
-        } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+        } else if (!mSlots[slot].mBufferState.isDequeued()) {
             BQ_LOGE("queueBuffer: slot %d is not owned by the producer "
-                    "(state = %d)", slot, mSlots[slot].mBufferState);
+                    "(state = %s)", slot, mSlots[slot].mBufferState.string());
             return BAD_VALUE;
         } else if (!mSlots[slot].mRequestBufferCalled) {
             BQ_LOGE("queueBuffer: slot %d was queued without requesting "
@@ -681,7 +721,8 @@
         }
 
         mSlots[slot].mFence = fence;
-        mSlots[slot].mBufferState = BufferSlot::QUEUED;
+        mSlots[slot].mBufferState.queue();
+
         ++mCore->mFrameCounter;
         mSlots[slot].mFrameNumber = mCore->mFrameCounter;
 
@@ -700,11 +741,23 @@
         item.mSlot = slot;
         item.mFence = fence;
         item.mIsDroppable = mCore->mAsyncMode ||
-                mCore->mDequeueBufferCannotBlock;
+                mCore->mDequeueBufferCannotBlock ||
+                (mCore->mSingleBufferMode && mCore->mSingleBufferSlot == slot);
         item.mSurfaceDamage = surfaceDamage;
+        item.mSingleBufferMode = mCore->mSingleBufferMode;
+        item.mQueuedBuffer = true;
 
         mStickyTransform = stickyTransform;
 
+        // Cache the shared buffer data so that the BufferItem can be recreated.
+        if (mCore->mSingleBufferMode) {
+            mCore->mSingleBufferCache.crop = crop;
+            mCore->mSingleBufferCache.transform = transform;
+            mCore->mSingleBufferCache.scalingMode = static_cast<uint32_t>(
+                    scalingMode);
+            mCore->mSingleBufferCache.dataspace = dataSpace;
+        }
+
         if (mCore->mQueue.empty()) {
             // When the queue is empty, we can ignore mDequeueBufferCannotBlock
             // and simply queue this buffer
@@ -718,8 +771,19 @@
                 // If the front queued buffer is still being tracked, we first
                 // mark it as freed
                 if (mCore->stillTracking(front)) {
-                    mSlots[front->mSlot].mBufferState = BufferSlot::FREE;
-                    mCore->mFreeBuffers.push_front(front->mSlot);
+                    mSlots[front->mSlot].mBufferState.freeQueued();
+
+                    // After leaving single buffer mode, the shared buffer will
+                    // still be around. Mark it as no longer shared if this
+                    // operation causes it to be free.
+                    if (!mCore->mSingleBufferMode &&
+                            mSlots[front->mSlot].mBufferState.isFree()) {
+                        mSlots[front->mSlot].mBufferState.mShared = false;
+                    }
+                    // Don't put the shared buffer on the free list.
+                    if (!mSlots[front->mSlot].mBufferState.isShared()) {
+                        mCore->mFreeBuffers.push_front(front->mSlot);
+                    }
                 }
                 // Overwrite the droppable buffer with the incoming one
                 *front = item;
@@ -795,21 +859,36 @@
         return NO_INIT;
     }
 
+    if (mCore->mSingleBufferMode) {
+        BQ_LOGE("cancelBuffer: cannot cancel a buffer in single buffer mode");
+        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);
         return BAD_VALUE;
-    } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+    } else if (!mSlots[slot].mBufferState.isDequeued()) {
         BQ_LOGE("cancelBuffer: slot %d is not owned by the producer "
-                "(state = %d)", slot, mSlots[slot].mBufferState);
+                "(state = %s)", slot, mSlots[slot].mBufferState.string());
         return BAD_VALUE;
     } else if (fence == NULL) {
         BQ_LOGE("cancelBuffer: fence is NULL");
         return BAD_VALUE;
     }
 
-    mCore->mFreeBuffers.push_front(slot);
-    mSlots[slot].mBufferState = BufferSlot::FREE;
+    mSlots[slot].mBufferState.cancel();
+
+    // After leaving single buffer mode, the shared buffer will still be around.
+    // Mark it as no longer shared if this operation causes it to be free.
+    if (!mCore->mSingleBufferMode && mSlots[slot].mBufferState.isFree()) {
+        mSlots[slot].mBufferState.mShared = false;
+    }
+
+    // Don't put the shared buffer on the free list.
+    if (!mSlots[slot].mBufferState.isShared()) {
+        mCore->mFreeBuffers.push_front(slot);
+    }
     mSlots[slot].mFence = fence;
     mCore->mDequeueCondition.broadcast();
     mCore->validateConsistencyLocked();
@@ -878,26 +957,26 @@
     ATRACE_CALL();
     Mutex::Autolock lock(mCore->mMutex);
     mConsumerName = mCore->mConsumerName;
-    BQ_LOGV("connect(P): api=%d producerControlledByApp=%s", api,
+    BQ_LOGV("connect: api=%d producerControlledByApp=%s", api,
             producerControlledByApp ? "true" : "false");
 
     if (mCore->mIsAbandoned) {
-        BQ_LOGE("connect(P): BufferQueue has been abandoned");
+        BQ_LOGE("connect: BufferQueue has been abandoned");
         return NO_INIT;
     }
 
     if (mCore->mConsumerListener == NULL) {
-        BQ_LOGE("connect(P): BufferQueue has no consumer");
+        BQ_LOGE("connect: BufferQueue has no consumer");
         return NO_INIT;
     }
 
     if (output == NULL) {
-        BQ_LOGE("connect(P): output was NULL");
+        BQ_LOGE("connect: output was NULL");
         return BAD_VALUE;
     }
 
     if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) {
-        BQ_LOGE("connect(P): already connected (cur=%d req=%d)",
+        BQ_LOGE("connect: already connected (cur=%d req=%d)",
                 mCore->mConnectedApi, api);
         return BAD_VALUE;
     }
@@ -920,14 +999,14 @@
                 status = IInterface::asBinder(listener)->linkToDeath(
                         static_cast<IBinder::DeathRecipient*>(this));
                 if (status != NO_ERROR) {
-                    BQ_LOGE("connect(P): linkToDeath failed: %s (%d)",
+                    BQ_LOGE("connect: linkToDeath failed: %s (%d)",
                             strerror(-status), status);
                 }
             }
             mCore->mConnectedProducerListener = listener;
             break;
         default:
-            BQ_LOGE("connect(P): unknown API %d", api);
+            BQ_LOGE("connect: unknown API %d", api);
             status = BAD_VALUE;
             break;
     }
@@ -942,7 +1021,7 @@
 
 status_t BufferQueueProducer::disconnect(int api) {
     ATRACE_CALL();
-    BQ_LOGV("disconnect(P): api %d", api);
+    BQ_LOGV("disconnect: api %d", api);
 
     int status = NO_ERROR;
     sp<IConsumerListener> listener;
@@ -979,13 +1058,13 @@
                     mCore->mDequeueCondition.broadcast();
                     listener = mCore->mConsumerListener;
                 } else if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) {
-                    BQ_LOGE("disconnect(P): still connected to another API "
+                    BQ_LOGE("disconnect: still connected to another API "
                             "(cur=%d req=%d)", mCore->mConnectedApi, api);
                     status = BAD_VALUE;
                 }
                 break;
             default:
-                BQ_LOGE("disconnect(P): unknown API %d", api);
+                BQ_LOGE("disconnect: unknown API %d", api);
                 status = BAD_VALUE;
                 break;
         }
@@ -1038,7 +1117,7 @@
                 if (mSlots[slot].mGraphicBuffer != NULL) {
                     ++currentBufferCount;
                 } else {
-                    if (mSlots[slot].mBufferState != BufferSlot::FREE) {
+                    if (!mSlots[slot].mBufferState.isFree()) {
                         BQ_LOGE("allocateBuffers: slot %d without buffer is not FREE",
                                 slot);
                         continue;
@@ -1101,7 +1180,7 @@
 
             for (size_t i = 0; i < newBufferCount; ++i) {
                 int slot = freeSlots[i];
-                if (mSlots[slot].mBufferState != BufferSlot::FREE) {
+                if (!mSlots[slot].mBufferState.isFree()) {
                     // A consumer allocated the FREE slot with attachBuffer. Discard the buffer we
                     // allocated.
                     BQ_LOGV("allocateBuffers: slot %d was acquired while allocating. "
@@ -1159,6 +1238,19 @@
     return nextFrameNumber;
 }
 
+status_t BufferQueueProducer::setSingleBufferMode(bool singleBufferMode) {
+    ATRACE_CALL();
+    BQ_LOGV("setSingleBufferMode: %d", singleBufferMode);
+
+    Mutex::Autolock lock(mCore->mMutex);
+    if (!singleBufferMode) {
+        mCore->mSingleBufferSlot = BufferQueueCore::INVALID_BUFFER_SLOT;
+    }
+    mCore->mSingleBufferMode = singleBufferMode;
+
+    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/BufferSlot.cpp b/libs/gui/BufferSlot.cpp
index 01595de..b1cdc5d 100644
--- a/libs/gui/BufferSlot.cpp
+++ b/libs/gui/BufferSlot.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 The Android Open Source Project
+ * Copyright 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,14 +18,29 @@
 
 namespace android {
 
-const char* BufferSlot::bufferStateName(BufferState state) {
-    switch (state) {
-        case BufferSlot::DEQUEUED: return "DEQUEUED";
-        case BufferSlot::QUEUED: return "QUEUED";
-        case BufferSlot::FREE: return "FREE";
-        case BufferSlot::ACQUIRED: return "ACQUIRED";
+
+const char* BufferState::string() const {
+
+    if (isShared()) {
+        return "SHARED";
     }
-    return "Unknown";
+
+    if (isFree()) {
+        return "FREE";
+    }
+
+    if (isAcquired()) {
+        return "ACQUIRED";
+    }
+
+    if (isDequeued()) {
+        return "DEQUEUED";
+    }
+
+    if (isQueued()) {
+        return "QUEUED";
+    }
+    return "UNKNOWN";
 }
 
 } // namespace android
diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp
index 1572a89..83e4d66 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -424,6 +424,11 @@
                     mCurrentTextureImage->graphicBufferHandle() : 0,
             slot, mSlots[slot].mGraphicBuffer->handle);
 
+    // Hang onto the pointer so that it isn't freed in the call to
+    // releaseBufferLocked() if we're in single buffer mode and both buffers are
+    // the same.
+    sp<EglImage> nextTextureImage = mEglSlots[slot].mEglImage;
+
     // release old buffer
     if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
         status_t status = releaseBufferLocked(
@@ -439,7 +444,7 @@
 
     // Update the GLConsumer state.
     mCurrentTexture = slot;
-    mCurrentTextureImage = mEglSlots[slot].mEglImage;
+    mCurrentTextureImage = nextTextureImage;
     mCurrentCrop = item.mCrop;
     mCurrentTransform = item.mTransform;
     mCurrentScalingMode = item.mScalingMode;
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 640a9f2..d5310bd 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -50,7 +50,8 @@
     GET_CONSUMER_NAME,
     SET_MAX_DEQUEUED_BUFFER_COUNT,
     SET_ASYNC_MODE,
-    GET_NEXT_FRAME_NUMBER
+    GET_NEXT_FRAME_NUMBER,
+    SET_SINGLE_BUFFER_MODE
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -339,6 +340,19 @@
         uint64_t frameNumber = reply.readUint64();
         return frameNumber;
     }
+
+    virtual status_t setSingleBufferMode(bool singleBufferMode) {
+        Parcel data, reply;
+        data.writeInterfaceToken(
+                IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeInt32(singleBufferMode);
+        status_t result = remote()->transact(SET_SINGLE_BUFFER_MODE, data,
+                &reply);
+        if (result == NO_ERROR) {
+            result = reply.readInt32();
+        }
+        return result;
+    }
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -527,6 +541,13 @@
             reply->writeUint64(frameNumber);
             return NO_ERROR;
         }
+        case SET_SINGLE_BUFFER_MODE: {
+            CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+            bool singleBufferMode = data.readInt32();
+            status_t result = setSingleBufferMode(singleBufferMode);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 9191b2a..7578a3d 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -550,6 +550,9 @@
     case NATIVE_WINDOW_SET_SURFACE_DAMAGE:
         res = dispatchSetSurfaceDamage(args);
         break;
+    case NATIVE_WINDOW_SET_SINGLE_BUFFER_MODE:
+        res = dispatchSetSingleBufferMode(args);
+        break;
     default:
         res = NAME_NOT_FOUND;
         break;
@@ -660,6 +663,12 @@
     return NO_ERROR;
 }
 
+int Surface::dispatchSetSingleBufferMode(va_list args) {
+    bool singleBufferMode = va_arg(args, int);
+    setSingleBufferMode(singleBufferMode);
+    return NO_ERROR;
+}
+
 int Surface::connect(int api) {
     static sp<IProducerListener> listener = new DummyProducerListener();
     return connect(api, listener);
@@ -861,6 +870,19 @@
     return err;
 }
 
+int Surface::setSingleBufferMode(bool singleBufferMode) {
+    ATRACE_CALL();
+    ALOGV("Surface::setSingleBufferMode (%d)", singleBufferMode);
+    Mutex::Autolock lock(mMutex);
+
+    status_t err = mGraphicBufferProducer->setSingleBufferMode(
+            singleBufferMode);
+    ALOGE_IF(err, "IGraphicsBufferProducer::setSingleBufferMode(%d) returned"
+            "%s", singleBufferMode, strerror(-err));
+
+    return err;
+}
+
 int Surface::setBuffersDimensions(uint32_t width, uint32_t height)
 {
     ATRACE_CALL();
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 5244d82..a32a90c 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -465,4 +465,74 @@
     ASSERT_EQ(OK, mConsumer->attachBuffer(&outSlot, buffer));
 }
 
+TEST_F(BufferQueueTest, TestSingleBufferMode) {
+    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));
+
+    ASSERT_EQ(OK, mProducer->setSingleBufferMode(true));
+
+    // Get a buffer
+    int singleSlot;
+    sp<Fence> fence;
+    sp<GraphicBuffer> buffer;
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+            mProducer->dequeueBuffer(&singleSlot, &fence, 0, 0, 0, 0));
+    ASSERT_EQ(OK, mProducer->requestBuffer(singleSlot, &buffer));
+
+    // Queue the buffer
+    IGraphicBufferProducer::QueueBufferInput input(0, false,
+            HAL_DATASPACE_UNKNOWN, Rect(0, 0, 1, 1),
+            NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
+    ASSERT_EQ(OK, mProducer->queueBuffer(singleSlot, input, &output));
+
+    // Repeatedly acquire and release a buffer from the consumer side, it should
+    // always return the same one.
+    BufferItem item;
+    for (int i = 0; i < 5; i++) {
+        ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+        ASSERT_EQ(singleSlot, item.mSlot);
+        ASSERT_EQ(0, item.mTimestamp);
+        ASSERT_EQ(false, item.mIsAutoTimestamp);
+        ASSERT_EQ(HAL_DATASPACE_UNKNOWN, item.mDataSpace);
+        ASSERT_EQ(Rect(0, 0, 1, 1), item.mCrop);
+        ASSERT_EQ(NATIVE_WINDOW_SCALING_MODE_FREEZE, item.mScalingMode);
+        ASSERT_EQ(0, item.mTransform);
+        ASSERT_EQ(Fence::NO_FENCE, item.mFence);
+
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    }
+
+    // Repeatedly queue and dequeue a buffer from the producer side, it should
+    // always return the same one.
+    int slot;
+    for (int i = 0; i < 5; i++) {
+        ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+        ASSERT_EQ(singleSlot, slot);
+        ASSERT_EQ(OK, mProducer->queueBuffer(singleSlot, input, &output));
+    }
+
+    // Repeatedly acquire and release a buffer from the consumer side, it should
+    // always return the same one. First grabbing them from the queue and then
+    // when the queue is empty, returning the single buffer.
+    for (int i = 0; i < 10; i++) {
+        ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+        ASSERT_EQ(singleSlot, item.mSlot);
+        ASSERT_EQ(0, item.mTimestamp);
+        ASSERT_EQ(false, item.mIsAutoTimestamp);
+        ASSERT_EQ(HAL_DATASPACE_UNKNOWN, item.mDataSpace);
+        ASSERT_EQ(Rect(0, 0, 1, 1), item.mCrop);
+        ASSERT_EQ(NATIVE_WINDOW_SCALING_MODE_FREEZE, item.mScalingMode);
+        ASSERT_EQ(0, item.mTransform);
+        ASSERT_EQ(Fence::NO_FENCE, item.mFence);
+
+        ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+                EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+    }
+}
+
 } // namespace android
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index fb29276..5bd7464 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -1186,6 +1186,14 @@
         return setError(EGL_BAD_SURFACE, EGL_FALSE);
 
     egl_surface_t const * const s = get_surface(surface);
+
+    //XXX: temporary hack for the EGL hook-up for single buffer mode
+    if (attribute == EGL_RENDER_BUFFER && (value == EGL_BACK_BUFFER ||
+            value == EGL_SINGLE_BUFFER)) {
+        return (native_window_set_single_buffer_mode(s->win.get(),
+                value == EGL_SINGLE_BUFFER)) ? EGL_TRUE : EGL_FALSE;
+    }
+
     if (s->cnx->egl.eglSurfaceAttrib) {
         return s->cnx->egl.eglSurfaceAttrib(
                 dp->disp.dpy, s->surface, attribute, value);
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index 5b85481..f8fe8e1 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -547,6 +547,10 @@
     return 0;
 }
 
+status_t VirtualDisplaySurface::setSingleBufferMode(bool singleBufferMode) {
+    return mSource[SOURCE_SINK]->setSingleBufferMode(singleBufferMode);
+}
+
 void VirtualDisplaySurface::updateQueueBufferOutput(
         const QueueBufferOutput& qbo) {
     uint32_t w, h, transformHint, numPendingBuffers;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index fb37373..bde7fcf 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -120,6 +120,7 @@
     virtual status_t setGenerationNumber(uint32_t generationNumber);
     virtual String8 getConsumerName() const override;
     virtual uint64_t getNextFrameNumber() const override;
+    virtual status_t setSingleBufferMode(bool singleBufferMode);
 
     //
     // Utility methods
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index a7b167f..ad53226 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1254,7 +1254,7 @@
 // ----------------------------------------------------------------------------
 
 bool Layer::shouldPresentNow(const DispSync& dispSync) const {
-    if (mSidebandStreamChanged) {
+    if (mSidebandStreamChanged || mSingleBufferMode) {
         return true;
     }
 
@@ -1278,7 +1278,7 @@
 
 bool Layer::onPreComposition() {
     mRefreshPending = false;
-    return mQueuedFrames > 0 || mSidebandStreamChanged;
+    return mQueuedFrames > 0 || mSidebandStreamChanged || mSingleBufferMode;
 }
 
 void Layer::onPostComposition() {
@@ -1335,7 +1335,7 @@
     }
 
     Region outDirtyRegion;
-    if (mQueuedFrames > 0) {
+    if (mQueuedFrames > 0 || mSingleBufferMode) {
 
         // if we've already called updateTexImage() without going through
         // a composition step, we have to skip this layer at this point
@@ -1492,8 +1492,14 @@
             }
         }
 
+        // This boolean is used to make sure that SurfaceFlinger's shadow copy
+        // of the buffer queue isn't modified when the buffer queue is returning
+        // BufferItem's that weren't actually queued. This can happen in single
+        // buffer mode.
+        bool queuedBuffer = false;
         status_t updateResult = mSurfaceFlingerConsumer->updateTexImage(&r,
-                mFlinger->mPrimaryDispSync, maxFrameNumber);
+                mFlinger->mPrimaryDispSync, &mSingleBufferMode, &queuedBuffer,
+                maxFrameNumber);
         if (updateResult == BufferQueue::PRESENT_LATER) {
             // Producer doesn't want buffer to be displayed yet.  Signal a
             // layer update so we check again at the next opportunity.
@@ -1502,16 +1508,18 @@
         } else if (updateResult == SurfaceFlingerConsumer::BUFFER_REJECTED) {
             // If the buffer has been rejected, remove it from the shadow queue
             // and return early
-            Mutex::Autolock lock(mQueueItemLock);
-            mQueueItems.removeAt(0);
-            android_atomic_dec(&mQueuedFrames);
+            if (queuedBuffer) {
+                Mutex::Autolock lock(mQueueItemLock);
+                mQueueItems.removeAt(0);
+                android_atomic_dec(&mQueuedFrames);
+            }
             return outDirtyRegion;
         } else if (updateResult != NO_ERROR || mUpdateTexImageFailed) {
             // This can occur if something goes wrong when trying to create the
             // EGLImage for this buffer. If this happens, the buffer has already
             // been released, so we need to clean up the queue and bug out
             // early.
-            {
+            if (queuedBuffer) {
                 Mutex::Autolock lock(mQueueItemLock);
                 mQueueItems.clear();
                 android_atomic_and(0, &mQueuedFrames);
@@ -1526,7 +1534,8 @@
             return outDirtyRegion;
         }
 
-        { // Autolock scope
+        if (queuedBuffer) {
+            // Autolock scope
             auto currentFrameNumber = mSurfaceFlingerConsumer->getFrameNumber();
 
             Mutex::Autolock lock(mQueueItemLock);
@@ -1544,7 +1553,8 @@
 
         // Decrement the queued-frames count.  Signal another event if we
         // have more frames pending.
-        if (android_atomic_dec(&mQueuedFrames) > 1) {
+        if ((queuedBuffer && android_atomic_dec(&mQueuedFrames) > 1)
+                || mSingleBufferMode) {
             mFlinger->signalLayerUpdate();
         }
 
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 4ff9006..9e3c4db 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -306,7 +306,8 @@
     /*
      * Returns if a frame is queued.
      */
-    bool hasQueuedFrame() const { return mQueuedFrames > 0 || mSidebandStreamChanged; }
+    bool hasQueuedFrame() const { return mQueuedFrames > 0 ||
+            mSidebandStreamChanged || mSingleBufferMode; }
 
     // -----------------------------------------------------------------------
 
@@ -489,6 +490,8 @@
     Vector<BufferItem> mQueueItems;
     uint64_t mLastFrameNumberReceived;
     bool mUpdateTexImageFailed; // This is only modified from the main thread
+
+    bool mSingleBufferMode;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/MonitoredProducer.cpp b/services/surfaceflinger/MonitoredProducer.cpp
index 68b6b86..16879ca 100644
--- a/services/surfaceflinger/MonitoredProducer.cpp
+++ b/services/surfaceflinger/MonitoredProducer.cpp
@@ -131,6 +131,10 @@
     return mProducer->getNextFrameNumber();
 }
 
+status_t MonitoredProducer::setSingleBufferMode(bool singleBufferMode) {
+    return mProducer->setSingleBufferMode(singleBufferMode);
+}
+
 IBinder* MonitoredProducer::onAsBinder() {
     return IInterface::asBinder(mProducer).get();
 }
diff --git a/services/surfaceflinger/MonitoredProducer.h b/services/surfaceflinger/MonitoredProducer.h
index 4d2ed6e..c2b2e24 100644
--- a/services/surfaceflinger/MonitoredProducer.h
+++ b/services/surfaceflinger/MonitoredProducer.h
@@ -59,6 +59,7 @@
     virtual String8 getConsumerName() const override;
     virtual uint64_t getNextFrameNumber() const override;
     virtual IBinder* onAsBinder();
+    virtual status_t setSingleBufferMode(bool singleBufferMode);
 
 private:
     sp<IGraphicBufferProducer> mProducer;
diff --git a/services/surfaceflinger/SurfaceFlingerConsumer.cpp b/services/surfaceflinger/SurfaceFlingerConsumer.cpp
index 930e7c7..5722fb4 100644
--- a/services/surfaceflinger/SurfaceFlingerConsumer.cpp
+++ b/services/surfaceflinger/SurfaceFlingerConsumer.cpp
@@ -32,7 +32,8 @@
 // ---------------------------------------------------------------------------
 
 status_t SurfaceFlingerConsumer::updateTexImage(BufferRejecter* rejecter,
-        const DispSync& dispSync, uint64_t maxFrameNumber)
+        const DispSync& dispSync, bool* singleBufferMode, bool* queuedBuffer,
+        uint64_t maxFrameNumber)
 {
     ATRACE_CALL();
     ALOGV("updateTexImage");
@@ -68,7 +69,6 @@
         return err;
     }
 
-
     // We call the rejecter here, in case the caller has a reason to
     // not accept this buffer.  This is used by SurfaceFlinger to
     // reject buffers which have the wrong size
@@ -78,6 +78,14 @@
         return BUFFER_REJECTED;
     }
 
+    if (singleBufferMode) {
+        *singleBufferMode = item.mSingleBufferMode;
+    }
+
+    if (queuedBuffer) {
+        *queuedBuffer = item.mQueuedBuffer;
+    }
+
     // Release the previous buffer.
     err = updateAndReleaseLocked(item);
     if (err != NO_ERROR) {
diff --git a/services/surfaceflinger/SurfaceFlingerConsumer.h b/services/surfaceflinger/SurfaceFlingerConsumer.h
index 779e5b7..207c243 100644
--- a/services/surfaceflinger/SurfaceFlingerConsumer.h
+++ b/services/surfaceflinger/SurfaceFlingerConsumer.h
@@ -57,6 +57,7 @@
     // this does not guarantee that the buffer has been bound to the GL
     // texture.
     status_t updateTexImage(BufferRejecter* rejecter, const DispSync& dispSync,
+            bool* singleBufferMode, bool* queuedBuffer,
             uint64_t maxFrameNumber = 0);
 
     // See GLConsumer::bindTextureImageLocked().