OSDN Git Service

Harden BufferHubQueueProducerTest
authorJiwen 'Steve' Cai <jwcai@google.com>
Fri, 24 Mar 2017 21:03:06 +0000 (14:03 -0700)
committerJiwen 'Steve' Cai <jwcai@google.com>
Mon, 3 Apr 2017 20:37:46 +0000 (13:37 -0700)
This is a test that covers our implementation of bufferhubqueue-based
IGraphicBufferProducer, which eventually enables VrCore/VrFlinger to
access buffers produced from other Android compontents. Note that the
gtest itself is a (slightly) modified version of
IGraphicBufferProducer_test.cpp.

This also improves BufferHubQueue in the following way to support more
features of android::BufferQueue.
1/ Supports more buffer queue parameters that we query.
2/ BufferHubQueue (the PDX client) now supports default width, height,
and format configuration.
3/ Change default max_dequeue_buffer_count to 1 (which is the same
behavior current android::BufferQueue adopts).
4/ Fill in |QueueBufferOutput| during |enqueueBuffer|.

Bug: 34197998
Bug: 36266201
Test: build and run buffer_hub_queue_producer-test
Change-Id: I9d3e8bb66dbfb66e67ab7b0e5093e49a3f374e9c

libs/vr/libbufferhubqueue/buffer_hub_queue_core.cpp
libs/vr/libbufferhubqueue/buffer_hub_queue_producer.cpp
libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_core.h
libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_producer.h
libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp

index a108042..a826a69 100644 (file)
@@ -50,27 +50,7 @@ status_t BufferHubQueueCore::AllocateBuffer(uint32_t width, uint32_t height,
   LOG_ALWAYS_FATAL_IF(buffer_producer == nullptr,
                       "Failed to get buffer producer at slot: %zu", slot);
 
-  // Allocating a new buffer, |buffers_[slot]| should be in initial state.
-  LOG_ALWAYS_FATAL_IF(buffers_[slot].mGraphicBuffer != nullptr,
-                      "AllocateBuffer: slot %zu is not empty.", slot);
-
-  // Create new GraphicBuffer based on the newly created |buffer_producer|. Here
-  // we have to cast |buffer_handle_t| to |native_handle_t|, it's OK because
-  // internally, GraphicBuffer is still an |ANativeWindowBuffer| and |handle|
-  // is still type of |buffer_handle_t| and bears const property.
-  sp<GraphicBuffer> graphic_buffer(new GraphicBuffer(
-      buffer_producer->width(), buffer_producer->height(),
-      buffer_producer->format(),
-      1, /* layer count */
-      buffer_producer->usage(),
-      buffer_producer->stride(),
-      const_cast<native_handle_t*>(buffer_producer->buffer()->handle()),
-      false));
-
-  LOG_ALWAYS_FATAL_IF(NO_ERROR != graphic_buffer->initCheck(),
-                      "Failed to init GraphicBuffer.");
   buffers_[slot].mBufferProducer = buffer_producer;
-  buffers_[slot].mGraphicBuffer = graphic_buffer;
 
   return NO_ERROR;
 }
index ddf7fd2..3fe7642 100644 (file)
@@ -8,7 +8,7 @@ namespace dvr {
 
 BufferHubQueueProducer::BufferHubQueueProducer(
     const std::shared_ptr<BufferHubQueueCore>& core)
-    : core_(core), req_buffer_count_(kInvalidBufferCount) {}
+    : core_(core) {}
 
 status_t BufferHubQueueProducer::requestBuffer(int slot,
                                                sp<GraphicBuffer>* buf) {
@@ -16,18 +16,48 @@ status_t BufferHubQueueProducer::requestBuffer(int slot,
 
   std::unique_lock<std::mutex> lock(core_->mutex_);
 
-  if (slot < 0 || slot >= req_buffer_count_) {
+  if (core_->connected_api_ == BufferHubQueueCore::kNoConnectedApi) {
+    ALOGE("requestBuffer: BufferHubQueueProducer has no connected producer");
+    return NO_INIT;
+  }
+
+  if (slot < 0 || slot >= max_buffer_count_) {
     ALOGE("requestBuffer: slot index %d out of range [0, %d)", slot,
-          req_buffer_count_);
+          max_buffer_count_);
     return BAD_VALUE;
   } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
     ALOGE("requestBuffer: slot %d is not owned by the producer (state = %s)",
           slot, core_->buffers_[slot].mBufferState.string());
     return BAD_VALUE;
+  } else if (core_->buffers_[slot].mGraphicBuffer != nullptr) {
+    ALOGE("requestBuffer: slot %d is not empty.", slot);
+    return BAD_VALUE;
+  } else if (core_->buffers_[slot].mBufferProducer == nullptr) {
+    ALOGE("requestBuffer: slot %d is not dequeued.", slot);
+    return BAD_VALUE;
   }
 
+  const auto& buffer_producer = core_->buffers_[slot].mBufferProducer;
+
+  // Create new GraphicBuffer based on the newly created |buffer_producer|. Here
+  // we have to cast |buffer_handle_t| to |native_handle_t|, it's OK because
+  // internally, GraphicBuffer is still an |ANativeWindowBuffer| and |handle|
+  // is still type of |buffer_handle_t| and bears const property.
+  sp<GraphicBuffer> graphic_buffer(new GraphicBuffer(
+      buffer_producer->width(), buffer_producer->height(),
+      buffer_producer->format(),
+      1, /* layer count */
+      buffer_producer->usage(),
+      buffer_producer->stride(),
+      const_cast<native_handle_t*>(buffer_producer->buffer()->handle()),
+      false));
+
+  LOG_ALWAYS_FATAL_IF(NO_ERROR != graphic_buffer->initCheck(),
+                      "Failed to init GraphicBuffer.");
+  core_->buffers_[slot].mGraphicBuffer = graphic_buffer;
   core_->buffers_[slot].mRequestBufferCalled = true;
-  *buf = core_->buffers_[slot].mGraphicBuffer;
+
+  *buf = graphic_buffer;
   return NO_ERROR;
 }
 
@@ -46,30 +76,68 @@ status_t BufferHubQueueProducer::setMaxDequeuedBufferCount(
     return BAD_VALUE;
   }
 
-  req_buffer_count_ = max_dequeued_buffers;
+  // The new dequeued_buffers count should not be violated by the number
+  // of currently dequeued buffers.
+  int dequeued_count = 0;
+  for (const auto& buf : core_->buffers_) {
+    if (buf.mBufferState.isDequeued()) {
+      dequeued_count++;
+    }
+  }
+  if (dequeued_count > max_dequeued_buffers) {
+    ALOGE(
+        "setMaxDequeuedBufferCount: the requested dequeued_buffers"
+        "count (%d) exceeds the current dequeued buffer count (%d)",
+        max_dequeued_buffers, dequeued_count);
+    return BAD_VALUE;
+  }
+
+  max_dequeued_buffer_count_ = max_dequeued_buffers;
   return NO_ERROR;
 }
 
-status_t BufferHubQueueProducer::setAsyncMode(bool /* async */) {
-  ALOGE("BufferHubQueueProducer::setAsyncMode not implemented.");
-  return INVALID_OPERATION;
+status_t BufferHubQueueProducer::setAsyncMode(bool async) {
+  if (async) {
+    // TODO(b/36724099) BufferHubQueue's consumer end always acquires the buffer
+    // automatically and behaves differently from IGraphicBufferConsumer. Thus,
+    // android::BufferQueue's async mode (a.k.a. allocating an additional buffer
+    // to prevent dequeueBuffer from being blocking) technically does not apply
+    // here.
+    //
+    // In Daydream, non-blocking producer side dequeue is guaranteed by careful
+    // buffer consumer implementations. In another word, BufferHubQueue based
+    // dequeueBuffer should never block whether setAsyncMode(true) is set or
+    // not.
+    //
+    // See: IGraphicBufferProducer::setAsyncMode and
+    // BufferQueueProducer::setAsyncMode for more about original implementation.
+    ALOGW(
+        "BufferHubQueueProducer::setAsyncMode: BufferHubQueue should always be "
+        "asynchronous. This call makes no effact.");
+    return NO_ERROR;
+  }
+  return NO_ERROR;
 }
 
-status_t BufferHubQueueProducer::dequeueBuffer(int* out_slot,
-                                               sp<Fence>* out_fence,
-                                               uint32_t width, uint32_t height,
-                                               PixelFormat format,
-                                               uint32_t usage,
-                                               FrameEventHistoryDelta* /* outTimestamps */) {
+status_t BufferHubQueueProducer::dequeueBuffer(
+    int* out_slot, sp<Fence>* out_fence, uint32_t width, uint32_t height,
+    PixelFormat format, uint32_t usage,
+    FrameEventHistoryDelta* /* out_timestamps */) {
   ALOGD_IF(TRACE, "dequeueBuffer: w=%u, h=%u, format=%d, usage=%u", width,
            height, format, usage);
 
   status_t ret;
   std::unique_lock<std::mutex> lock(core_->mutex_);
 
-  if (static_cast<int32_t>(core_->producer_->capacity()) < req_buffer_count_) {
+  if (core_->connected_api_ == BufferHubQueueCore::kNoConnectedApi) {
+    ALOGE("dequeueBuffer: BufferQueue has no connected producer");
+    return NO_INIT;
+  }
+
+  if (static_cast<int32_t>(core_->producer_->capacity()) <
+      max_dequeued_buffer_count_) {
     // Lazy allocation. When the capacity of |core_->producer_| has not reach
-    // |req_buffer_count_|, allocate new buffer.
+    // |max_dequeued_buffer_count_|, allocate new buffer.
     // TODO(jwcai) To save memory, the really reasonable thing to do is to go
     // over existing slots and find first existing one to dequeue.
     ret = core_->AllocateBuffer(width, height, format, usage, 1);
@@ -126,8 +194,8 @@ status_t BufferHubQueueProducer::dequeueBuffer(int* out_slot,
   // BufferHubQueue).
   // TODO(jwcai) Clean this up, make mBufferState compatible with BufferHub's
   // model.
-  LOG_ALWAYS_FATAL_IF(!core_->buffers_[slot].mBufferState.isFree() &&
-                          !core_->buffers_[slot].mBufferState.isQueued(),
+  LOG_ALWAYS_FATAL_IF((!core_->buffers_[slot].mBufferState.isFree() &&
+                       !core_->buffers_[slot].mBufferState.isQueued()),
                       "dequeueBuffer: slot %zu is not free or queued.", slot);
 
   core_->buffers_[slot].mBufferState.freeQueued();
@@ -170,22 +238,39 @@ status_t BufferHubQueueProducer::attachBuffer(
 
 status_t BufferHubQueueProducer::queueBuffer(int slot,
                                              const QueueBufferInput& input,
-                                             QueueBufferOutput* /* output */) {
+                                             QueueBufferOutput* output) {
   ALOGD_IF(TRACE, "queueBuffer: slot %d", slot);
 
+  if (output == nullptr) {
+    return BAD_VALUE;
+  }
+
   int64_t timestamp;
+  int scaling_mode;
   sp<Fence> fence;
+  Rect crop(Rect::EMPTY_RECT);
 
   // TODO(jwcai) The following attributes are ignored.
   bool is_auto_timestamp;
   android_dataspace data_space;
-  Rect crop(Rect::EMPTY_RECT);
-  int scaling_mode;
   uint32_t transform;
 
   input.deflate(&timestamp, &is_auto_timestamp, &data_space, &crop,
                 &scaling_mode, &transform, &fence);
 
+  // Check input scaling mode is valid.
+  switch (scaling_mode) {
+    case NATIVE_WINDOW_SCALING_MODE_FREEZE:
+    case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
+    case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
+    case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
+      break;
+    default:
+      ALOGE("queueBuffer: unknown scaling mode %d", scaling_mode);
+      return BAD_VALUE;
+  }
+
+  // Check input fence is valid.
   if (fence == nullptr) {
     ALOGE("queueBuffer: fence is NULL");
     return BAD_VALUE;
@@ -194,25 +279,61 @@ status_t BufferHubQueueProducer::queueBuffer(int slot,
   status_t ret;
   std::unique_lock<std::mutex> lock(core_->mutex_);
 
-  if (slot < 0 || slot >= req_buffer_count_) {
+  if (core_->connected_api_ == BufferHubQueueCore::kNoConnectedApi) {
+    ALOGE("queueBuffer: BufferQueue has no connected producer");
+    return NO_INIT;
+  }
+
+  if (slot < 0 || slot >= max_buffer_count_) {
     ALOGE("queueBuffer: slot index %d out of range [0, %d)", slot,
-          req_buffer_count_);
+          max_buffer_count_);
     return BAD_VALUE;
   } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
     ALOGE("queueBuffer: slot %d is not owned by the producer (state = %s)",
           slot, core_->buffers_[slot].mBufferState.string());
     return BAD_VALUE;
+  } else if ((!core_->buffers_[slot].mRequestBufferCalled ||
+              core_->buffers_[slot].mGraphicBuffer == nullptr)) {
+    ALOGE(
+        "queueBuffer: slot %d is not requested (mRequestBufferCalled=%d, "
+        "mGraphicBuffer=%p)",
+        slot, core_->buffers_[slot].mRequestBufferCalled,
+        core_->buffers_[slot].mGraphicBuffer.get());
+    return BAD_VALUE;
   }
 
   // Post the buffer producer with timestamp in the metadata.
-  auto buffer_producer = core_->buffers_[slot].mBufferProducer;
+  const auto& buffer_producer = core_->buffers_[slot].mBufferProducer;
+
+  // Check input crop is not out of boundary of current buffer.
+  Rect buffer_rect(buffer_producer->width(), buffer_producer->height());
+  Rect cropped_rect(Rect::EMPTY_RECT);
+  crop.intersect(buffer_rect, &cropped_rect);
+  if (cropped_rect != crop) {
+    ALOGE("queueBuffer: slot %d has out-of-boundary crop.", slot);
+    return BAD_VALUE;
+  }
+
   LocalHandle fence_fd(fence->isValid() ? fence->dup() : -1);
 
   BufferHubQueueCore::BufferMetadata meta_data = {.timestamp = timestamp};
   buffer_producer->Post(fence_fd, &meta_data, sizeof(meta_data));
   core_->buffers_[slot].mBufferState.queue();
 
-  // TODO(jwcai) check how to fill in output properly.
+  output->width = buffer_producer->width();
+  output->height = buffer_producer->height();
+  output->transformHint = 0; // default value, we don't use it yet.
+
+  // |numPendingBuffers| counts of the number of buffers that has been enqueued
+  // by the producer but not yet acquired by the consumer. Due to the nature
+  // of BufferHubQueue design, this is hard to trace from the producer's client
+  // side, but it's safe to assume it's zero.
+  output->numPendingBuffers = 0;
+
+  // Note that we are not setting nextFrameNumber here as it seems to be only
+  // used by surface flinger. See more at b/22802885, ag/791760.
+  output->nextFrameNumber = 0;
+
   return NO_ERROR;
 }
 
@@ -222,15 +343,20 @@ status_t BufferHubQueueProducer::cancelBuffer(int slot,
 
   std::unique_lock<std::mutex> lock(core_->mutex_);
 
-  if (slot < 0 || slot >= req_buffer_count_) {
+  if (core_->connected_api_ == BufferHubQueueCore::kNoConnectedApi) {
+    ALOGE("cancelBuffer: BufferQueue has no connected producer");
+    return NO_INIT;
+  }
+
+  if (slot < 0 || slot >= max_buffer_count_) {
     ALOGE("cancelBuffer: slot index %d out of range [0, %d)", slot,
-          req_buffer_count_);
+          max_buffer_count_);
     return BAD_VALUE;
   } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
     ALOGE("cancelBuffer: slot %d is not owned by the producer (state = %s)",
           slot, core_->buffers_[slot].mBufferState.string());
     return BAD_VALUE;
-  } else if (fence == NULL) {
+  } else if (fence == nullptr) {
     ALOGE("cancelBuffer: fence is NULL");
     return BAD_VALUE;
   }
@@ -249,7 +375,7 @@ status_t BufferHubQueueProducer::query(int what, int* out_value) {
 
   std::unique_lock<std::mutex> lock(core_->mutex_);
 
-  if (out_value == NULL) {
+  if (out_value == nullptr) {
     ALOGE("query: out_value was NULL");
     return BAD_VALUE;
   }
@@ -262,15 +388,30 @@ status_t BufferHubQueueProducer::query(int what, int* out_value) {
     case NATIVE_WINDOW_BUFFER_AGE:
       value = 0;
       break;
-    // The following queries are currently considered as unsupported.
-    // TODO(jwcai) Need to carefully check the whether they should be
-    // supported after all.
     case NATIVE_WINDOW_WIDTH:
+      value = core_->producer_->default_width();
+      break;
     case NATIVE_WINDOW_HEIGHT:
+      value = core_->producer_->default_height();
+      break;
     case NATIVE_WINDOW_FORMAT:
-    case NATIVE_WINDOW_STICKY_TRANSFORM:
+      value = core_->producer_->default_format();
+      break;
     case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+      // BufferHubQueue is always operating in async mode, thus semantically
+      // consumer can never be running behind. See BufferQueueCore.cpp core
+      // for more information about the original meaning of this flag.
+      value = 0;
+      break;
     case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
+      // TODO(jwcai) This is currently not implement as we don't need
+      // IGraphicBufferConsumer parity.
+      value = 0;
+      break;
+    // The following queries are currently considered as unsupported.
+    // TODO(jwcai) Need to carefully check the whether they should be
+    // supported after all.
+    case NATIVE_WINDOW_STICKY_TRANSFORM:
     case NATIVE_WINDOW_DEFAULT_DATASPACE:
     default:
       return BAD_VALUE;
@@ -282,24 +423,58 @@ status_t BufferHubQueueProducer::query(int what, int* out_value) {
 }
 
 status_t BufferHubQueueProducer::connect(
-    const sp<IProducerListener>& /* listener */, int /* api */,
-    bool /* producer_controlled_by_app */, QueueBufferOutput* /* output */) {
+    const sp<IProducerListener>& /* listener */, int api,
+    bool /* producer_controlled_by_app */, QueueBufferOutput* output) {
   // Consumer interaction are actually handled by buffer hub, and we need
-  // to maintain consumer operations here. Hence |connect| is a NO-OP.
+  // to maintain consumer operations here. We only need to perform basic input
+  // parameter checks here.
   ALOGD_IF(TRACE, __FUNCTION__);
+
+  if (output == nullptr) {
+    return BAD_VALUE;
+  }
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (core_->connected_api_ != BufferHubQueueCore::kNoConnectedApi) {
+    return BAD_VALUE;
+  }
+
+  switch (api) {
+    case NATIVE_WINDOW_API_EGL:
+    case NATIVE_WINDOW_API_CPU:
+    case NATIVE_WINDOW_API_MEDIA:
+    case NATIVE_WINDOW_API_CAMERA:
+      core_->connected_api_ = api;
+      // TODO(jwcai) Fill output.
+      break;
+    default:
+      ALOGE("BufferHubQueueProducer::connect: unknow API %d", api);
+      return BAD_VALUE;
+  }
+
   return NO_ERROR;
 }
 
-status_t BufferHubQueueProducer::disconnect(int /* api */, DisconnectMode /* mode */) {
+status_t BufferHubQueueProducer::disconnect(int api, DisconnectMode mode) {
   // Consumer interaction are actually handled by buffer hub, and we need
-  // to maintain consumer operations here. Hence |disconnect| is a NO-OP.
+  // to maintain consumer operations here.  We only need to perform basic input
+  // parameter checks here.
   ALOGD_IF(TRACE, __FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (api != core_->connected_api_) {
+    return BAD_VALUE;
+  }
+
+  core_->connected_api_ = BufferHubQueueCore::kNoConnectedApi;
   return NO_ERROR;
 }
 
 status_t BufferHubQueueProducer::setSidebandStream(
     const sp<NativeHandle>& stream) {
-  if (stream != NULL) {
+  if (stream != nullptr) {
     // TODO(jwcai) Investigate how is is used, maybe use BufferHubBuffer's
     // metadata.
     ALOGE("SidebandStream is not currently supported.");
@@ -314,7 +489,7 @@ void BufferHubQueueProducer::allocateBuffers(uint32_t /* width */,
                                              uint32_t /* usage */) {
   // TODO(jwcai) |allocateBuffers| aims to preallocate up to the maximum number
   // of buffers permitted by the current BufferQueue configuration (aka
-  // |req_buffer_count_|).
+  // |max_buffer_count_|).
   ALOGE("BufferHubQueueProducer::allocateBuffers not implemented.");
 }
 
@@ -343,6 +518,7 @@ String8 BufferHubQueueProducer::getConsumerName() const {
 status_t BufferHubQueueProducer::setSharedBufferMode(
     bool /* shared_buffer_mode */) {
   ALOGE("BufferHubQueueProducer::setSharedBufferMode not implemented.");
+  // TODO(b/36373181) Front buffer mode for buffer hub queue as ANativeWindow.
   return INVALID_OPERATION;
 }
 
index f786356..a020dca 100644 (file)
@@ -32,6 +32,15 @@ class BufferHubQueue : public pdx::Client {
   // a new consumer queue client or nullptr on failure.
   std::unique_ptr<ConsumerQueue> CreateConsumerQueue();
 
+  // Return the default buffer width of this buffer queue.
+  size_t default_width() const { return default_width_; }
+
+  // Return the default buffer height of this buffer queue.
+  size_t default_height() const { return default_height_; }
+
+  // Return the default buffer format of this buffer queue.
+  int32_t default_format() const { return default_format_; }
+
   // Return the number of buffers avaiable for dequeue.
   size_t count() const { return available_buffers_.GetSize(); }
 
@@ -169,6 +178,18 @@ class BufferHubQueue : public pdx::Client {
     void operator=(BufferInfo&) = delete;
   };
 
+  // Default buffer width that can be set to override the buffer width when a
+  // width and height of 0 are specified in AllocateBuffer.
+  size_t default_width_{1};
+
+  // Default buffer height that can be set to override the buffer height when a
+  // width and height of 0 are specified in AllocateBuffer.
+  size_t default_height_{1};
+
+  // Default buffer format that can be set to override the buffer format when it
+  // isn't specified in AllocateBuffer.
+  int32_t default_format_{PIXEL_FORMAT_RGBA_8888};
+
   // Buffer queue:
   // |buffers_| tracks all |BufferHubBuffer|s created by this |BufferHubQueue|.
   std::vector<std::shared_ptr<BufferHubBuffer>> buffers_;
index ba0c0c5..e353187 100644 (file)
@@ -17,6 +17,8 @@ class BufferHubQueueCore {
   friend class BufferHubQueueProducer;
 
  public:
+  static constexpr int kNoConnectedApi = -1;
+
   // Create a BufferHubQueueCore instance by creating a new producer queue.
   static std::shared_ptr<BufferHubQueueCore> Create();
 
@@ -87,6 +89,9 @@ class BufferHubQueueCore {
   // Mutex for thread safety.
   std::mutex mutex_;
 
+  // Connect client API, should be one of the NATIVE_WINDOW_API_* flags.
+  int connected_api_{kNoConnectedApi};
+
   // |buffers_| stores the buffers that have been dequeued from
   // |dvr::BufferHubQueue|, It is initialized to invalid buffers, and gets
   // filled in with the result of |Dequeue|.
index 5b1a7e0..43e5ce3 100644 (file)
@@ -103,13 +103,15 @@ class BufferHubQueueProducer : public IGraphicBufferProducer {
  private:
   using LocalHandle = pdx::LocalHandle;
 
-  static constexpr int kInvalidBufferCount = -1;
-
   // |core_| holds the actually buffer slots.
   std::shared_ptr<BufferHubQueueCore> core_;
 
-  // |req_buffer_count_| sets the capacity of the underlying buffer queue.
-  int32_t req_buffer_count_;
+  // |max_buffer_count_| sets the capacity of the underlying buffer queue.
+  int32_t max_buffer_count_{BufferHubQueue::kMaxQueueCapacity};
+
+  // |max_dequeued_buffer_count_| set the maximum number of buffers that can
+  // be dequeued at the same momment.
+  int32_t max_dequeued_buffer_count_{1};
 };
 
 }  // namespace dvr
index 5bb121a..64034e8 100644 (file)
@@ -1,7 +1,9 @@
 #include <private/dvr/buffer_hub_queue_producer.h>
 
 #include <base/logging.h>
+#include <gui/IProducerListener.h>
 #include <gui/Surface.h>
+
 #include <gtest/gtest.h>
 
 namespace android {
@@ -9,12 +11,500 @@ namespace dvr {
 
 namespace {
 
-class BufferHubQueueProducerTest : public ::testing::Test {};
+// Default dimensions before setDefaultBufferSize is called by the consumer.
+constexpr uint32_t kDefaultWidth = 1;
+constexpr uint32_t kDefaultHeight = 1;
+
+// Default format before setDefaultBufferFormat is called by the consumer.
+constexpr PixelFormat kDefaultFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+constexpr int kDefaultConsumerUsageBits = 0;
+
+// Default transform hint before setTransformHint is called by the consumer.
+constexpr uint32_t kDefaultTransformHint = 0;
+
+constexpr int kTestApi = NATIVE_WINDOW_API_CPU;
+constexpr int kTestApiOther = NATIVE_WINDOW_API_EGL;
+constexpr int kTestApiInvalid = 0xDEADBEEF;
+constexpr int kTestProducerUsageBits = 0;
+constexpr bool kTestControlledByApp = true;
+
+// Builder pattern to slightly vary *almost* correct input
+// -- avoids copying and pasting
+struct QueueBufferInputBuilder {
+  IGraphicBufferProducer::QueueBufferInput build() {
+    return IGraphicBufferProducer::QueueBufferInput(
+        mTimestamp, mIsAutoTimestamp, mDataSpace, mCrop, mScalingMode,
+        mTransform, mFence);
+  }
+
+  QueueBufferInputBuilder& setTimestamp(int64_t timestamp) {
+    this->mTimestamp = timestamp;
+    return *this;
+  }
+
+  QueueBufferInputBuilder& setIsAutoTimestamp(bool isAutoTimestamp) {
+    this->mIsAutoTimestamp = isAutoTimestamp;
+    return *this;
+  }
+
+  QueueBufferInputBuilder& setDataSpace(android_dataspace dataSpace) {
+    this->mDataSpace = dataSpace;
+    return *this;
+  }
+
+  QueueBufferInputBuilder& setCrop(Rect crop) {
+    this->mCrop = crop;
+    return *this;
+  }
+
+  QueueBufferInputBuilder& setScalingMode(int scalingMode) {
+    this->mScalingMode = scalingMode;
+    return *this;
+  }
+
+  QueueBufferInputBuilder& setTransform(uint32_t transform) {
+    this->mTransform = transform;
+    return *this;
+  }
+
+  QueueBufferInputBuilder& setFence(sp<Fence> fence) {
+    this->mFence = fence;
+    return *this;
+  }
+
+ private:
+  int64_t mTimestamp{1384888611};
+  bool mIsAutoTimestamp{false};
+  android_dataspace mDataSpace{HAL_DATASPACE_UNKNOWN};
+  Rect mCrop{Rect(kDefaultWidth, kDefaultHeight)};
+  int mScalingMode{0};
+  uint32_t mTransform{0};
+  sp<Fence> mFence{Fence::NO_FENCE};
+};
+
+// This is a test that covers our implementation of bufferhubqueue-based
+// IGraphicBufferProducer.
+class BufferHubQueueProducerTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    const ::testing::TestInfo* const testInfo =
+        ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD_IF(TRACE, "Begin test: %s.%s", testInfo->test_case_name(),
+             testInfo->name());
+
+    auto core = BufferHubQueueCore::Create();
+    mProducer = new BufferHubQueueProducer(core);
+    ASSERT_TRUE(mProducer != nullptr);
+    mSurface = new Surface(mProducer, true);
+    ASSERT_TRUE(mSurface != nullptr);
+  }
+
+  // Connect to a producer in a 'correct' fashion.
+  void ConnectProducer() {
+    IGraphicBufferProducer::QueueBufferOutput output;
+    // Can connect the first time.
+    ASSERT_EQ(NO_ERROR, mProducer->connect(kDummyListener, kTestApi,
+                                           kTestControlledByApp, &output));
+  }
+
+  // Dequeue a buffer in a 'correct' fashion.
+  //   Precondition: Producer is connected.
+  void DequeueBuffer(int* outSlot) {
+    sp<Fence> fence;
+    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(outSlot, &fence));
+  }
+
+  void DequeueBuffer(int* outSlot, sp<Fence>* outFence) {
+    ASSERT_NE(nullptr, outSlot);
+    ASSERT_NE(nullptr, outFence);
+
+    int ret = mProducer->dequeueBuffer(outSlot, outFence, kDefaultWidth,
+                                       kDefaultHeight, kDefaultFormat,
+                                       kTestProducerUsageBits, nullptr);
+    // BUFFER_NEEDS_REALLOCATION can be either on or off.
+    ASSERT_EQ(0, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION & ret);
+
+    // Slot number should be in boundary.
+    ASSERT_LE(0, *outSlot);
+    ASSERT_GT(BufferQueueDefs::NUM_BUFFER_SLOTS, *outSlot);
+  }
+
+  // Create a generic "valid" input for queueBuffer
+  // -- uses the default buffer format, width, etc.
+  static IGraphicBufferProducer::QueueBufferInput CreateBufferInput() {
+    return QueueBufferInputBuilder().build();
+  }
+
+  const sp<IProducerListener> kDummyListener{new DummyProducerListener};
+
+  sp<BufferHubQueueProducer> mProducer;
+  sp<Surface> mSurface;
+};
+
+TEST_F(BufferHubQueueProducerTest, ConnectFirst_ReturnsError) {
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  // NULL output returns BAD_VALUE
+  EXPECT_EQ(BAD_VALUE, mProducer->connect(kDummyListener, kTestApi,
+                                          kTestControlledByApp, nullptr));
+
+  // Invalid API returns bad value
+  EXPECT_EQ(BAD_VALUE, mProducer->connect(kDummyListener, kTestApiInvalid,
+                                          kTestControlledByApp, &output));
+}
+
+TEST_F(BufferHubQueueProducerTest, ConnectAgain_ReturnsError) {
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  // Can't connect when there is already a producer connected.
+  IGraphicBufferProducer::QueueBufferOutput output;
+  EXPECT_EQ(BAD_VALUE, mProducer->connect(kDummyListener, kTestApi,
+                                          kTestControlledByApp, &output));
+}
+
+TEST_F(BufferHubQueueProducerTest, Disconnect_Succeeds) {
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  ASSERT_EQ(NO_ERROR, mProducer->disconnect(kTestApi));
+}
+
+TEST_F(BufferHubQueueProducerTest, Disconnect_ReturnsError) {
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  // Must disconnect with same API number
+  EXPECT_EQ(BAD_VALUE, mProducer->disconnect(kTestApiOther));
+  // API must not be out of range
+  EXPECT_EQ(BAD_VALUE, mProducer->disconnect(kTestApiInvalid));
+}
+
+TEST_F(BufferHubQueueProducerTest, Query_Succeeds) {
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  int32_t value = -1;
+  EXPECT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_WIDTH, &value));
+  EXPECT_EQ(kDefaultWidth, static_cast<uint32_t>(value));
+
+  EXPECT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_HEIGHT, &value));
+  EXPECT_EQ(kDefaultHeight, static_cast<uint32_t>(value));
+
+  EXPECT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_FORMAT, &value));
+  EXPECT_EQ(kDefaultFormat, value);
+
+  EXPECT_EQ(NO_ERROR,
+            mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &value));
+  EXPECT_LE(0, value);
+  EXPECT_GE(BufferQueueDefs::NUM_BUFFER_SLOTS, static_cast<size_t>(value));
+
+  EXPECT_EQ(NO_ERROR,
+            mProducer->query(NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &value));
+  EXPECT_FALSE(value);  // Can't run behind when we haven't touched the queue
+
+  EXPECT_EQ(NO_ERROR,
+            mProducer->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &value));
+  EXPECT_EQ(kDefaultConsumerUsageBits, value);
+}
+
+TEST_F(BufferHubQueueProducerTest, Query_ReturnsError) {
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  // One past the end of the last 'query' enum value. Update this if we add more
+  // enums.
+  const int NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE = NATIVE_WINDOW_BUFFER_AGE + 1;
+
+  int value;
+  // What was out of range
+  EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/ -1, &value));
+  EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/ 0xDEADBEEF, &value));
+  EXPECT_EQ(BAD_VALUE,
+            mProducer->query(NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE, &value));
+
+  // Some enums from window.h are 'invalid'
+  EXPECT_EQ(BAD_VALUE,
+            mProducer->query(NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER, &value));
+  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_CONCRETE_TYPE, &value));
+  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_WIDTH, &value));
+  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_HEIGHT, &value));
+  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_TRANSFORM_HINT, &value));
+
+  // Value was NULL
+  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_FORMAT, /*value*/ NULL));
+}
+
+TEST_F(BufferHubQueueProducerTest, Queue_Succeeds) {
+  int slot = -1;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+
+  // Request the buffer (pre-requisite for queueing)
+  sp<GraphicBuffer> buffer;
+  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));
+
+  // A generic "valid" input
+  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  // Queue the buffer back into the BQ
+  ASSERT_EQ(NO_ERROR, mProducer->queueBuffer(slot, input, &output));
+
+  EXPECT_EQ(kDefaultWidth, output.width);
+  EXPECT_EQ(kDefaultHeight, output.height);
+  EXPECT_EQ(kDefaultTransformHint, output.transformHint);
+
+  // BufferHubQueue delivers buffers to consumer immediately.
+  EXPECT_EQ(0u, output.numPendingBuffers);
+
+  // Note that BufferHubQueue doesn't support nextFrameNumber as it seems to
+  // be a SurfaceFlinger specific optimization.
+  EXPECT_EQ(0u, output.nextFrameNumber);
+
+  // Buffer was not in the dequeued state
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
+}
+
+// Test invalid slot number
+TEST_F(BufferHubQueueProducerTest, QueueInvalidSlot_ReturnsError) {
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  // A generic "valid" input
+  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/ -1, input, &output));
+  EXPECT_EQ(BAD_VALUE,
+            mProducer->queueBuffer(/*slot*/ 0xDEADBEEF, input, &output));
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(BufferQueueDefs::NUM_BUFFER_SLOTS,
+                                              input, &output));
+}
+
+// Slot was not in the dequeued state (all slots start out in Free state)
+TEST_F(BufferHubQueueProducerTest, QueueNotDequeued_ReturnsError) {
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/ 0, input, &output));
+}
+
+// Slot was enqueued without requesting a buffer
+TEST_F(BufferHubQueueProducerTest, QueueNotRequested_ReturnsError) {
+  int slot = -1;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+
+  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
+}
+
+// Test when fence was NULL
+TEST_F(BufferHubQueueProducerTest, QueueNoFence_ReturnsError) {
+  int slot = -1;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+
+  sp<GraphicBuffer> buffer;
+  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));
+
+  sp<Fence> nullFence = NULL;
+
+  IGraphicBufferProducer::QueueBufferInput input =
+      QueueBufferInputBuilder().setFence(nullFence).build();
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
+}
+
+// Test scaling mode was invalid
+TEST_F(BufferHubQueueProducerTest, QueueTestInvalidScalingMode_ReturnsError) {
+  int slot = -1;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+
+  sp<GraphicBuffer> buffer;
+  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));
+
+  IGraphicBufferProducer::QueueBufferInput input =
+      QueueBufferInputBuilder().setScalingMode(-1).build();
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
+
+  input = QueueBufferInputBuilder().setScalingMode(0xDEADBEEF).build();
+
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
+}
+
+// Test crop rect is out of bounds of the buffer dimensions
+TEST_F(BufferHubQueueProducerTest, QueueCropOutOfBounds_ReturnsError) {
+  int slot = -1;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+
+  sp<GraphicBuffer> buffer;
+  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));
+
+  IGraphicBufferProducer::QueueBufferInput input =
+      QueueBufferInputBuilder()
+          .setCrop(Rect(kDefaultWidth + 1, kDefaultHeight + 1))
+          .build();
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
+}
+
+TEST_F(BufferHubQueueProducerTest, CancelBuffer_Succeeds) {
+  int slot = -1;
+  sp<Fence> fence;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot, &fence));
+
+  // Should be able to cancel buffer after a dequeue.
+  EXPECT_EQ(NO_ERROR, mProducer->cancelBuffer(slot, fence));
+}
+
+TEST_F(BufferHubQueueProducerTest, SetMaxDequeuedBufferCount_Succeeds) {
+  return;
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  int minUndequeuedBuffers;
+  ASSERT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+                                       &minUndequeuedBuffers));
+
+  const int minBuffers = 1;
+  const int maxBuffers =
+      BufferQueueDefs::NUM_BUFFER_SLOTS - minUndequeuedBuffers;
+
+  ASSERT_EQ(NO_ERROR, mProducer->setAsyncMode(false))
+      << "async mode: " << false;
+  ASSERT_EQ(NO_ERROR, mProducer->setMaxDequeuedBufferCount(minBuffers))
+      << "bufferCount: " << minBuffers;
+
+  // Should now be able to dequeue up to minBuffers times
+  // Should now be able to dequeue up to maxBuffers times
+  int slot = -1;
+  for (int i = 0; i < minBuffers; ++i) {
+    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+  }
+
+  ASSERT_EQ(NO_ERROR, mProducer->setMaxDequeuedBufferCount(maxBuffers));
+
+  // queue the first buffer to enable max dequeued buffer count checking
+  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+  IGraphicBufferProducer::QueueBufferOutput output;
+  sp<GraphicBuffer> buffer;
+  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));
+  ASSERT_EQ(NO_ERROR, mProducer->queueBuffer(slot, input, &output));
+
+  sp<Fence> fence;
+  for (int i = 0; i < maxBuffers; ++i) {
+    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot, &fence));
+  }
+
+  // Cancel a buffer, so we can decrease the buffer count
+  ASSERT_EQ(NO_ERROR, mProducer->cancelBuffer(slot, fence));
+
+  // Should now be able to decrease the max dequeued count by 1
+  ASSERT_EQ(NO_ERROR, mProducer->setMaxDequeuedBufferCount(maxBuffers - 1));
+}
+
+TEST_F(BufferHubQueueProducerTest, SetMaxDequeuedBufferCount_Fails) {
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+  int minUndequeuedBuffers;
+  ASSERT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+                                       &minUndequeuedBuffers));
+
+  const int minBuffers = 1;
+  const int maxBuffers =
+      BufferQueueDefs::NUM_BUFFER_SLOTS - minUndequeuedBuffers;
+
+  ASSERT_EQ(NO_ERROR, mProducer->setAsyncMode(false))
+      << "async mode: " << false;
+  // Buffer count was out of range
+  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(0))
+      << "bufferCount: " << 0;
+  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(maxBuffers + 1))
+      << "bufferCount: " << maxBuffers + 1;
+
+  // Set max dequeue count to 2
+  ASSERT_EQ(NO_ERROR, mProducer->setMaxDequeuedBufferCount(2));
+  // Dequeue 2 buffers
+  int slot = -1;
+  sp<Fence> fence;
+  for (int i = 0; i < 2; i++) {
+    ASSERT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
+                      (mProducer->dequeueBuffer(
+                          &slot, &fence, kDefaultWidth, kDefaultHeight,
+                          kDefaultFormat, kTestProducerUsageBits, nullptr)))
+        << "slot: " << slot;
+  }
+
+  // Client has too many buffers dequeued
+  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(1))
+      << "bufferCount: " << minBuffers;
+}
+
+TEST_F(BufferHubQueueProducerTest,
+       DisconnectedProducerReturnsError_dequeueBuffer) {
+  int slot = -1;
+  sp<Fence> fence;
+
+  ASSERT_EQ(NO_INIT, mProducer->dequeueBuffer(&slot, &fence, kDefaultWidth,
+                                              kDefaultHeight, kDefaultFormat,
+                                              kTestProducerUsageBits, nullptr));
+}
+
+TEST_F(BufferHubQueueProducerTest,
+       DisconnectedProducerReturnsError_requestBuffer) {
+  int slot = -1;
+  sp<GraphicBuffer> buffer;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+
+  // Shouldn't be able to request buffer after disconnect.
+  ASSERT_EQ(NO_ERROR, mProducer->disconnect(kTestApi));
+  ASSERT_EQ(NO_INIT, mProducer->requestBuffer(slot, &buffer));
+}
+
+TEST_F(BufferHubQueueProducerTest,
+       DisconnectedProducerReturnsError_queueBuffer) {
+  int slot = -1;
+  sp<GraphicBuffer> buffer;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));
+
+  // A generic "valid" input
+  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+  IGraphicBufferProducer::QueueBufferOutput output;
+
+  // Shouldn't be able to queue buffer after disconnect.
+  ASSERT_EQ(NO_ERROR, mProducer->disconnect(kTestApi));
+  ASSERT_EQ(NO_INIT, mProducer->queueBuffer(slot, input, &output));
+}
+
+TEST_F(BufferHubQueueProducerTest,
+       DisconnectedProducerReturnsError_cancelBuffer) {
+  int slot = -1;
+  sp<GraphicBuffer> buffer;
+
+  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
+  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));
 
-TEST_F(BufferHubQueueProducerTest, TempTestBufferHubQueueProducer) {
-  auto core = BufferHubQueueCore::Create();
-  sp<BufferHubQueueProducer> producer = new BufferHubQueueProducer(core);
-  sp<Surface> surface = new Surface(producer, true);
+  // Shouldn't be able to cancel buffer after disconnect.
+  ASSERT_EQ(NO_ERROR, mProducer->disconnect(kTestApi));
+  ASSERT_EQ(NO_INIT, mProducer->cancelBuffer(slot, Fence::NO_FENCE));
 }
 
 }  // namespace