Merge "EVS HAL with multi-buffer support via BufferDesc"
diff --git a/evs/1.0/IEvsCamera.hal b/evs/1.0/IEvsCamera.hal
index a2fc565..d0559d7 100644
--- a/evs/1.0/IEvsCamera.hal
+++ b/evs/1.0/IEvsCamera.hal
@@ -65,7 +65,7 @@
      * as one), and if the supply is exhausted, no further frames may be
      * delivered until a buffer is returned.
      */
-    doneWithFrame(uint32_t frameId, handle bufferHandle) generates (EvsResult result);
+    oneway doneWithFrame(BufferDesc buffer);
 
     /**
      * Stop the delivery of EVS camera frames.
diff --git a/evs/1.0/IEvsCameraStream.hal b/evs/1.0/IEvsCameraStream.hal
index ef5460f..fcd5717 100644
--- a/evs/1.0/IEvsCameraStream.hal
+++ b/evs/1.0/IEvsCameraStream.hal
@@ -32,5 +32,5 @@
      * must be delivered, signifying the end of the stream. No further frame
      * deliveries may happen thereafter.
      */
-    oneway deliverFrame(uint32_t frameId, handle bufferHandle);
+    oneway deliverFrame(BufferDesc buffer);
 };
diff --git a/evs/1.0/IEvsDisplay.hal b/evs/1.0/IEvsDisplay.hal
index a473872..8352221 100644
--- a/evs/1.0/IEvsDisplay.hal
+++ b/evs/1.0/IEvsDisplay.hal
@@ -65,7 +65,7 @@
      * must be returned via a call to returnTargetBufferForDisplay() even if the
      * display is no longer visible.
      */
-     getTargetBuffer() generates (handle bufferHandle);
+     getTargetBuffer() generates (BufferDesc buffer);
 
 
     /**
@@ -76,5 +76,5 @@
      * call. The buffer may be returned at any time and in any DisplayState, but all
      * buffers are expected to be returned before the IEvsDisplay interface is destroyed.
      */
-    returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);
+    returnTargetBufferForDisplay(BufferDesc buffer) generates (EvsResult result);
 };
diff --git a/evs/1.0/IEvsEnumerator.hal b/evs/1.0/IEvsEnumerator.hal
index e3a1382..3779866 100644
--- a/evs/1.0/IEvsEnumerator.hal
+++ b/evs/1.0/IEvsEnumerator.hal
@@ -67,5 +67,15 @@
      * NOTE: All buffer must have been returned to the display before making this call.
      */
     closeDisplay(IEvsDisplay display);
+
+    /**
+     * This call requests the current state of the display
+     *
+     * If there is no open display, this returns DisplayState::NOT_OPEN. otherwise, it returns
+     * the actual state of the active display.  This call is replicated on the IEvsEnumerator
+     * interface in order to allow secondary clients to monitor the state of the EVS display
+     * without acquiring exclusive ownership of the display.
+     */
+    getDisplayState() generates (DisplayState state);
 };
 
diff --git a/evs/1.0/default/Android.bp b/evs/1.0/default/Android.bp
index 3bda250..e3bff25 100644
--- a/evs/1.0/default/Android.bp
+++ b/evs/1.0/default/Android.bp
@@ -11,7 +11,6 @@
 
     shared_libs: [
         "android.hardware.evs@1.0",
-        "android.hardware.graphics.allocator@2.0",
         "libui",
         "libbase",
         "libbinder",
diff --git a/evs/1.0/default/EvsCamera.cpp b/evs/1.0/default/EvsCamera.cpp
index 6715a2e..c62f7b6 100644
--- a/evs/1.0/default/EvsCamera.cpp
+++ b/evs/1.0/default/EvsCamera.cpp
@@ -33,18 +33,22 @@
 const char EvsCamera::kCameraName_Backup[]    = "backup";
 const char EvsCamera::kCameraName_RightTurn[] = "Right Turn";
 
+// Arbitrary limit on number of graphics buffers allowed to be allocated
+// Safeguards against unreasonable resource consumption and provides a testable limit
+const unsigned MAX_BUFFERS_IN_FLIGHT = 100;
+
 
 // TODO(b/31632518):  Need to get notification when our client dies so we can close the camera.
-// As it stands, if the client dies suddently, the buffer may be stranded.
-// As possible work around would be to give the client a HIDL object to exclusively hold
-// and use it's destructor to perform some work in the server side.
+// As it stands, if the client dies suddenly, the buffer may be stranded.
 
-EvsCamera::EvsCamera(const char *id) {
+EvsCamera::EvsCamera(const char *id) :
+    mFramesAllowed(0),
+    mFramesInUse(0),
+    mStreamState(STOPPED) {
+
     ALOGD("EvsCamera instantiated");
 
     mDescription.cameraId = id;
-    mFrameBusy = false;
-    mStreamState = STOPPED;
 
     // Set up dummy data for testing
     if (mDescription.cameraId == kCameraName_Backup) {
@@ -52,16 +56,23 @@
         mDescription.vendorFlags            = 0xFFFFFFFF;   // Arbitrary value
         mDescription.defaultHorResolution   = 320;          // 1/2 NTSC/VGA
         mDescription.defaultVerResolution   = 240;          // 1/2 NTSC/VGA
-    }
-    else if (mDescription.cameraId == kCameraName_RightTurn) {
+    } else if (mDescription.cameraId == kCameraName_RightTurn) {
         // Nothing but the name and the usage hint
         mDescription.hints                  = static_cast<uint32_t>(UsageHint::USAGE_HINT_RIGHT_TURN);
-    }
-    else {
+    } else {
         // Leave empty for a minimalist camera description without even a hint
     }
+
+
+    // Set our buffer properties
+    mWidth  = (mDescription.defaultHorResolution) ? mDescription.defaultHorResolution : 640;
+    mHeight = (mDescription.defaultVerResolution) ? mDescription.defaultVerResolution : 480;
+
+    mFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+    mUsage  = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE;
 }
 
+
 EvsCamera::~EvsCamera() {
     ALOGD("EvsCamera being destroyed");
     std::lock_guard<std::mutex> lock(mAccessLock);
@@ -70,11 +81,14 @@
     // (It really should be already)
     stopVideoStream();
 
-    // Drop the graphics buffer we've been using
-    if (mBuffer) {
-        // Drop the graphics buffer we've been using
-        GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
-        alloc.free(mBuffer);
+    // Drop all the graphics buffers we've been using
+    GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
+    for (auto&& rec : mBuffers) {
+        if (rec.inUse) {
+            ALOGE("Error - releasing buffer despite remote ownership");
+        }
+        alloc.free(rec.handle);
+        rec.handle = nullptr;
     }
 
     ALOGD("EvsCamera destroyed");
@@ -95,113 +109,109 @@
     ALOGD("setMaxFramesInFlight");
     std::lock_guard<std::mutex> lock(mAccessLock);
 
-    // TODO:  Update our stored value
-
-    // TODO:  Adjust our buffer count right now if we can.  Otherwise, it'll adjust in doneWithFrame
-
-    // For now we support only one!
-    if (bufferCount != 1) {
-        return EvsResult::BUFFER_NOT_AVAILABLE;
+    // We cannot function without at least one video buffer to send data
+    if (bufferCount < 1) {
+        ALOGE("Ignoring setMaxFramesInFlight with less than one buffer requested");
+        return EvsResult::INVALID_ARG;
     }
 
-    return EvsResult::OK;
+    // Update our internal state
+    if (setAvailableFrames_Locked(bufferCount)) {
+        return EvsResult::OK;
+    } else {
+        return EvsResult::BUFFER_NOT_AVAILABLE;
+    }
 }
 
+
 Return<EvsResult> EvsCamera::startVideoStream(const ::android::sp<IEvsCameraStream>& stream)  {
     ALOGD("startVideoStream");
     std::lock_guard<std::mutex> lock(mAccessLock);
 
-    // We only support a single stream at a time
     if (mStreamState != STOPPED) {
         ALOGE("ignoring startVideoStream call when a stream is already running.");
         return EvsResult::STREAM_ALREADY_RUNNING;
     }
 
+    // If the client never indicated otherwise, configure ourselves for a single streaming buffer
+    if (mFramesAllowed < 1) {
+        if (!setAvailableFrames_Locked(1)) {
+            ALOGE("Failed to start stream because we couldn't get a graphics buffer");
+            return EvsResult::BUFFER_NOT_AVAILABLE;
+        }
+    }
+
     // Record the user's callback for use when we have a frame ready
     mStream = stream;
 
-    // Allocate a graphics buffer into which we'll put our test images
-    if (!mBuffer) {
-        mWidth  = (mDescription.defaultHorResolution) ? mDescription.defaultHorResolution : 640;
-        mHeight = (mDescription.defaultVerResolution) ? mDescription.defaultVerResolution : 480;
-        // TODO:  What about stride?  Assume no padding for now...
-        mStride = 4* mWidth;    // Special cased to assume 4 byte pixels with no padding for now
-
-        ALOGD("Allocating buffer for camera frame");
-        GraphicBufferAllocator &alloc(GraphicBufferAllocator::get());
-        status_t result = alloc.allocate(mWidth, mHeight,
-                                         HAL_PIXEL_FORMAT_RGBA_8888, 1, GRALLOC_USAGE_HW_TEXTURE,
-                                         &mBuffer, &mStride, 0, "EvsCamera");
-        if (result != NO_ERROR) {
-            ALOGE("Error %d allocating %d x %d graphics buffer", result, mWidth, mHeight);
-            return EvsResult::BUFFER_NOT_AVAILABLE;
-        }
-        if (!mBuffer) {
-            ALOGE("We didn't get a buffer handle back from the allocator");
-            return EvsResult::BUFFER_NOT_AVAILABLE;
-        }
-    }
-
     // Start the frame generation thread
     mStreamState = RUNNING;
-    mCaptureThread = std::thread([this](){GenerateFrames();});
+    mCaptureThread = std::thread([this](){ generateFrames(); });
 
     return EvsResult::OK;
 }
 
-Return<EvsResult> EvsCamera::doneWithFrame(uint32_t /* frameId */, const hidl_handle& bufferHandle)  {
+
+Return<void> EvsCamera::doneWithFrame(const BufferDesc& buffer)  {
     ALOGD("doneWithFrame");
-    std::lock_guard<std::mutex> lock(mAccessLock);
-
-    if (!bufferHandle)
-    {
-        ALOGE("ignoring doneWithFrame called with invalid handle");
-        return EvsResult::INVALID_ARG;
-    }
-
-    // TODO:  Track which frames we've delivered and validate this is one of them
-
-    // Mark the frame buffer as available for a new frame
-    mFrameBusy = false;
-
-    // TODO:  If we currently have too many buffers, drop this one
-
-    return EvsResult::OK;
-}
-
-Return<void> EvsCamera::stopVideoStream()  {
-    ALOGD("stopVideoStream");
-
-    bool waitForJoin = false;
-    // Lock scope
-    {
+    {  // lock context
         std::lock_guard <std::mutex> lock(mAccessLock);
 
-        if (mStreamState == RUNNING) {
-            // Tell the GenerateFrames loop we want it to stop
-            mStreamState = STOPPING;
+        if (buffer.memHandle == nullptr) {
+            ALOGE("ignoring doneWithFrame called with null handle");
+        } else if (buffer.bufferId >= mBuffers.size()) {
+            ALOGE("ignoring doneWithFrame called with invalid bufferId %d (max is %lu)",
+                  buffer.bufferId, mBuffers.size()-1);
+        } else if (!mBuffers[buffer.bufferId].inUse) {
+            ALOGE("ignoring doneWithFrame called on frame %d which is already free",
+                  buffer.bufferId);
+        } else {
+            // Mark the frame as available
+            mBuffers[buffer.bufferId].inUse = false;
+            mFramesInUse--;
 
-            // Note that we asked the thread to stop and should wait for it do so
-            waitForJoin = true;
-        }
-    }
-
-    if (waitForJoin) {
-        // Block outside the mutex until the "stop" flag has been acknowledged
-        // NOTE:  We won't send any more frames, but the client might still get one already in flight
-        ALOGD("Waiting for stream thread to end...");
-        mCaptureThread.join();
-
-        // Lock scope
-        {
-            std::lock_guard <std::mutex> lock(mAccessLock);
-            mStreamState = STOPPED;
+            // If this frame's index is high in the array, try to move it down
+            // to improve locality after mFramesAllowed has been reduced.
+            if (buffer.bufferId >= mFramesAllowed) {
+                // Find an empty slot lower in the array (which should always exist in this case)
+                for (auto&& rec : mBuffers) {
+                    if (rec.handle == nullptr) {
+                        rec.handle = mBuffers[buffer.bufferId].handle;
+                        mBuffers[buffer.bufferId].handle = nullptr;
+                        break;
+                    }
+                }
+            }
         }
     }
 
     return Void();
 }
 
+
+Return<void> EvsCamera::stopVideoStream()  {
+    ALOGD("stopVideoStream");
+    std::unique_lock <std::mutex> lock(mAccessLock);
+
+    if (mStreamState == RUNNING) {
+        // Tell the GenerateFrames loop we want it to stop
+        mStreamState = STOPPING;
+
+        // Block outside the mutex until the "stop" flag has been acknowledged
+        // We won't send any more frames, but the client might still get some already in flight
+        ALOGD("Waiting for stream thread to end...");
+        lock.unlock();
+        mCaptureThread.join();
+        lock.lock();
+
+        mStreamState = STOPPED;
+        ALOGD("Stream marked STOPPED.");
+    }
+
+    return Void();
+}
+
+
 Return<int32_t> EvsCamera::getExtendedInfo(uint32_t opaqueIdentifier)  {
     ALOGD("getExtendedInfo");
     std::lock_guard<std::mutex> lock(mAccessLock);
@@ -215,6 +225,7 @@
     return 0;
 }
 
+
 Return<EvsResult> EvsCamera::setExtendedInfo(uint32_t /*opaqueIdentifier*/, int32_t /*opaqueValue*/)  {
     ALOGD("setExtendedInfo");
     std::lock_guard<std::mutex> lock(mAccessLock);
@@ -224,10 +235,124 @@
 }
 
 
-void EvsCamera::GenerateFrames() {
-    ALOGD("Frame generate loop started");
+bool EvsCamera::setAvailableFrames_Locked(unsigned bufferCount) {
+    if (bufferCount < 1) {
+        ALOGE("Ignoring request to set buffer count to zero");
+        return false;
+    }
+    if (bufferCount > MAX_BUFFERS_IN_FLIGHT) {
+        ALOGE("Rejecting buffer request in excess of internal limit");
+        return false;
+    }
 
-    uint32_t frameNumber;
+    // Is an increase required?
+    if (mFramesAllowed < bufferCount) {
+        // An increase is required
+        unsigned needed = bufferCount - mFramesAllowed;
+        ALOGI("Allocating %d buffers for camera frames", needed);
+
+        unsigned added = increaseAvailableFrames_Locked(needed);
+        if (added != needed) {
+            // If we didn't add all the frames we needed, then roll back to the previous state
+            ALOGE("Rolling back to previous frame queue size");
+            decreaseAvailableFrames_Locked(added);
+            return false;
+        }
+    } else if (mFramesAllowed > bufferCount) {
+        // A decrease is required
+        unsigned framesToRelease = mFramesAllowed - bufferCount;
+        ALOGI("Returning %d camera frame buffers", framesToRelease);
+
+        unsigned released = decreaseAvailableFrames_Locked(framesToRelease);
+        if (released != framesToRelease) {
+            // This shouldn't happen with a properly behaving client because the client
+            // should only make this call after returning sufficient outstanding buffers
+            // to allow a clean resize.
+            ALOGE("Buffer queue shrink failed -- too many buffers currently in use?");
+        }
+    }
+
+    return true;
+}
+
+
+unsigned EvsCamera::increaseAvailableFrames_Locked(unsigned numToAdd) {
+    // Acquire the graphics buffer allocator
+    GraphicBufferAllocator &alloc(GraphicBufferAllocator::get());
+
+    unsigned added = 0;
+
+    while (added < numToAdd) {
+        buffer_handle_t memHandle = nullptr;
+        status_t result = alloc.allocate(mWidth, mHeight,
+                                         mFormat, 1,
+                                         mUsage,
+                                         &memHandle, &mStride, 0, "EvsCamera");
+        if (result != NO_ERROR) {
+            ALOGE("Error %d allocating %d x %d graphics buffer", result, mWidth, mHeight);
+            break;
+        }
+        if (!memHandle) {
+            ALOGE("We didn't get a buffer handle back from the allocator");
+            break;
+        }
+
+        // Find a place to store the new buffer
+        bool stored = false;
+        for (auto&& rec : mBuffers) {
+            if (rec.handle == nullptr) {
+                // Use this existing entry
+                rec.handle = memHandle;
+                rec.inUse = false;
+                stored = true;
+                break;
+            }
+        }
+        if (!stored) {
+            // Add a BufferRecord wrapping this handle to our set of available buffers
+            mBuffers.emplace_back(memHandle);
+        }
+
+        mFramesAllowed++;
+        added++;
+    }
+
+    return added;
+}
+
+
+unsigned EvsCamera::decreaseAvailableFrames_Locked(unsigned numToRemove) {
+    // Acquire the graphics buffer allocator
+    GraphicBufferAllocator &alloc(GraphicBufferAllocator::get());
+
+    unsigned removed = 0;
+
+    for (auto&& rec : mBuffers) {
+        // Is this record not in use, but holding a buffer that we can free?
+        if ((rec.inUse == false) && (rec.handle != nullptr)) {
+            // Release buffer and update the record so we can recognize it as "empty"
+            alloc.free(rec.handle);
+            rec.handle = nullptr;
+
+            mFramesAllowed--;
+            removed++;
+
+            if (removed == numToRemove) {
+                break;
+            }
+        }
+    }
+
+    return removed;
+}
+
+
+// This is the asynchronous frame generation thread that runs in parallel with the
+// main serving thread.  There is one for each active camera instance.
+void EvsCamera::generateFrames() {
+    ALOGD("Frame generation loop started");
+
+    unsigned idx;
 
     while (true) {
         bool timeForFrame = false;
@@ -235,57 +360,69 @@
         {
             std::lock_guard<std::mutex> lock(mAccessLock);
 
-            // Tick the frame counter -- rollover is tolerated
-            frameNumber = mFrameId++;
-
             if (mStreamState != RUNNING) {
                 // Break out of our main thread loop
                 break;
             }
 
-            if (mFrameBusy) {
+            // Are we allowed to issue another buffer?
+            if (mFramesInUse >= mFramesAllowed) {
                 // Can't do anything right now -- skip this frame
-                ALOGW("Skipped a frame because client hasn't returned a buffer\n");
-            }
-            else {
-                // We're going to make the frame busy
-                mFrameBusy = true;
-                timeForFrame = true;
+                ALOGW("Skipped a frame because too many are in flight\n");
+            } else {
+                // Identify an available buffer to fill
+                for (idx = 0; idx < mBuffers.size(); idx++) {
+                    if (!mBuffers[idx].inUse) {
+                        if (mBuffers[idx].handle != nullptr) {
+                            // Found an available record, so stop looking
+                            break;
+                        }
+                    }
+                }
+                if (idx >= mBuffers.size()) {
+                    // This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed
+                    ALOGE("Failed to find an available buffer slot\n");
+                } else {
+                    // We're going to make the frame busy
+                    mBuffers[idx].inUse = true;
+                    mFramesInUse++;
+                    timeForFrame = true;
+                }
             }
         }
 
         if (timeForFrame) {
-            // Lock our output buffer for writing
-            uint32_t *pixels = nullptr;
-            GraphicBufferMapper &mapper = GraphicBufferMapper::get();
-            mapper.lock(mBuffer,
-                        GRALLOC_USAGE_SW_WRITE_OFTEN,
-                        android::Rect(mWidth, mHeight),
-                        (void **) &pixels);
+            // Assemble the buffer description we'll transmit below
+            BufferDesc buff = {};
+            buff.width      = mWidth;
+            buff.height     = mHeight;
+            buff.stride     = mStride;
+            buff.format     = mFormat;
+            buff.usage      = mUsage;
+            buff.bufferId   = idx;
+            buff.memHandle  = mBuffers[idx].handle;
 
-            // If we failed to lock the pixel buffer, we're about to crash, but log it first
-            if (!pixels) {
-                ALOGE("Camera failed to gain access to image buffer for writing");
+            // Write test data into the image buffer
+            fillTestFrame(buff);
+
+            // Issue the (asynchronous) callback to the client -- can't be holding the lock
+            auto result = mStream->deliverFrame(buff);
+            if (result.isOk()) {
+                ALOGD("Delivered %p as id %d", buff.memHandle.getNativeHandle(), buff.bufferId);
+            } else {
+                // This can happen if the client dies and is likely unrecoverable.
+                // To avoid consuming resources generating failing calls, we stop sending
+                // frames.  Note, however, that the stream remains in the "STREAMING" state
+                // until cleaned up on the main thread.
+                ALOGE("Frame delivery call failed in the transport layer.");
+
+                // Since we didn't actually deliver it, mark the frame as available
+                std::lock_guard<std::mutex> lock(mAccessLock);
+                mBuffers[idx].inUse = false;
+                mFramesInUse--;
+
+                break;
             }
-
-            // Fill in the test pixels
-            for (unsigned row = 0; row < mHeight; row++) {
-                for (unsigned col = 0; col < mWidth; col++) {
-                    // Index into the row to set the pixel at this column
-                    // (We're making vertical gradient in the green channel, and
-                    // horitzontal gradient in the blue channel)
-                    pixels[col] = 0xFF0000FF | ((row & 0xFF) << 16) | ((col & 0xFF) << 8);
-                }
-                // Point to the next row
-                pixels = pixels + (mStride / sizeof(*pixels));
-            }
-
-            // Release our output buffer
-            mapper.unlock(mBuffer);
-
-            // Issue the (asynchronous) callback to the client
-            mStream->deliverFrame(frameNumber, mBuffer);
-            ALOGD("Delivered %p as frame %d", mBuffer, frameNumber);
         }
 
         // We arbitrarily choose to generate frames at 10 fps (1/10 * uSecPerSec)
@@ -293,11 +430,58 @@
     }
 
     // If we've been asked to stop, send one last NULL frame to signal the actual end of stream
-    mStream->deliverFrame(frameNumber, nullptr);
+    BufferDesc nullBuff = {};
+    auto result = mStream->deliverFrame(nullBuff);
+    if (!result.isOk()) {
+        ALOGE("Error delivering end of stream marker");
+    }
 
     return;
 }
 
+
+void EvsCamera::fillTestFrame(BufferDesc buff) {
+    // Lock our output buffer for writing
+    uint32_t *pixels = nullptr;
+    GraphicBufferMapper &mapper = GraphicBufferMapper::get();
+    mapper.lock(buff.memHandle,
+                GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER,
+                android::Rect(buff.width, buff.height),
+                (void **) &pixels);
+
+    // If we failed to lock the pixel buffer, we're about to crash, but log it first
+    if (!pixels) {
+        ALOGE("Camera failed to gain access to image buffer for writing");
+    }
+
+    // Fill in the test pixels
+    for (unsigned row = 0; row < buff.height; row++) {
+        for (unsigned col = 0; col < buff.width; col++) {
+            // Index into the row to check the pixel at this column.
+            // We expect 0xFF in the LSB channel, a vertical gradient in the
+            // second channel, a horitzontal gradient in the third channel, and
+            // 0xFF in the MSB.
+            // The exception is the very first 32 bits which is used for the
+            // time varying frame signature to avoid getting fooled by a static image.
+            uint32_t expectedPixel = 0xFF0000FF           | // MSB and LSB
+                                     ((row & 0xFF) <<  8) | // vertical gradient
+                                     ((col & 0xFF) << 16);  // horizontal gradient
+            if ((row | col) == 0) {
+                static uint32_t sFrameTicker = 0;
+                expectedPixel = (sFrameTicker) & 0xFF;
+                sFrameTicker++;
+            }
+            pixels[col] = expectedPixel;
+        }
+        // Point to the next row
+        pixels = pixels + (buff.stride / sizeof(*pixels));
+    }
+
+    // Release our output buffer
+    mapper.unlock(buff.memHandle);
+}
+
+
 } // namespace implementation
 } // namespace V1_0
 } // namespace evs
diff --git a/evs/1.0/default/EvsCamera.h b/evs/1.0/default/EvsCamera.h
index 5d29125..8d644a0 100644
--- a/evs/1.0/default/EvsCamera.h
+++ b/evs/1.0/default/EvsCamera.h
@@ -23,19 +23,21 @@
 
 #include <thread>
 
+
 namespace android {
 namespace hardware {
 namespace evs {
 namespace V1_0 {
 namespace implementation {
 
+
 class EvsCamera : public IEvsCamera {
 public:
     // Methods from ::android::hardware::evs::V1_0::IEvsCamera follow.
     Return<void> getId(getId_cb id_cb)  override;
     Return<EvsResult> setMaxFramesInFlight(uint32_t bufferCount)  override;
     Return<EvsResult> startVideoStream(const ::android::sp<IEvsCameraStream>& stream) override;
-    Return<EvsResult> doneWithFrame(uint32_t frameId, const hidl_handle& bufferHandle)  override;
+    Return<void> doneWithFrame(const BufferDesc& buffer)  override;
     Return<void> stopVideoStream()  override;
     Return<int32_t> getExtendedInfo(uint32_t opaqueIdentifier)  override;
     Return<EvsResult> setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue)  override;
@@ -45,34 +47,49 @@
     virtual ~EvsCamera() override;
 
     const CameraDesc& getDesc() { return mDescription; };
-    void GenerateFrames();
 
     static const char kCameraName_Backup[];
     static const char kCameraName_RightTurn[];
 
 private:
-    CameraDesc              mDescription = {};  // The properties of this camera
+    // These three functions are expected to be called while mAccessLock is held
+    bool     setAvailableFrames_Locked(unsigned bufferCount);
+    unsigned increaseAvailableFrames_Locked(unsigned numToAdd);
+    unsigned decreaseAvailableFrames_Locked(unsigned numToRemove);
 
-    buffer_handle_t         mBuffer = nullptr;  // A graphics buffer into which we'll store images
-    uint32_t                mWidth  = 0;        // number of pixels across the buffer
-    uint32_t                mHeight = 0;        // number of pixels vertically in the buffer
-    uint32_t                mStride = 0;        // Bytes per line in the buffer
+    void generateFrames();
+    void fillTestFrame(BufferDesc buff);
 
-    sp<IEvsCameraStream>    mStream = nullptr;  // The callback the user expects when a frame is ready
+    CameraDesc                  mDescription = {};  // The properties of this camera
 
-    std::thread             mCaptureThread;     // The thread we'll use to synthesize frames
+    std::thread                 mCaptureThread;     // The thread we'll use to synthesize frames
 
-    uint32_t                mFrameId;           // A frame counter used to identify specific frames
+    uint32_t                    mWidth  = 0;        // Horizontal pixel count in the buffers
+    uint32_t                    mHeight = 0;        // Vertical pixel count in the buffers
+    uint32_t                    mFormat = 0;        // Values from android_pixel_format_t [TODO: YUV?  Leave opaque?]
+    uint32_t                    mUsage  = 0;        // Values from from Gralloc.h
+    uint32_t                    mStride = 0;        // Bytes per line in the buffers
+
+    sp<IEvsCameraStream>        mStream = nullptr;  // The callback used to deliver each frame
+
+    struct BufferRecord {
+        buffer_handle_t     handle;
+        bool                inUse;
+        explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false) {};
+    };
+    std::vector<BufferRecord>   mBuffers;           // Graphics buffers to transfer images
+    unsigned                    mFramesAllowed;     // How many buffers are we currently using
+    unsigned                    mFramesInUse;       // How many buffers are currently outstanding
 
     enum StreamStateValues {
         STOPPED,
         RUNNING,
         STOPPING,
     };
-    StreamStateValues       mStreamState;
-    bool                    mFrameBusy;         // A flag telling us our one buffer is in use
+    StreamStateValues           mStreamState;
 
-    std::mutex              mAccessLock;
+    // Syncrhonization necessary to deconflict mCaptureThread from the main service thread
+    std::mutex                  mAccessLock;
 };
 
 } // namespace implementation
diff --git a/evs/1.0/default/EvsDisplay.cpp b/evs/1.0/default/EvsDisplay.cpp
index 9dba6fc..dff4c49 100644
--- a/evs/1.0/default/EvsDisplay.cpp
+++ b/evs/1.0/default/EvsDisplay.cpp
@@ -39,6 +39,7 @@
     ALOGD("EvsDisplay instantiated");
 
     // Set up our self description
+    // NOTE:  These are arbitrary values chosen for testing
     mInfo.displayId             = "Mock Display";
     mInfo.vendorFlags           = 3870;
     mInfo.defaultHorResolution  = 320;
@@ -50,16 +51,17 @@
     ALOGD("EvsDisplay being destroyed");
     std::lock_guard<std::mutex> lock(mAccessLock);
 
-    // Report if we're going away while a buffer is outstanding.  This could be bad.
+    // Report if we're going away while a buffer is outstanding
     if (mFrameBusy) {
-        ALOGE("EvsDisplay going down while client is holding a buffer\n");
+        ALOGE("EvsDisplay going down while client is holding a buffer");
     }
 
     // Make sure we release our frame buffer
-    if (mBuffer) {
+    if (mBuffer.memHandle) {
         // Drop the graphics buffer we've been using
         GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
-        alloc.free(mBuffer);
+        alloc.free(mBuffer.memHandle);
+        mBuffer.memHandle = nullptr;
     }
     ALOGD("EvsDisplay destroyed");
 }
@@ -135,36 +137,60 @@
     std::lock_guard<std::mutex> lock(mAccessLock);
 
     // If we don't already have a buffer, allocate one now
-    if (!mBuffer) {
+    if (!mBuffer.memHandle) {
+        // Assemble the buffer description we'll use for our render target
+        mBuffer.width       = mInfo.defaultHorResolution;
+        mBuffer.height      = mInfo.defaultVerResolution;
+        mBuffer.format      = HAL_PIXEL_FORMAT_RGBA_8888;
+        mBuffer.usage       = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER;
+        mBuffer.bufferId    = 0x3870;  // Arbitrary magic number for self recognition
+
+        buffer_handle_t handle = nullptr;
         GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
-        status_t result = alloc.allocate(mInfo.defaultHorResolution, mInfo.defaultVerResolution,
-                                         HAL_PIXEL_FORMAT_RGBA_8888, 1,
-                                         GRALLOC_USAGE_HW_FB | GRALLOC_USAGE_HW_COMPOSER,
-                                         &mBuffer, &mStride, 0, "EvsDisplay");
+        status_t result = alloc.allocate(mBuffer.width, mBuffer.height,
+                                         mBuffer.format, 1, mBuffer.usage,
+                                         &handle, &mBuffer.stride,
+                                         0, "EvsDisplay");
+        if (result != NO_ERROR) {
+            ALOGE("Error %d allocating %d x %d graphics buffer",
+                  result, mBuffer.width, mBuffer.height);
+            BufferDesc nullBuff = {};
+            _hidl_cb(nullBuff);
+            return Void();
+        }
+        if (!handle) {
+            ALOGE("We didn't get a buffer handle back from the allocator");
+            BufferDesc nullBuff = {};
+            _hidl_cb(nullBuff);
+            return Void();
+        }
+
+        mBuffer.memHandle = handle;
         mFrameBusy = false;
-        ALOGD("Allocated new buffer %p with stride %u", mBuffer, mStride);
+        ALOGD("Allocated new buffer %p with stride %u",
+              mBuffer.memHandle.getNativeHandle(), mStride);
     }
 
     // Do we have a frame available?
     if (mFrameBusy) {
         // This means either we have a 2nd client trying to compete for buffers
         // (an unsupported mode of operation) or else the client hasn't returned
-        // a previously issues buffer yet (they're behaving badly).
-        // NOTE:  We have to make callback even if we have nothing to provide
+        // a previously issued buffer yet (they're behaving badly).
+        // NOTE:  We have to make the callback even if we have nothing to provide
         ALOGE("getTargetBuffer called while no buffers available.");
-        _hidl_cb(nullptr);
-    }
-    else {
+        BufferDesc nullBuff = {};
+        _hidl_cb(nullBuff);
+        return Void();
+    } else {
         // Mark our buffer as busy
         mFrameBusy = true;
 
         // Send the buffer to the client
-        ALOGD("Providing display buffer %p", mBuffer);
+        ALOGD("Providing display buffer handle %p as id %d",
+              mBuffer.memHandle.getNativeHandle(), mBuffer.bufferId);
         _hidl_cb(mBuffer);
+        return Void();
     }
-
-    // All done
-    return Void();
 }
 
 
@@ -172,22 +198,19 @@
  * This call tells the display that the buffer is ready for display.
  * The buffer is no longer valid for use by the client after this call.
  */
-Return<EvsResult> EvsDisplay::returnTargetBufferForDisplay(const hidl_handle& bufferHandle)  {
-    ALOGD("returnTargetBufferForDisplay %p", bufferHandle.getNativeHandle());
+Return<EvsResult> EvsDisplay::returnTargetBufferForDisplay(const BufferDesc& buffer)  {
+    ALOGD("returnTargetBufferForDisplay %p", buffer.memHandle.getNativeHandle());
     std::lock_guard<std::mutex> lock(mAccessLock);
 
-    // This shouldn't happen if we haven't issued the buffer!
-    if (!bufferHandle) {
+    // Nobody should call us with a null handle
+    if (!buffer.memHandle.getNativeHandle()) {
         ALOGE ("returnTargetBufferForDisplay called without a valid buffer handle.\n");
         return EvsResult::INVALID_ARG;
     }
-    /* TODO(b/33492405): It would be nice to validate we got back the buffer we expect,
-     * but HIDL doesn't support that (yet?)
-    if (bufferHandle != mBuffer) {
+    if (buffer.bufferId != mBuffer.bufferId) {
         ALOGE ("Got an unrecognized frame returned.\n");
         return EvsResult::INVALID_ARG;
     }
-    */
     if (!mFrameBusy) {
         ALOGE ("A frame was returned with no outstanding frames.\n");
         return EvsResult::BUFFER_NOT_AVAILABLE;
@@ -204,10 +227,71 @@
     if (mRequestedState != DisplayState::VISIBLE) {
         // We shouldn't get frames back when we're not visible.
         ALOGE ("Got an unexpected frame returned while not visible - ignoring.\n");
-    }
-    else {
-        // Make this buffer visible
-        // TODO:  Add code to put this image on the screen (or validate it somehow?)
+    } else {
+        // This is where the buffer would be made visible.
+        // For now we simply validate it has the data we expect in it by reading it back
+
+        // Lock our display buffer for reading
+        uint32_t* pixels = nullptr;
+        GraphicBufferMapper &mapper = GraphicBufferMapper::get();
+        mapper.lock(mBuffer.memHandle,
+                    GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER,
+                    android::Rect(mBuffer.width, mBuffer.height),
+                    (void **)&pixels);
+
+        // If we failed to lock the pixel buffer, we're about to crash, but log it first
+        if (!pixels) {
+            ALOGE("Camera failed to gain access to image buffer for reading");
+        }
+
+        // Check the test pixels
+        bool frameLooksGood = true;
+        for (unsigned row = 0; row < mInfo.defaultVerResolution; row++) {
+            for (unsigned col = 0; col < mInfo.defaultHorResolution; col++) {
+                // Index into the row to check the pixel at this column.
+                // We expect 0xFF in the LSB channel, a vertical gradient in the
+                // second channel, a horitzontal gradient in the third channel, and
+                // 0xFF in the MSB.
+                // The exception is the very first 32 bits which is used for the
+                // time varying frame signature to avoid getting fooled by a static image.
+                uint32_t expectedPixel = 0xFF0000FF           | // MSB and LSB
+                                         ((row & 0xFF) <<  8) | // vertical gradient
+                                         ((col & 0xFF) << 16);  // horizontal gradient
+                if ((row | col) == 0) {
+                    // we'll check the "uniqueness" of the frame signature below
+                    continue;
+                }
+                // Walk across this row (we'll step rows below)
+                if (pixels[col] != expectedPixel) {
+                    ALOGE("Pixel check mismatch in frame buffer");
+                    frameLooksGood = false;
+                    break;
+                }
+            }
+
+            if (!frameLooksGood) {
+                break;
+            }
+
+            // Point to the next row
+            pixels = pixels + (mStride / sizeof(*pixels));
+        }
+
+        // Ensure we don't see the same buffer twice without it being rewritten
+        static uint32_t prevSignature = ~0;
+        uint32_t signature = pixels[0] & 0xFF;
+        if (prevSignature == signature) {
+            frameLooksGood = false;
+            ALOGE("Duplicate, likely stale frame buffer detected");
+        }
+
+
+        // Release our output buffer
+        mapper.unlock(mBuffer.memHandle);
+
+        if (!frameLooksGood) {
+            return EvsResult::UNDERLYING_SERVICE_ERROR;
+        }
     }
 
     return EvsResult::OK;
diff --git a/evs/1.0/default/EvsDisplay.h b/evs/1.0/default/EvsDisplay.h
index a2d5d3e..6e0111e 100644
--- a/evs/1.0/default/EvsDisplay.h
+++ b/evs/1.0/default/EvsDisplay.h
@@ -33,7 +33,7 @@
     Return<EvsResult> setDisplayState(DisplayState state)  override;
     Return<DisplayState> getDisplayState()  override;
     Return<void> getTargetBuffer(getTargetBuffer_cb _hidl_cb)  override;
-    Return<EvsResult> returnTargetBufferForDisplay(const hidl_handle& bufferHandle)  override;
+    Return<EvsResult> returnTargetBufferForDisplay(const BufferDesc& buffer)  override;
 
     // Implementation details
     EvsDisplay();
@@ -41,10 +41,10 @@
 
 private:
     DisplayDesc     mInfo           = {};
-    buffer_handle_t mBuffer         = nullptr;      // A graphics buffer into which we'll store images
-    uint32_t        mStride         = 0;            // Bytes per line in the buffer
+    BufferDesc      mBuffer         = {};       // A graphics buffer into which we'll store images
+    uint32_t        mStride         = 0;        // Bytes per line in the buffer
 
-    bool            mFrameBusy      = false;        // A flag telling us our buffer is in use
+    bool            mFrameBusy      = false;    // A flag telling us our buffer is in use
     DisplayState    mRequestedState = DisplayState::NOT_VISIBLE;
 
     std::mutex      mAccessLock;
diff --git a/evs/1.0/default/EvsEnumerator.cpp b/evs/1.0/default/EvsEnumerator.cpp
index ba8da00..4bf77f3 100644
--- a/evs/1.0/default/EvsEnumerator.cpp
+++ b/evs/1.0/default/EvsEnumerator.cpp
@@ -27,6 +27,11 @@
 namespace implementation {
 
 
+// TODO(b/31632518):  Need to get notification when our client dies so we can close the camera.
+// As it stands, if the client dies suddenly, the camera will be stuck "open".
+// NOTE:  Display should already be safe by virtue of holding only a weak pointer.
+
+
 EvsEnumerator::EvsEnumerator() {
     ALOGD("EvsEnumerator created");
 
@@ -78,15 +83,11 @@
     if (!pRecord) {
         ALOGE("Requested camera %s not found", cameraId.c_str());
         return nullptr;
-    }
-    else if (pRecord->inUse) {
+    } else if (pRecord->inUse) {
         ALOGE("Cannot open camera %s which is already in use", cameraId.c_str());
         return nullptr;
-    }
-    else {
-        /* TODO(b/33492405):  Do this, When HIDL can give us back a recognizable pointer
+    } else {
         pRecord->inUse = true;
-         */
         return(pRecord->pCamera);
     }
 }
@@ -96,14 +97,21 @@
 
     if (camera == nullptr) {
         ALOGE("Ignoring call to closeCamera with null camera pointer");
-    }
-    else {
-        // Make sure the camera has stopped streaming
-        camera->stopVideoStream();
+    } else {
+        // Find this camera in our list
+        auto it = std::find_if(mCameraList.begin(),
+                               mCameraList.end(),
+                               [camera](const CameraRecord& rec) {
+                                   return (rec.pCamera == camera);
+                               });
+        if (it == mCameraList.end()) {
+            ALOGE("Ignoring close on unrecognized camera");
+        } else {
+            // Make sure the camera has stopped streaming
+            camera->stopVideoStream();
 
-        /* TODO(b/33492405):  Do this, When HIDL can give us back a recognizable pointer
-        pRecord->inUse = false;
-         */
+            it->inUse = false;
+        }
     }
 
     return Void();
@@ -113,41 +121,49 @@
     ALOGD("openDisplay");
 
     // If we already have a display active, then this request must be denied
-    if (mActiveDisplay != nullptr) {
+    sp<IEvsDisplay> pActiveDisplay = mActiveDisplay.promote();
+    if (pActiveDisplay != nullptr) {
         ALOGW("Rejecting openDisplay request the display is already in use.");
         return nullptr;
-    }
-    else {
+    } else {
         // Create a new display interface and return it
-        mActiveDisplay = new EvsDisplay();
-        ALOGD("Returning new EvsDisplay object %p", mActiveDisplay.get());
-        return mActiveDisplay;
+        pActiveDisplay = new EvsDisplay();
+        mActiveDisplay = pActiveDisplay;
+        ALOGD("Returning new EvsDisplay object %p", pActiveDisplay.get());
+        return pActiveDisplay;
     }
 }
 
 Return<void> EvsEnumerator::closeDisplay(const ::android::sp<IEvsDisplay>& display) {
     ALOGD("closeDisplay");
 
-    if (mActiveDisplay == nullptr) {
-        ALOGE("Ignoring closeDisplay when display is not active");
-    }
-    else if (display == nullptr) {
-        ALOGE("Ignoring closeDisplay with null display pointer");
-    }
-    else {
+    // Do we still have a display object we think should be active?
+    sp<IEvsDisplay> pActiveDisplay = mActiveDisplay.promote();
+
+    if (pActiveDisplay == nullptr) {
+        ALOGE("Ignoring closeDisplay when there is no active display.");
+    } else if (display != pActiveDisplay) {
+        ALOGE("Ignoring closeDisplay on a display we didn't issue");
+        ALOGI("Got %p while active display is %p.", display.get(), pActiveDisplay.get());
+    } else {
         // Drop the active display
-        // TODO(b/33492405):  When HIDL provides recognizable pointers, add validation here.
         mActiveDisplay = nullptr;
     }
 
     return Void();
 }
 
+Return<DisplayState> EvsEnumerator::getDisplayState()  {
+    ALOGD("getDisplayState");
 
-// TODO(b/31632518):  Need to get notification when our client dies so we can close the camera.
-// As possible work around would be to give the client a HIDL object to exclusively hold
-// and use it's destructor to perform some work in the server side.
-
+    // Do we still have a display object we think should be active?
+    sp<IEvsDisplay> pActiveDisplay = mActiveDisplay.promote();
+    if (pActiveDisplay != nullptr) {
+        return pActiveDisplay->getDisplayState();
+    } else {
+        return DisplayState::NOT_OPEN;
+    }
+}
 
 } // namespace implementation
 } // namespace V1_0
diff --git a/evs/1.0/default/EvsEnumerator.h b/evs/1.0/default/EvsEnumerator.h
index 90df837..0e719bd 100644
--- a/evs/1.0/default/EvsEnumerator.h
+++ b/evs/1.0/default/EvsEnumerator.h
@@ -38,6 +38,7 @@
     Return<void> closeCamera(const ::android::sp<IEvsCamera>& carCamera)  override;
     Return<sp<IEvsDisplay>> openDisplay()  override;
     Return<void> closeDisplay(const ::android::sp<IEvsDisplay>& display)  override;
+    Return<DisplayState> getDisplayState()  override;
 
     // Implementation details
     EvsEnumerator();
@@ -50,7 +51,7 @@
     };
     std::list<CameraRecord> mCameraList;
 
-    sp<IEvsDisplay>         mActiveDisplay;
+    wp<IEvsDisplay>         mActiveDisplay; // Weak pointer -> object destructs if client dies
 };
 
 } // namespace implementation
diff --git a/evs/1.0/types.hal b/evs/1.0/types.hal
index fd9dcdc..6b580cf 100644
--- a/evs/1.0/types.hal
+++ b/evs/1.0/types.hal
@@ -72,6 +72,29 @@
 
 
 /*
+ * Structure representing an image buffer through our APIs
+ *
+ * In addition to the handle to the graphics memory, we need to retain
+ * the properties of the buffer for easy reference and reconstruction of
+ * an ANativeWindowBuffer object on the remote side of API calls.
+ * (Not least because OpenGL expect an ANativeWindowBuffer* for us as a
+ * texture via eglCreateImageKHR().
+ * See also related types from android.hardware.graphics.common
+ * TODO:  b/34722508  Review details of interaction of this structure with gralloc and OpenGL.
+ *        Specifically consider if format and/or usage should become enumerated types.
+ */
+struct BufferDesc {
+    uint32_t    width;      // Units of pixels
+    uint32_t    height;     // Units of pixels
+    uint32_t    stride;     // Units of bytes
+    uint32_t    format;     // May contain values from android_pixel_format_t
+    uint32_t    usage;      // May contain values from from Gralloc.h
+    uint32_t    bufferId;   // Opaque value from driver
+    handle      memHandle;  // gralloc memory buffer handle
+};
+
+
+/*
  * States for control of the EVS display
  *
  * The DisplayInfo structure describes the basic properties of an EVS display. Any EVS
@@ -81,7 +104,8 @@
  * presentation device.
  */
 enum DisplayState : uint32_t {
-    NOT_VISIBLE = 0,        // Display is inhibited
+    NOT_OPEN = 0,           // Display has not been requested by any application
+    NOT_VISIBLE,            // Display is inhibited
     VISIBLE_ON_NEXT_FRAME,  // Will become visible with next frame
     VISIBLE,                // Display is currently active
     NUM_STATES              // Must be last
@@ -94,4 +118,5 @@
     INVALID_ARG,
     STREAM_ALREADY_RUNNING,
     BUFFER_NOT_AVAILABLE,
+    UNDERLYING_SERVICE_ERROR,
 };
\ No newline at end of file