Merge "Allow specifying ANGLE in manifest via meta-data"
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 5a8d8db..2b0a461 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -77,6 +77,9 @@
         output.writeBool(false);
     }
 
+    memcpy(output.writeInplace(16 * sizeof(float)),
+           colorTransform.asArray(), 16 * sizeof(float));
+
     return NO_ERROR;
 }
 
@@ -130,6 +133,8 @@
         sidebandStream = NativeHandle::create(input.readNativeHandle(), true);
     }
 
+    colorTransform = mat4(static_cast<const float*>(input.readInplace(16 * sizeof(float))));
+
     return NO_ERROR;
 }
 
@@ -314,6 +319,10 @@
         what |= eSidebandStreamChanged;
         sidebandStream = other.sidebandStream;
     }
+    if (other.what & eColorTransformChanged) {
+        what |= eColorTransformChanged;
+        colorTransform = other.colorTransform;
+    }
 
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 09ea0f6..1ac9609 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -602,6 +602,18 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setColorTransform(
+    const sp<SurfaceControl>& sc, const mat3& matrix, const vec3& translation) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eColorTransformChanged;
+    s->colorTransform = mat4(matrix, translation);
+    return *this;
+}
+
 // ---------------------------------------------------------------------------
 
 DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 9a9f633..e06e2b1 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -23,6 +23,7 @@
 #include <utils/Errors.h>
 
 #include <gui/IGraphicBufferProducer.h>
+#include <math/mat4.h>
 #include <math/vec3.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
@@ -72,6 +73,7 @@
         eSurfaceDamageRegionChanged = 0x02000000,
         eApiChanged = 0x04000000,
         eSidebandStreamChanged = 0x08000000,
+        eColorTransformChanged = 0x10000000,
     };
 
     layer_state_t()
@@ -94,7 +96,8 @@
             crop(Rect::INVALID_RECT),
             dataspace(ui::Dataspace::UNKNOWN),
             surfaceDamageRegion(),
-            api(-1) {
+            api(-1),
+            colorTransform(mat4()) {
         matrix.dsdx = matrix.dtdy = 1.0f;
         matrix.dsdy = matrix.dtdx = 0.0f;
         hdrMetadata.validTypes = 0;
@@ -150,6 +153,7 @@
     Region surfaceDamageRegion;
     int32_t api;
     sp<NativeHandle> sidebandStream;
+    mat4 colorTransform;
 };
 
 struct ComposerState {
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 314b118..69a759f 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -269,6 +269,10 @@
 
         Transaction& destroySurface(const sp<SurfaceControl>& sc);
 
+        // Set a color transform matrix on the given layer on the built-in display.
+        Transaction& setColorTransform(const sp<SurfaceControl>& sc, const mat3& matrix,
+                                       const vec3& translation);
+
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
 
diff --git a/libs/vr/libbufferhub/Android.bp b/libs/vr/libbufferhub/Android.bp
index 69b6422..edc9131 100644
--- a/libs/vr/libbufferhub/Android.bp
+++ b/libs/vr/libbufferhub/Android.bp
@@ -13,10 +13,13 @@
 // limitations under the License.
 
 sourceFiles = [
+    "buffer_hub_base.cpp",
     "buffer_hub_client.cpp",
     "buffer_hub_rpc.cpp",
+    "consumer_buffer.cpp",
     "detached_buffer.cpp",
     "ion_buffer.cpp",
+    "producer_buffer.cpp",
 ]
 
 localIncludeFiles = [
diff --git a/libs/vr/libbufferhub/buffer_hub_base.cpp b/libs/vr/libbufferhub/buffer_hub_base.cpp
new file mode 100644
index 0000000..b2bcda7
--- /dev/null
+++ b/libs/vr/libbufferhub/buffer_hub_base.cpp
@@ -0,0 +1,224 @@
+#include <poll.h>
+#include <sys/epoll.h>
+
+#include <pdx/default_transport/client_channel.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <private/dvr/buffer_hub_base.h>
+
+using android::pdx::LocalChannelHandle;
+using android::pdx::LocalHandle;
+using android::pdx::Status;
+using android::pdx::default_transport::ClientChannel;
+using android::pdx::default_transport::ClientChannelFactory;
+
+namespace android {
+namespace dvr {
+
+BufferHubBase::BufferHubBase(LocalChannelHandle channel_handle)
+    : Client{pdx::default_transport::ClientChannel::Create(
+          std::move(channel_handle))},
+      id_(-1),
+      cid_(-1) {}
+BufferHubBase::BufferHubBase(const std::string& endpoint_path)
+    : Client{pdx::default_transport::ClientChannelFactory::Create(
+          endpoint_path)},
+      id_(-1),
+      cid_(-1) {}
+
+BufferHubBase::~BufferHubBase() {
+  if (metadata_header_ != nullptr) {
+    metadata_buffer_.Unlock();
+  }
+}
+
+Status<LocalChannelHandle> BufferHubBase::CreateConsumer() {
+  Status<LocalChannelHandle> status =
+      InvokeRemoteMethod<BufferHubRPC::NewConsumer>();
+  ALOGE_IF(!status,
+           "BufferHub::CreateConsumer: Failed to create consumer channel: %s",
+           status.GetErrorMessage().c_str());
+  return status;
+}
+
+int BufferHubBase::ImportBuffer() {
+  ATRACE_NAME("BufferHubBase::ImportBuffer");
+
+  Status<BufferDescription<LocalHandle>> status =
+      InvokeRemoteMethod<BufferHubRPC::GetBuffer>();
+  if (!status) {
+    ALOGE("BufferHubBase::ImportBuffer: Failed to get buffer: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  } else if (status.get().id() < 0) {
+    ALOGE("BufferHubBase::ImportBuffer: Received an invalid id!");
+    return -EIO;
+  }
+
+  auto buffer_desc = status.take();
+
+  // Stash the buffer id to replace the value in id_.
+  const int new_id = buffer_desc.id();
+
+  // Import the buffer.
+  IonBuffer ion_buffer;
+  ALOGD_IF(TRACE, "BufferHubBase::ImportBuffer: id=%d.", buffer_desc.id());
+
+  if (const int ret = buffer_desc.ImportBuffer(&ion_buffer))
+    return ret;
+
+  // Import the metadata.
+  IonBuffer metadata_buffer;
+  if (const int ret = buffer_desc.ImportMetadata(&metadata_buffer)) {
+    ALOGE("Failed to import metadata buffer, error=%d", ret);
+    return ret;
+  }
+  size_t metadata_buf_size = metadata_buffer.width();
+  if (metadata_buf_size < BufferHubDefs::kMetadataHeaderSize) {
+    ALOGE("BufferHubBase::ImportBuffer: metadata buffer too small: %zu",
+          metadata_buf_size);
+    return -ENOMEM;
+  }
+
+  // If all imports succee, replace the previous buffer and id.
+  buffer_ = std::move(ion_buffer);
+  metadata_buffer_ = std::move(metadata_buffer);
+  metadata_buf_size_ = metadata_buf_size;
+  user_metadata_size_ = metadata_buf_size_ - BufferHubDefs::kMetadataHeaderSize;
+
+  void* metadata_ptr = nullptr;
+  if (const int ret =
+          metadata_buffer_.Lock(BufferHubDefs::kMetadataUsage, /*x=*/0,
+                                /*y=*/0, metadata_buf_size_,
+                                /*height=*/1, &metadata_ptr)) {
+    ALOGE("BufferHubBase::ImportBuffer: Failed to lock metadata.");
+    return ret;
+  }
+
+  // Set up shared fences.
+  shared_acquire_fence_ = buffer_desc.take_acquire_fence();
+  shared_release_fence_ = buffer_desc.take_release_fence();
+  if (!shared_acquire_fence_ || !shared_release_fence_) {
+    ALOGE("BufferHubBase::ImportBuffer: Failed to import shared fences.");
+    return -EIO;
+  }
+
+  metadata_header_ =
+      reinterpret_cast<BufferHubDefs::MetadataHeader*>(metadata_ptr);
+  if (user_metadata_size_) {
+    user_metadata_ptr_ =
+        reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(metadata_ptr) +
+                                BufferHubDefs::kMetadataHeaderSize);
+  } else {
+    user_metadata_ptr_ = nullptr;
+  }
+
+  id_ = new_id;
+  cid_ = buffer_desc.buffer_cid();
+  buffer_state_bit_ = buffer_desc.buffer_state_bit();
+
+  // Note that here the buffer state is mapped from shared memory as an atomic
+  // object. The std::atomic's constructor will not be called so that the
+  // original value stored in the memory region will be preserved.
+  buffer_state_ = &metadata_header_->buffer_state;
+  ALOGD_IF(TRACE,
+           "BufferHubBase::ImportBuffer: id=%d, buffer_state=%" PRIx64 ".",
+           id(), buffer_state_->load());
+  fence_state_ = &metadata_header_->fence_state;
+  ALOGD_IF(TRACE,
+           "BufferHubBase::ImportBuffer: id=%d, fence_state=%" PRIx64 ".", id(),
+           fence_state_->load());
+
+  return 0;
+}
+
+int BufferHubBase::CheckMetadata(size_t user_metadata_size) const {
+  if (user_metadata_size && !user_metadata_ptr_) {
+    ALOGE("BufferHubBase::CheckMetadata: doesn't support custom metadata.");
+    return -EINVAL;
+  }
+  if (user_metadata_size > user_metadata_size_) {
+    ALOGE("BufferHubBase::CheckMetadata: too big: %zu, maximum: %zu.",
+          user_metadata_size, user_metadata_size_);
+    return -E2BIG;
+  }
+  return 0;
+}
+
+int BufferHubBase::UpdateSharedFence(const LocalHandle& new_fence,
+                                     const LocalHandle& shared_fence) {
+  if (pending_fence_fd_.Get() != new_fence.Get()) {
+    // First, replace the old fd if there was already one. Skipping if the new
+    // one is the same as the old.
+    if (pending_fence_fd_.IsValid()) {
+      const int ret = epoll_ctl(shared_fence.Get(), EPOLL_CTL_DEL,
+                                pending_fence_fd_.Get(), nullptr);
+      ALOGW_IF(ret,
+               "BufferHubBase::UpdateSharedFence: failed to remove old fence "
+               "fd from epoll set, error: %s.",
+               strerror(errno));
+    }
+
+    if (new_fence.IsValid()) {
+      // If ready fence is valid, we put that into the epoll set.
+      epoll_event event;
+      event.events = EPOLLIN;
+      event.data.u64 = buffer_state_bit();
+      pending_fence_fd_ = new_fence.Duplicate();
+      if (epoll_ctl(shared_fence.Get(), EPOLL_CTL_ADD, pending_fence_fd_.Get(),
+                    &event) < 0) {
+        const int error = errno;
+        ALOGE(
+            "BufferHubBase::UpdateSharedFence: failed to add new fence fd "
+            "into epoll set, error: %s.",
+            strerror(error));
+        return -error;
+      }
+      // Set bit in fence state to indicate that there is a fence from this
+      // producer or consumer.
+      fence_state_->fetch_or(buffer_state_bit());
+    } else {
+      // Unset bit in fence state to indicate that there is no fence, so that
+      // when consumer to acquire or producer to acquire, it knows no need to
+      // check fence for this buffer.
+      fence_state_->fetch_and(~buffer_state_bit());
+    }
+  }
+
+  return 0;
+}
+
+int BufferHubBase::Poll(int timeout_ms) {
+  ATRACE_NAME("BufferHubBase::Poll");
+  pollfd p = {event_fd(), POLLIN, 0};
+  return poll(&p, 1, timeout_ms);
+}
+
+int BufferHubBase::Lock(int usage, int x, int y, int width, int height,
+                        void** address) {
+  return buffer_.Lock(usage, x, y, width, height, address);
+}
+
+int BufferHubBase::Unlock() { return buffer_.Unlock(); }
+
+int BufferHubBase::GetBlobReadWritePointer(size_t size, void** addr) {
+  int width = static_cast<int>(size);
+  int height = 1;
+  int ret = Lock(usage(), 0, 0, width, height, addr);
+  if (ret == 0)
+    Unlock();
+  return ret;
+}
+
+int BufferHubBase::GetBlobReadOnlyPointer(size_t size, void** addr) {
+  return GetBlobReadWritePointer(size, addr);
+}
+
+void BufferHubBase::GetBlobFds(int* fds, size_t* fds_count,
+                               size_t max_fds_count) const {
+  size_t numFds = static_cast<size_t>(native_handle()->numFds);
+  *fds_count = std::min(max_fds_count, numFds);
+  std::copy(native_handle()->data, native_handle()->data + *fds_count, fds);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhub/buffer_hub_client.cpp b/libs/vr/libbufferhub/buffer_hub_client.cpp
index 577cba9..3f20024 100644
--- a/libs/vr/libbufferhub/buffer_hub_client.cpp
+++ b/libs/vr/libbufferhub/buffer_hub_client.cpp
@@ -1,20 +1,12 @@
-#include <private/dvr/buffer_hub_client.h>
-
-#include <log/log.h>
-#include <poll.h>
-#include <sys/epoll.h>
-#include <utils/Trace.h>
-
 #include <mutex>
 
+#include <log/log.h>
 #include <pdx/default_transport/client_channel.h>
 #include <pdx/default_transport/client_channel_factory.h>
-
-#include "include/private/dvr/bufferhub_rpc.h"
+#include <private/dvr/buffer_hub_client.h>
+#include <utils/Trace.h>
 
 using android::pdx::LocalChannelHandle;
-using android::pdx::LocalHandle;
-using android::pdx::Status;
 using android::pdx::default_transport::ClientChannel;
 using android::pdx::default_transport::ClientChannelFactory;
 
@@ -39,615 +31,5 @@
   }
 }
 
-BufferHubBuffer::BufferHubBuffer(LocalChannelHandle channel_handle)
-    : Client{pdx::default_transport::ClientChannel::Create(
-          std::move(channel_handle))},
-      id_(-1),
-      cid_(-1) {}
-BufferHubBuffer::BufferHubBuffer(const std::string& endpoint_path)
-    : Client{pdx::default_transport::ClientChannelFactory::Create(
-          endpoint_path)},
-      id_(-1),
-      cid_(-1) {}
-
-BufferHubBuffer::~BufferHubBuffer() {
-  if (metadata_header_ != nullptr) {
-    metadata_buffer_.Unlock();
-  }
-}
-
-Status<LocalChannelHandle> BufferHubBuffer::CreateConsumer() {
-  Status<LocalChannelHandle> status =
-      InvokeRemoteMethod<BufferHubRPC::NewConsumer>();
-  ALOGE_IF(!status,
-           "BufferHub::CreateConsumer: Failed to create consumer channel: %s",
-           status.GetErrorMessage().c_str());
-  return status;
-}
-
-int BufferHubBuffer::ImportBuffer() {
-  ATRACE_NAME("BufferHubBuffer::ImportBuffer");
-
-  Status<BufferDescription<LocalHandle>> status =
-      InvokeRemoteMethod<BufferHubRPC::GetBuffer>();
-  if (!status) {
-    ALOGE("BufferHubBuffer::ImportBuffer: Failed to get buffer: %s",
-          status.GetErrorMessage().c_str());
-    return -status.error();
-  } else if (status.get().id() < 0) {
-    ALOGE("BufferHubBuffer::ImportBuffer: Received an invalid id!");
-    return -EIO;
-  }
-
-  auto buffer_desc = status.take();
-
-  // Stash the buffer id to replace the value in id_.
-  const int new_id = buffer_desc.id();
-
-  // Import the buffer.
-  IonBuffer ion_buffer;
-  ALOGD_IF(TRACE, "BufferHubBuffer::ImportBuffer: id=%d.", buffer_desc.id());
-
-  if (const int ret = buffer_desc.ImportBuffer(&ion_buffer))
-    return ret;
-
-  // Import the metadata.
-  IonBuffer metadata_buffer;
-  if (const int ret = buffer_desc.ImportMetadata(&metadata_buffer)) {
-    ALOGE("Failed to import metadata buffer, error=%d", ret);
-    return ret;
-  }
-  size_t metadata_buf_size = metadata_buffer.width();
-  if (metadata_buf_size < BufferHubDefs::kMetadataHeaderSize) {
-    ALOGE("BufferHubBuffer::ImportBuffer: metadata buffer too small: %zu",
-          metadata_buf_size);
-    return -ENOMEM;
-  }
-
-  // If all imports succee, replace the previous buffer and id.
-  buffer_ = std::move(ion_buffer);
-  metadata_buffer_ = std::move(metadata_buffer);
-  metadata_buf_size_ = metadata_buf_size;
-  user_metadata_size_ = metadata_buf_size_ - BufferHubDefs::kMetadataHeaderSize;
-
-  void* metadata_ptr = nullptr;
-  if (const int ret =
-          metadata_buffer_.Lock(BufferHubDefs::kMetadataUsage, /*x=*/0,
-                                /*y=*/0, metadata_buf_size_,
-                                /*height=*/1, &metadata_ptr)) {
-    ALOGE("BufferHubBuffer::ImportBuffer: Failed to lock metadata.");
-    return ret;
-  }
-
-  // Set up shared fences.
-  shared_acquire_fence_ = buffer_desc.take_acquire_fence();
-  shared_release_fence_ = buffer_desc.take_release_fence();
-  if (!shared_acquire_fence_ || !shared_release_fence_) {
-    ALOGE("BufferHubBuffer::ImportBuffer: Failed to import shared fences.");
-    return -EIO;
-  }
-
-  metadata_header_ =
-      reinterpret_cast<BufferHubDefs::MetadataHeader*>(metadata_ptr);
-  if (user_metadata_size_) {
-    user_metadata_ptr_ =
-        reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(metadata_ptr) +
-                                BufferHubDefs::kMetadataHeaderSize);
-  } else {
-    user_metadata_ptr_ = nullptr;
-  }
-
-  id_ = new_id;
-  cid_ = buffer_desc.buffer_cid();
-  buffer_state_bit_ = buffer_desc.buffer_state_bit();
-
-  // Note that here the buffer state is mapped from shared memory as an atomic
-  // object. The std::atomic's constructor will not be called so that the
-  // original value stored in the memory region will be preserved.
-  buffer_state_ = &metadata_header_->buffer_state;
-  ALOGD_IF(TRACE,
-           "BufferHubBuffer::ImportBuffer: id=%d, buffer_state=%" PRIx64 ".",
-           id(), buffer_state_->load());
-  fence_state_ = &metadata_header_->fence_state;
-  ALOGD_IF(TRACE,
-           "BufferHubBuffer::ImportBuffer: id=%d, fence_state=%" PRIx64 ".",
-           id(), fence_state_->load());
-
-  return 0;
-}
-
-inline int BufferHubBuffer::CheckMetadata(size_t user_metadata_size) const {
-  if (user_metadata_size && !user_metadata_ptr_) {
-    ALOGE("BufferHubBuffer::CheckMetadata: doesn't support custom metadata.");
-    return -EINVAL;
-  }
-  if (user_metadata_size > user_metadata_size_) {
-    ALOGE("BufferHubBuffer::CheckMetadata: too big: %zu, maximum: %zu.",
-          user_metadata_size, user_metadata_size_);
-    return -E2BIG;
-  }
-  return 0;
-}
-
-int BufferHubBuffer::UpdateSharedFence(const LocalHandle& new_fence,
-                                       const LocalHandle& shared_fence) {
-  if (pending_fence_fd_.Get() != new_fence.Get()) {
-    // First, replace the old fd if there was already one. Skipping if the new
-    // one is the same as the old.
-    if (pending_fence_fd_.IsValid()) {
-      const int ret = epoll_ctl(shared_fence.Get(), EPOLL_CTL_DEL,
-                                pending_fence_fd_.Get(), nullptr);
-      ALOGW_IF(ret,
-               "BufferHubBuffer::UpdateSharedFence: failed to remove old fence "
-               "fd from epoll set, error: %s.",
-               strerror(errno));
-    }
-
-    if (new_fence.IsValid()) {
-      // If ready fence is valid, we put that into the epoll set.
-      epoll_event event;
-      event.events = EPOLLIN;
-      event.data.u64 = buffer_state_bit();
-      pending_fence_fd_ = new_fence.Duplicate();
-      if (epoll_ctl(shared_fence.Get(), EPOLL_CTL_ADD, pending_fence_fd_.Get(),
-                    &event) < 0) {
-        const int error = errno;
-        ALOGE(
-            "BufferHubBuffer::UpdateSharedFence: failed to add new fence fd "
-            "into epoll set, error: %s.",
-            strerror(error));
-        return -error;
-      }
-      // Set bit in fence state to indicate that there is a fence from this
-      // producer or consumer.
-      fence_state_->fetch_or(buffer_state_bit());
-    } else {
-      // Unset bit in fence state to indicate that there is no fence, so that
-      // when consumer to acquire or producer to acquire, it knows no need to
-      // check fence for this buffer.
-      fence_state_->fetch_and(~buffer_state_bit());
-    }
-  }
-
-  return 0;
-}
-
-int BufferHubBuffer::Poll(int timeout_ms) {
-  ATRACE_NAME("BufferHubBuffer::Poll");
-  pollfd p = {event_fd(), POLLIN, 0};
-  return poll(&p, 1, timeout_ms);
-}
-
-int BufferHubBuffer::Lock(int usage, int x, int y, int width, int height,
-                          void** address) {
-  return buffer_.Lock(usage, x, y, width, height, address);
-}
-
-int BufferHubBuffer::Unlock() { return buffer_.Unlock(); }
-
-int BufferHubBuffer::GetBlobReadWritePointer(size_t size, void** addr) {
-  int width = static_cast<int>(size);
-  int height = 1;
-  int ret = Lock(usage(), 0, 0, width, height, addr);
-  if (ret == 0)
-    Unlock();
-  return ret;
-}
-
-int BufferHubBuffer::GetBlobReadOnlyPointer(size_t size, void** addr) {
-  return GetBlobReadWritePointer(size, addr);
-}
-
-void BufferHubBuffer::GetBlobFds(int* fds, size_t* fds_count,
-                                 size_t max_fds_count) const {
-  size_t numFds = static_cast<size_t>(native_handle()->numFds);
-  *fds_count = std::min(max_fds_count, numFds);
-  std::copy(native_handle()->data, native_handle()->data + *fds_count, fds);
-}
-
-BufferConsumer::BufferConsumer(LocalChannelHandle channel)
-    : BASE(std::move(channel)) {
-  const int ret = ImportBuffer();
-  if (ret < 0) {
-    ALOGE("BufferConsumer::BufferConsumer: Failed to import buffer: %s",
-          strerror(-ret));
-    Close(ret);
-  }
-}
-
-std::unique_ptr<BufferConsumer> BufferConsumer::Import(
-    LocalChannelHandle channel) {
-  ATRACE_NAME("BufferConsumer::Import");
-  ALOGD_IF(TRACE, "BufferConsumer::Import: channel=%d", channel.value());
-  return BufferConsumer::Create(std::move(channel));
-}
-
-std::unique_ptr<BufferConsumer> BufferConsumer::Import(
-    Status<LocalChannelHandle> status) {
-  return Import(status ? status.take()
-                       : LocalChannelHandle{nullptr, -status.error()});
-}
-
-int BufferConsumer::LocalAcquire(DvrNativeBufferMetadata* out_meta,
-                                 LocalHandle* out_fence) {
-  if (!out_meta)
-    return -EINVAL;
-
-  // Only check producer bit and this consumer buffer's particular consumer bit.
-  // The buffer is can be acquired iff: 1) producer bit is set; 2) consumer bit
-  // is not set.
-  uint64_t buffer_state = buffer_state_->load();
-  if (!BufferHubDefs::IsBufferPosted(buffer_state, buffer_state_bit())) {
-    ALOGE("BufferConsumer::LocalAcquire: not posted, id=%d state=%" PRIx64
-          " buffer_state_bit=%" PRIx64 ".",
-          id(), buffer_state, buffer_state_bit());
-    return -EBUSY;
-  }
-
-  // Copy the canonical metadata.
-  void* metadata_ptr = reinterpret_cast<void*>(&metadata_header_->metadata);
-  memcpy(out_meta, metadata_ptr, sizeof(DvrNativeBufferMetadata));
-  // Fill in the user_metadata_ptr in address space of the local process.
-  if (out_meta->user_metadata_size) {
-    out_meta->user_metadata_ptr =
-        reinterpret_cast<uint64_t>(user_metadata_ptr_);
-  } else {
-    out_meta->user_metadata_ptr = 0;
-  }
-
-  uint64_t fence_state = fence_state_->load();
-  // If there is an acquire fence from producer, we need to return it.
-  if (fence_state & BufferHubDefs::kProducerStateBit) {
-    *out_fence = shared_acquire_fence_.Duplicate();
-  }
-
-  // Set the consumer bit unique to this consumer.
-  BufferHubDefs::ModifyBufferState(buffer_state_, 0ULL, buffer_state_bit());
-  return 0;
-}
-
-int BufferConsumer::Acquire(LocalHandle* ready_fence) {
-  return Acquire(ready_fence, nullptr, 0);
-}
-
-int BufferConsumer::Acquire(LocalHandle* ready_fence, void* meta,
-                            size_t user_metadata_size) {
-  ATRACE_NAME("BufferConsumer::Acquire");
-
-  if (const int error = CheckMetadata(user_metadata_size))
-    return error;
-
-  DvrNativeBufferMetadata canonical_meta;
-  if (const int error = LocalAcquire(&canonical_meta, ready_fence))
-    return error;
-
-  if (meta && user_metadata_size) {
-    void* metadata_src =
-        reinterpret_cast<void*>(canonical_meta.user_metadata_ptr);
-    if (metadata_src) {
-      memcpy(meta, metadata_src, user_metadata_size);
-    } else {
-      ALOGW("BufferConsumer::Acquire: no user-defined metadata.");
-    }
-  }
-
-  auto status = InvokeRemoteMethod<BufferHubRPC::ConsumerAcquire>();
-  if (!status)
-    return -status.error();
-  return 0;
-}
-
-int BufferConsumer::AcquireAsync(DvrNativeBufferMetadata* out_meta,
-                                 LocalHandle* out_fence) {
-  ATRACE_NAME("BufferConsumer::AcquireAsync");
-
-  if (const int error = LocalAcquire(out_meta, out_fence))
-    return error;
-
-  auto status = SendImpulse(BufferHubRPC::ConsumerAcquire::Opcode);
-  if (!status)
-    return -status.error();
-  return 0;
-}
-
-int BufferConsumer::LocalRelease(const DvrNativeBufferMetadata* meta,
-                                 const LocalHandle& release_fence) {
-  if (const int error = CheckMetadata(meta->user_metadata_size))
-    return error;
-
-  // Check invalid state transition.
-  uint64_t buffer_state = buffer_state_->load();
-  if (!BufferHubDefs::IsBufferAcquired(buffer_state)) {
-    ALOGE("BufferConsumer::LocalRelease: not acquired id=%d state=%" PRIx64 ".",
-          id(), buffer_state);
-    return -EBUSY;
-  }
-
-  // On release, only the user requested metadata is copied back into the shared
-  // memory for metadata. Since there are multiple consumers, it doesn't make
-  // sense to send the canonical metadata back to the producer. However, one of
-  // the consumer can still choose to write up to user_metadata_size bytes of
-  // data into user_metadata_ptr.
-  if (meta->user_metadata_ptr && meta->user_metadata_size) {
-    void* metadata_src = reinterpret_cast<void*>(meta->user_metadata_ptr);
-    memcpy(user_metadata_ptr_, metadata_src, meta->user_metadata_size);
-  }
-
-  // Send out the release fence through the shared epoll fd. Note that during
-  // releasing the producer is not expected to be polling on the fence.
-  if (const int error = UpdateSharedFence(release_fence, shared_release_fence_))
-    return error;
-
-  // For release operation, the client don't need to change the state as it's
-  // bufferhubd's job to flip the produer bit once all consumers are released.
-  return 0;
-}
-
-int BufferConsumer::Release(const LocalHandle& release_fence) {
-  ATRACE_NAME("BufferConsumer::Release");
-
-  DvrNativeBufferMetadata meta;
-  if (const int error = LocalRelease(&meta, release_fence))
-    return error;
-
-  return ReturnStatusOrError(InvokeRemoteMethod<BufferHubRPC::ConsumerRelease>(
-      BorrowedFence(release_fence.Borrow())));
-}
-
-int BufferConsumer::ReleaseAsync() {
-  DvrNativeBufferMetadata meta;
-  return ReleaseAsync(&meta, LocalHandle());
-}
-
-int BufferConsumer::ReleaseAsync(const DvrNativeBufferMetadata* meta,
-                                 const LocalHandle& release_fence) {
-  ATRACE_NAME("BufferConsumer::ReleaseAsync");
-
-  if (const int error = LocalRelease(meta, release_fence))
-    return error;
-
-  return ReturnStatusOrError(
-      SendImpulse(BufferHubRPC::ConsumerRelease::Opcode));
-}
-
-int BufferConsumer::Discard() { return Release(LocalHandle()); }
-
-int BufferConsumer::SetIgnore(bool ignore) {
-  return ReturnStatusOrError(
-      InvokeRemoteMethod<BufferHubRPC::ConsumerSetIgnore>(ignore));
-}
-
-BufferProducer::BufferProducer(uint32_t width, uint32_t height, uint32_t format,
-                               uint64_t usage, size_t user_metadata_size)
-    : BASE(BufferHubRPC::kClientPath) {
-  ATRACE_NAME("BufferProducer::BufferProducer");
-  ALOGD_IF(TRACE,
-           "BufferProducer::BufferProducer: fd=%d width=%u height=%u format=%u "
-           "usage=%" PRIx64 " user_metadata_size=%zu",
-           event_fd(), width, height, format, usage, user_metadata_size);
-
-  auto status = InvokeRemoteMethod<BufferHubRPC::CreateBuffer>(
-      width, height, format, usage, user_metadata_size);
-  if (!status) {
-    ALOGE(
-        "BufferProducer::BufferProducer: Failed to create producer buffer: %s",
-        status.GetErrorMessage().c_str());
-    Close(-status.error());
-    return;
-  }
-
-  const int ret = ImportBuffer();
-  if (ret < 0) {
-    ALOGE(
-        "BufferProducer::BufferProducer: Failed to import producer buffer: %s",
-        strerror(-ret));
-    Close(ret);
-  }
-}
-
-BufferProducer::BufferProducer(uint64_t usage, size_t size)
-    : BASE(BufferHubRPC::kClientPath) {
-  ATRACE_NAME("BufferProducer::BufferProducer");
-  ALOGD_IF(TRACE, "BufferProducer::BufferProducer: usage=%" PRIx64 " size=%zu",
-           usage, size);
-  const int width = static_cast<int>(size);
-  const int height = 1;
-  const int format = HAL_PIXEL_FORMAT_BLOB;
-  const size_t user_metadata_size = 0;
-
-  auto status = InvokeRemoteMethod<BufferHubRPC::CreateBuffer>(
-      width, height, format, usage, user_metadata_size);
-  if (!status) {
-    ALOGE("BufferProducer::BufferProducer: Failed to create blob: %s",
-          status.GetErrorMessage().c_str());
-    Close(-status.error());
-    return;
-  }
-
-  const int ret = ImportBuffer();
-  if (ret < 0) {
-    ALOGE(
-        "BufferProducer::BufferProducer: Failed to import producer buffer: %s",
-        strerror(-ret));
-    Close(ret);
-  }
-}
-
-BufferProducer::BufferProducer(LocalChannelHandle channel)
-    : BASE(std::move(channel)) {
-  const int ret = ImportBuffer();
-  if (ret < 0) {
-    ALOGE(
-        "BufferProducer::BufferProducer: Failed to import producer buffer: %s",
-        strerror(-ret));
-    Close(ret);
-  }
-}
-
-int BufferProducer::LocalPost(const DvrNativeBufferMetadata* meta,
-                              const LocalHandle& ready_fence) {
-  if (const int error = CheckMetadata(meta->user_metadata_size))
-    return error;
-
-  // Check invalid state transition.
-  uint64_t buffer_state = buffer_state_->load();
-  if (!BufferHubDefs::IsBufferGained(buffer_state)) {
-    ALOGE("BufferProducer::LocalPost: not gained, id=%d state=%" PRIx64 ".",
-          id(), buffer_state);
-    return -EBUSY;
-  }
-
-  // Copy the canonical metadata.
-  void* metadata_ptr = reinterpret_cast<void*>(&metadata_header_->metadata);
-  memcpy(metadata_ptr, meta, sizeof(DvrNativeBufferMetadata));
-  // Copy extra user requested metadata.
-  if (meta->user_metadata_ptr && meta->user_metadata_size) {
-    void* metadata_src = reinterpret_cast<void*>(meta->user_metadata_ptr);
-    memcpy(user_metadata_ptr_, metadata_src, meta->user_metadata_size);
-  }
-
-  // Send out the acquire fence through the shared epoll fd. Note that during
-  // posting no consumer is not expected to be polling on the fence.
-  if (const int error = UpdateSharedFence(ready_fence, shared_acquire_fence_))
-    return error;
-
-  // Set the producer bit atomically to transit into posted state.
-  BufferHubDefs::ModifyBufferState(buffer_state_, 0ULL,
-                                   BufferHubDefs::kProducerStateBit);
-  return 0;
-}
-
-int BufferProducer::Post(const LocalHandle& ready_fence, const void* meta,
-                         size_t user_metadata_size) {
-  ATRACE_NAME("BufferProducer::Post");
-
-  // Populate cononical metadata for posting.
-  DvrNativeBufferMetadata canonical_meta;
-  canonical_meta.user_metadata_ptr = reinterpret_cast<uint64_t>(meta);
-  canonical_meta.user_metadata_size = user_metadata_size;
-
-  if (const int error = LocalPost(&canonical_meta, ready_fence))
-    return error;
-
-  return ReturnStatusOrError(InvokeRemoteMethod<BufferHubRPC::ProducerPost>(
-      BorrowedFence(ready_fence.Borrow())));
-}
-
-int BufferProducer::PostAsync(const DvrNativeBufferMetadata* meta,
-                              const LocalHandle& ready_fence) {
-  ATRACE_NAME("BufferProducer::PostAsync");
-
-  if (const int error = LocalPost(meta, ready_fence))
-    return error;
-
-  return ReturnStatusOrError(SendImpulse(BufferHubRPC::ProducerPost::Opcode));
-}
-
-int BufferProducer::LocalGain(DvrNativeBufferMetadata* out_meta,
-                              LocalHandle* out_fence) {
-  uint64_t buffer_state = buffer_state_->load();
-  ALOGD_IF(TRACE, "BufferProducer::LocalGain: buffer=%d, state=%" PRIx64 ".",
-           id(), buffer_state);
-
-  if (!out_meta)
-    return -EINVAL;
-
-  if (!BufferHubDefs::IsBufferReleased(buffer_state)) {
-    if (BufferHubDefs::IsBufferGained(buffer_state)) {
-      // We don't want to log error when gaining a newly allocated
-      // buffer.
-      ALOGI("BufferProducer::LocalGain: already gained id=%d.", id());
-      return -EALREADY;
-    }
-    ALOGE("BufferProducer::LocalGain: not released id=%d state=%" PRIx64 ".",
-          id(), buffer_state);
-    return -EBUSY;
-  }
-
-  // Canonical metadata is undefined on Gain. Except for user_metadata and
-  // release_fence_mask. Fill in the user_metadata_ptr in address space of the
-  // local process.
-  if (metadata_header_->metadata.user_metadata_size && user_metadata_ptr_) {
-    out_meta->user_metadata_size =
-        metadata_header_->metadata.user_metadata_size;
-    out_meta->user_metadata_ptr =
-        reinterpret_cast<uint64_t>(user_metadata_ptr_);
-  } else {
-    out_meta->user_metadata_size = 0;
-    out_meta->user_metadata_ptr = 0;
-  }
-
-  uint64_t fence_state = fence_state_->load();
-  // If there is an release fence from consumer, we need to return it.
-  if (fence_state & BufferHubDefs::kConsumerStateMask) {
-    *out_fence = shared_release_fence_.Duplicate();
-    out_meta->release_fence_mask =
-        fence_state & BufferHubDefs::kConsumerStateMask;
-  }
-
-  // Clear out all bits and the buffer is now back to gained state.
-  buffer_state_->store(0ULL);
-  return 0;
-}
-
-int BufferProducer::Gain(LocalHandle* release_fence) {
-  ATRACE_NAME("BufferProducer::Gain");
-
-  DvrNativeBufferMetadata meta;
-  if (const int error = LocalGain(&meta, release_fence))
-    return error;
-
-  auto status = InvokeRemoteMethod<BufferHubRPC::ProducerGain>();
-  if (!status)
-    return -status.error();
-  return 0;
-}
-
-int BufferProducer::GainAsync(DvrNativeBufferMetadata* out_meta,
-                              LocalHandle* release_fence) {
-  ATRACE_NAME("BufferProducer::GainAsync");
-
-  if (const int error = LocalGain(out_meta, release_fence))
-    return error;
-
-  return ReturnStatusOrError(SendImpulse(BufferHubRPC::ProducerGain::Opcode));
-}
-
-int BufferProducer::GainAsync() {
-  DvrNativeBufferMetadata meta;
-  LocalHandle fence;
-  return GainAsync(&meta, &fence);
-}
-
-std::unique_ptr<BufferProducer> BufferProducer::Import(
-    LocalChannelHandle channel) {
-  ALOGD_IF(TRACE, "BufferProducer::Import: channel=%d", channel.value());
-  return BufferProducer::Create(std::move(channel));
-}
-
-std::unique_ptr<BufferProducer> BufferProducer::Import(
-    Status<LocalChannelHandle> status) {
-  return Import(status ? status.take()
-                       : LocalChannelHandle{nullptr, -status.error()});
-}
-
-Status<LocalChannelHandle> BufferProducer::Detach() {
-  uint64_t buffer_state = buffer_state_->load();
-  if (!BufferHubDefs::IsBufferGained(buffer_state)) {
-    // Can only detach a BufferProducer when it's in gained state.
-    ALOGW("BufferProducer::Detach: The buffer (id=%d, state=0x%" PRIx64
-          ") is not in gained state.",
-          id(), buffer_state);
-    return {};
-  }
-
-  Status<LocalChannelHandle> status =
-      InvokeRemoteMethod<BufferHubRPC::ProducerBufferDetach>();
-  ALOGE_IF(!status,
-           "BufferProducer::Detach: Failed to detach buffer (id=%d): %s.", id(),
-           status.GetErrorMessage().c_str());
-  return status;
-}
-
 }  // namespace dvr
 }  // namespace android
diff --git a/libs/vr/libbufferhub/consumer_buffer.cpp b/libs/vr/libbufferhub/consumer_buffer.cpp
new file mode 100644
index 0000000..4e8c36b
--- /dev/null
+++ b/libs/vr/libbufferhub/consumer_buffer.cpp
@@ -0,0 +1,183 @@
+#include <private/dvr/consumer_buffer.h>
+
+using android::pdx::LocalChannelHandle;
+using android::pdx::LocalHandle;
+using android::pdx::Status;
+
+namespace android {
+namespace dvr {
+
+ConsumerBuffer::ConsumerBuffer(LocalChannelHandle channel)
+    : BASE(std::move(channel)) {
+  const int ret = ImportBuffer();
+  if (ret < 0) {
+    ALOGE("ConsumerBuffer::ConsumerBuffer: Failed to import buffer: %s",
+          strerror(-ret));
+    Close(ret);
+  }
+}
+
+std::unique_ptr<ConsumerBuffer> ConsumerBuffer::Import(
+    LocalChannelHandle channel) {
+  ATRACE_NAME("ConsumerBuffer::Import");
+  ALOGD_IF(TRACE, "ConsumerBuffer::Import: channel=%d", channel.value());
+  return ConsumerBuffer::Create(std::move(channel));
+}
+
+std::unique_ptr<ConsumerBuffer> ConsumerBuffer::Import(
+    Status<LocalChannelHandle> status) {
+  return Import(status ? status.take()
+                       : LocalChannelHandle{nullptr, -status.error()});
+}
+
+int ConsumerBuffer::LocalAcquire(DvrNativeBufferMetadata* out_meta,
+                                 LocalHandle* out_fence) {
+  if (!out_meta)
+    return -EINVAL;
+
+  // Only check producer bit and this consumer buffer's particular consumer bit.
+  // The buffer is can be acquired iff: 1) producer bit is set; 2) consumer bit
+  // is not set.
+  uint64_t buffer_state = buffer_state_->load();
+  if (!BufferHubDefs::IsBufferPosted(buffer_state, buffer_state_bit())) {
+    ALOGE("ConsumerBuffer::LocalAcquire: not posted, id=%d state=%" PRIx64
+          " buffer_state_bit=%" PRIx64 ".",
+          id(), buffer_state, buffer_state_bit());
+    return -EBUSY;
+  }
+
+  // Copy the canonical metadata.
+  void* metadata_ptr = reinterpret_cast<void*>(&metadata_header_->metadata);
+  memcpy(out_meta, metadata_ptr, sizeof(DvrNativeBufferMetadata));
+  // Fill in the user_metadata_ptr in address space of the local process.
+  if (out_meta->user_metadata_size) {
+    out_meta->user_metadata_ptr =
+        reinterpret_cast<uint64_t>(user_metadata_ptr_);
+  } else {
+    out_meta->user_metadata_ptr = 0;
+  }
+
+  uint64_t fence_state = fence_state_->load();
+  // If there is an acquire fence from producer, we need to return it.
+  if (fence_state & BufferHubDefs::kProducerStateBit) {
+    *out_fence = shared_acquire_fence_.Duplicate();
+  }
+
+  // Set the consumer bit unique to this consumer.
+  BufferHubDefs::ModifyBufferState(buffer_state_, 0ULL, buffer_state_bit());
+  return 0;
+}
+
+int ConsumerBuffer::Acquire(LocalHandle* ready_fence) {
+  return Acquire(ready_fence, nullptr, 0);
+}
+
+int ConsumerBuffer::Acquire(LocalHandle* ready_fence, void* meta,
+                            size_t user_metadata_size) {
+  ATRACE_NAME("ConsumerBuffer::Acquire");
+
+  if (const int error = CheckMetadata(user_metadata_size))
+    return error;
+
+  DvrNativeBufferMetadata canonical_meta;
+  if (const int error = LocalAcquire(&canonical_meta, ready_fence))
+    return error;
+
+  if (meta && user_metadata_size) {
+    void* metadata_src =
+        reinterpret_cast<void*>(canonical_meta.user_metadata_ptr);
+    if (metadata_src) {
+      memcpy(meta, metadata_src, user_metadata_size);
+    } else {
+      ALOGW("ConsumerBuffer::Acquire: no user-defined metadata.");
+    }
+  }
+
+  auto status = InvokeRemoteMethod<BufferHubRPC::ConsumerAcquire>();
+  if (!status)
+    return -status.error();
+  return 0;
+}
+
+int ConsumerBuffer::AcquireAsync(DvrNativeBufferMetadata* out_meta,
+                                 LocalHandle* out_fence) {
+  ATRACE_NAME("ConsumerBuffer::AcquireAsync");
+
+  if (const int error = LocalAcquire(out_meta, out_fence))
+    return error;
+
+  auto status = SendImpulse(BufferHubRPC::ConsumerAcquire::Opcode);
+  if (!status)
+    return -status.error();
+  return 0;
+}
+
+int ConsumerBuffer::LocalRelease(const DvrNativeBufferMetadata* meta,
+                                 const LocalHandle& release_fence) {
+  if (const int error = CheckMetadata(meta->user_metadata_size))
+    return error;
+
+  // Check invalid state transition.
+  uint64_t buffer_state = buffer_state_->load();
+  if (!BufferHubDefs::IsBufferAcquired(buffer_state)) {
+    ALOGE("ConsumerBuffer::LocalRelease: not acquired id=%d state=%" PRIx64 ".",
+          id(), buffer_state);
+    return -EBUSY;
+  }
+
+  // On release, only the user requested metadata is copied back into the shared
+  // memory for metadata. Since there are multiple consumers, it doesn't make
+  // sense to send the canonical metadata back to the producer. However, one of
+  // the consumer can still choose to write up to user_metadata_size bytes of
+  // data into user_metadata_ptr.
+  if (meta->user_metadata_ptr && meta->user_metadata_size) {
+    void* metadata_src = reinterpret_cast<void*>(meta->user_metadata_ptr);
+    memcpy(user_metadata_ptr_, metadata_src, meta->user_metadata_size);
+  }
+
+  // Send out the release fence through the shared epoll fd. Note that during
+  // releasing the producer is not expected to be polling on the fence.
+  if (const int error = UpdateSharedFence(release_fence, shared_release_fence_))
+    return error;
+
+  // For release operation, the client don't need to change the state as it's
+  // bufferhubd's job to flip the produer bit once all consumers are released.
+  return 0;
+}
+
+int ConsumerBuffer::Release(const LocalHandle& release_fence) {
+  ATRACE_NAME("ConsumerBuffer::Release");
+
+  DvrNativeBufferMetadata meta;
+  if (const int error = LocalRelease(&meta, release_fence))
+    return error;
+
+  return ReturnStatusOrError(InvokeRemoteMethod<BufferHubRPC::ConsumerRelease>(
+      BorrowedFence(release_fence.Borrow())));
+}
+
+int ConsumerBuffer::ReleaseAsync() {
+  DvrNativeBufferMetadata meta;
+  return ReleaseAsync(&meta, LocalHandle());
+}
+
+int ConsumerBuffer::ReleaseAsync(const DvrNativeBufferMetadata* meta,
+                                 const LocalHandle& release_fence) {
+  ATRACE_NAME("ConsumerBuffer::ReleaseAsync");
+
+  if (const int error = LocalRelease(meta, release_fence))
+    return error;
+
+  return ReturnStatusOrError(
+      SendImpulse(BufferHubRPC::ConsumerRelease::Opcode));
+}
+
+int ConsumerBuffer::Discard() { return Release(LocalHandle()); }
+
+int ConsumerBuffer::SetIgnore(bool ignore) {
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<BufferHubRPC::ConsumerSetIgnore>(ignore));
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhub/include/private/dvr/buffer_hub_base.h b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_base.h
new file mode 100644
index 0000000..b75fd61
--- /dev/null
+++ b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_base.h
@@ -0,0 +1,167 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_BASE_H_
+#define ANDROID_DVR_BUFFER_HUB_BASE_H_
+
+#include <vector>
+
+#include <private/dvr/bufferhub_rpc.h>
+
+namespace android {
+namespace dvr {
+
+// Base class of two types of BufferHub clients: dvr::ProducerBuffer and
+// dvr::ConsumerBuffer.
+class BufferHubBase : public pdx::Client {
+ public:
+  using LocalHandle = pdx::LocalHandle;
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+  template <typename T>
+  using Status = pdx::Status<T>;
+
+  // Create a new consumer channel that is attached to the producer. Returns
+  // a file descriptor for the new channel or a negative error code.
+  Status<LocalChannelHandle> CreateConsumer();
+
+  // Polls the fd for |timeout_ms| milliseconds (-1 for infinity).
+  int Poll(int timeout_ms);
+
+  // Locks the area specified by (x, y, width, height) for a specific usage. If
+  // the usage is software then |addr| will be updated to point to the address
+  // of the buffer in virtual memory. The caller should only access/modify the
+  // pixels in the specified area. anything else is undefined behavior.
+  int Lock(int usage, int x, int y, int width, int height, void** addr);
+
+  // Must be called after Lock() when the caller has finished changing the
+  // buffer.
+  int Unlock();
+
+  // Gets a blob buffer that was created with ProducerBuffer::CreateBlob.
+  // Locking and Unlocking is handled internally. There's no need to Unlock
+  // after calling this method.
+  int GetBlobReadWritePointer(size_t size, void** addr);
+
+  // Gets a blob buffer that was created with ProducerBuffer::CreateBlob.
+  // Locking and Unlocking is handled internally. There's no need to Unlock
+  // after calling this method.
+  int GetBlobReadOnlyPointer(size_t size, void** addr);
+
+  // Returns a dup'd file descriptor for accessing the blob shared memory. The
+  // caller takes ownership of the file descriptor and must close it or pass on
+  // ownership. Some GPU API extensions can take file descriptors to bind shared
+  // memory gralloc buffers to GPU buffer objects.
+  LocalHandle GetBlobFd() const {
+    // Current GPU vendor puts the buffer allocation in one FD. If we change GPU
+    // vendors and this is the wrong fd, late-latching and EDS will very clearly
+    // stop working and we will need to correct this. The alternative is to use
+    // a GL context in the pose service to allocate this buffer or to use the
+    // ION API directly instead of gralloc.
+    return LocalHandle(dup(native_handle()->data[0]));
+  }
+
+  // Get up to |max_fds_count| file descriptors for accessing the blob shared
+  // memory. |fds_count| will contain the actual number of file descriptors.
+  void GetBlobFds(int* fds, size_t* fds_count, size_t max_fds_count) const;
+
+  using Client::event_fd;
+
+  Status<int> GetEventMask(int events) {
+    if (auto* client_channel = GetChannel()) {
+      return client_channel->GetEventMask(events);
+    } else {
+      return pdx::ErrorStatus(EINVAL);
+    }
+  }
+
+  std::vector<pdx::ClientChannel::EventSource> GetEventSources() const {
+    if (auto* client_channel = GetChannel()) {
+      return client_channel->GetEventSources();
+    } else {
+      return {};
+    }
+  }
+
+  native_handle_t* native_handle() const {
+    return const_cast<native_handle_t*>(buffer_.handle());
+  }
+
+  IonBuffer* buffer() { return &buffer_; }
+  const IonBuffer* buffer() const { return &buffer_; }
+
+  // Gets ID of the buffer client. All BufferHub clients derived from the same
+  // buffer in bufferhubd share the same buffer id.
+  int id() const { return id_; }
+
+  // Gets the channel id of the buffer client. Each BufferHub client has its
+  // system unique channel id.
+  int cid() const { return cid_; }
+
+  // Returns the buffer buffer state.
+  uint64_t buffer_state() { return buffer_state_->load(); };
+
+  // A state mask which is unique to a buffer hub client among all its siblings
+  // sharing the same concrete graphic buffer.
+  uint64_t buffer_state_bit() const { return buffer_state_bit_; }
+
+  // The following methods return settings of the first buffer. Currently,
+  // it is only possible to create multi-buffer BufferHubBases with the same
+  // settings.
+  uint32_t width() const { return buffer_.width(); }
+  uint32_t height() const { return buffer_.height(); }
+  uint32_t stride() const { return buffer_.stride(); }
+  uint32_t format() const { return buffer_.format(); }
+  uint32_t usage() const { return buffer_.usage(); }
+  uint32_t layer_count() const { return buffer_.layer_count(); }
+
+  uint64_t GetQueueIndex() const { return metadata_header_->queue_index; }
+  void SetQueueIndex(uint64_t index) { metadata_header_->queue_index = index; }
+
+ protected:
+  explicit BufferHubBase(LocalChannelHandle channel);
+  explicit BufferHubBase(const std::string& endpoint_path);
+  virtual ~BufferHubBase();
+
+  // Initialization helper.
+  int ImportBuffer();
+
+  // Check invalid metadata operation. Returns 0 if requested metadata is valid.
+  int CheckMetadata(size_t user_metadata_size) const;
+
+  // Send out the new fence by updating the shared fence (shared_release_fence
+  // for producer and shared_acquire_fence for consumer). Note that during this
+  // should only be used in LocalPost() or LocalRelease, and the shared fence
+  // shouldn't be poll'ed by the other end.
+  int UpdateSharedFence(const LocalHandle& new_fence,
+                        const LocalHandle& shared_fence);
+
+  // IonBuffer that is shared between bufferhubd, producer, and consumers.
+  size_t metadata_buf_size_{0};
+  size_t user_metadata_size_{0};
+  BufferHubDefs::MetadataHeader* metadata_header_{nullptr};
+  void* user_metadata_ptr_{nullptr};
+  std::atomic<uint64_t>* buffer_state_{nullptr};
+  std::atomic<uint64_t>* fence_state_{nullptr};
+
+  LocalHandle shared_acquire_fence_;
+  LocalHandle shared_release_fence_;
+
+  // A local fence fd that holds the ownership of the fence fd on Post (for
+  // producer) and Release (for consumer).
+  LocalHandle pending_fence_fd_;
+
+ private:
+  BufferHubBase(const BufferHubBase&) = delete;
+  void operator=(const BufferHubBase&) = delete;
+
+  // Global id for the buffer that is consistent across processes. It is meant
+  // for logging and debugging purposes only and should not be used for lookup
+  // or any other functional purpose as a security precaution.
+  int id_;
+  int cid_;
+  uint64_t buffer_state_bit_{0ULL};
+  IonBuffer buffer_;
+  IonBuffer metadata_buffer_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_BASE_H_
diff --git a/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
index 0b2666a..7b317d1 100644
--- a/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
+++ b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
@@ -1,17 +1,10 @@
 #ifndef ANDROID_DVR_BUFFER_HUB_CLIENT_H_
 #define ANDROID_DVR_BUFFER_HUB_CLIENT_H_
 
-#include <hardware/gralloc.h>
 #include <pdx/channel_handle.h>
 #include <pdx/client.h>
-#include <pdx/file_handle.h>
-#include <pdx/status.h>
-
-#include <vector>
-
-#include <private/dvr/ion_buffer.h>
-
-#include "bufferhub_rpc.h"
+#include <private/dvr/consumer_buffer.h>
+#include <private/dvr/producer_buffer.h>
 
 namespace android {
 namespace dvr {
@@ -31,322 +24,6 @@
   using pdx::Client::event_fd;
 };
 
-class BufferHubBuffer : public pdx::Client {
- public:
-  using LocalHandle = pdx::LocalHandle;
-  using LocalChannelHandle = pdx::LocalChannelHandle;
-  template <typename T>
-  using Status = pdx::Status<T>;
-
-  // Create a new consumer channel that is attached to the producer. Returns
-  // a file descriptor for the new channel or a negative error code.
-  Status<LocalChannelHandle> CreateConsumer();
-
-  // Polls the fd for |timeout_ms| milliseconds (-1 for infinity).
-  int Poll(int timeout_ms);
-
-  // Locks the area specified by (x, y, width, height) for a specific usage. If
-  // the usage is software then |addr| will be updated to point to the address
-  // of the buffer in virtual memory. The caller should only access/modify the
-  // pixels in the specified area. anything else is undefined behavior.
-  int Lock(int usage, int x, int y, int width, int height, void** addr);
-
-  // Must be called after Lock() when the caller has finished changing the
-  // buffer.
-  int Unlock();
-
-  // Gets a blob buffer that was created with BufferProducer::CreateBlob.
-  // Locking and Unlocking is handled internally. There's no need to Unlock
-  // after calling this method.
-  int GetBlobReadWritePointer(size_t size, void** addr);
-
-  // Gets a blob buffer that was created with BufferProducer::CreateBlob.
-  // Locking and Unlocking is handled internally. There's no need to Unlock
-  // after calling this method.
-  int GetBlobReadOnlyPointer(size_t size, void** addr);
-
-  // Returns a dup'd file descriptor for accessing the blob shared memory. The
-  // caller takes ownership of the file descriptor and must close it or pass on
-  // ownership. Some GPU API extensions can take file descriptors to bind shared
-  // memory gralloc buffers to GPU buffer objects.
-  LocalHandle GetBlobFd() const {
-    // Current GPU vendor puts the buffer allocation in one FD. If we change GPU
-    // vendors and this is the wrong fd, late-latching and EDS will very clearly
-    // stop working and we will need to correct this. The alternative is to use
-    // a GL context in the pose service to allocate this buffer or to use the
-    // ION API directly instead of gralloc.
-    return LocalHandle(dup(native_handle()->data[0]));
-  }
-
-  // Get up to |max_fds_count| file descriptors for accessing the blob shared
-  // memory. |fds_count| will contain the actual number of file descriptors.
-  void GetBlobFds(int* fds, size_t* fds_count, size_t max_fds_count) const;
-
-  using Client::event_fd;
-
-  Status<int> GetEventMask(int events) {
-    if (auto* client_channel = GetChannel()) {
-      return client_channel->GetEventMask(events);
-    } else {
-      return pdx::ErrorStatus(EINVAL);
-    }
-  }
-
-  std::vector<pdx::ClientChannel::EventSource> GetEventSources() const {
-    if (auto* client_channel = GetChannel()) {
-      return client_channel->GetEventSources();
-    } else {
-      return {};
-    }
-  }
-
-  native_handle_t* native_handle() const {
-    return const_cast<native_handle_t*>(buffer_.handle());
-  }
-
-  IonBuffer* buffer() { return &buffer_; }
-  const IonBuffer* buffer() const { return &buffer_; }
-
-  // Gets ID of the buffer client. All BufferHubBuffer clients derived from the
-  // same buffer in bufferhubd share the same buffer id.
-  int id() const { return id_; }
-
-  // Gets the channel id of the buffer client. Each BufferHubBuffer client has
-  // its system unique channel id.
-  int cid() const { return cid_; }
-
-  // Returns the buffer buffer state.
-  uint64_t buffer_state() { return buffer_state_->load(); };
-
-  // A state mask which is unique to a buffer hub client among all its siblings
-  // sharing the same concrete graphic buffer.
-  uint64_t buffer_state_bit() const { return buffer_state_bit_; }
-
-  // The following methods return settings of the first buffer. Currently,
-  // it is only possible to create multi-buffer BufferHubBuffers with the same
-  // settings.
-  uint32_t width() const { return buffer_.width(); }
-  uint32_t height() const { return buffer_.height(); }
-  uint32_t stride() const { return buffer_.stride(); }
-  uint32_t format() const { return buffer_.format(); }
-  uint32_t usage() const { return buffer_.usage(); }
-  uint32_t layer_count() const { return buffer_.layer_count(); }
-
-  uint64_t GetQueueIndex() const { return metadata_header_->queue_index; }
-  void SetQueueIndex(uint64_t index) { metadata_header_->queue_index = index; }
-
- protected:
-  explicit BufferHubBuffer(LocalChannelHandle channel);
-  explicit BufferHubBuffer(const std::string& endpoint_path);
-  virtual ~BufferHubBuffer();
-
-  // Initialization helper.
-  int ImportBuffer();
-
-  // Check invalid metadata operation. Returns 0 if requested metadata is valid.
-  int CheckMetadata(size_t user_metadata_size) const;
-
-  // Send out the new fence by updating the shared fence (shared_release_fence
-  // for producer and shared_acquire_fence for consumer). Note that during this
-  // should only be used in LocalPost() or LocalRelease, and the shared fence
-  // shouldn't be poll'ed by the other end.
-  int UpdateSharedFence(const LocalHandle& new_fence,
-                        const LocalHandle& shared_fence);
-
-  // IonBuffer that is shared between bufferhubd, producer, and consumers.
-  size_t metadata_buf_size_{0};
-  size_t user_metadata_size_{0};
-  BufferHubDefs::MetadataHeader* metadata_header_{nullptr};
-  void* user_metadata_ptr_{nullptr};
-  std::atomic<uint64_t>* buffer_state_{nullptr};
-  std::atomic<uint64_t>* fence_state_{nullptr};
-
-  LocalHandle shared_acquire_fence_;
-  LocalHandle shared_release_fence_;
-
-  // A local fence fd that holds the ownership of the fence fd on Post (for
-  // producer) and Release (for consumer).
-  LocalHandle pending_fence_fd_;
-
- private:
-  BufferHubBuffer(const BufferHubBuffer&) = delete;
-  void operator=(const BufferHubBuffer&) = delete;
-
-  // Global id for the buffer that is consistent across processes. It is meant
-  // for logging and debugging purposes only and should not be used for lookup
-  // or any other functional purpose as a security precaution.
-  int id_;
-  int cid_;
-  uint64_t buffer_state_bit_{0ULL};
-  IonBuffer buffer_;
-  IonBuffer metadata_buffer_;
-};
-
-// This represents a writable buffer. Calling Post notifies all clients and
-// makes the buffer read-only. Call Gain to acquire write access. A buffer
-// may have many consumers.
-//
-// The user of BufferProducer is responsible with making sure that the Post() is
-// done with the correct metadata type and size. The user is also responsible
-// for making sure that remote ends (BufferConsumers) are also using the correct
-// metadata when acquiring the buffer. The API guarantees that a Post() with a
-// metadata of wrong size will fail. However, it currently does not do any
-// type checking.
-// The API also assumes that metadata is a serializable type (plain old data).
-class BufferProducer : public pdx::ClientBase<BufferProducer, BufferHubBuffer> {
- public:
-  // Imports a bufferhub producer channel, assuming ownership of its handle.
-  static std::unique_ptr<BufferProducer> Import(LocalChannelHandle channel);
-  static std::unique_ptr<BufferProducer> Import(
-      Status<LocalChannelHandle> status);
-
-  // Asynchronously posts a buffer. The fence and metadata are passed to
-  // consumer via shared fd and shared memory.
-  int PostAsync(const DvrNativeBufferMetadata* meta,
-                const LocalHandle& ready_fence);
-
-  // Post this buffer, passing |ready_fence| to the consumers. The bytes in
-  // |meta| are passed unaltered to the consumers. The producer must not modify
-  // the buffer until it is re-gained.
-  // This returns zero or a negative unix error code.
-  int Post(const LocalHandle& ready_fence, const void* meta,
-           size_t user_metadata_size);
-
-  int Post(const LocalHandle& ready_fence) {
-    return Post(ready_fence, nullptr, 0);
-  }
-
-  template <typename Meta, typename = typename std::enable_if<
-                               !std::is_void<Meta>::value>::type>
-  int Post(const LocalHandle& ready_fence, const Meta& meta) {
-    return Post(ready_fence, &meta, sizeof(meta));
-  }
-
-  // Attempt to re-gain the buffer for writing. If |release_fence| is valid, it
-  // must be waited on before using the buffer. If it is not valid then the
-  // buffer is free for immediate use. This call will only succeed if the buffer
-  // is in the released state.
-  // This returns zero or a negative unix error code.
-  int Gain(LocalHandle* release_fence);
-  int GainAsync();
-
-  // Asynchronously marks a released buffer as gained. This method is similar to
-  // the synchronous version above, except that it does not wait for BufferHub
-  // to acknowledge success or failure. Because of the asynchronous nature of
-  // the underlying message, no error is returned if this method is called when
-  // the buffer is in an incorrect state. Returns zero if sending the message
-  // succeeded, or a negative errno code if local error check fails.
-  int GainAsync(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
-
-  // Detaches a ProducerBuffer from an existing producer/consumer set. Can only
-  // be called when a producer buffer has exclusive access to the buffer (i.e.
-  // in the gain'ed state). On the successful return of the IPC call, a new
-  // LocalChannelHandle representing a detached buffer will be returned and all
-  // existing producer and consumer channels will be closed. Further IPCs
-  // towards those channels will return error.
-  Status<LocalChannelHandle> Detach();
-
- private:
-  friend BASE;
-
-  // Constructors are automatically exposed through BufferProducer::Create(...)
-  // static template methods inherited from ClientBase, which take the same
-  // arguments as the constructors.
-
-  // Constructs a buffer with the given geometry and parameters.
-  BufferProducer(uint32_t width, uint32_t height, uint32_t format,
-                 uint64_t usage, size_t metadata_size = 0);
-
-  // Constructs a blob (flat) buffer with the given usage flags.
-  BufferProducer(uint64_t usage, size_t size);
-
-  // Imports the given file handle to a producer channel, taking ownership.
-  explicit BufferProducer(LocalChannelHandle channel);
-
-  // Local state transition helpers.
-  int LocalGain(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
-  int LocalPost(const DvrNativeBufferMetadata* meta,
-                const LocalHandle& ready_fence);
-};
-
-// This is a connection to a producer buffer, which can be located in another
-// application. When that buffer is Post()ed, this fd will be signaled and
-// Acquire allows read access. The user is responsible for making sure that
-// Acquire is called with the correct metadata structure. The only guarantee the
-// API currently provides is that an Acquire() with metadata of the wrong size
-// will fail.
-class BufferConsumer : public pdx::ClientBase<BufferConsumer, BufferHubBuffer> {
- public:
-  // This call assumes ownership of |fd|.
-  static std::unique_ptr<BufferConsumer> Import(LocalChannelHandle channel);
-  static std::unique_ptr<BufferConsumer> Import(
-      Status<LocalChannelHandle> status);
-
-  // Attempt to retrieve a post event from buffer hub. If successful,
-  // |ready_fence| will be set to a fence to wait on until the buffer is ready.
-  // This call will only succeed after the fd is signalled. This call may be
-  // performed as an alternative to the Acquire() with metadata. In such cases
-  // the metadata is not read.
-  //
-  // This returns zero or negative unix error code.
-  int Acquire(LocalHandle* ready_fence);
-
-  // Attempt to retrieve a post event from buffer hub. If successful,
-  // |ready_fence| is set to a fence signaling that the contents of the buffer
-  // are available. This call will only succeed if the buffer is in the posted
-  // state.
-  // Returns zero on success, or a negative errno code otherwise.
-  int Acquire(LocalHandle* ready_fence, void* meta, size_t user_metadata_size);
-
-  // Attempt to retrieve a post event from buffer hub. If successful,
-  // |ready_fence| is set to a fence to wait on until the buffer is ready. This
-  // call will only succeed after the fd is signaled. This returns zero or a
-  // negative unix error code.
-  template <typename Meta>
-  int Acquire(LocalHandle* ready_fence, Meta* meta) {
-    return Acquire(ready_fence, meta, sizeof(*meta));
-  }
-
-  // Asynchronously acquires a bufer.
-  int AcquireAsync(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
-
-  // This should be called after a successful Acquire call. If the fence is
-  // valid the fence determines the buffer usage, otherwise the buffer is
-  // released immediately.
-  // This returns zero or a negative unix error code.
-  int Release(const LocalHandle& release_fence);
-  int ReleaseAsync();
-
-  // Asynchronously releases a buffer. Similar to the synchronous version above,
-  // except that it does not wait for BufferHub to reply with success or error.
-  // The fence and metadata are passed to consumer via shared fd and shared
-  // memory.
-  int ReleaseAsync(const DvrNativeBufferMetadata* meta,
-                   const LocalHandle& release_fence);
-
-  // May be called after or instead of Acquire to indicate that the consumer
-  // does not need to access the buffer this cycle. This returns zero or a
-  // negative unix error code.
-  int Discard();
-
-  // When set, this consumer is no longer notified when this buffer is
-  // available. The system behaves as if Discard() is immediately called
-  // whenever the buffer is posted. If ignore is set to true while a buffer is
-  // pending, it will act as if Discard() was also called.
-  // This returns zero or a negative unix error code.
-  int SetIgnore(bool ignore);
-
- private:
-  friend BASE;
-
-  explicit BufferConsumer(LocalChannelHandle channel);
-
-  // Local state transition helpers.
-  int LocalAcquire(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
-  int LocalRelease(const DvrNativeBufferMetadata* meta,
-                   const LocalHandle& release_fence);
-};
-
 }  // namespace dvr
 }  // namespace android
 
diff --git a/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h b/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
index e163216..225914a 100644
--- a/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
+++ b/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
@@ -113,13 +113,15 @@
   BufferDescription(BufferDescription&& other) = default;
   BufferDescription& operator=(BufferDescription&& other) = default;
 
-  // ID of the buffer client. All BufferHubBuffer clients derived from the same
-  // buffer in bufferhubd share the same buffer id.
+  // ID of the buffer client. All BufferHub clients derived from the same buffer
+  // in bufferhubd share the same buffer id.
   int id() const { return id_; }
-  // Channel ID of the buffer client. Each BufferHubBuffer client has its system
+
+  // Channel ID of the buffer client. Each BufferHub client has its system
   // unique channel id.
   int buffer_cid() const { return buffer_cid_; }
-  // State mask of the buffer client. Each BufferHubBuffer client backed by the
+
+  // State mask of the buffer client. Each BufferHub client backed by the
   // same buffer channel has uniqued state bit among its siblings. For a
   // producer buffer the bit must be kProducerStateBit; for a consumer the bit
   // must be one of the kConsumerStateMask.
diff --git a/libs/vr/libbufferhub/include/private/dvr/consumer_buffer.h b/libs/vr/libbufferhub/include/private/dvr/consumer_buffer.h
new file mode 100644
index 0000000..089eff8
--- /dev/null
+++ b/libs/vr/libbufferhub/include/private/dvr/consumer_buffer.h
@@ -0,0 +1,100 @@
+#ifndef ANDROID_DVR_CONSUMER_BUFFER_H_
+#define ANDROID_DVR_CONSUMER_BUFFER_H_
+
+#include <private/dvr/buffer_hub_base.h>
+
+namespace android {
+namespace dvr {
+
+// BufferConsumer was originally poorly named and gets easily confused with
+// IGraphicBufferConsumer. Actually, BufferConsumer is a single buffer that can
+// consume (i.e. read) data from a buffer, but it doesn't consume buffer. On
+// the other hand, IGraphicBufferConsumer is the consumer end of a BufferQueue
+// and it is used to consume buffers.
+//
+// TODO(b/116855254): Remove this typedef once rename is complete in other
+// projects and/or branches.
+typedef class ConsumerBuffer BufferConsumer;
+
+// This is a connection to a producer buffer, which can be located in another
+// application. When that buffer is Post()ed, this fd will be signaled and
+// Acquire allows read access. The user is responsible for making sure that
+// Acquire is called with the correct metadata structure. The only guarantee the
+// API currently provides is that an Acquire() with metadata of the wrong size
+// will fail.
+class ConsumerBuffer : public pdx::ClientBase<ConsumerBuffer, BufferHubBase> {
+ public:
+  // This call assumes ownership of |fd|.
+  static std::unique_ptr<ConsumerBuffer> Import(LocalChannelHandle channel);
+  static std::unique_ptr<ConsumerBuffer> Import(
+      Status<LocalChannelHandle> status);
+
+  // Attempt to retrieve a post event from buffer hub. If successful,
+  // |ready_fence| will be set to a fence to wait on until the buffer is ready.
+  // This call will only succeed after the fd is signalled. This call may be
+  // performed as an alternative to the Acquire() with metadata. In such cases
+  // the metadata is not read.
+  //
+  // This returns zero or negative unix error code.
+  int Acquire(LocalHandle* ready_fence);
+
+  // Attempt to retrieve a post event from buffer hub. If successful,
+  // |ready_fence| is set to a fence signaling that the contents of the buffer
+  // are available. This call will only succeed if the buffer is in the posted
+  // state.
+  // Returns zero on success, or a negative errno code otherwise.
+  int Acquire(LocalHandle* ready_fence, void* meta, size_t user_metadata_size);
+
+  // Attempt to retrieve a post event from buffer hub. If successful,
+  // |ready_fence| is set to a fence to wait on until the buffer is ready. This
+  // call will only succeed after the fd is signaled. This returns zero or a
+  // negative unix error code.
+  template <typename Meta>
+  int Acquire(LocalHandle* ready_fence, Meta* meta) {
+    return Acquire(ready_fence, meta, sizeof(*meta));
+  }
+
+  // Asynchronously acquires a bufer.
+  int AcquireAsync(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
+
+  // This should be called after a successful Acquire call. If the fence is
+  // valid the fence determines the buffer usage, otherwise the buffer is
+  // released immediately.
+  // This returns zero or a negative unix error code.
+  int Release(const LocalHandle& release_fence);
+  int ReleaseAsync();
+
+  // Asynchronously releases a buffer. Similar to the synchronous version above,
+  // except that it does not wait for BufferHub to reply with success or error.
+  // The fence and metadata are passed to consumer via shared fd and shared
+  // memory.
+  int ReleaseAsync(const DvrNativeBufferMetadata* meta,
+                   const LocalHandle& release_fence);
+
+  // May be called after or instead of Acquire to indicate that the consumer
+  // does not need to access the buffer this cycle. This returns zero or a
+  // negative unix error code.
+  int Discard();
+
+  // When set, this consumer is no longer notified when this buffer is
+  // available. The system behaves as if Discard() is immediately called
+  // whenever the buffer is posted. If ignore is set to true while a buffer is
+  // pending, it will act as if Discard() was also called.
+  // This returns zero or a negative unix error code.
+  int SetIgnore(bool ignore);
+
+ private:
+  friend BASE;
+
+  explicit ConsumerBuffer(LocalChannelHandle channel);
+
+  // Local state transition helpers.
+  int LocalAcquire(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
+  int LocalRelease(const DvrNativeBufferMetadata* meta,
+                   const LocalHandle& release_fence);
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_CONSUMER_BUFFER_H_
diff --git a/libs/vr/libbufferhub/include/private/dvr/producer_buffer.h b/libs/vr/libbufferhub/include/private/dvr/producer_buffer.h
new file mode 100644
index 0000000..b5e1c5b
--- /dev/null
+++ b/libs/vr/libbufferhub/include/private/dvr/producer_buffer.h
@@ -0,0 +1,109 @@
+#ifndef ANDROID_DVR_PRODUCER_BUFFER_H_
+#define ANDROID_DVR_PRODUCER_BUFFER_H_
+
+#include <private/dvr/buffer_hub_base.h>
+
+namespace android {
+namespace dvr {
+
+// BufferProducer was originally poorly named and gets easily confused with
+// IGraphicBufferProducer. Actually, BufferProducer is a single buffer that can
+// produce (i.e. write) data into a buffer, but it doesn't produce buffer. On
+// the other hand, IGraphicBufferProducer is the producer end of a BufferQueue
+// and it is used to produce buffers.
+//
+// TODO(b/116855254): Remove this typedef once rename is complete in other
+// projects and/or branches.
+typedef class ProducerBuffer BufferProducer;
+
+// This represents a writable buffer. Calling Post notifies all clients and
+// makes the buffer read-only. Call Gain to acquire write access. A buffer
+// may have many consumers.
+//
+// The user of ProducerBuffer is responsible with making sure that the Post() is
+// done with the correct metadata type and size. The user is also responsible
+// for making sure that remote ends (BufferConsumers) are also using the correct
+// metadata when acquiring the buffer. The API guarantees that a Post() with a
+// metadata of wrong size will fail. However, it currently does not do any
+// type checking.
+// The API also assumes that metadata is a serializable type (plain old data).
+class ProducerBuffer : public pdx::ClientBase<ProducerBuffer, BufferHubBase> {
+ public:
+  // Imports a bufferhub producer channel, assuming ownership of its handle.
+  static std::unique_ptr<ProducerBuffer> Import(LocalChannelHandle channel);
+  static std::unique_ptr<ProducerBuffer> Import(
+      Status<LocalChannelHandle> status);
+
+  // Asynchronously posts a buffer. The fence and metadata are passed to
+  // consumer via shared fd and shared memory.
+  int PostAsync(const DvrNativeBufferMetadata* meta,
+                const LocalHandle& ready_fence);
+
+  // Post this buffer, passing |ready_fence| to the consumers. The bytes in
+  // |meta| are passed unaltered to the consumers. The producer must not modify
+  // the buffer until it is re-gained.
+  // This returns zero or a negative unix error code.
+  int Post(const LocalHandle& ready_fence, const void* meta,
+           size_t user_metadata_size);
+
+  int Post(const LocalHandle& ready_fence) {
+    return Post(ready_fence, nullptr, 0);
+  }
+
+  template <typename Meta, typename = typename std::enable_if<
+                               !std::is_void<Meta>::value>::type>
+  int Post(const LocalHandle& ready_fence, const Meta& meta) {
+    return Post(ready_fence, &meta, sizeof(meta));
+  }
+
+  // Attempt to re-gain the buffer for writing. If |release_fence| is valid, it
+  // must be waited on before using the buffer. If it is not valid then the
+  // buffer is free for immediate use. This call will only succeed if the buffer
+  // is in the released state.
+  // This returns zero or a negative unix error code.
+  int Gain(LocalHandle* release_fence);
+  int GainAsync();
+
+  // Asynchronously marks a released buffer as gained. This method is similar to
+  // the synchronous version above, except that it does not wait for BufferHub
+  // to acknowledge success or failure. Because of the asynchronous nature of
+  // the underlying message, no error is returned if this method is called when
+  // the buffer is in an incorrect state. Returns zero if sending the message
+  // succeeded, or a negative errno code if local error check fails.
+  int GainAsync(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
+
+  // Detaches a ProducerBuffer from an existing producer/consumer set. Can only
+  // be called when a producer buffer has exclusive access to the buffer (i.e.
+  // in the gain'ed state). On the successful return of the IPC call, a new
+  // LocalChannelHandle representing a detached buffer will be returned and all
+  // existing producer and consumer channels will be closed. Further IPCs
+  // towards those channels will return error.
+  Status<LocalChannelHandle> Detach();
+
+ private:
+  friend BASE;
+
+  // Constructors are automatically exposed through ProducerBuffer::Create(...)
+  // static template methods inherited from ClientBase, which take the same
+  // arguments as the constructors.
+
+  // Constructs a buffer with the given geometry and parameters.
+  ProducerBuffer(uint32_t width, uint32_t height, uint32_t format,
+                 uint64_t usage, size_t metadata_size = 0);
+
+  // Constructs a blob (flat) buffer with the given usage flags.
+  ProducerBuffer(uint64_t usage, size_t size);
+
+  // Imports the given file handle to a producer channel, taking ownership.
+  explicit ProducerBuffer(LocalChannelHandle channel);
+
+  // Local state transition helpers.
+  int LocalGain(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
+  int LocalPost(const DvrNativeBufferMetadata* meta,
+                const LocalHandle& ready_fence);
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_PRODUCER_BUFFER_H_
diff --git a/libs/vr/libbufferhub/producer_buffer.cpp b/libs/vr/libbufferhub/producer_buffer.cpp
new file mode 100644
index 0000000..c4f1a3b
--- /dev/null
+++ b/libs/vr/libbufferhub/producer_buffer.cpp
@@ -0,0 +1,243 @@
+#include <private/dvr/producer_buffer.h>
+
+using android::pdx::LocalChannelHandle;
+using android::pdx::LocalHandle;
+using android::pdx::Status;
+
+namespace android {
+namespace dvr {
+
+ProducerBuffer::ProducerBuffer(uint32_t width, uint32_t height, uint32_t format,
+                               uint64_t usage, size_t user_metadata_size)
+    : BASE(BufferHubRPC::kClientPath) {
+  ATRACE_NAME("ProducerBuffer::ProducerBuffer");
+  ALOGD_IF(TRACE,
+           "ProducerBuffer::ProducerBuffer: fd=%d width=%u height=%u format=%u "
+           "usage=%" PRIx64 " user_metadata_size=%zu",
+           event_fd(), width, height, format, usage, user_metadata_size);
+
+  auto status = InvokeRemoteMethod<BufferHubRPC::CreateBuffer>(
+      width, height, format, usage, user_metadata_size);
+  if (!status) {
+    ALOGE(
+        "ProducerBuffer::ProducerBuffer: Failed to create producer buffer: %s",
+        status.GetErrorMessage().c_str());
+    Close(-status.error());
+    return;
+  }
+
+  const int ret = ImportBuffer();
+  if (ret < 0) {
+    ALOGE(
+        "ProducerBuffer::ProducerBuffer: Failed to import producer buffer: %s",
+        strerror(-ret));
+    Close(ret);
+  }
+}
+
+ProducerBuffer::ProducerBuffer(uint64_t usage, size_t size)
+    : BASE(BufferHubRPC::kClientPath) {
+  ATRACE_NAME("ProducerBuffer::ProducerBuffer");
+  ALOGD_IF(TRACE, "ProducerBuffer::ProducerBuffer: usage=%" PRIx64 " size=%zu",
+           usage, size);
+  const int width = static_cast<int>(size);
+  const int height = 1;
+  const int format = HAL_PIXEL_FORMAT_BLOB;
+  const size_t user_metadata_size = 0;
+
+  auto status = InvokeRemoteMethod<BufferHubRPC::CreateBuffer>(
+      width, height, format, usage, user_metadata_size);
+  if (!status) {
+    ALOGE("ProducerBuffer::ProducerBuffer: Failed to create blob: %s",
+          status.GetErrorMessage().c_str());
+    Close(-status.error());
+    return;
+  }
+
+  const int ret = ImportBuffer();
+  if (ret < 0) {
+    ALOGE(
+        "ProducerBuffer::ProducerBuffer: Failed to import producer buffer: %s",
+        strerror(-ret));
+    Close(ret);
+  }
+}
+
+ProducerBuffer::ProducerBuffer(LocalChannelHandle channel)
+    : BASE(std::move(channel)) {
+  const int ret = ImportBuffer();
+  if (ret < 0) {
+    ALOGE(
+        "ProducerBuffer::ProducerBuffer: Failed to import producer buffer: %s",
+        strerror(-ret));
+    Close(ret);
+  }
+}
+
+int ProducerBuffer::LocalPost(const DvrNativeBufferMetadata* meta,
+                              const LocalHandle& ready_fence) {
+  if (const int error = CheckMetadata(meta->user_metadata_size))
+    return error;
+
+  // Check invalid state transition.
+  uint64_t buffer_state = buffer_state_->load();
+  if (!BufferHubDefs::IsBufferGained(buffer_state)) {
+    ALOGE("ProducerBuffer::LocalPost: not gained, id=%d state=%" PRIx64 ".",
+          id(), buffer_state);
+    return -EBUSY;
+  }
+
+  // Copy the canonical metadata.
+  void* metadata_ptr = reinterpret_cast<void*>(&metadata_header_->metadata);
+  memcpy(metadata_ptr, meta, sizeof(DvrNativeBufferMetadata));
+  // Copy extra user requested metadata.
+  if (meta->user_metadata_ptr && meta->user_metadata_size) {
+    void* metadata_src = reinterpret_cast<void*>(meta->user_metadata_ptr);
+    memcpy(user_metadata_ptr_, metadata_src, meta->user_metadata_size);
+  }
+
+  // Send out the acquire fence through the shared epoll fd. Note that during
+  // posting no consumer is not expected to be polling on the fence.
+  if (const int error = UpdateSharedFence(ready_fence, shared_acquire_fence_))
+    return error;
+
+  // Set the producer bit atomically to transit into posted state.
+  BufferHubDefs::ModifyBufferState(buffer_state_, 0ULL,
+                                   BufferHubDefs::kProducerStateBit);
+  return 0;
+}
+
+int ProducerBuffer::Post(const LocalHandle& ready_fence, const void* meta,
+                         size_t user_metadata_size) {
+  ATRACE_NAME("ProducerBuffer::Post");
+
+  // Populate cononical metadata for posting.
+  DvrNativeBufferMetadata canonical_meta;
+  canonical_meta.user_metadata_ptr = reinterpret_cast<uint64_t>(meta);
+  canonical_meta.user_metadata_size = user_metadata_size;
+
+  if (const int error = LocalPost(&canonical_meta, ready_fence))
+    return error;
+
+  return ReturnStatusOrError(InvokeRemoteMethod<BufferHubRPC::ProducerPost>(
+      BorrowedFence(ready_fence.Borrow())));
+}
+
+int ProducerBuffer::PostAsync(const DvrNativeBufferMetadata* meta,
+                              const LocalHandle& ready_fence) {
+  ATRACE_NAME("ProducerBuffer::PostAsync");
+
+  if (const int error = LocalPost(meta, ready_fence))
+    return error;
+
+  return ReturnStatusOrError(SendImpulse(BufferHubRPC::ProducerPost::Opcode));
+}
+
+int ProducerBuffer::LocalGain(DvrNativeBufferMetadata* out_meta,
+                              LocalHandle* out_fence) {
+  uint64_t buffer_state = buffer_state_->load();
+  ALOGD_IF(TRACE, "ProducerBuffer::LocalGain: buffer=%d, state=%" PRIx64 ".",
+           id(), buffer_state);
+
+  if (!out_meta)
+    return -EINVAL;
+
+  if (!BufferHubDefs::IsBufferReleased(buffer_state)) {
+    if (BufferHubDefs::IsBufferGained(buffer_state)) {
+      // We don't want to log error when gaining a newly allocated
+      // buffer.
+      ALOGI("ProducerBuffer::LocalGain: already gained id=%d.", id());
+      return -EALREADY;
+    }
+    ALOGE("ProducerBuffer::LocalGain: not released id=%d state=%" PRIx64 ".",
+          id(), buffer_state);
+    return -EBUSY;
+  }
+
+  // Canonical metadata is undefined on Gain. Except for user_metadata and
+  // release_fence_mask. Fill in the user_metadata_ptr in address space of the
+  // local process.
+  if (metadata_header_->metadata.user_metadata_size && user_metadata_ptr_) {
+    out_meta->user_metadata_size =
+        metadata_header_->metadata.user_metadata_size;
+    out_meta->user_metadata_ptr =
+        reinterpret_cast<uint64_t>(user_metadata_ptr_);
+  } else {
+    out_meta->user_metadata_size = 0;
+    out_meta->user_metadata_ptr = 0;
+  }
+
+  uint64_t fence_state = fence_state_->load();
+  // If there is an release fence from consumer, we need to return it.
+  if (fence_state & BufferHubDefs::kConsumerStateMask) {
+    *out_fence = shared_release_fence_.Duplicate();
+    out_meta->release_fence_mask =
+        fence_state & BufferHubDefs::kConsumerStateMask;
+  }
+
+  // Clear out all bits and the buffer is now back to gained state.
+  buffer_state_->store(0ULL);
+  return 0;
+}
+
+int ProducerBuffer::Gain(LocalHandle* release_fence) {
+  ATRACE_NAME("ProducerBuffer::Gain");
+
+  DvrNativeBufferMetadata meta;
+  if (const int error = LocalGain(&meta, release_fence))
+    return error;
+
+  auto status = InvokeRemoteMethod<BufferHubRPC::ProducerGain>();
+  if (!status)
+    return -status.error();
+  return 0;
+}
+
+int ProducerBuffer::GainAsync(DvrNativeBufferMetadata* out_meta,
+                              LocalHandle* release_fence) {
+  ATRACE_NAME("ProducerBuffer::GainAsync");
+
+  if (const int error = LocalGain(out_meta, release_fence))
+    return error;
+
+  return ReturnStatusOrError(SendImpulse(BufferHubRPC::ProducerGain::Opcode));
+}
+
+int ProducerBuffer::GainAsync() {
+  DvrNativeBufferMetadata meta;
+  LocalHandle fence;
+  return GainAsync(&meta, &fence);
+}
+
+std::unique_ptr<ProducerBuffer> ProducerBuffer::Import(
+    LocalChannelHandle channel) {
+  ALOGD_IF(TRACE, "ProducerBuffer::Import: channel=%d", channel.value());
+  return ProducerBuffer::Create(std::move(channel));
+}
+
+std::unique_ptr<ProducerBuffer> ProducerBuffer::Import(
+    Status<LocalChannelHandle> status) {
+  return Import(status ? status.take()
+                       : LocalChannelHandle{nullptr, -status.error()});
+}
+
+Status<LocalChannelHandle> ProducerBuffer::Detach() {
+  uint64_t buffer_state = buffer_state_->load();
+  if (!BufferHubDefs::IsBufferGained(buffer_state)) {
+    // Can only detach a ProducerBuffer when it's in gained state.
+    ALOGW("ProducerBuffer::Detach: The buffer (id=%d, state=0x%" PRIx64
+          ") is not in gained state.",
+          id(), buffer_state);
+    return {};
+  }
+
+  Status<LocalChannelHandle> status =
+      InvokeRemoteMethod<BufferHubRPC::ProducerBufferDetach>();
+  ALOGE_IF(!status,
+           "ProducerBuffer::Detach: Failed to detach buffer (id=%d): %s.", id(),
+           status.GetErrorMessage().c_str());
+  return status;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
index 44276ba..e1c1aa9 100644
--- a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
@@ -278,7 +278,7 @@
 }
 
 Status<void> BufferHubQueue::AddBuffer(
-    const std::shared_ptr<BufferHubBuffer>& buffer, size_t slot) {
+    const std::shared_ptr<BufferHubBase>& buffer, size_t slot) {
   ALOGD_IF(TRACE, "BufferHubQueue::AddBuffer: buffer_id=%d slot=%zu",
            buffer->id(), slot);
 
@@ -356,8 +356,8 @@
   }
 }
 
-Status<std::shared_ptr<BufferHubBuffer>> BufferHubQueue::Dequeue(int timeout,
-                                                                 size_t* slot) {
+Status<std::shared_ptr<BufferHubBase>> BufferHubQueue::Dequeue(int timeout,
+                                                               size_t* slot) {
   ALOGD_IF(TRACE, "BufferHubQueue::Dequeue: count=%zu, timeout=%d", count(),
            timeout);
 
@@ -372,7 +372,7 @@
   PDX_TRACE_FORMAT("buffer|buffer_id=%d;slot=%zu|", entry.buffer->id(),
                    entry.slot);
 
-  std::shared_ptr<BufferHubBuffer> buffer = std::move(entry.buffer);
+  std::shared_ptr<BufferHubBase> buffer = std::move(entry.buffer);
   *slot = entry.slot;
 
   available_buffers_.pop();
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
index df500b4..c69002d 100644
--- a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
@@ -31,13 +31,13 @@
 
 class ConsumerQueue;
 
-// |BufferHubQueue| manages a queue of |BufferHubBuffer|s. Buffers are
+// |BufferHubQueue| manages a queue of |BufferHubBase|s. Buffers are
 // automatically re-requeued when released by the remote side.
 class BufferHubQueue : public pdx::Client {
  public:
   using BufferAvailableCallback = std::function<void()>;
   using BufferRemovedCallback =
-      std::function<void(const std::shared_ptr<BufferHubBuffer>&)>;
+      std::function<void(const std::shared_ptr<BufferHubBase>&)>;
 
   virtual ~BufferHubQueue() {}
 
@@ -93,7 +93,7 @@
                                                       : -1;
   }
 
-  std::shared_ptr<BufferHubBuffer> GetBuffer(size_t slot) const {
+  std::shared_ptr<BufferHubBase> GetBuffer(size_t slot) const {
     return buffers_[slot];
   }
 
@@ -142,7 +142,7 @@
 
   // Register a buffer for management by the queue. Used by subclasses to add a
   // buffer to internal bookkeeping.
-  pdx::Status<void> AddBuffer(const std::shared_ptr<BufferHubBuffer>& buffer,
+  pdx::Status<void> AddBuffer(const std::shared_ptr<BufferHubBase>& buffer,
                               size_t slot);
 
   // Called by ProducerQueue::RemoveBuffer and ConsumerQueue::RemoveBuffer only
@@ -158,8 +158,8 @@
   // 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.
-  pdx::Status<std::shared_ptr<BufferHubBuffer>> Dequeue(int timeout,
-                                                        size_t* slot);
+  pdx::Status<std::shared_ptr<BufferHubBase>> Dequeue(int timeout,
+                                                      size_t* slot);
 
   // Waits for buffers to become available and adds them to the available queue.
   bool WaitForBuffers(int timeout);
@@ -172,10 +172,10 @@
   // per-buffer data.
   struct Entry {
     Entry() : slot(0) {}
-    Entry(const std::shared_ptr<BufferHubBuffer>& in_buffer, size_t in_slot,
+    Entry(const std::shared_ptr<BufferHubBase>& in_buffer, size_t in_slot,
           uint64_t in_index)
         : buffer(in_buffer), slot(in_slot), index(in_index) {}
-    Entry(const std::shared_ptr<BufferHubBuffer>& in_buffer,
+    Entry(const std::shared_ptr<BufferHubBase>& in_buffer,
           std::unique_ptr<uint8_t[]> in_metadata, pdx::LocalHandle in_fence,
           size_t in_slot)
         : buffer(in_buffer),
@@ -185,7 +185,7 @@
     Entry(Entry&&) = default;
     Entry& operator=(Entry&&) = default;
 
-    std::shared_ptr<BufferHubBuffer> buffer;
+    std::shared_ptr<BufferHubBase> buffer;
     std::unique_ptr<uint8_t[]> metadata;
     pdx::LocalHandle fence;
     size_t slot;
@@ -250,7 +250,7 @@
   // Tracks the buffers belonging to this queue. Buffers are stored according to
   // "slot" in this vector. Each slot is a logical id of the buffer within this
   // queue regardless of its queue position or presence in the ring buffer.
-  std::array<std::shared_ptr<BufferHubBuffer>, kMaxQueueCapacity> buffers_;
+  std::array<std::shared_ptr<BufferHubBase>, kMaxQueueCapacity> buffers_;
 
   // Buffers and related data that are available for dequeue.
   std::priority_queue<Entry, std::vector<Entry>, EntryComparator>
diff --git a/libs/vr/libdvr/dvr_buffer_queue.cpp b/libs/vr/libdvr/dvr_buffer_queue.cpp
index 571558a..f4c6600 100644
--- a/libs/vr/libdvr/dvr_buffer_queue.cpp
+++ b/libs/vr/libdvr/dvr_buffer_queue.cpp
@@ -9,7 +9,7 @@
 
 using namespace android;
 using android::dvr::BufferConsumer;
-using android::dvr::BufferHubBuffer;
+using android::dvr::BufferHubBase;
 using android::dvr::BufferProducer;
 using android::dvr::ConsumerQueue;
 using android::dvr::ProducerQueue;
@@ -439,7 +439,7 @@
     consumer_queue_->SetBufferRemovedCallback(nullptr);
   } else {
     consumer_queue_->SetBufferRemovedCallback(
-        [callback, context](const std::shared_ptr<BufferHubBuffer>& buffer) {
+        [callback, context](const std::shared_ptr<BufferHubBase>& buffer) {
           // When buffer is removed from the queue, the slot is already invalid.
           auto read_buffer = std::make_unique<DvrReadBuffer>();
           read_buffer->read_buffer =
diff --git a/libs/vr/libdvr/dvr_internal.h b/libs/vr/libdvr/dvr_internal.h
index de8bb96..df8125a 100644
--- a/libs/vr/libdvr/dvr_internal.h
+++ b/libs/vr/libdvr/dvr_internal.h
@@ -16,8 +16,11 @@
 namespace android {
 namespace dvr {
 
-class BufferProducer;
-class BufferConsumer;
+// TODO(b/116855254): Remove this typedef once rename is complete in libdvr.
+// Note that the dvr::BufferProducer and dvr::BufferConsumer were poorly named,
+// they should really be named as ProducerBuffer and ConsumerBuffer.
+typedef class ProducerBuffer BufferProducer;
+typedef class ConsumerBuffer BufferConsumer;
 class IonBuffer;
 
 DvrBuffer* CreateDvrBufferFromIonBuffer(
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 642ed2f..cafe26b 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -258,6 +258,7 @@
     getBE().compositionInfo.hwc.dataspace = mCurrentDataSpace;
     getBE().compositionInfo.hwc.hdrMetadata = getDrawingHdrMetadata();
     getBE().compositionInfo.hwc.supportedPerFrameMetadata = display->getSupportedPerFrameMetadata();
+    getBE().compositionInfo.hwc.colorTransform = getColorTransform();
 
     setHwcLayerBuffer(display);
 }
@@ -319,7 +320,8 @@
     return true;
 }
 
-Region BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) {
+Region BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
+                                const sp<Fence>& releaseFence) {
     ATRACE_CALL();
 
     std::optional<Region> sidebandStreamDirtyRegion = latchSidebandStream(recomputeVisibleRegions);
@@ -360,7 +362,7 @@
         return dirtyRegion;
     }
 
-    status_t err = updateTexImage(recomputeVisibleRegions, latchTime);
+    status_t err = updateTexImage(recomputeVisibleRegions, latchTime, releaseFence);
     if (err != NO_ERROR) {
         return dirtyRegion;
     }
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 92bf132..a294793 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -91,7 +91,11 @@
     // the visible regions need to be recomputed (this is a fairly heavy
     // operation, so this should be set only if needed). Typically this is used
     // to figure out if the content or size of a surface has changed.
-    Region latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) override;
+    // If there was a GL composition step rendering the previous frame, then
+    // releaseFence will be populated with a native fence that fires when
+    // composition has completed.
+    Region latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
+                       const sp<Fence>& releaseFence) override;
 
     bool isBufferLatched() const override { return mRefreshPending; }
 
@@ -135,7 +139,8 @@
     virtual void setFilteringEnabled(bool enabled) = 0;
 
     virtual status_t bindTextureImage() const = 0;
-    virtual status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime) = 0;
+    virtual status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
+                                    const sp<Fence>& flushFence) = 0;
 
     virtual status_t updateActiveBuffer() = 0;
     virtual status_t updateFrameNumber(nsecs_t latchTime) = 0;
diff --git a/services/surfaceflinger/BufferLayerConsumer.cpp b/services/surfaceflinger/BufferLayerConsumer.cpp
index fa181ad..f499f06 100644
--- a/services/surfaceflinger/BufferLayerConsumer.cpp
+++ b/services/surfaceflinger/BufferLayerConsumer.cpp
@@ -147,7 +147,8 @@
 
 status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, const DispSync& dispSync,
                                              bool* autoRefresh, bool* queuedBuffer,
-                                             uint64_t maxFrameNumber) {
+                                             uint64_t maxFrameNumber,
+                                             const sp<Fence>& releaseFence) {
     ATRACE_CALL();
     BLC_LOGV("updateTexImage");
     Mutex::Autolock lock(mMutex);
@@ -198,12 +199,12 @@
     }
 
     // Release the previous buffer.
-    err = updateAndReleaseLocked(item, &mPendingRelease);
+    err = updateAndReleaseLocked(item, &mPendingRelease, releaseFence);
     if (err != NO_ERROR) {
         return err;
     }
 
-    if (mRE.useNativeFenceSync()) {
+    if (!mRE.useNativeFenceSync()) {
         // Bind the new buffer to the GL texture.
         //
         // Older devices require the "implicit" synchronization provided
@@ -278,14 +279,15 @@
 }
 
 status_t BufferLayerConsumer::updateAndReleaseLocked(const BufferItem& item,
-                                                     PendingRelease* pendingRelease) {
+                                                     PendingRelease* pendingRelease,
+                                                     const sp<Fence>& releaseFence) {
     status_t err = NO_ERROR;
 
     int slot = item.mSlot;
 
     // Do whatever sync ops we need to do before releasing the old slot.
     if (slot != mCurrentTexture) {
-        err = syncForReleaseLocked();
+        err = syncForReleaseLocked(releaseFence);
         if (err != NO_ERROR) {
             // Release the buffer we just acquired.  It's not safe to
             // release the old buffer, so instead we just drop the new frame.
@@ -367,19 +369,19 @@
     return doFenceWaitLocked();
 }
 
-status_t BufferLayerConsumer::syncForReleaseLocked() {
+status_t BufferLayerConsumer::syncForReleaseLocked(const sp<Fence>& releaseFence) {
     BLC_LOGV("syncForReleaseLocked");
 
     if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
-        if (mRE.useNativeFenceSync()) {
-            base::unique_fd fenceFd = mRE.flush();
-            if (fenceFd == -1) {
+        if (mRE.useNativeFenceSync() && releaseFence != Fence::NO_FENCE) {
+            // TODO(alecmouri): fail further upstream if the fence is invalid
+            if (!releaseFence->isValid()) {
                 BLC_LOGE("syncForReleaseLocked: failed to flush RenderEngine");
                 return UNKNOWN_ERROR;
             }
-            sp<Fence> fence(new Fence(std::move(fenceFd)));
-            status_t err = addReleaseFenceLocked(mCurrentTexture,
-                                                 mCurrentTextureImage->graphicBuffer(), fence);
+            status_t err =
+                    addReleaseFenceLocked(mCurrentTexture, mCurrentTextureImage->graphicBuffer(),
+                                          releaseFence);
             if (err != OK) {
                 BLC_LOGE("syncForReleaseLocked: error adding release fence: "
                          "%s (%d)",
diff --git a/services/surfaceflinger/BufferLayerConsumer.h b/services/surfaceflinger/BufferLayerConsumer.h
index 257a4e5..dc00487 100644
--- a/services/surfaceflinger/BufferLayerConsumer.h
+++ b/services/surfaceflinger/BufferLayerConsumer.h
@@ -94,7 +94,8 @@
     // used to reject the newly acquired buffer.  It also does not bind the
     // RenderEngine texture until bindTextureImage is called.
     status_t updateTexImage(BufferRejecter* rejecter, const DispSync& dispSync, bool* autoRefresh,
-                            bool* queuedBuffer, uint64_t maxFrameNumber);
+                            bool* queuedBuffer, uint64_t maxFrameNumber,
+                            const sp<Fence>& releaseFence);
 
     // See BufferLayerConsumer::bindTextureImageLocked().
     status_t bindTextureImage();
@@ -208,7 +209,8 @@
     // completion of the method will instead be returned to the caller, so that
     // it may call releaseBufferLocked itself later.
     status_t updateAndReleaseLocked(const BufferItem& item,
-                                    PendingRelease* pendingRelease = nullptr);
+                                    PendingRelease* pendingRelease = nullptr,
+                                    const sp<Fence>& releaseFence = Fence::NO_FENCE);
 
     // Binds mTexName and the current buffer to TEXTURE_EXTERNAL target.  Uses
     // mCurrentTexture if it's set, mCurrentTextureImage if not.  If the
@@ -282,7 +284,7 @@
     // current slot from RenderEngine.  If needed it will set the current
     // slot's fence to guard against a producer accessing the buffer before
     // the outstanding accesses have completed.
-    status_t syncForReleaseLocked();
+    status_t syncForReleaseLocked(const sp<Fence>& releaseFence);
 
     // The default consumer usage flags that BufferLayerConsumer always sets on its
     // BufferQueue instance; these will be OR:d with any additional flags passed
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index 17706b5..6822298 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -221,7 +221,8 @@
     return mConsumer->bindTextureImage();
 }
 
-status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime) {
+status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
+                                          const sp<Fence>& releaseFence) {
     // This boolean is used to make sure that SurfaceFlinger's shadow copy
     // of the buffer queue isn't modified when the buffer queue is returning
     // BufferItem's that weren't actually queued. This can happen in shared
@@ -232,7 +233,7 @@
                     getTransformToDisplayInverse(), mFreezeGeometryUpdates);
     status_t updateResult =
             mConsumer->updateTexImage(&r, *mFlinger->mPrimaryDispSync, &mAutoRefresh, &queuedBuffer,
-                                      mLastFrameNumberReceived);
+                                      mLastFrameNumberReceived, releaseFence);
     if (updateResult == BufferQueue::PRESENT_LATER) {
         // Producer doesn't want buffer to be displayed yet.  Signal a
         // layer update so we check again at the next opportunity.
diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h
index 051b15d..dcf39ee 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -90,7 +90,8 @@
     void setFilteringEnabled(bool enabled) override;
 
     status_t bindTextureImage() const override;
-    status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime) override;
+    status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
+                            const sp<Fence>& releaseFence) override;
 
     status_t updateActiveBuffer() override;
     status_t updateFrameNumber(nsecs_t latchTime) override;
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 9c10868..2ecf71c 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -377,7 +377,8 @@
     return NO_ERROR;
 }
 
-status_t BufferStateLayer::updateTexImage(bool& /*recomputeVisibleRegions*/, nsecs_t latchTime) {
+status_t BufferStateLayer::updateTexImage(bool& /*recomputeVisibleRegions*/, nsecs_t latchTime,
+                                          const sp<Fence>& releaseFence) {
     const State& s(getDrawingState());
 
     if (!s.buffer) {
@@ -419,16 +420,13 @@
     }
 
     // Handle sync fences
-    if (SyncFeatures::getInstance().useNativeFenceSync()) {
-        base::unique_fd fenceFd = engine.flush();
-        if (fenceFd == -1) {
-            ALOGE("failed to flush RenderEngine");
+    if (SyncFeatures::getInstance().useNativeFenceSync() && releaseFence != Fence::NO_FENCE) {
+        // TODO(alecmouri): Fail somewhere upstream if the fence is invalid.
+        if (!releaseFence->isValid()) {
             mTimeStats.clearLayerRecord(getName().c_str());
             return UNKNOWN_ERROR;
         }
 
-        sp<Fence> fence(new Fence(std::move(fenceFd)));
-
         // Check status of fences first because merging is expensive.
         // Merging an invalid fence with any other fence results in an
         // invalid fence.
@@ -439,10 +437,10 @@
             return BAD_VALUE;
         }
 
-        auto incomingStatus = fence->getStatus();
+        auto incomingStatus = releaseFence->getStatus();
         if (incomingStatus == Fence::Status::Invalid) {
             ALOGE("New fence has invalid state");
-            mDrawingState.acquireFence = fence;
+            mDrawingState.acquireFence = releaseFence;
             mTimeStats.clearLayerRecord(getName().c_str());
             return BAD_VALUE;
         }
@@ -452,12 +450,13 @@
         if (currentStatus == incomingStatus) {
             char fenceName[32] = {};
             snprintf(fenceName, 32, "%.28s:%d", mName.string(), mFrameNumber);
-            sp<Fence> mergedFence = Fence::merge(fenceName, mDrawingState.acquireFence, fence);
+            sp<Fence> mergedFence =
+                    Fence::merge(fenceName, mDrawingState.acquireFence, releaseFence);
             if (!mergedFence.get()) {
                 ALOGE("failed to merge release fences");
                 // synchronization is broken, the best we can do is hope fences
                 // signal in order so the new fence will act like a union
-                mDrawingState.acquireFence = fence;
+                mDrawingState.acquireFence = releaseFence;
                 mTimeStats.clearLayerRecord(getName().c_str());
                 return BAD_VALUE;
             }
@@ -470,7 +469,7 @@
             // by this point, they will have both signaled and only the timestamp
             // will be slightly off; any dependencies after this point will
             // already have been met.
-            mDrawingState.acquireFence = fence;
+            mDrawingState.acquireFence = releaseFence;
         }
     } else {
         // Bind the new buffer to the GL texture.
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index b662b96..db11110 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -115,7 +115,8 @@
     void setFilteringEnabled(bool enabled) override;
 
     status_t bindTextureImage() const override;
-    status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime) override;
+    status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
+                            const sp<Fence>& releaseFence) override;
 
     status_t updateActiveBuffer() override;
     status_t updateFrameNumber(nsecs_t latchTime) override;
diff --git a/services/surfaceflinger/ColorLayer.cpp b/services/surfaceflinger/ColorLayer.cpp
index b02c16c..3a554c9 100644
--- a/services/surfaceflinger/ColorLayer.cpp
+++ b/services/surfaceflinger/ColorLayer.cpp
@@ -75,6 +75,7 @@
 
     // Clear out the transform, because it doesn't make sense absent a source buffer
     getBE().compositionInfo.hwc.transform = HWC2::Transform::None;
+    getBE().compositionInfo.hwc.colorTransform = getColorTransform();
 }
 
 // ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index f9bc1e7..8afd3b3 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1546,6 +1546,25 @@
     return true;
 }
 
+bool Layer::setColorTransform(const mat4& matrix) {
+    if (mCurrentState.colorTransform == matrix) {
+        return false;
+    }
+    ++mCurrentState.sequence;
+    mCurrentState.colorTransform = matrix;
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
+const mat4& Layer::getColorTransform() const {
+    return getDrawingState().colorTransform;
+}
+
+bool Layer::hasColorTransform() const {
+    static const mat4 identityMatrix = mat4();
+    return getDrawingState().colorTransform != identityMatrix;
+}
+
 bool Layer::isLegacyDataSpace() const {
     // return true when no higher bits are set
     return !(mCurrentDataSpace & (ui::Dataspace::STANDARD_MASK |
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 874b551..3eb8327 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -181,6 +181,7 @@
         int32_t api;
 
         sp<NativeHandle> sidebandStream;
+        mat4 colorTransform;
     };
 
     explicit Layer(const LayerCreationArgs& args);
@@ -255,6 +256,9 @@
     virtual void setChildrenDrawingParent(const sp<Layer>& layer);
     virtual bool reparent(const sp<IBinder>& newParentHandle);
     virtual bool detachChildren();
+    virtual bool setColorTransform(const mat4& matrix);
+    virtual const mat4& getColorTransform() const;
+    virtual bool hasColorTransform() const;
 
     // Used only to set BufferStateLayer state
     virtual bool setTransform(uint32_t /*transform*/) { return false; };
@@ -457,7 +461,8 @@
      * operation, so this should be set only if needed). Typically this is used
      * to figure out if the content or size of a surface has changed.
      */
-    virtual Region latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/) {
+    virtual Region latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/,
+                               const sp<Fence>& /*releaseFence*/) {
         return {};
     }
 
diff --git a/services/surfaceflinger/LayerBE.cpp b/services/surfaceflinger/LayerBE.cpp
index ef017aa..c9b7933 100644
--- a/services/surfaceflinger/LayerBE.cpp
+++ b/services/surfaceflinger/LayerBE.cpp
@@ -112,6 +112,20 @@
         hwc.surfaceDamage.dump(regionString, "surfaceDamage");
         result += regionString.string();
     }
+
+    result += base::StringPrintf("\tcolor transform matrix:\n"
+                                 "\t\t[%f, %f, %f, %f,\n"
+                                 "\t\t %f, %f, %f, %f,\n"
+                                 "\t\t %f, %f, %f, %f,\n"
+                                 "\t\t %f, %f, %f, %f]\n",
+                                 hwc.colorTransform[0][0], hwc.colorTransform[1][0],
+                                 hwc.colorTransform[2][0], hwc.colorTransform[3][0],
+                                 hwc.colorTransform[0][1], hwc.colorTransform[1][1],
+                                 hwc.colorTransform[2][1], hwc.colorTransform[3][1],
+                                 hwc.colorTransform[0][2], hwc.colorTransform[1][2],
+                                 hwc.colorTransform[2][2], hwc.colorTransform[3][2],
+                                 hwc.colorTransform[0][3], hwc.colorTransform[1][3],
+                                 hwc.colorTransform[2][3], hwc.colorTransform[3][3]);
 }
 
 void CompositionInfo::dumpRe(std::string& result, const char* tag) const {
diff --git a/services/surfaceflinger/LayerBE.h b/services/surfaceflinger/LayerBE.h
index 2722b01..d63d16f 100644
--- a/services/surfaceflinger/LayerBE.h
+++ b/services/surfaceflinger/LayerBE.h
@@ -60,6 +60,7 @@
         bool clearClientTarget = false;
         bool supportedPerFrameMetadata = false;
         HdrMetadata hdrMetadata;
+        mat4 colorTransform;
     } hwc;
     struct {
         bool blackoutLayer = false;
diff --git a/services/surfaceflinger/RenderEngine/gl/GLES20RenderEngine.cpp b/services/surfaceflinger/RenderEngine/gl/GLES20RenderEngine.cpp
index 813c9e6..1b0a539 100644
--- a/services/surfaceflinger/RenderEngine/gl/GLES20RenderEngine.cpp
+++ b/services/surfaceflinger/RenderEngine/gl/GLES20RenderEngine.cpp
@@ -743,7 +743,7 @@
     mState.textureEnabled = true;
 }
 
-void GLES20RenderEngine::setupColorTransform(const mat4& colorTransform) {
+void GLES20RenderEngine::setColorTransform(const mat4& colorTransform) {
     mState.colorMatrix = colorTransform;
 }
 
diff --git a/services/surfaceflinger/RenderEngine/gl/GLES20RenderEngine.h b/services/surfaceflinger/RenderEngine/gl/GLES20RenderEngine.h
index fa01410..4f03a90 100644
--- a/services/surfaceflinger/RenderEngine/gl/GLES20RenderEngine.h
+++ b/services/surfaceflinger/RenderEngine/gl/GLES20RenderEngine.h
@@ -86,7 +86,7 @@
     void setupLayerTexturing(const Texture& texture) override;
     void setupLayerBlackedOut() override;
     void setupFillWithColor(float r, float g, float b, float a) override;
-    void setupColorTransform(const mat4& colorTransform) override;
+    void setColorTransform(const mat4& colorTransform) override;
     void disableTexturing() override;
     void disableBlending() override;
 
diff --git a/services/surfaceflinger/RenderEngine/include/renderengine/RenderEngine.h b/services/surfaceflinger/RenderEngine/include/renderengine/RenderEngine.h
index 05668f8..122271f 100644
--- a/services/surfaceflinger/RenderEngine/include/renderengine/RenderEngine.h
+++ b/services/surfaceflinger/RenderEngine/include/renderengine/RenderEngine.h
@@ -115,7 +115,9 @@
     virtual void setupLayerTexturing(const Texture& texture) = 0;
     virtual void setupLayerBlackedOut() = 0;
     virtual void setupFillWithColor(float r, float g, float b, float a) = 0;
-    virtual void setupColorTransform(const mat4& /* colorTransform */) = 0;
+
+    // Set a color transform matrix that is applied in linear space right before OETF.
+    virtual void setColorTransform(const mat4& /* colorTransform */) = 0;
     virtual void disableTexturing() = 0;
     virtual void disableBlending() = 0;
 
@@ -163,7 +165,6 @@
 
     bool useNativeFenceSync() const override;
     bool useWaitSync() const override;
-    void setupColorTransform(const mat4& /* colorTransform */) override {}
 
 protected:
     RenderEngine(uint32_t featureFlags);
diff --git a/services/surfaceflinger/RenderEngine/include/renderengine/private/Description.h b/services/surfaceflinger/RenderEngine/include/renderengine/private/Description.h
index efab8ff..911bb7a 100644
--- a/services/surfaceflinger/RenderEngine/include/renderengine/private/Description.h
+++ b/services/surfaceflinger/RenderEngine/include/renderengine/private/Description.h
@@ -68,6 +68,8 @@
 
     // projection matrix
     mat4 projectionMatrix;
+
+    // The color matrix will be applied in linear space right before OETF.
     mat4 colorMatrix;
     mat4 inputTransformMatrix;
     mat4 outputTransformMatrix;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9410cdb..37b32ed 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1528,6 +1528,18 @@
                 getBE().mHwc->hasClientComposition(display->getId());
     }
 
+    // Setup RenderEngine sync fences if native sync is supported.
+    if (getBE().mRenderEngine->useNativeFenceSync()) {
+        if (mHadClientComposition) {
+            base::unique_fd flushFence(getRenderEngine().flush());
+            ALOGE_IF(flushFence < 0, "Failed to flush RenderEngine!");
+            getBE().flushFence = new Fence(std::move(flushFence));
+        } else {
+            // Cleanup for hygiene.
+            getBE().flushFence = Fence::NO_FENCE;
+        }
+    }
+
     mVsyncModulator.onRefreshed(mHadClientComposition);
 
     getBE().mEndOfFrameCompositionInfo = std::move(getBE().mCompositionInfo);
@@ -1604,6 +1616,11 @@
                 layer->forceClientComposition(displayId);
             }
 
+            // TODO(b/111562338) remove when composer 2.3 is shipped.
+            if (layer->hasColorTransform()) {
+                layer->forceClientComposition(displayId);
+            }
+
             if (layer->getForceClientComposition(displayId)) {
                 ALOGV("[%s] Requesting Client composition", layer->getName().string());
                 layer->setCompositionType(displayId, HWC2::Composition::Client);
@@ -2141,6 +2158,8 @@
     ALOGE_IF(error != HWC2::Error::None,
             "[SF] Failed to set surface damage: %s (%d)",
             to_string(error).c_str(), static_cast<int32_t>(error));
+
+    error = (compositionInfo.hwc.hwcLayer)->setColorTransform(compositionInfo.hwc.colorTransform);
 }
 
 void SurfaceFlinger::configureDeviceComposition(const CompositionInfo& compositionInfo) const
@@ -3031,7 +3050,7 @@
     });
 
     for (auto& layer : mLayersWithQueuedFrames) {
-        const Region dirty(layer->latchBuffer(visibleRegions, latchTime));
+        const Region dirty(layer->latchBuffer(visibleRegions, latchTime, getBE().flushFence));
         layer->useSurfaceDamage();
         invalidateLayerStack(layer, dirty);
         if (layer->isBufferLatched()) {
@@ -3039,6 +3058,11 @@
         }
     }
 
+    // Clear the renderengine fence here...
+    // downstream code assumes that a cleared fence == NO_FENCE, so reassign to
+    // clear instead of sp::clear.
+    getBE().flushFence = Fence::NO_FENCE;
+
     mVisibleRegionsDirty |= visibleRegions;
 
     // If we will need to wake up at some time in the future to deal with a
@@ -3091,6 +3115,7 @@
     const bool hasClientComposition = getBE().mHwc->hasClientComposition(displayId);
     ATRACE_INT("hasClientComposition", hasClientComposition);
 
+    mat4 colorMatrix;
     bool applyColorMatrix = false;
     bool needsEnhancedColorMatrix = false;
 
@@ -3109,7 +3134,7 @@
         const bool skipClientColorTransform = getBE().mHwc->hasCapability(
             HWC2::Capability::SkipClientColorTransform);
 
-        mat4 colorMatrix;
+        // Compute the global color transform matrix.
         applyColorMatrix = !hasDeviceComposition && !skipClientColorTransform;
         if (applyColorMatrix) {
             colorMatrix = mDrawingState.colorMatrix;
@@ -3125,8 +3150,6 @@
             colorMatrix *= mEnhancedSaturationMatrix;
         }
 
-        getRenderEngine().setupColorTransform(colorMatrix);
-
         if (!display->makeCurrent()) {
             ALOGW("DisplayDevice::makeCurrent failed. Aborting surface composition for display %s",
                   display->getDisplayName().c_str());
@@ -3205,6 +3228,19 @@
                     break;
                 }
                 case HWC2::Composition::Client: {
+                    if (layer->hasColorTransform()) {
+                        mat4 tmpMatrix;
+                        if (applyColorMatrix) {
+                            tmpMatrix = mDrawingState.colorMatrix;
+                        }
+                        tmpMatrix *= layer->getColorTransform();
+                        if (needsEnhancedColorMatrix) {
+                            tmpMatrix *= mEnhancedSaturationMatrix;
+                        }
+                        getRenderEngine().setColorTransform(tmpMatrix);
+                    } else {
+                        getRenderEngine().setColorTransform(colorMatrix);
+                    }
                     layer->draw(renderArea, clip);
                     break;
                 }
@@ -3217,9 +3253,8 @@
         firstLayer = false;
     }
 
-    if (applyColorMatrix || needsEnhancedColorMatrix) {
-        getRenderEngine().setupColorTransform(mat4());
-    }
+    // Clear color transform matrix at the end of the frame.
+    getRenderEngine().setColorTransform(mat4());
 
     // disable scissor at the end of the frame
     getBE().mRenderEngine->disableScissor();
@@ -3596,6 +3631,11 @@
         if (layer->setColor(s.color))
             flags |= eTraversalNeeded;
     }
+    if (what & layer_state_t::eColorTransformChanged) {
+        if (layer->setColorTransform(s.colorTransform)) {
+            flags |= eTraversalNeeded;
+        }
+    }
     if (what & layer_state_t::eMatrixChanged) {
         // TODO: b/109894387
         //
@@ -5441,7 +5481,9 @@
     engine.clearWithColor(0, 0, 0, alpha);
 
     traverseLayers([&](Layer* layer) {
+        engine.setColorTransform(layer->getColorTransform());
         layer->draw(renderArea, useIdentityTransform);
+        engine.setColorTransform(mat4());
     });
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index b77bf48..a0f7c75 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -191,6 +191,9 @@
     nsecs_t mTotalTime;
     std::atomic<nsecs_t> mLastSwapTime;
 
+    // Synchronization fence from a GL composition.
+    sp<Fence> flushFence = Fence::NO_FENCE;
+
     // Double- vs. triple-buffering stats
     struct BufferingStats {
         BufferingStats()
@@ -454,7 +457,6 @@
     status_t getCompositionPreference(ui::Dataspace* outDataSpace,
                                       ui::PixelFormat* outPixelFormat) const override;
 
-
     /* ------------------------------------------------------------------------
      * DeathRecipient interface
      */
diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp
index 3af98e5..3166a8c 100644
--- a/services/surfaceflinger/tests/Transaction_test.cpp
+++ b/services/surfaceflinger/tests/Transaction_test.cpp
@@ -2064,6 +2064,34 @@
     Transaction().setSidebandStream(layer, nullptr).apply();
 }
 
+TEST_F(LayerTransactionTest, SetColorTransformBasic) {
+    sp<SurfaceControl> colorLayer;
+    ASSERT_NO_FATAL_FAILURE(
+            colorLayer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceColor));
+
+    Transaction().setLayer(colorLayer, mLayerZBase + 1).apply();
+    {
+        SCOPED_TRACE("default color");
+        screenshot()->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
+    }
+
+    const half3 color(50.0f / 255.0f, 100.0f / 255.0f, 150.0f / 255.0f);
+    const Color expected = {90, 90, 90, 255};
+    // this is handwavy, but the precison loss scaled by 255 (8-bit per
+    // channel) should be less than one
+    const uint8_t tolerance = 1;
+    mat3 matrix;
+    matrix[0][0] = 0.3; matrix[1][0] = 0.59; matrix[2][0] = 0.11;
+    matrix[0][1] = 0.3; matrix[1][1] = 0.59; matrix[2][1] = 0.11;
+    matrix[0][2] = 0.3; matrix[1][2] = 0.59; matrix[2][2] = 0.11;
+    Transaction().setColor(colorLayer, color)
+        .setColorTransform(colorLayer, matrix, vec3()).apply();
+    {
+        SCOPED_TRACE("new color");
+        screenshot()->expectColor(Rect(0, 0, 32, 32), expected, tolerance);
+    }
+}
+
 class LayerUpdateTest : public LayerTransactionTest {
 protected:
     virtual void SetUp() {
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 52e64d8..7b79fbd 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -325,7 +325,7 @@
         EXPECT_CALL(*test->mRenderEngine, setOutputDataSpace(ui::Dataspace::UNKNOWN)).Times(1);
         EXPECT_CALL(*test->mRenderEngine, setDisplayMaxLuminance(DEFAULT_DISPLAY_MAX_LUMINANCE))
                 .Times(1);
-        EXPECT_CALL(*test->mRenderEngine, setupColorTransform(_)).Times(2);
+        EXPECT_CALL(*test->mRenderEngine, setColorTransform(_)).Times(2);
         // These expectations retire on saturation as the code path these
         // expectations are for appears to make an extra call to them.
         // TODO: Investigate this extra call
@@ -478,11 +478,8 @@
         EXPECT_CALL(*test->mRenderEngine, useNativeFenceSync()).WillRepeatedly(Return(true));
         EXPECT_CALL(*test->mRenderEngine, createImage())
                 .WillOnce(Return(ByMove(std::unique_ptr<renderengine::Image>(test->mReImage))));
-        EXPECT_CALL(*test->mRenderEngine, bindExternalTextureImage(DEFAULT_TEXTURE_ID, _)).Times(1);
-        EXPECT_CALL(*test->mRenderEngine, checkErrors()).Times(1);
-        EXPECT_CALL(*test->mReImage, setNativeWindowBuffer(_, false)).WillOnce(Return(true));
         bool ignoredRecomputeVisibleRegions;
-        layer->latchBuffer(ignoredRecomputeVisibleRegions, 0);
+        layer->latchBuffer(ignoredRecomputeVisibleRegions, 0, Fence::NO_FENCE);
         Mock::VerifyAndClear(test->mRenderEngine);
         Mock::VerifyAndClear(test->mReImage);
     }
diff --git a/services/surfaceflinger/tests/unittests/mock/RenderEngine/MockRenderEngine.h b/services/surfaceflinger/tests/unittests/mock/RenderEngine/MockRenderEngine.h
index c29452c..6813cda 100644
--- a/services/surfaceflinger/tests/unittests/mock/RenderEngine/MockRenderEngine.h
+++ b/services/surfaceflinger/tests/unittests/mock/RenderEngine/MockRenderEngine.h
@@ -62,7 +62,7 @@
     MOCK_METHOD1(setupLayerTexturing, void(const Texture&));
     MOCK_METHOD0(setupLayerBlackedOut, void());
     MOCK_METHOD4(setupFillWithColor, void(float, float, float, float));
-    MOCK_METHOD1(setupColorTransform, void(const mat4&));
+    MOCK_METHOD1(setColorTransform, void(const mat4&));
     MOCK_METHOD1(setSaturationMatrix, void(const mat4&));
     MOCK_METHOD0(disableTexturing, void());
     MOCK_METHOD0(disableBlending, void());