Add DaydreamVR native libraries and services

Upstreaming the main VR system components from master-dreamos-dev
into goog/master.

Bug: None
Test: `m -j32` succeeds. Sailfish boots and basic_vr sample app works
Change-Id: I853015872afc443aecee10411ef2d6b79184d051
diff --git a/libs/vr/libbufferhubqueue/Android.mk b/libs/vr/libbufferhubqueue/Android.mk
new file mode 100644
index 0000000..46b83e7
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/Android.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2016 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.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	buffer_hub_queue_client.cpp \
+	buffer_hub_queue_core.cpp \
+	buffer_hub_queue_consumer.cpp \
+	buffer_hub_queue_producer.cpp \
+
+includeFiles := \
+	$(LOCAL_PATH)/include
+
+staticLibraries := \
+	libbufferhub \
+	libchrome \
+	libdvrcommon \
+	libpdx_default_transport \
+
+sharedLibraries := \
+	libbase \
+	libbinder \
+	libcutils \
+	libhardware \
+	liblog \
+	libui \
+	libutils \
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := libbufferhubqueue
+include $(BUILD_STATIC_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
new file mode 100644
index 0000000..4fbfcf6
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
@@ -0,0 +1,414 @@
+#include "include/private/dvr/buffer_hub_queue_client.h"
+
+#include <base/logging.h>
+#include <sys/epoll.h>
+
+#include <array>
+
+#include <pdx/default_transport/client_channel.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <pdx/file_handle.h>
+#include <private/dvr/bufferhub_rpc.h>
+
+using android::pdx::LocalHandle;
+using android::pdx::LocalChannelHandle;
+
+namespace android {
+namespace dvr {
+
+BufferHubQueue::BufferHubQueue(LocalChannelHandle channel_handle,
+                               size_t meta_size)
+    : Client{pdx::default_transport::ClientChannel::Create(
+          std::move(channel_handle))},
+      meta_size_(meta_size),
+      meta_buffer_tmp_(meta_size ? new uint8_t[meta_size] : nullptr),
+      buffers_(BufferHubQueue::kMaxQueueCapacity),
+      available_buffers_(BufferHubQueue::kMaxQueueCapacity),
+      capacity_(0) {
+  Initialize();
+}
+
+BufferHubQueue::BufferHubQueue(const std::string& endpoint_path,
+                               size_t meta_size)
+    : Client{pdx::default_transport::ClientChannelFactory::Create(
+          endpoint_path)},
+      meta_size_(meta_size),
+      meta_buffer_tmp_(meta_size ? new uint8_t[meta_size] : nullptr),
+      buffers_(BufferHubQueue::kMaxQueueCapacity),
+      available_buffers_(BufferHubQueue::kMaxQueueCapacity),
+      capacity_(0) {
+  Initialize();
+}
+
+void BufferHubQueue::Initialize() {
+  int ret = epoll_fd_.Create();
+  if (ret < 0) {
+    LOG(ERROR) << "BufferHubQueue::BufferHubQueue: Failed to create epoll fd:"
+               << strerror(-ret);
+    return;
+  }
+
+  epoll_event event = {.events = EPOLLIN | EPOLLET,
+                       .data = {.u64 = static_cast<uint64_t>(
+                                    BufferHubQueue::kEpollQueueEventIndex)}};
+  ret = epoll_fd_.Control(EPOLL_CTL_ADD, event_fd(), &event);
+  if (ret < 0) {
+    LOG(ERROR) << "Failed to register ConsumerQueue into epoll event: "
+               << strerror(-ret);
+  }
+}
+
+std::unique_ptr<ConsumerQueue> BufferHubQueue::CreateConsumerQueue() {
+  Status<std::pair<LocalChannelHandle, size_t>> status =
+      InvokeRemoteMethod<BufferHubRPC::CreateConsumerQueue>();
+
+  if (!status) {
+    LOG(ERROR) << "Cannot create ConsumerQueue: " << status.GetErrorMessage();
+    return nullptr;
+  }
+
+  auto return_value = status.take();
+
+  VLOG(1) << "CreateConsumerQueue: meta_size_bytes=" << return_value.second;
+  return ConsumerQueue::Create(std::move(return_value.first),
+                               return_value.second);
+}
+
+bool BufferHubQueue::WaitForBuffers(int timeout) {
+  std::array<epoll_event, kMaxEvents> events;
+
+  while (count() == 0) {
+    int ret = epoll_fd_.Wait(events.data(), events.size(), timeout);
+
+    if (ret == 0) {
+      VLOG(1) << "Wait on epoll returns nothing before timeout.";
+      return false;
+    }
+
+    if (ret < 0 && ret != -EINTR) {
+      LOG(ERROR) << "Failed to wait for buffers:" << strerror(-ret);
+      return false;
+    }
+
+    const int num_events = ret;
+
+    // A BufferQueue's epoll fd tracks N+1 events, where there are N events,
+    // one for each buffer, in the queue and one extra event for the queue
+    // client itself.
+    for (int i = 0; i < num_events; i++) {
+      int64_t index = static_cast<int64_t>(events[i].data.u64);
+
+      VLOG(1) << "New BufferHubQueue event " << i << ": index=" << index;
+
+      if (is_buffer_event_index(index) && (events[i].events & EPOLLIN)) {
+        auto buffer = buffers_[index];
+        ret = OnBufferReady(buffer);
+        if (ret < 0) {
+          LOG(ERROR) << "Failed to set buffer ready:" << strerror(-ret);
+          continue;
+        }
+        Enqueue(buffer, index);
+      } else if (is_buffer_event_index(index) &&
+                 (events[i].events & EPOLLHUP)) {
+        // This maybe caused by producer replacing an exising buffer slot.
+        // Currently the epoll FD is cleaned up when the replacement consumer
+        // client is imported.
+        LOG(WARNING) << "Receives EPOLLHUP at slot: " << index;
+      } else if (is_queue_event_index(index) && (events[i].events & EPOLLIN)) {
+        // Note that after buffer imports, if |count()| still returns 0, epoll
+        // wait will be tried again to acquire the newly imported buffer.
+        ret = OnBufferAllocated();
+        if (ret < 0) {
+          LOG(ERROR) << "Failed to import buffer:" << strerror(-ret);
+          continue;
+        }
+      } else {
+        LOG(WARNING) << "Unknown event " << i << ": u64=" << index
+                     << ": events=" << events[i].events;
+      }
+    }
+  }
+
+  return true;
+}
+
+int BufferHubQueue::AddBuffer(const std::shared_ptr<BufferHubBuffer>& buf,
+                              size_t slot) {
+  if (is_full()) {
+    // TODO(jwcai) Move the check into Producer's AllocateBuffer and consumer's
+    // import buffer.
+    LOG(ERROR) << "BufferHubQueue::AddBuffer queue is at maximum capacity: "
+               << capacity_;
+    return -E2BIG;
+  }
+
+  if (buffers_[slot] != nullptr) {
+    // Replace the buffer if the slot is preoccupied. This could happen when the
+    // producer side replaced the slot with a newly allocated buffer. Detach the
+    // buffer and set up with the new one.
+    DetachBuffer(slot);
+  }
+
+  epoll_event event = {.events = EPOLLIN | EPOLLET, .data = {.u64 = slot}};
+  const int ret = epoll_fd_.Control(EPOLL_CTL_ADD, buf->event_fd(), &event);
+  if (ret < 0) {
+    LOG(ERROR)
+        << "BufferHubQueue::AddBuffer: Failed to add buffer to epoll set:"
+        << strerror(-ret);
+    return ret;
+  }
+
+  buffers_[slot] = buf;
+  capacity_++;
+  return 0;
+}
+
+int BufferHubQueue::DetachBuffer(size_t slot) {
+  auto& buf = buffers_[slot];
+  if (buf == nullptr) {
+    LOG(ERROR) << "BufferHubQueue::DetachBuffer: Invalid slot: " << slot;
+    return -EINVAL;
+  }
+
+  const int ret = epoll_fd_.Control(EPOLL_CTL_DEL, buf->event_fd(), nullptr);
+  if (ret < 0) {
+    LOG(ERROR) << "BufferHubQueue::DetachBuffer: Failed to detach buffer from  "
+                  "epoll set:"
+               << strerror(-ret);
+    return ret;
+  }
+
+  buffers_[slot] = nullptr;
+  capacity_--;
+  return 0;
+}
+
+void BufferHubQueue::Enqueue(std::shared_ptr<BufferHubBuffer> buf,
+                             size_t slot) {
+  if (count() == capacity_) {
+    LOG(ERROR) << "Buffer queue is full!";
+    return;
+  }
+
+  // Set slot buffer back to vector.
+  // TODO(jwcai) Here have to dynamically allocate BufferInfo::metadata due to
+  // the limitation of the RingBuffer we are using. Would be better to refactor
+  // that.
+  BufferInfo buffer_info(slot, meta_size_);
+  // Swap buffer into vector.
+  std::swap(buffer_info.buffer, buf);
+  // Swap metadata loaded during onBufferReady into vector.
+  std::swap(buffer_info.metadata, meta_buffer_tmp_);
+
+  available_buffers_.Append(std::move(buffer_info));
+}
+
+std::shared_ptr<BufferHubBuffer> BufferHubQueue::Dequeue(int timeout,
+                                                         size_t* slot,
+                                                         void* meta) {
+  VLOG(1) << "Dequeue: count=" << count() << ", timeout=" << timeout;
+
+  if (count() == 0 && !WaitForBuffers(timeout))
+    return nullptr;
+
+  std::shared_ptr<BufferHubBuffer> buf;
+  BufferInfo& buffer_info = available_buffers_.Front();
+
+  // Report current pos as the output slot.
+  std::swap(buffer_info.slot, *slot);
+  // Swap buffer from vector to be returned later.
+  std::swap(buffer_info.buffer, buf);
+  // Swap metadata from vector into tmp so that we can write out to |meta|.
+  std::swap(buffer_info.metadata, meta_buffer_tmp_);
+
+  available_buffers_.PopFront();
+
+  if (!buf) {
+    LOG(ERROR) << "Dequeue: Buffer to be dequeued is nullptr";
+    return nullptr;
+  }
+
+  if (meta) {
+    std::copy(meta_buffer_tmp_.get(), meta_buffer_tmp_.get() + meta_size_,
+              reinterpret_cast<uint8_t*>(meta));
+  }
+
+  return buf;
+}
+
+ProducerQueue::ProducerQueue(size_t meta_size)
+    : ProducerQueue(meta_size, 0, 0, 0, 0) {}
+
+ProducerQueue::ProducerQueue(LocalChannelHandle handle, size_t meta_size)
+    : BASE(std::move(handle), meta_size) {}
+
+ProducerQueue::ProducerQueue(size_t meta_size, int usage_set_mask,
+                             int usage_clear_mask, int usage_deny_set_mask,
+                             int usage_deny_clear_mask)
+    : BASE(BufferHubRPC::kClientPath, meta_size) {
+  auto status = InvokeRemoteMethod<BufferHubRPC::CreateProducerQueue>(
+      meta_size_, usage_set_mask, usage_clear_mask, usage_deny_set_mask,
+      usage_deny_clear_mask);
+  if (!status) {
+    LOG(ERROR)
+        << "ProducerQueue::ProducerQueue: Failed to create producer queue: %s"
+        << status.GetErrorMessage();
+    Close(-status.error());
+    return;
+  }
+}
+
+int ProducerQueue::AllocateBuffer(int width, int height, int format, int usage,
+                                  size_t slice_count, size_t* out_slot) {
+  if (out_slot == nullptr) {
+    LOG(ERROR) << "Parameter out_slot cannot be null.";
+    return -EINVAL;
+  }
+
+  if (is_full()) {
+    LOG(ERROR) << "ProducerQueue::AllocateBuffer queue is at maximum capacity: "
+               << capacity();
+    return -E2BIG;
+  }
+
+  const size_t kBufferCount = 1U;
+
+  Status<std::vector<std::pair<LocalChannelHandle, size_t>>> status =
+      InvokeRemoteMethod<BufferHubRPC::ProducerQueueAllocateBuffers>(
+          width, height, format, usage, slice_count, kBufferCount);
+  if (!status) {
+    LOG(ERROR) << "ProducerQueue::AllocateBuffer failed to create producer "
+                  "buffer through BufferHub.";
+    return -status.error();
+  }
+
+  auto buffer_handle_slots = status.take();
+  CHECK_EQ(buffer_handle_slots.size(), kBufferCount)
+      << "BufferHubRPC::ProducerQueueAllocateBuffers should return one and "
+         "only one buffer handle.";
+
+  // We only allocate one buffer at a time.
+  auto& buffer_handle = buffer_handle_slots[0].first;
+  size_t buffer_slot = buffer_handle_slots[0].second;
+  VLOG(1) << "ProducerQueue::AllocateBuffer, new buffer, channel_handle: "
+          << buffer_handle.value();
+
+  *out_slot = buffer_slot;
+  return AddBuffer(BufferProducer::Import(std::move(buffer_handle)),
+                   buffer_slot);
+}
+
+int ProducerQueue::AddBuffer(const std::shared_ptr<BufferProducer>& buf,
+                             size_t slot) {
+  // For producer buffer, we need to enqueue the newly added buffer
+  // immediately. Producer queue starts with all buffers in available state.
+  const int ret = BufferHubQueue::AddBuffer(buf, slot);
+  if (ret < 0)
+    return ret;
+
+  Enqueue(buf, slot);
+  return 0;
+}
+
+int ProducerQueue::DetachBuffer(size_t slot) {
+  Status<int> status =
+      InvokeRemoteMethod<BufferHubRPC::ProducerQueueDetachBuffer>(slot);
+  if (!status) {
+    LOG(ERROR) << "ProducerQueue::DetachBuffer failed to detach producer "
+                  "buffer through BufferHub, error: "
+               << status.GetErrorMessage();
+    return -status.error();
+  }
+
+  return BufferHubQueue::DetachBuffer(slot);
+}
+
+std::shared_ptr<BufferProducer> ProducerQueue::Dequeue(int timeout,
+                                                       size_t* slot) {
+  auto buf = BufferHubQueue::Dequeue(timeout, slot, nullptr);
+  return std::static_pointer_cast<BufferProducer>(buf);
+}
+
+int ProducerQueue::OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) {
+  auto buffer = std::static_pointer_cast<BufferProducer>(buf);
+  return buffer->GainAsync();
+}
+
+ConsumerQueue::ConsumerQueue(LocalChannelHandle handle, size_t meta_size)
+    : BASE(std::move(handle), meta_size) {
+  // TODO(b/34387835) Import consumer queue in case the ProducerQueue we are
+  // based on was not empty.
+}
+
+int ConsumerQueue::ImportBuffers() {
+  Status<std::vector<std::pair<LocalChannelHandle, size_t>>> status =
+      InvokeRemoteMethod<BufferHubRPC::ConsumerQueueImportBuffers>();
+  if (!status) {
+    LOG(ERROR) << "ConsumerQueue::ImportBuffers failed to import consumer "
+                  "buffer through BufferBub, error: "
+               << status.GetErrorMessage();
+    return -status.error();
+  }
+
+  int last_error = 0;
+  int imported_buffers = 0;
+
+  auto buffer_handle_slots = status.take();
+  for (auto& buffer_handle_slot : buffer_handle_slots) {
+    VLOG(1) << "ConsumerQueue::ImportBuffers, new buffer, buffer_handle: "
+            << buffer_handle_slot.first.value();
+
+    std::unique_ptr<BufferConsumer> buffer_consumer =
+        BufferConsumer::Import(std::move(buffer_handle_slot.first));
+    int ret = AddBuffer(std::move(buffer_consumer), buffer_handle_slot.second);
+    if (ret < 0) {
+      LOG(ERROR) << "ConsumerQueue::ImportBuffers failed to add buffer, ret: "
+                 << strerror(-ret);
+      last_error = ret;
+      continue;
+    } else {
+      imported_buffers++;
+    }
+  }
+
+  return imported_buffers > 0 ? imported_buffers : last_error;
+}
+
+int ConsumerQueue::AddBuffer(const std::shared_ptr<BufferConsumer>& buf,
+                             size_t slot) {
+  // Consumer queue starts with all buffers in unavailable state.
+  return BufferHubQueue::AddBuffer(buf, slot);
+}
+
+std::shared_ptr<BufferConsumer> ConsumerQueue::Dequeue(int timeout,
+                                                       size_t* slot, void* meta,
+                                                       size_t meta_size) {
+  if (meta_size != meta_size_) {
+    LOG(ERROR) << "metadata size (" << meta_size
+               << ") for the dequeuing buffer does not match metadata size ("
+               << meta_size_ << ") for the queue.";
+    return nullptr;
+  }
+  auto buf = BufferHubQueue::Dequeue(timeout, slot, meta);
+  return std::static_pointer_cast<BufferConsumer>(buf);
+}
+
+int ConsumerQueue::OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) {
+  auto buffer = std::static_pointer_cast<BufferConsumer>(buf);
+  LocalHandle fence;
+  return buffer->Acquire(&fence, meta_buffer_tmp_.get(), meta_size_);
+}
+
+int ConsumerQueue::OnBufferAllocated() {
+  const int ret = ImportBuffers();
+  if (ret == 0) {
+    LOG(WARNING) << "No new buffer can be imported on buffer allocated event.";
+  } else if (ret < 0) {
+    LOG(ERROR) << "Failed to import buffers on buffer allocated event.";
+  }
+  VLOG(1) << "Imported " << ret << " consumer buffers.";
+  return ret;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_consumer.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_consumer.cpp
new file mode 100644
index 0000000..1ea3994
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_consumer.cpp
@@ -0,0 +1,11 @@
+#include "include/private/dvr/buffer_hub_queue_consumer.h"
+
+namespace android {
+namespace dvr {
+
+BufferHubQueueConsumer::BufferHubQueueConsumer(
+    const std::shared_ptr<BufferHubQueueCore>& core)
+    : core_(core) {}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_core.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_core.cpp
new file mode 100644
index 0000000..3fc0600
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_core.cpp
@@ -0,0 +1,93 @@
+#include "include/private/dvr/buffer_hub_queue_core.h"
+
+namespace android {
+namespace dvr {
+
+/* static */
+std::shared_ptr<BufferHubQueueCore> BufferHubQueueCore::Create() {
+  auto core = std::shared_ptr<BufferHubQueueCore>(new BufferHubQueueCore());
+  core->producer_ = ProducerQueue::Create<BufferMetadata>();
+  return core;
+}
+
+/* static */
+std::shared_ptr<BufferHubQueueCore> BufferHubQueueCore::Create(
+    const std::shared_ptr<ProducerQueue>& producer) {
+  if (producer->metadata_size() != sizeof(BufferMetadata)) {
+    LOG(ERROR)
+        << "BufferHubQueueCore::Create producer's metadata size is "
+        << "different than the size of BufferHubQueueCore::BufferMetadata";
+    return nullptr;
+  }
+
+  auto core = std::shared_ptr<BufferHubQueueCore>(new BufferHubQueueCore());
+  core->producer_ = producer;
+  return core;
+}
+
+BufferHubQueueCore::BufferHubQueueCore()
+    : generation_number_(0),
+      dequeue_timeout_ms_(BufferHubQueue::kNoTimeOut),
+      unique_id_(getUniqueId()) {}
+
+status_t BufferHubQueueCore::AllocateBuffer(uint32_t width, uint32_t height,
+                                            PixelFormat format, uint32_t usage,
+                                            size_t slice_count) {
+  size_t slot;
+
+  // Allocate new buffer through BufferHub and add it into |producer_| queue for
+  // bookkeeping.
+  if (producer_->AllocateBuffer(width, height, format, usage, slice_count,
+                                &slot) < 0) {
+    LOG(ERROR) << "Failed to allocate new buffer in BufferHub.";
+    return NO_MEMORY;
+  }
+
+  auto buffer_producer = producer_->GetBuffer(slot);
+
+  CHECK(buffer_producer != nullptr) << "Failed to get buffer producer at slot: "
+                                    << slot;
+
+  // Allocating a new buffer, |buffers_[slot]| should be in initial state.
+  CHECK(buffers_[slot].mGraphicBuffer == nullptr) << "AllocateBuffer: slot "
+                                                  << slot << " is not empty.";
+
+  // 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));
+
+  CHECK_EQ(NO_ERROR, graphic_buffer->initCheck())
+      << "Failed to init GraphicBuffer.";
+  buffers_[slot].mBufferProducer = buffer_producer;
+  buffers_[slot].mGraphicBuffer = graphic_buffer;
+
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueCore::DetachBuffer(size_t slot) {
+  // Detach the buffer producer via BufferHubRPC.
+  int ret = producer_->DetachBuffer(slot);
+  if (ret < 0) {
+    LOG(ERROR) << "BufferHubQueueCore::DetachBuffer failed through RPC, ret="
+               << strerror(-ret);
+    return ret;
+  }
+
+  // Reset in memory objects related the the buffer.
+  buffers_[slot].mBufferProducer = nullptr;
+  buffers_[slot].mGraphicBuffer = nullptr;
+  buffers_[slot].mBufferState.detachProducer();
+  return NO_ERROR;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_producer.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_producer.cpp
new file mode 100644
index 0000000..93d7307
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_producer.cpp
@@ -0,0 +1,390 @@
+#include "include/private/dvr/buffer_hub_queue_producer.h"
+
+namespace android {
+namespace dvr {
+
+BufferHubQueueProducer::BufferHubQueueProducer(
+    const std::shared_ptr<BufferHubQueueCore>& core)
+    : core_(core), req_buffer_count_(kInvalidBufferCount) {}
+
+status_t BufferHubQueueProducer::requestBuffer(int slot,
+                                               sp<GraphicBuffer>* buf) {
+  VLOG(1) << "requestBuffer: slot=" << slot;;
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (slot < 0 || slot >= req_buffer_count_) {
+    LOG(ERROR) << "requestBuffer: slot index " << slot << " out of range [0, "
+               << req_buffer_count_ << ")";
+    return BAD_VALUE;
+  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
+    LOG(ERROR) << "requestBuffer: slot " << slot
+               << " is not owned by the producer (state = "
+               << core_->buffers_[slot].mBufferState.string() << " )";
+    return BAD_VALUE;
+  }
+
+  core_->buffers_[slot].mRequestBufferCalled = true;
+  *buf = core_->buffers_[slot].mGraphicBuffer;
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::setMaxDequeuedBufferCount(
+    int max_dequeued_buffers) {
+  VLOG(1) << "setMaxDequeuedBufferCount: max_dequeued_buffers="
+          << max_dequeued_buffers;
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (max_dequeued_buffers <= 0 ||
+      max_dequeued_buffers >
+          static_cast<int>(BufferHubQueue::kMaxQueueCapacity)) {
+    LOG(ERROR) << "setMaxDequeuedBufferCount: " << max_dequeued_buffers
+               << " out of range (0, " << BufferHubQueue::kMaxQueueCapacity
+               << "]";
+    return BAD_VALUE;
+  }
+
+  req_buffer_count_ = max_dequeued_buffers;
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::setAsyncMode(bool /* async */) {
+  LOG(ERROR) << "BufferHubQueueProducer::setAsyncMode not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::dequeueBuffer(int* out_slot,
+                                               sp<Fence>* out_fence,
+                                               uint32_t width, uint32_t height,
+                                               PixelFormat format,
+                                               uint32_t usage,
+                                               FrameEventHistoryDelta* /* outTimestamps */) {
+  VLOG(1) << "dequeueBuffer: w=" << width << ", h=" << height
+          << " format=" << format << ", usage=" << usage;
+
+  status_t ret;
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (static_cast<int32_t>(core_->producer_->capacity()) < req_buffer_count_) {
+    // Lazy allocation. When the capacity of |core_->producer_| has not reach
+    // |req_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);
+    if (ret < 0)
+      return ret;
+  }
+
+  size_t slot;
+  std::shared_ptr<BufferProducer> buffer_producer;
+
+  for (size_t retry = 0; retry < BufferHubQueue::kMaxQueueCapacity; retry++) {
+    buffer_producer =
+        core_->producer_->Dequeue(core_->dequeue_timeout_ms_, &slot);
+    if (!buffer_producer)
+      return NO_MEMORY;
+
+    if (static_cast<int>(width) == buffer_producer->width() &&
+        static_cast<int>(height) == buffer_producer->height() &&
+        static_cast<int>(format) == buffer_producer->format()) {
+      // The producer queue returns a buffer producer matches the request.
+      break;
+    }
+
+    // Needs reallocation.
+    // TODO(jwcai) Consider use VLOG instead if we find this log is not useful.
+    LOG(INFO) << "dequeueBuffer,: requested buffer (w=" << width
+              << ", h=" << height << ", format=" << format
+              << ") is different from the buffer returned at slot: " << slot
+              << " (w=" << buffer_producer->width()
+              << ", h=" << buffer_producer->height()
+              << ", format=" << buffer_producer->format()
+              << "). Need re-allocattion.";
+    // Mark the slot as reallocating, so that later we can set
+    // BUFFER_NEEDS_REALLOCATION when the buffer actually get dequeued.
+    core_->buffers_[slot].mIsReallocating = true;
+
+    // Detach the old buffer once the allocation before allocating its
+    // replacement.
+    core_->DetachBuffer(slot);
+
+    // Allocate a new producer buffer with new buffer configs. Note that if
+    // there are already multiple buffers in the queue, the next one returned
+    // from |core_->producer_->Dequeue| may not be the new buffer we just
+    // reallocated. Retry up to BufferHubQueue::kMaxQueueCapacity times.
+    ret = core_->AllocateBuffer(width, height, format, usage, 1);
+    if (ret < 0)
+      return ret;
+  }
+
+  // With the BufferHub backed solution. Buffer slot returned from
+  // |core_->producer_->Dequeue| is guaranteed to avaiable for producer's use.
+  // It's either in free state (if the buffer has never been used before) or
+  // in queued state (if the buffer has been dequeued and queued back to
+  // BufferHubQueue).
+  // TODO(jwcai) Clean this up, make mBufferState compatible with BufferHub's
+  // model.
+  CHECK(core_->buffers_[slot].mBufferState.isFree() ||
+        core_->buffers_[slot].mBufferState.isQueued())
+      << "dequeueBuffer: slot " << slot << " is not free or queued.";
+
+  core_->buffers_[slot].mBufferState.freeQueued();
+  core_->buffers_[slot].mBufferState.dequeue();
+  VLOG(1) << "dequeueBuffer: slot=" << slot;
+
+  // TODO(jwcai) Handle fence properly. |BufferHub| has full fence support, we
+  // just need to exopose that through |BufferHubQueue| once we need fence.
+  *out_fence = Fence::NO_FENCE;
+  *out_slot = slot;
+  ret = NO_ERROR;
+
+  if (core_->buffers_[slot].mIsReallocating) {
+    ret |= BUFFER_NEEDS_REALLOCATION;
+    core_->buffers_[slot].mIsReallocating = false;
+  }
+
+  return ret;
+}
+
+status_t BufferHubQueueProducer::detachBuffer(int /* slot */) {
+  LOG(ERROR) << "BufferHubQueueProducer::detachBuffer not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::detachNextBuffer(
+    sp<GraphicBuffer>* /* out_buffer */, sp<Fence>* /* out_fence */) {
+  LOG(ERROR) << "BufferHubQueueProducer::detachNextBuffer not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::attachBuffer(
+    int* /* out_slot */, const sp<GraphicBuffer>& /* buffer */) {
+  // With this BufferHub backed implementation, we assume (for now) all buffers
+  // are allocated and owned by the BufferHub. Thus the attempt of transfering
+  // ownership of a buffer to the buffer queue is intentionally unsupported.
+  LOG(FATAL) << "BufferHubQueueProducer::attachBuffer not supported.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::queueBuffer(int slot,
+                                             const QueueBufferInput& input,
+                                             QueueBufferOutput* /* output */) {
+  VLOG(1) << "queueBuffer: slot " << slot;
+
+  int64_t timestamp;
+  sp<Fence> fence;
+
+  // 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);
+
+  if (fence == nullptr) {
+    LOG(ERROR) << "queueBuffer: fence is NULL";
+    return BAD_VALUE;
+  }
+
+  status_t ret;
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (slot < 0 || slot >= req_buffer_count_) {
+    LOG(ERROR) << "queueBuffer: slot index " << slot << " out of range [0, "
+               << req_buffer_count_ << ")";
+    return BAD_VALUE;
+  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
+    LOG(ERROR) << "queueBuffer: slot " << slot
+               << " is not owned by the producer (state = "
+               << core_->buffers_[slot].mBufferState.string() << " )";
+    return BAD_VALUE;
+  }
+
+  // Post the buffer producer with timestamp in the metadata.
+  auto buffer_producer = core_->buffers_[slot].mBufferProducer;
+  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.
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::cancelBuffer(int slot,
+                                              const sp<Fence>& fence) {
+  VLOG(1) << (__FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (slot < 0 || slot >= req_buffer_count_) {
+    LOG(ERROR) << "cancelBuffer: slot index " << slot << " out of range [0, "
+               << req_buffer_count_ << ")";
+    return BAD_VALUE;
+  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
+    LOG(ERROR) << "cancelBuffer: slot " << slot
+               << " is not owned by the producer (state = "
+               << core_->buffers_[slot].mBufferState.string() << " )";
+    return BAD_VALUE;
+  } else if (fence == NULL) {
+    LOG(ERROR) << "cancelBuffer: fence is NULL";
+    return BAD_VALUE;
+  }
+
+  auto buffer_producer = core_->buffers_[slot].mBufferProducer;
+  core_->producer_->Enqueue(buffer_producer, slot);
+  core_->buffers_[slot].mBufferState.cancel();
+  core_->buffers_[slot].mFence = fence;
+  VLOG(1) << "cancelBuffer: slot " << slot;
+
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::query(int what, int* out_value) {
+  VLOG(1) << (__FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (out_value == NULL) {
+    LOG(ERROR) << "query: out_value was NULL";
+    return BAD_VALUE;
+  }
+
+  int value = 0;
+  switch (what) {
+    case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+      value = 0;
+      break;
+    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:
+    case NATIVE_WINDOW_HEIGHT:
+    case NATIVE_WINDOW_FORMAT:
+    case NATIVE_WINDOW_STICKY_TRANSFORM:
+    case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+    case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
+    case NATIVE_WINDOW_DEFAULT_DATASPACE:
+    default:
+      return BAD_VALUE;
+  }
+
+  VLOG(1) << "query: key=" << what << ", v=" << value;
+  *out_value = value;
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::connect(
+    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.
+  VLOG(1) << (__FUNCTION__);
+  return NO_ERROR;
+}
+
+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.
+  VLOG(1) << (__FUNCTION__);
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::setSidebandStream(
+    const sp<NativeHandle>& stream) {
+  if (stream != NULL) {
+    // TODO(jwcai) Investigate how is is used, maybe use BufferHubBuffer's
+    // metadata.
+    LOG(ERROR) << "SidebandStream is not currently supported.";
+    return INVALID_OPERATION;
+  }
+  return NO_ERROR;
+}
+
+void BufferHubQueueProducer::allocateBuffers(uint32_t /* width */,
+                                             uint32_t /* height */,
+                                             PixelFormat /* format */,
+                                             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_|).
+  LOG(ERROR) << "BufferHubQueueProducer::allocateBuffers not implemented.";
+}
+
+status_t BufferHubQueueProducer::allowAllocation(bool /* allow */) {
+  LOG(ERROR) << "BufferHubQueueProducer::allowAllocation not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::setGenerationNumber(
+    uint32_t generation_number) {
+  VLOG(1) << (__FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+  core_->generation_number_ = generation_number;
+  return NO_ERROR;
+}
+
+String8 BufferHubQueueProducer::getConsumerName() const {
+  // BufferHub based implementation could have one to many producer/consumer
+  // relationship, thus |getConsumerName| from the producer side does not
+  // make any sense.
+  LOG(ERROR) << "BufferHubQueueProducer::getConsumerName not supported.";
+  return String8("BufferHubQueue::DummyConsumer");
+}
+
+status_t BufferHubQueueProducer::setSharedBufferMode(
+    bool /* shared_buffer_mode */) {
+  LOG(ERROR) << "BufferHubQueueProducer::setSharedBufferMode not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::setAutoRefresh(bool /* auto_refresh */) {
+  LOG(ERROR) << "BufferHubQueueProducer::setAutoRefresh not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::setDequeueTimeout(nsecs_t timeout) {
+  VLOG(1) << (__FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+  core_->dequeue_timeout_ms_ = static_cast<int>(timeout / (1000 * 1000));
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::getLastQueuedBuffer(
+    sp<GraphicBuffer>* /* out_buffer */, sp<Fence>* /* out_fence */,
+    float /*out_transform_matrix*/[16]) {
+  LOG(ERROR) << "BufferHubQueueProducer::getLastQueuedBuffer not implemented.";
+  return INVALID_OPERATION;
+}
+
+void BufferHubQueueProducer::getFrameTimestamps(
+    FrameEventHistoryDelta* /*outDelta*/) {
+  LOG(ERROR) << "BufferHubQueueProducer::getFrameTimestamps not implemented.";
+}
+
+status_t BufferHubQueueProducer::getUniqueId(uint64_t* out_id) const {
+  VLOG(1) << (__FUNCTION__);
+
+  *out_id = core_->unique_id_;
+  return NO_ERROR;
+}
+
+IBinder* BufferHubQueueProducer::onAsBinder() {
+  // BufferHubQueueProducer is a non-binder implementation of
+  // IGraphicBufferProducer.
+  LOG(WARNING) << "BufferHubQueueProducer::onAsBinder is not supported.";
+  return nullptr;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
new file mode 100644
index 0000000..83e77d4
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
@@ -0,0 +1,311 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
+#define ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
+
+#include <gui/BufferQueueDefs.h>
+
+#include <pdx/client.h>
+#include <private/dvr/buffer_hub_client.h>
+#include <private/dvr/epoll_file_descriptor.h>
+#include <private/dvr/ring_buffer.h>
+
+#include <memory>
+#include <vector>
+
+namespace android {
+namespace dvr {
+
+class ConsumerQueue;
+
+// |BufferHubQueue| manages a queue of |BufferHubBuffer|s. Buffers are
+// automatically re-requeued when released by the remote side.
+class BufferHubQueue : public pdx::Client {
+ public:
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+  template <typename T>
+  using Status = pdx::Status<T>;
+
+  virtual ~BufferHubQueue() {}
+  void Initialize();
+
+  // Create a new consumer queue that is attached to the producer. Returns
+  // a new consumer queue client or nullptr on failure.
+  std::unique_ptr<ConsumerQueue> CreateConsumerQueue();
+
+  // Return the number of buffers avaiable for dequeue.
+  size_t count() const { return available_buffers_.GetSize(); }
+
+  // Return the total number of buffers that the queue is tracking.
+  size_t capacity() const { return capacity_; }
+
+  // Return the size of metadata structure associated with this BufferBubQueue.
+  size_t metadata_size() const { return meta_size_; }
+
+  // Return whether the buffer queue is alrady full.
+  bool is_full() const { return  available_buffers_.IsFull(); }
+
+  explicit operator bool() const { return epoll_fd_.IsValid(); }
+
+  std::shared_ptr<BufferHubBuffer> GetBuffer(size_t slot) const {
+    return buffers_[slot];
+  }
+
+  // Enqueue a buffer marks buffer to be available (|Gain|'ed for producer
+  // and |Acquire|'ed for consumer. This is only used for internal bookkeeping.
+  void Enqueue(std::shared_ptr<BufferHubBuffer> buf, size_t slot);
+
+  // |BufferHubQueue| will keep track of at most this value of buffers.
+  static constexpr size_t kMaxQueueCapacity =
+      android::BufferQueueDefs::NUM_BUFFER_SLOTS;
+
+  // Special epoll data field indicating that the epoll event refers to the
+  // queue.
+  static constexpr int64_t kEpollQueueEventIndex = -1;
+
+  // When pass |kNoTimeout| to |Dequeue|, it will block indefinitely without a
+  // timeout.
+  static constexpr int kNoTimeOut = -1;
+
+ protected:
+  BufferHubQueue(LocalChannelHandle channel, size_t meta_size);
+  BufferHubQueue(const std::string& endpoint_path, size_t meta_size);
+
+  // Called by ProducerQueue::AddBuffer and ConsumerQueue::AddBuffer only. to
+  // register a buffer for epoll and internal bookkeeping.
+  int AddBuffer(const std::shared_ptr<BufferHubBuffer>& buf, size_t slot);
+
+  // Called by ProducerQueue::DetachBuffer and ConsumerQueue::DetachBuffer only.
+  // to deregister a buffer for epoll and internal bookkeeping.
+  virtual int DetachBuffer(size_t slot);
+
+  // Dequeue a buffer from the free queue, blocking until one is available. The
+  // timeout argument specifies the number of milliseconds that |Dequeue()| will
+  // block. Specifying a timeout of -1 causes |Dequeue()| to block indefinitely,
+  // while specifying a timeout equal to zero cause |Dequeue()| to return
+  // immediately, even if no buffers are available.
+  std::shared_ptr<BufferHubBuffer> Dequeue(int timeout, size_t* slot,
+                                           void* meta);
+
+  // Wait for buffers to be released and re-add them to the queue.
+  bool WaitForBuffers(int timeout);
+  virtual int OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) = 0;
+
+  // Called when a buffer is allocated remotely.
+  virtual int OnBufferAllocated() = 0;
+
+  // Data members to handle arbitrary metadata passed through BufferHub. It is
+  // fair to enforce that all buffers in the same queue share the same metadata
+  // type. |meta_size_| is used to store the size of metadata on queue creation;
+  // and |meta_buffer_tmp_| is allocated and resized to |meta_size_| on queue
+  // creation to be later used as temporary space so that we can avoid
+  // additional dynamic memory allocation in each |Enqueue| and |Dequeue| call.
+  size_t meta_size_;
+
+  // Here we intentionally choose |unique_ptr<uint8_t[]>| over vector<uint8_t>
+  // to disallow dynamic resizing for stability reasons.
+  std::unique_ptr<uint8_t[]> meta_buffer_tmp_;
+
+ private:
+  static constexpr size_t kMaxEvents = 128;
+
+  // The |u64| data field of an epoll event is interpreted as int64_t:
+  // When |index| >= 0 and |index| < kMaxQueueCapacity it refers to a specific
+  // element of |buffers_| as a direct index;
+  static bool is_buffer_event_index(int64_t index) {
+    return index >= 0 &&
+           index < static_cast<int64_t>(BufferHubQueue::kMaxQueueCapacity);
+  }
+
+  // When |index| == kEpollQueueEventIndex, it refers to the queue itself.
+  static bool is_queue_event_index(int64_t index) {
+    return index == BufferHubQueue::kEpollQueueEventIndex;
+  }
+
+  struct BufferInfo {
+    // A logical slot number that is assigned to a buffer at allocation time.
+    // The slot number remains unchanged during the entire life cycle of the
+    // buffer and should not be confused with the enqueue and dequeue order.
+    size_t slot;
+
+    // A BufferHubBuffer client.
+    std::shared_ptr<BufferHubBuffer> buffer;
+
+    // Metadata associated with the buffer.
+    std::unique_ptr<uint8_t[]> metadata;
+
+    BufferInfo() : BufferInfo(-1, 0) {}
+
+    BufferInfo(size_t slot, size_t metadata_size)
+        : slot(slot),
+          buffer(nullptr),
+          metadata(metadata_size ? new uint8_t[metadata_size] : nullptr) {}
+
+    BufferInfo(BufferInfo&& other)
+        : slot(other.slot),
+          buffer(std::move(other.buffer)),
+          metadata(std::move(other.metadata)) {}
+
+    BufferInfo& operator=(BufferInfo&& other) {
+      slot = other.slot;
+      buffer = std::move(other.buffer);
+      metadata = std::move(other.metadata);
+      return *this;
+    }
+
+   private:
+    BufferInfo(const BufferInfo&) = delete;
+    void operator=(BufferInfo&) = delete;
+  };
+
+  // Buffer queue:
+  // |buffers_| tracks all |BufferHubBuffer|s created by this |BufferHubQueue|.
+  std::vector<std::shared_ptr<BufferHubBuffer>> buffers_;
+
+  // |available_buffers_| uses |dvr::RingBuffer| to implementation queue
+  // sematics. When |Dequeue|, we pop the front element from
+  // |available_buffers_|, and  that buffer's reference count will decrease by
+  // one, while another reference in |buffers_| keeps the last reference to
+  // prevent the buffer from being deleted.
+  RingBuffer<BufferInfo> available_buffers_;
+
+  // Keep track with how many buffers have been added into the queue.
+  size_t capacity_;
+
+  // Epoll fd used to wait for BufferHub events.
+  EpollFileDescriptor epoll_fd_;
+
+  BufferHubQueue(const BufferHubQueue&) = delete;
+  void operator=(BufferHubQueue&) = delete;
+};
+
+class ProducerQueue : public pdx::ClientBase<ProducerQueue, BufferHubQueue> {
+ public:
+  template <typename Meta>
+  static std::unique_ptr<ProducerQueue> Create() {
+    return BASE::Create(sizeof(Meta));
+  }
+
+  // Usage bits in |usage_set_mask| will be automatically masked on. Usage bits
+  // in |usage_clear_mask| will be automatically masked off. Note that
+  // |usage_set_mask| and |usage_clear_mask| may conflict with each other, but
+  // |usage_set_mask| takes precedence over |usage_clear_mask|. All buffer
+  // allocation through this producer queue shall not have any of the usage bits
+  // in |usage_deny_set_mask| set. Allocation calls violating this will be
+  // rejected. All buffer allocation through this producer queue must have all
+  // the usage bits in |usage_deny_clear_mask| set. Allocation calls violating
+  // this will be rejected. Note that |usage_deny_set_mask| and
+  // |usage_deny_clear_mask| shall not conflict with each other. Such
+  // configuration will be treated as invalid input on creation.
+  template <typename Meta>
+  static std::unique_ptr<ProducerQueue> Create(int usage_set_mask,
+                                               int usage_clear_mask,
+                                               int usage_deny_set_mask,
+                                               int usage_deny_clear_mask) {
+    return BASE::Create(sizeof(Meta), usage_set_mask, usage_clear_mask,
+                        usage_deny_set_mask, usage_deny_clear_mask);
+  }
+
+  // Import a |ProducerQueue| from a channel handle.
+  template <typename Meta>
+  static std::unique_ptr<ProducerQueue> Import(LocalChannelHandle handle) {
+    return BASE::Create(std::move(handle), sizeof(Meta));
+  }
+
+  // Get a buffer producer. Note that the method doesn't check whether the
+  // buffer slot has a valid buffer that has been allocated already. When no
+  // buffer has been imported before it returns |nullptr|; otherwise it returns
+  // a shared pointer to a |BufferProducer|.
+  std::shared_ptr<BufferProducer> GetBuffer(size_t slot) const {
+    return std::static_pointer_cast<BufferProducer>(
+        BufferHubQueue::GetBuffer(slot));
+  }
+
+  // Allocate producer buffer to populate the queue. Once allocated, a producer
+  // buffer is automatically enqueue'd into the ProducerQueue and available to
+  // use (i.e. in |Gain|'ed mode).
+  // Returns Zero on success and negative error code when buffer allocation
+  // fails.
+  int AllocateBuffer(int width, int height, int format, int usage,
+                     size_t buffer_count, size_t* out_slot);
+
+  // Add a producer buffer to populate the queue. Once added, a producer buffer
+  // is available to use (i.e. in |Gain|'ed mode).
+  int AddBuffer(const std::shared_ptr<BufferProducer>& buf, size_t slot);
+
+  // Detach producer buffer from the queue.
+  // Returns Zero on success and negative error code when buffer detach
+  // fails.
+  int DetachBuffer(size_t slot) override;
+
+  // Dequeue a producer buffer to write. The returned buffer in |Gain|'ed mode,
+  // and caller should call Post() once it's done writing to release the buffer
+  // to the consumer side.
+  std::shared_ptr<BufferProducer> Dequeue(int timeout, size_t* slot);
+
+ private:
+  friend BASE;
+
+  // Constructors are automatically exposed through ProducerQueue::Create(...)
+  // static template methods inherited from ClientBase, which take the same
+  // arguments as the constructors.
+  explicit ProducerQueue(size_t meta_size);
+  ProducerQueue(LocalChannelHandle handle, size_t meta_size);
+  ProducerQueue(size_t meta_size, int usage_set_mask, int usage_clear_mask,
+                int usage_deny_set_mask, int usage_deny_clear_mask);
+
+  int OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) override;
+
+  // Producer buffer is always allocated from the client (i.e. local) side.
+  int OnBufferAllocated() override { return 0; }
+};
+
+class ConsumerQueue : public pdx::ClientBase<ConsumerQueue, BufferHubQueue> {
+ public:
+  // Get a buffer consumer. Note that the method doesn't check whether the
+  // buffer slot has a valid buffer that has been imported already. When no
+  // buffer has been imported before it returns |nullptr|; otherwise it returns
+  // a shared pointer to a |BufferConsumer|.
+  std::shared_ptr<BufferConsumer> GetBuffer(size_t slot) const {
+    return std::static_pointer_cast<BufferConsumer>(
+        BufferHubQueue::GetBuffer(slot));
+  }
+
+  // Import newly created buffers from the service side.
+  // Returns number of buffers successfully imported; or negative error code
+  // when buffer import fails.
+  int ImportBuffers();
+
+  // Dequeue a consumer buffer to read. The returned buffer in |Acquired|'ed
+  // mode, and caller should call Releasse() once it's done writing to release
+  // the buffer to the producer side. |meta| is passed along from BufferHub,
+  // The user of BufferProducer is responsible with making sure that the
+  // Dequeue() is done with the corect metadata type and size with those used
+  // when the buffer is orignally created.
+  template <typename Meta>
+  std::shared_ptr<BufferConsumer> Dequeue(int timeout, size_t* slot,
+                                          Meta* meta) {
+    return Dequeue(timeout, slot, meta, sizeof(*meta));
+  }
+
+  std::shared_ptr<BufferConsumer> Dequeue(int timeout, size_t* slot, void* meta,
+                                          size_t meta_size);
+
+ private:
+  friend BASE;
+
+  ConsumerQueue(LocalChannelHandle handle, size_t meta_size);
+
+  // Add a consumer buffer to populate the queue. Once added, a consumer buffer
+  // is NOT available to use until the producer side |Post| it. |WaitForBuffers|
+  // will catch the |Post| and |Acquire| the buffer to make it available for
+  // consumer.
+  int AddBuffer(const std::shared_ptr<BufferConsumer>& buf, size_t slot);
+
+  int OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) override;
+
+  int OnBufferAllocated() override;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_consumer.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_consumer.h
new file mode 100644
index 0000000..8d7bfcc
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_consumer.h
@@ -0,0 +1,22 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_CONSUMER_H_
+#define ANDROID_DVR_BUFFER_HUB_QUEUE_CONSUMER_H_
+
+#include <private/dvr/buffer_hub_queue_core.h>
+
+#include <gui/IGraphicBufferConsumer.h>
+
+namespace android {
+namespace dvr {
+
+class BufferHubQueueConsumer : public IGraphicBufferConsumer {
+ public:
+  BufferHubQueueConsumer(const std::shared_ptr<BufferHubQueueCore>& core);
+
+ private:
+  std::shared_ptr<BufferHubQueueCore> core_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_CONSUMER_H_
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_core.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_core.h
new file mode 100644
index 0000000..ba0c0c5
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_core.h
@@ -0,0 +1,116 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_CORE_H_
+#define ANDROID_DVR_BUFFER_HUB_QUEUE_CORE_H_
+
+#include <private/dvr/buffer_hub_queue_client.h>
+
+#include <gui/BufferSlot.h>
+#include <utils/Atomic.h>
+#include <utils/String8.h>
+
+#include <mutex>
+
+namespace android {
+namespace dvr {
+
+class BufferHubQueueCore {
+ private:
+  friend class BufferHubQueueProducer;
+
+ public:
+  // Create a BufferHubQueueCore instance by creating a new producer queue.
+  static std::shared_ptr<BufferHubQueueCore> Create();
+
+  // Create a BufferHubQueueCore instance by importing an existing prodcuer queue.
+  static std::shared_ptr<BufferHubQueueCore> Create(
+      const std::shared_ptr<ProducerQueue>& producer);
+
+  struct BufferMetadata {
+    // Timestamp of the frame.
+    int64_t timestamp;
+  };
+
+  class NativeBuffer
+      : public ANativeObjectBase<ANativeWindowBuffer, NativeBuffer, RefBase> {
+   public:
+    explicit NativeBuffer(const std::shared_ptr<BufferHubBuffer>& buffer)
+        : buffer_(buffer) {
+      ANativeWindowBuffer::width = buffer_->width();
+      ANativeWindowBuffer::height = buffer_->height();
+      ANativeWindowBuffer::stride = buffer_->stride();
+      ANativeWindowBuffer::format = buffer_->format();
+      ANativeWindowBuffer::usage = buffer_->usage();
+      ANativeWindowBuffer::handle = buffer_->buffer()->handle();
+    }
+
+    std::shared_ptr<BufferHubBuffer> buffer() { return buffer_; }
+
+   private:
+    std::shared_ptr<BufferHubBuffer> buffer_;
+  };
+
+  // Get the unique buffer producer queue backing this BufferHubQueue.
+  std::shared_ptr<ProducerQueue> GetProducerQueue() { return producer_; }
+
+ private:
+  using LocalHandle = pdx::LocalHandle;
+
+  struct BufferHubSlot : public BufferSlot {
+    BufferHubSlot() : mBufferProducer(nullptr), mIsReallocating(false) {}
+    // BufferSlot comes from android framework, using m prefix to comply with
+    // the name convention with the reset of data fields from BufferSlot.
+    std::shared_ptr<BufferProducer> mBufferProducer;
+    bool mIsReallocating;
+  };
+
+  static String8 getUniqueName() {
+    static volatile int32_t counter = 0;
+    return String8::format("unnamed-%d-%d", getpid(),
+                           android_atomic_inc(&counter));
+  }
+
+  static uint64_t getUniqueId() {
+    static std::atomic<uint32_t> counter{0};
+    static uint64_t id = static_cast<uint64_t>(getpid()) << 32;
+    return id | counter++;
+  }
+
+  // Private constructor to force use of |Create|.
+  BufferHubQueueCore();
+
+  // Allocate a new buffer producer through BufferHub.
+  int AllocateBuffer(uint32_t width, uint32_t height, PixelFormat format,
+                     uint32_t usage, size_t slice_count);
+
+  // Detach a buffer producer through BufferHub.
+  int DetachBuffer(size_t slot);
+
+  // Mutex for thread safety.
+  std::mutex mutex_;
+
+  // |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|.
+  // TODO(jwcai) The buffer allocated to a slot will also be replaced if the
+  // requested buffer usage or geometry differs from that of the buffer
+  // allocated to a slot.
+  BufferHubSlot buffers_[BufferHubQueue::kMaxQueueCapacity];
+
+  // Concreate implementation backed by BufferHubBuffer.
+  std::shared_ptr<ProducerQueue> producer_;
+
+  // |generation_number_| stores the current generation number of the attached
+  // producer. Any attempt to attach a buffer with a different generation
+  // number will fail.
+  uint32_t generation_number_;
+
+  // Sets how long dequeueBuffer or attachBuffer will block if a buffer or
+  // slot is not yet available. The timeout is stored in milliseconds.
+  int dequeue_timeout_ms_;
+
+  const uint64_t unique_id_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_CORE_H_
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_producer.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_producer.h
new file mode 100644
index 0000000..5b1a7e0
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_producer.h
@@ -0,0 +1,118 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_PRODUCER_H_
+#define ANDROID_DVR_BUFFER_HUB_QUEUE_PRODUCER_H_
+
+#include <private/dvr/buffer_hub_queue_core.h>
+
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+namespace dvr {
+
+class BufferHubQueueProducer : public IGraphicBufferProducer {
+ public:
+  BufferHubQueueProducer(const std::shared_ptr<BufferHubQueueCore>& core);
+
+  // See |IGraphicBufferProducer::requestBuffer|
+  status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) override;
+
+  // For the BufferHub based implementation. All buffers in the queue are
+  // allowed to be dequeued from the consumer side. It call always returns
+  // 0 for |NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS| query. Thus setting
+  // |max_dequeued_buffers| here can be considered the same as setting queue
+  // capacity.
+  //
+  // See |IGraphicBufferProducer::setMaxDequeuedBufferCount| for more info
+  status_t setMaxDequeuedBufferCount(int max_dequeued_buffers) override;
+
+  // See |IGraphicBufferProducer::setAsyncMode|
+  status_t setAsyncMode(bool async) override;
+
+  // See |IGraphicBufferProducer::dequeueBuffer|
+  status_t dequeueBuffer(int* out_slot, sp<Fence>* out_fence, uint32_t width,
+                         uint32_t height, PixelFormat format,
+                         uint32_t usage,
+                         FrameEventHistoryDelta* outTimestamps) override;
+
+  // See |IGraphicBufferProducer::detachBuffer|
+  status_t detachBuffer(int slot) override;
+
+  // See |IGraphicBufferProducer::detachNextBuffer|
+  status_t detachNextBuffer(sp<GraphicBuffer>* out_buffer,
+                            sp<Fence>* out_fence) override;
+
+  // See |IGraphicBufferProducer::attachBuffer|
+  status_t attachBuffer(int* out_slot, const sp<GraphicBuffer>& buffer) override;
+
+  // See |IGraphicBufferProducer::queueBuffer|
+  status_t queueBuffer(int slot, const QueueBufferInput& input,
+                       QueueBufferOutput* output) override;
+
+  // See |IGraphicBufferProducer::cancelBuffer|
+  status_t cancelBuffer(int slot, const sp<Fence>& fence) override;
+
+  // See |IGraphicBufferProducer::query|
+  status_t query(int what, int* out_value) override;
+
+  // See |IGraphicBufferProducer::connect|
+  status_t connect(const sp<IProducerListener>& listener, int api,
+                   bool producer_controlled_by_app,
+                   QueueBufferOutput* output) override;
+
+  // See |IGraphicBufferProducer::disconnect|
+  status_t disconnect(int api, DisconnectMode mode = DisconnectMode::Api) override;
+
+  // See |IGraphicBufferProducer::setSidebandStream|
+  status_t setSidebandStream(const sp<NativeHandle>& stream) override;
+
+  // See |IGraphicBufferProducer::allocateBuffers|
+  void allocateBuffers(uint32_t width, uint32_t height, PixelFormat format,
+                       uint32_t usage) override;
+
+  // See |IGraphicBufferProducer::allowAllocation|
+  status_t allowAllocation(bool allow) override;
+
+  // See |IGraphicBufferProducer::setGenerationNumber|
+  status_t setGenerationNumber(uint32_t generation_number) override;
+
+  // See |IGraphicBufferProducer::getConsumerName|
+  String8 getConsumerName() const override;
+
+  // See |IGraphicBufferProducer::setSharedBufferMode|
+  status_t setSharedBufferMode(bool shared_buffer_mode) override;
+
+  // See |IGraphicBufferProducer::setAutoRefresh|
+  status_t setAutoRefresh(bool auto_refresh) override;
+
+  // See |IGraphicBufferProducer::setDequeueTimeout|
+  status_t setDequeueTimeout(nsecs_t timeout) override;
+
+  // See |IGraphicBufferProducer::getLastQueuedBuffer|
+  status_t getLastQueuedBuffer(sp<GraphicBuffer>* out_buffer,
+                               sp<Fence>* out_fence,
+                               float out_transform_matrix[16]) override;
+
+  // See |IGraphicBufferProducer::getFrameTimestamps|
+  void getFrameTimestamps(FrameEventHistoryDelta* /*outDelta*/) override;
+
+  // See |IGraphicBufferProducer::getUniqueId|
+  status_t getUniqueId(uint64_t* out_id) const override;
+
+ protected:
+  IBinder* onAsBinder() override;
+
+ 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_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_PRODUCER_H_
diff --git a/libs/vr/libbufferhubqueue/tests/Android.mk b/libs/vr/libbufferhubqueue/tests/Android.mk
new file mode 100644
index 0000000..59061e6
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/tests/Android.mk
@@ -0,0 +1,38 @@
+LOCAL_PATH := $(call my-dir)
+
+shared_libraries := \
+	libbase \
+	libbinder \
+	libcutils \
+	libgui \
+	liblog \
+	libhardware \
+	libui \
+	libutils \
+
+static_libraries := \
+	libbufferhubqueue \
+	libbufferhub \
+	libchrome \
+	libdvrcommon \
+	libpdx_default_transport \
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := buffer_hub_queue-test.cpp
+LOCAL_STATIC_LIBRARIES := $(static_libraries)
+LOCAL_SHARED_LIBRARIES := $(shared_libraries)
+LOCAL_EXPORT_C_INCLUDE_DIRS := ${LOCAL_C_INCLUDES}
+LOCAL_CFLAGS := -DTRACE=0 -O0 -g
+LOCAL_MODULE := buffer_hub_queue-test
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := buffer_hub_queue_producer-test.cpp
+LOCAL_STATIC_LIBRARIES := $(static_libraries)
+LOCAL_SHARED_LIBRARIES := $(shared_libraries)
+LOCAL_EXPORT_C_INCLUDE_DIRS := ${LOCAL_C_INCLUDES}
+LOCAL_CFLAGS := -DTRACE=0 -O0 -g
+LOCAL_MODULE := buffer_hub_queue_producer-test
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
new file mode 100644
index 0000000..841554e
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
@@ -0,0 +1,292 @@
+#include <base/logging.h>
+#include <private/dvr/buffer_hub_client.h>
+#include <private/dvr/buffer_hub_queue_client.h>
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+namespace android {
+namespace dvr {
+
+using pdx::LocalHandle;
+
+namespace {
+
+constexpr int kBufferWidth = 100;
+constexpr int kBufferHeight = 1;
+constexpr int kBufferFormat = HAL_PIXEL_FORMAT_BLOB;
+constexpr int kBufferUsage = GRALLOC_USAGE_SW_READ_RARELY;
+constexpr int kBufferSliceCount = 1;  // number of slices in each buffer
+
+class BufferHubQueueTest : public ::testing::Test {
+ public:
+  template <typename Meta>
+  void CreateQueues(int usage_set_mask = 0, int usage_clear_mask = 0,
+                    int usage_deny_set_mask = 0,
+                    int usage_deny_clear_mask = 0) {
+    producer_queue_ =
+        ProducerQueue::Create<Meta>(usage_set_mask, usage_clear_mask,
+                                    usage_deny_set_mask, usage_deny_clear_mask);
+    ASSERT_NE(nullptr, producer_queue_);
+
+    consumer_queue_ = producer_queue_->CreateConsumerQueue();
+    ASSERT_NE(nullptr, consumer_queue_);
+  }
+
+  void AllocateBuffer() {
+    // Create producer buffer.
+    size_t slot;
+    int ret = producer_queue_->AllocateBuffer(kBufferWidth, kBufferHeight,
+                                              kBufferFormat, kBufferUsage,
+                                              kBufferSliceCount, &slot);
+    ASSERT_EQ(ret, 0);
+  }
+
+ protected:
+  std::unique_ptr<ProducerQueue> producer_queue_;
+  std::unique_ptr<ConsumerQueue> consumer_queue_;
+};
+
+TEST_F(BufferHubQueueTest, TestDequeue) {
+  const size_t nb_dequeue_times = 16;
+
+  CreateQueues<size_t>();
+
+  // Allocate only one buffer.
+  AllocateBuffer();
+
+  // But dequeue multiple times.
+  for (size_t i = 0; i < nb_dequeue_times; i++) {
+    size_t slot;
+    auto p1 = producer_queue_->Dequeue(0, &slot);
+    ASSERT_NE(nullptr, p1);
+    size_t mi = i;
+    ASSERT_EQ(p1->Post(LocalHandle(), &mi, sizeof(mi)), 0);
+    size_t mo;
+    auto c1 = consumer_queue_->Dequeue(100, &slot, &mo);
+    ASSERT_NE(nullptr, c1);
+    ASSERT_EQ(mi, mo);
+    c1->Release(LocalHandle());
+  }
+}
+
+TEST_F(BufferHubQueueTest, TestProducerConsumer) {
+  const size_t nb_buffer = 16;
+  size_t slot;
+  uint64_t seq;
+
+  CreateQueues<uint64_t>();
+
+  for (size_t i = 0; i < nb_buffer; i++) {
+    AllocateBuffer();
+
+    // Producer queue has all the available buffers on initialize.
+    ASSERT_EQ(producer_queue_->count(), i + 1);
+    ASSERT_EQ(producer_queue_->capacity(), i + 1);
+
+    // Consumer queue has no avaiable buffer on initialize.
+    ASSERT_EQ(consumer_queue_->count(), 0U);
+    // Consumer queue does not import buffers until a dequeue is issued.
+    ASSERT_EQ(consumer_queue_->capacity(), i);
+    // Dequeue returns nullptr since no buffer is ready to consumer, but
+    // this implicitly triggers buffer import and bump up |capacity|.
+    auto consumer_null = consumer_queue_->Dequeue(0, &slot, &seq);
+    ASSERT_EQ(nullptr, consumer_null);
+    ASSERT_EQ(consumer_queue_->capacity(), i + 1);
+  }
+
+  for (size_t i = 0; i < nb_buffer; i++) {
+    // First time, there is no buffer available to dequeue.
+    auto buffer_null = consumer_queue_->Dequeue(0, &slot, &seq);
+    ASSERT_EQ(nullptr, buffer_null);
+
+    // Make sure Producer buffer is Post()'ed so that it's ready to Accquire
+    // in the consumer's Dequeue() function.
+    auto producer = producer_queue_->Dequeue(0, &slot);
+    ASSERT_NE(nullptr, producer);
+
+    uint64_t seq_in = static_cast<uint64_t>(i);
+    ASSERT_EQ(producer->Post({}, &seq_in, sizeof(seq_in)), 0);
+
+    // Second time, the just |Post()|'ed buffer should be dequeued.
+    uint64_t seq_out = 0;
+    auto consumer = consumer_queue_->Dequeue(0, &slot, &seq_out);
+    ASSERT_NE(nullptr, consumer);
+    ASSERT_EQ(seq_in, seq_out);
+  }
+}
+
+struct TestMetadata {
+  char a;
+  int32_t b;
+  int64_t c;
+};
+
+TEST_F(BufferHubQueueTest, TestMetadata) {
+  CreateQueues<TestMetadata>();
+  AllocateBuffer();
+
+  std::vector<TestMetadata> ms = {
+      {'0', 0, 0}, {'1', 10, 3333}, {'@', 123, 1000000000}};
+
+  for (auto mi : ms) {
+    size_t slot;
+    auto p1 = producer_queue_->Dequeue(0, &slot);
+    ASSERT_NE(nullptr, p1);
+    ASSERT_EQ(p1->Post(LocalHandle(-1), &mi, sizeof(mi)), 0);
+    TestMetadata mo;
+    auto c1 = consumer_queue_->Dequeue(0, &slot, &mo);
+    ASSERT_EQ(mi.a, mo.a);
+    ASSERT_EQ(mi.b, mo.b);
+    ASSERT_EQ(mi.c, mo.c);
+    c1->Release(LocalHandle(-1));
+  }
+}
+
+TEST_F(BufferHubQueueTest, TestMetadataMismatch) {
+  CreateQueues<int64_t>();
+  AllocateBuffer();
+
+  int64_t mi = 3;
+  size_t slot;
+  auto p1 = producer_queue_->Dequeue(0, &slot);
+  ASSERT_NE(nullptr, p1);
+  ASSERT_EQ(p1->Post(LocalHandle(-1), &mi, sizeof(mi)), 0);
+
+  int32_t mo;
+  // Acquire a buffer with mismatched metadata is not OK.
+  auto c1 = consumer_queue_->Dequeue(0, &slot, &mo);
+  ASSERT_EQ(nullptr, c1);
+}
+
+TEST_F(BufferHubQueueTest, TestEnqueue) {
+  CreateQueues<int64_t>();
+  AllocateBuffer();
+
+  size_t slot;
+  auto p1 = producer_queue_->Dequeue(0, &slot);
+  ASSERT_NE(nullptr, p1);
+
+  int64_t mo;
+  producer_queue_->Enqueue(p1, slot);
+  auto c1 = consumer_queue_->Dequeue(0, &slot, &mo);
+  ASSERT_EQ(nullptr, c1);
+}
+
+TEST_F(BufferHubQueueTest, TestAllocateBuffer) {
+  CreateQueues<int64_t>();
+
+  size_t s1;
+  AllocateBuffer();
+  auto p1 = producer_queue_->Dequeue(0, &s1);
+  ASSERT_NE(nullptr, p1);
+
+  // producer queue is exhausted
+  size_t s2;
+  auto p2_null = producer_queue_->Dequeue(0, &s2);
+  ASSERT_EQ(nullptr, p2_null);
+
+  // dynamically add buffer.
+  AllocateBuffer();
+  ASSERT_EQ(producer_queue_->count(), 1U);
+  ASSERT_EQ(producer_queue_->capacity(), 2U);
+
+  // now we can dequeue again
+  auto p2 = producer_queue_->Dequeue(0, &s2);
+  ASSERT_NE(nullptr, p2);
+  ASSERT_EQ(producer_queue_->count(), 0U);
+  // p1 and p2 should have different slot number
+  ASSERT_NE(s1, s2);
+
+  // Consumer queue does not import buffers until |Dequeue| or |ImportBuffers|
+  // are called. So far consumer_queue_ should be empty.
+  ASSERT_EQ(consumer_queue_->count(), 0U);
+
+  int64_t seq = 1;
+  ASSERT_EQ(p1->Post(LocalHandle(), seq), 0);
+  size_t cs1, cs2;
+  auto c1 = consumer_queue_->Dequeue(0, &cs1, &seq);
+  ASSERT_NE(nullptr, c1);
+  ASSERT_EQ(consumer_queue_->count(), 0U);
+  ASSERT_EQ(consumer_queue_->capacity(), 2U);
+  ASSERT_EQ(cs1, s1);
+
+  ASSERT_EQ(p2->Post(LocalHandle(), seq), 0);
+  auto c2 = consumer_queue_->Dequeue(0, &cs2, &seq);
+  ASSERT_NE(nullptr, c2);
+  ASSERT_EQ(cs2, s2);
+}
+
+TEST_F(BufferHubQueueTest, TestUsageSetMask) {
+  const int set_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
+  CreateQueues<int64_t>(set_mask, 0, 0, 0);
+
+  // When allocation, leave out |set_mask| from usage bits on purpose.
+  size_t slot;
+  int ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage & ~set_mask,
+      kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, 0);
+
+  auto p1 = producer_queue_->Dequeue(0, &slot);
+  ASSERT_EQ(p1->usage() & set_mask, set_mask);
+}
+
+TEST_F(BufferHubQueueTest, TestUsageClearMask) {
+  const int clear_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
+  CreateQueues<int64_t>(0, clear_mask, 0, 0);
+
+  // When allocation, add |clear_mask| into usage bits on purpose.
+  size_t slot;
+  int ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage | clear_mask,
+      kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, 0);
+
+  auto p1 = producer_queue_->Dequeue(0, &slot);
+  ASSERT_EQ(p1->usage() & clear_mask, 0);
+}
+
+TEST_F(BufferHubQueueTest, TestUsageDenySetMask) {
+  const int deny_set_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
+  CreateQueues<int64_t>(0, 0, deny_set_mask, 0);
+
+  // Now that |deny_set_mask| is illegal, allocation without those bits should
+  // be able to succeed.
+  size_t slot;
+  int ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage & ~deny_set_mask,
+      kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, 0);
+
+  // While allocation with those bits should fail.
+  ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage | deny_set_mask,
+      kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, -EINVAL);
+}
+
+TEST_F(BufferHubQueueTest, TestUsageDenyClearMask) {
+  const int deny_clear_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
+  CreateQueues<int64_t>(0, 0, 0, deny_clear_mask);
+
+  // Now that clearing |deny_clear_mask| is illegal (i.e. setting these bits are
+  // mandatory), allocation with those bits should be able to succeed.
+  size_t slot;
+  int ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat,
+      kBufferUsage | deny_clear_mask, kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, 0);
+
+  // While allocation without those bits should fail.
+  ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat,
+      kBufferUsage & ~deny_clear_mask, kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, -EINVAL);
+}
+
+}  // namespace
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
new file mode 100644
index 0000000..5bb121a
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
@@ -0,0 +1,23 @@
+#include <private/dvr/buffer_hub_queue_producer.h>
+
+#include <base/logging.h>
+#include <gui/Surface.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace dvr {
+
+namespace {
+
+class BufferHubQueueProducerTest : public ::testing::Test {};
+
+TEST_F(BufferHubQueueProducerTest, TempTestBufferHubQueueProducer) {
+  auto core = BufferHubQueueCore::Create();
+  sp<BufferHubQueueProducer> producer = new BufferHubQueueProducer(core);
+  sp<Surface> surface = new Surface(producer, true);
+}
+
+}  // namespace
+
+}  // namespace dvr
+}  // namespace android