Move detached buffer to libui

Move it into libui early so that new modifications towards it can be
coded in the libui style

This CL only moves the file and updates Android.bp with proper
dependencies. Will re-format the coding style in a follow up CL. The
reason behind this is to keep this CL small enough so that the "git
mv" operation will be considered as an renaming rather than a complete
rewrite.

Note that DetachedBuffer is not exposed to VNDK, so that we won't
need to worry about ABI compatibility. Also, it temporarily introduces
some clang warning exceptions, we should be able to remove them very
soon once pdx to binder refactor is done for detached buffer.

Bug: 112010261
Test: atest BufferHubMetadata_test
Change-Id: I63659b9a9b7cb56f30fc2ae8cc5b87977d79b59c
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 1605050..24b1986 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -54,6 +54,8 @@
 
     srcs: [
         "ColorSpace.cpp",
+        "BufferHubBuffer.cpp",
+        "BufferHubMetadata.cpp",
         "DebugUtils.cpp",
         "Fence.cpp",
         "FenceTime.cpp",
@@ -91,6 +93,7 @@
         "libutils",
         "libutilscallstack",
         "liblog",
+        "libpdx_default_transport",  // TODO(b/112338294): Remove this once BufferHub moved to use Binder.
     ],
 
     export_shared_lib_headers: [
@@ -103,8 +106,27 @@
         "libmath",
     ],
 
+    // bufferhub is not used when building libgui for vendors
+    target: {
+        vendor: {
+            exclude_srcs: [
+                "BufferHubBuffer.cpp",
+                "BufferHubMetadata.cpp",
+            ],
+            exclude_header_libs: [
+                "libbufferhub_headers",
+                "libdvr_headers",
+            ],
+            exclude_shared_libs: [
+                "libpdx_default_transport",
+            ],
+        },
+    },
+
     header_libs: [
         "libbase_headers",
+        "libbufferhub_headers",
+        "libdvr_headers",
         "libnativebase_headers",
         "libhardware_headers",
         "libui_headers",
diff --git a/libs/ui/BufferHubBuffer.cpp b/libs/ui/BufferHubBuffer.cpp
new file mode 100644
index 0000000..9293711
--- /dev/null
+++ b/libs/ui/BufferHubBuffer.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// We would eliminate the clang warnings introduced by libdpx.
+// TODO(b/112338294): Remove those once BufferHub moved to use Binder
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wdouble-promotion"
+#pragma clang diagnostic ignored "-Wgnu-case-range"
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#pragma clang diagnostic ignored "-Winconsistent-missing-destructor-override"
+#pragma clang diagnostic ignored "-Wnested-anon-types"
+#pragma clang diagnostic ignored "-Wpacked"
+#pragma clang diagnostic ignored "-Wshadow"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wswitch-enum"
+#pragma clang diagnostic ignored "-Wundefined-func-template"
+#pragma clang diagnostic ignored "-Wunused-template"
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#include <pdx/default_transport/client_channel.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <pdx/file_handle.h>
+#include <private/dvr/bufferhub_rpc.h>
+#pragma clang diagnostic pop
+
+#include <ui/BufferHubBuffer.h>
+#include <ui/DetachedBufferHandle.h>
+
+#include <poll.h>
+
+using android::dvr::BufferHubMetadata;
+using android::dvr::BufferTraits;
+using android::dvr::DetachedBufferRPC;
+using android::dvr::NativeHandleWrapper;
+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 {
+
+// TODO(b/112338294): Remove this string literal after refactoring BufferHub
+// to use Binder.
+static constexpr char kBufferHubClientPath[] = "system/buffer_hub/client";
+
+}  // namespace
+
+BufferHubClient::BufferHubClient()
+    : Client(ClientChannelFactory::Create(kBufferHubClientPath)) {}
+
+BufferHubClient::BufferHubClient(LocalChannelHandle channel_handle)
+    : Client(ClientChannel::Create(std::move(channel_handle))) {}
+
+BufferHubClient::~BufferHubClient() {}
+
+bool BufferHubClient::IsValid() const {
+  return IsConnected() && GetChannelHandle().valid();
+}
+
+LocalChannelHandle BufferHubClient::TakeChannelHandle() {
+  if (IsConnected()) {
+    return std::move(GetChannelHandle());
+  } else {
+    return {};
+  }
+}
+
+DetachedBuffer::DetachedBuffer(uint32_t width, uint32_t height,
+                               uint32_t layer_count, uint32_t format,
+                               uint64_t usage, size_t user_metadata_size) {
+  ATRACE_NAME("DetachedBuffer::DetachedBuffer");
+  ALOGD("DetachedBuffer::DetachedBuffer: width=%u height=%u layer_count=%u, format=%u "
+        "usage=%" PRIx64 " user_metadata_size=%zu",
+        width, height, layer_count, format, usage, user_metadata_size);
+
+  auto status = client_.InvokeRemoteMethod<DetachedBufferRPC::Create>(
+      width, height, layer_count, format, usage, user_metadata_size);
+  if (!status) {
+    ALOGE(
+        "DetachedBuffer::DetachedBuffer: Failed to create detached buffer: %s",
+        status.GetErrorMessage().c_str());
+    client_.Close(-status.error());
+  }
+
+  const int ret = ImportGraphicBuffer();
+  if (ret < 0) {
+    ALOGE("DetachedBuffer::DetachedBuffer: Failed to import buffer: %s",
+          strerror(-ret));
+    client_.Close(ret);
+  }
+}
+
+DetachedBuffer::DetachedBuffer(LocalChannelHandle channel_handle)
+    : client_(std::move(channel_handle)) {
+  const int ret = ImportGraphicBuffer();
+  if (ret < 0) {
+    ALOGE("DetachedBuffer::DetachedBuffer: Failed to import buffer: %s",
+          strerror(-ret));
+    client_.Close(ret);
+  }
+}
+
+int DetachedBuffer::ImportGraphicBuffer() {
+  ATRACE_NAME("DetachedBuffer::ImportGraphicBuffer");
+
+  auto status = client_.InvokeRemoteMethod<DetachedBufferRPC::Import>();
+  if (!status) {
+    ALOGE("DetachedBuffer::DetachedBuffer: Failed to import GraphicBuffer: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+
+  BufferTraits<LocalHandle> buffer_traits = status.take();
+  if (buffer_traits.id() < 0) {
+    ALOGE("DetachedBuffer::DetachedBuffer: Received an invalid id!");
+    return -EIO;
+  }
+
+  // Stash the buffer id to replace the value in id_.
+  const int buffer_id = buffer_traits.id();
+
+  // Import the metadata.
+  metadata_ = BufferHubMetadata::Import(buffer_traits.take_metadata_handle());
+
+  if (!metadata_.IsValid()) {
+    ALOGE("DetachedBuffer::ImportGraphicBuffer: invalid metadata.");
+    return -ENOMEM;
+  }
+
+  if (metadata_.metadata_size() != buffer_traits.metadata_size()) {
+    ALOGE(
+        "DetachedBuffer::ImportGraphicBuffer: metadata buffer too small: "
+        "%zu, expected: %" PRIu64 ".",
+        metadata_.metadata_size(), buffer_traits.metadata_size());
+    return -ENOMEM;
+  }
+
+  size_t metadata_buf_size = static_cast<size_t>(buffer_traits.metadata_size());
+  if (metadata_buf_size < dvr::BufferHubDefs::kMetadataHeaderSize) {
+    ALOGE("DetachedBuffer::ImportGraphicBuffer: metadata too small: %zu",
+          metadata_buf_size);
+    return -EINVAL;
+  }
+
+  // Import the buffer: We only need to hold on the native_handle_t here so that
+  // GraphicBuffer instance can be created in future.
+  buffer_handle_ = buffer_traits.take_buffer_handle();
+
+  // If all imports succeed, replace the previous buffer and id.
+  id_ = buffer_id;
+  buffer_state_bit_ = buffer_traits.buffer_state_bit();
+
+  // TODO(b/112012161) Set up shared fences.
+  ALOGD("DetachedBuffer::ImportGraphicBuffer: id=%d, buffer_state=%" PRIx64 ".", id(),
+        metadata_.metadata_header()->buffer_state.load(std::memory_order_acquire));
+  return 0;
+}
+
+int DetachedBuffer::Poll(int timeout_ms) {
+  ATRACE_NAME("DetachedBuffer::Poll");
+  pollfd p = {client_.event_fd(), POLLIN, 0};
+  return poll(&p, 1, timeout_ms);
+}
+
+Status<LocalChannelHandle> DetachedBuffer::Promote() {
+  // TODO(b/112338294) remove after migrate producer buffer to binder
+  ALOGW("DetachedBuffer::Promote: not supported operation during migration");
+  return {};
+
+  ATRACE_NAME("DetachedBuffer::Promote");
+  ALOGD("DetachedBuffer::Promote: id=%d.", id_);
+
+  auto status_or_handle =
+      client_.InvokeRemoteMethod<DetachedBufferRPC::Promote>();
+  if (status_or_handle.ok()) {
+    // Invalidate the buffer.
+    buffer_handle_ = {};
+  } else {
+    ALOGE("DetachedBuffer::Promote: Failed to promote buffer (id=%d): %s.", id_,
+          status_or_handle.GetErrorMessage().c_str());
+  }
+  return status_or_handle;
+}
+
+Status<LocalChannelHandle> DetachedBuffer::Duplicate() {
+  ATRACE_NAME("DetachedBuffer::Duplicate");
+  ALOGD("DetachedBuffer::Duplicate: id=%d.", id_);
+
+  auto status_or_handle =
+      client_.InvokeRemoteMethod<DetachedBufferRPC::Duplicate>();
+
+  if (!status_or_handle.ok()) {
+    ALOGE("DetachedBuffer::Duplicate: Failed to duplicate buffer (id=%d): %s.",
+          id_, status_or_handle.GetErrorMessage().c_str());
+  }
+  return status_or_handle;
+}
+
+}  // namespace android
diff --git a/libs/ui/BufferHubMetadata.cpp b/libs/ui/BufferHubMetadata.cpp
new file mode 100644
index 0000000..0e5fce0
--- /dev/null
+++ b/libs/ui/BufferHubMetadata.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <sys/mman.h>
+
+#include <cutils/ashmem.h>
+#include <log/log.h>
+#include <ui/BufferHubMetadata.h>
+
+namespace android {
+namespace dvr {
+
+namespace {
+
+static const int kAshmemProt = PROT_READ | PROT_WRITE;
+
+}  // namespace
+
+using BufferHubDefs::kMetadataHeaderSize;
+using BufferHubDefs::MetadataHeader;
+
+/* static */
+BufferHubMetadata BufferHubMetadata::Create(size_t user_metadata_size) {
+  // The size the of metadata buffer is used as the "width" parameter during
+  // allocation. Thus it cannot overflow uint32_t.
+  if (user_metadata_size >=
+      (std::numeric_limits<uint32_t>::max() - kMetadataHeaderSize)) {
+    ALOGE("BufferHubMetadata::Create: metadata size too big: %zu.",
+          user_metadata_size);
+    return {};
+  }
+
+  const size_t metadata_size = user_metadata_size + kMetadataHeaderSize;
+  int fd = ashmem_create_region(/*name=*/"BufferHubMetadata", metadata_size);
+  if (fd < 0) {
+    ALOGE("BufferHubMetadata::Create: failed to create ashmem region.");
+    return {};
+  }
+
+  // Hand over the ownership of the fd to a pdx::LocalHandle immediately after
+  // the successful return of ashmem_create_region. The ashmem_handle is going
+  // to own the fd and to prevent fd leaks during error handling.
+  pdx::LocalHandle ashmem_handle{fd};
+
+  if (ashmem_set_prot_region(ashmem_handle.Get(), kAshmemProt) != 0) {
+    ALOGE("BufferHubMetadata::Create: failed to set protect region.");
+    return {};
+  }
+
+  return BufferHubMetadata::Import(std::move(ashmem_handle));
+}
+
+/* static */
+BufferHubMetadata BufferHubMetadata::Import(pdx::LocalHandle ashmem_handle) {
+  if (!ashmem_valid(ashmem_handle.Get())) {
+    ALOGE("BufferHubMetadata::Import: invalid ashmem fd.");
+    return {};
+  }
+
+  size_t metadata_size = static_cast<size_t>(ashmem_get_size_region(ashmem_handle.Get()));
+  size_t user_metadata_size = metadata_size - kMetadataHeaderSize;
+
+  // 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 can be preserved.
+  auto metadata_header = static_cast<MetadataHeader*>(
+      mmap(nullptr, metadata_size, kAshmemProt, MAP_SHARED, ashmem_handle.Get(),
+           /*offset=*/0));
+  if (metadata_header == nullptr) {
+    ALOGE("BufferHubMetadata::Import: failed to map region.");
+    return {};
+  }
+
+  return BufferHubMetadata(user_metadata_size, std::move(ashmem_handle),
+                           metadata_header);
+}
+
+BufferHubMetadata::BufferHubMetadata(size_t user_metadata_size,
+                                     pdx::LocalHandle ashmem_handle,
+                                     MetadataHeader* metadata_header)
+    : user_metadata_size_(user_metadata_size),
+      ashmem_handle_(std::move(ashmem_handle)),
+      metadata_header_(metadata_header) {}
+
+BufferHubMetadata::~BufferHubMetadata() {
+  if (metadata_header_ != nullptr) {
+    int ret = munmap(metadata_header_, metadata_size());
+    ALOGE_IF(ret != 0,
+             "BufferHubMetadata::~BufferHubMetadata: failed to unmap ashmem, "
+             "error=%d.",
+             errno);
+    metadata_header_ = nullptr;
+  }
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/ui/include/ui/BufferHubBuffer.h b/libs/ui/include/ui/BufferHubBuffer.h
new file mode 100644
index 0000000..8cf1a6d
--- /dev/null
+++ b/libs/ui/include/ui/BufferHubBuffer.h
@@ -0,0 +1,141 @@
+#ifndef ANDROID_DVR_DETACHED_BUFFER_H_
+#define ANDROID_DVR_DETACHED_BUFFER_H_
+
+// We would eliminate the clang warnings introduced by libdpx.
+// TODO(b/112338294): Remove those once BufferHub moved to use Binder
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wdouble-promotion"
+#pragma clang diagnostic ignored "-Wgnu-case-range"
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#pragma clang diagnostic ignored "-Winconsistent-missing-destructor-override"
+#pragma clang diagnostic ignored "-Wnested-anon-types"
+#pragma clang diagnostic ignored "-Wpacked"
+#pragma clang diagnostic ignored "-Wshadow"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wswitch-enum"
+#pragma clang diagnostic ignored "-Wundefined-func-template"
+#pragma clang diagnostic ignored "-Wunused-template"
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#include <pdx/client.h>
+#include <private/dvr/buffer_hub_defs.h>
+#include <private/dvr/native_handle_wrapper.h>
+#include <pdx/client.h>
+#pragma clang diagnostic pop
+
+#include <ui/BufferHubMetadata.h>
+
+namespace android {
+
+class BufferHubClient : public pdx::Client {
+ public:
+  BufferHubClient();
+  virtual ~BufferHubClient();
+  explicit BufferHubClient(pdx::LocalChannelHandle channel_handle);
+
+  bool IsValid() const;
+  pdx::LocalChannelHandle TakeChannelHandle();
+
+  using pdx::Client::Close;
+  using pdx::Client::event_fd;
+  using pdx::Client::GetChannel;
+  using pdx::Client::InvokeRemoteMethod;
+};
+
+class DetachedBuffer {
+ public:
+  // Allocates a standalone DetachedBuffer not associated with any producer
+  // consumer set.
+  static std::unique_ptr<DetachedBuffer> Create(uint32_t width, uint32_t height,
+                                                uint32_t layer_count,
+                                                uint32_t format, uint64_t usage,
+                                                size_t user_metadata_size) {
+    return std::unique_ptr<DetachedBuffer>(new DetachedBuffer(
+        width, height, layer_count, format, usage, user_metadata_size));
+  }
+
+  // Imports the given channel handle to a DetachedBuffer, taking ownership.
+  static std::unique_ptr<DetachedBuffer> Import(
+      pdx::LocalChannelHandle channel_handle) {
+    return std::unique_ptr<DetachedBuffer>(
+        new DetachedBuffer(std::move(channel_handle)));
+  }
+
+  DetachedBuffer(const DetachedBuffer&) = delete;
+  void operator=(const DetachedBuffer&) = delete;
+
+  // Gets ID of the buffer client. All DetachedBuffer clients derived from the
+  // same buffer in bufferhubd share the same buffer id.
+  int id() const { return id_; }
+
+  const native_handle_t* DuplicateHandle() {
+    return buffer_handle_.DuplicateHandle();
+  }
+
+  // Returns the current value of MetadataHeader::buffer_state.
+  uint64_t buffer_state() {
+    return metadata_.metadata_header()->buffer_state.load(
+        std::memory_order_acquire);
+  }
+
+  // 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_; }
+
+  size_t user_metadata_size() const { return metadata_.user_metadata_size(); }
+
+  // Returns true if the buffer holds an open PDX channels towards bufferhubd.
+  bool IsConnected() const { return client_.IsValid(); }
+
+  // Returns true if the buffer holds an valid native buffer handle that's
+  // availble for the client to read from and/or write into.
+  bool IsValid() const { return buffer_handle_.IsValid(); }
+
+  // Returns the event mask for all the events that are pending on this buffer
+  // (see sys/poll.h for all possible bits).
+  pdx::Status<int> GetEventMask(int events) {
+    if (auto* channel = client_.GetChannel()) {
+      return channel->GetEventMask(events);
+    } else {
+      return pdx::ErrorStatus(EINVAL);
+    }
+  }
+
+  // Polls the fd for |timeout_ms| milliseconds (-1 for infinity).
+  int Poll(int timeout_ms);
+
+  // Promotes a DetachedBuffer to become a ProducerBuffer. Once promoted the
+  // DetachedBuffer channel will be closed automatically on successful IPC
+  // return. Further IPCs towards this channel will return error.
+  pdx::Status<pdx::LocalChannelHandle> Promote();
+
+  // Creates a DetachedBuffer client from an existing one. The new client will
+  // share the same underlying gralloc buffer and ashmem region for metadata.
+  pdx::Status<pdx::LocalChannelHandle> Duplicate();
+
+ private:
+  DetachedBuffer(uint32_t width, uint32_t height, uint32_t layer_count,
+                 uint32_t format, uint64_t usage, size_t user_metadata_size);
+
+  DetachedBuffer(pdx::LocalChannelHandle channel_handle);
+
+  int ImportGraphicBuffer();
+
+  // Global id for the buffer that is consistent across processes.
+  int id_;
+  uint64_t buffer_state_bit_;
+
+  // Wrapps the gralloc buffer handle of this buffer.
+  dvr::NativeHandleWrapper<pdx::LocalHandle> buffer_handle_;
+
+  // An ashmem-based metadata object. The same shared memory are mapped to the
+  // bufferhubd daemon and all buffer clients.
+  dvr::BufferHubMetadata metadata_;
+
+  // PDX backend.
+  BufferHubClient client_;
+};
+
+}  // namespace android
+
+#endif  // ANDROID_DVR_DETACHED_BUFFER_H_
diff --git a/libs/ui/include/ui/BufferHubMetadata.h b/libs/ui/include/ui/BufferHubMetadata.h
new file mode 100644
index 0000000..52d156d
--- /dev/null
+++ b/libs/ui/include/ui/BufferHubMetadata.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_DVR_BUFFER_HUB_METADATA_H_
+#define ANDROID_DVR_BUFFER_HUB_METADATA_H_
+
+// We would eliminate the clang warnings introduced by libdpx.
+// TODO(b/112338294): Remove those once BufferHub moved to use Binder
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wdouble-promotion"
+#pragma clang diagnostic ignored "-Wgnu-case-range"
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#pragma clang diagnostic ignored "-Winconsistent-missing-destructor-override"
+#pragma clang diagnostic ignored "-Wnested-anon-types"
+#pragma clang diagnostic ignored "-Wpacked"
+#pragma clang diagnostic ignored "-Wshadow"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wswitch-enum"
+#pragma clang diagnostic ignored "-Wundefined-func-template"
+#pragma clang diagnostic ignored "-Wunused-template"
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#include <pdx/file_handle.h>
+#include <private/dvr/buffer_hub_defs.h>
+#pragma clang diagnostic pop
+
+namespace android {
+namespace dvr {
+
+class BufferHubMetadata {
+ public:
+  // Creates a new BufferHubMetadata backed by an ashmem region.
+  //
+  // @param user_metadata_size Size in bytes of the user defined metadata. The
+  //        entire metadata shared memory region to be allocated is the size of
+  //        canonical BufferHubDefs::MetadataHeader plus user_metadata_size.
+  static BufferHubMetadata Create(size_t user_metadata_size);
+
+  // Imports an existing BufferHubMetadata from an ashmem FD.
+  //
+  // TODO(b/112338294): Refactor BufferHub to use Binder as its internal IPC
+  // backend instead of UDS.
+  //
+  // @param ashmem_handle Ashmem file handle representing an ashmem region.
+  static BufferHubMetadata Import(pdx::LocalHandle ashmem_handle);
+
+  BufferHubMetadata() = default;
+
+  BufferHubMetadata(BufferHubMetadata&& other) { *this = std::move(other); }
+
+  ~BufferHubMetadata();
+
+  BufferHubMetadata& operator=(BufferHubMetadata&& other) {
+    if (this != &other) {
+      user_metadata_size_ = other.user_metadata_size_;
+      other.user_metadata_size_ = 0;
+
+      ashmem_handle_ = std::move(other.ashmem_handle_);
+
+      // The old raw metadata_header_ pointer must be cleared, otherwise the
+      // destructor will automatically mummap() the shared memory.
+      metadata_header_ = other.metadata_header_;
+      other.metadata_header_ = nullptr;
+    }
+    return *this;
+  }
+
+  // Returns true if the metadata is valid, i.e. the metadata has a valid ashmem
+  // fd and the ashmem has been mapped into virtual address space.
+  bool IsValid() const {
+    return ashmem_handle_.IsValid() && metadata_header_ != nullptr;
+  }
+
+  size_t user_metadata_size() const { return user_metadata_size_; }
+  size_t metadata_size() const {
+    return user_metadata_size_ + BufferHubDefs::kMetadataHeaderSize;
+  }
+
+  const pdx::LocalHandle& ashmem_handle() const { return ashmem_handle_; }
+  BufferHubDefs::MetadataHeader* metadata_header() { return metadata_header_; }
+
+ private:
+  BufferHubMetadata(size_t user_metadata_size, pdx::LocalHandle ashmem_handle,
+                    BufferHubDefs::MetadataHeader* metadata_header);
+
+  BufferHubMetadata(const BufferHubMetadata&) = delete;
+  void operator=(const BufferHubMetadata&) = delete;
+
+  size_t user_metadata_size_ = 0;
+  pdx::LocalHandle ashmem_handle_;
+  BufferHubDefs::MetadataHeader* metadata_header_ = nullptr;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_METADATA_H_
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index aef6428..6e73960 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -34,3 +34,11 @@
     srcs: ["GraphicBuffer_test.cpp"],
     cflags: ["-Wall", "-Werror"],
 }
+
+cc_test {
+    name: "BufferHubMetadata_test",
+    header_libs: ["libbufferhub_headers", "libdvr_headers"],
+    shared_libs: ["libpdx_default_transport", "libui", "libutils"],
+    srcs: ["BufferHubMetadata_test.cpp"],
+    cflags: ["-Wall", "-Werror"],
+}
diff --git a/libs/ui/tests/BufferHubMetadata_test.cpp b/libs/ui/tests/BufferHubMetadata_test.cpp
new file mode 100644
index 0000000..4209392
--- /dev/null
+++ b/libs/ui/tests/BufferHubMetadata_test.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <ui/BufferHubMetadata.h>
+
+using android::dvr::BufferHubDefs::IsBufferGained;
+
+namespace android {
+namespace dvr {
+
+constexpr size_t kEmptyUserMetadataSize = 0;
+
+class BufferHubMetadataTest : public ::testing::Test {};
+
+TEST_F(BufferHubMetadataTest, Create_UserMetdataSizeTooBig) {
+  BufferHubMetadata m1 =
+      BufferHubMetadata::Create(std::numeric_limits<uint32_t>::max());
+  EXPECT_FALSE(m1.IsValid());
+}
+
+TEST_F(BufferHubMetadataTest, Create_Success) {
+  BufferHubMetadata m1 = BufferHubMetadata::Create(kEmptyUserMetadataSize);
+  EXPECT_TRUE(m1.IsValid());
+  EXPECT_NE(m1.metadata_header(), nullptr);
+}
+
+TEST_F(BufferHubMetadataTest, Import_Success) {
+  BufferHubMetadata m1 = BufferHubMetadata::Create(kEmptyUserMetadataSize);
+  EXPECT_TRUE(m1.IsValid());
+  EXPECT_NE(m1.metadata_header(), nullptr);
+
+  pdx::LocalHandle h2 = m1.ashmem_handle().Duplicate();
+  EXPECT_TRUE(h2.IsValid());
+
+  BufferHubMetadata m2 = BufferHubMetadata::Import(std::move(h2));
+  EXPECT_FALSE(h2.IsValid());
+  EXPECT_TRUE(m1.IsValid());
+  BufferHubDefs::MetadataHeader* mh1 = m1.metadata_header();
+  EXPECT_NE(mh1, nullptr);
+
+  // TODO(b/111976433): Update this test once BufferHub state machine gets
+  // updated. In the old model, buffer starts in the gained state (i.e.
+  // valued 0). In the new model, buffer states in the released state.
+  EXPECT_TRUE(IsBufferGained(mh1->fence_state.load()));
+
+  EXPECT_TRUE(m2.IsValid());
+  BufferHubDefs::MetadataHeader* mh2 = m2.metadata_header();
+  EXPECT_NE(mh2, nullptr);
+
+  // TODO(b/111976433): Update this test once BufferHub state machine gets
+  // updated. In the old model, buffer starts in the gained state (i.e.
+  // valued 0). In the new model, buffer states in the released state.
+  EXPECT_TRUE(IsBufferGained(mh2->fence_state.load()));
+}
+
+TEST_F(BufferHubMetadataTest, MoveMetadataInvalidatesOldOne) {
+  BufferHubMetadata m1 = BufferHubMetadata::Create(sizeof(int));
+  EXPECT_TRUE(m1.IsValid());
+  EXPECT_NE(m1.metadata_header(), nullptr);
+  EXPECT_TRUE(m1.ashmem_handle().IsValid());
+  EXPECT_EQ(m1.user_metadata_size(), sizeof(int));
+
+  BufferHubMetadata m2 = std::move(m1);
+
+  // After the move, the metadata header (a raw pointer) should be reset in the
+  // older buffer.
+  EXPECT_EQ(m1.metadata_header(), nullptr);
+  EXPECT_NE(m2.metadata_header(), nullptr);
+
+  EXPECT_FALSE(m1.ashmem_handle().IsValid());
+  EXPECT_TRUE(m2.ashmem_handle().IsValid());
+
+  EXPECT_EQ(m1.user_metadata_size(), 0U);
+  EXPECT_EQ(m2.user_metadata_size(), sizeof(int));
+
+  BufferHubMetadata m3{std::move(m2)};
+
+  // After the move, the metadata header (a raw pointer) should be reset in the
+  // older buffer.
+  EXPECT_EQ(m2.metadata_header(), nullptr);
+  EXPECT_NE(m3.metadata_header(), nullptr);
+
+  EXPECT_FALSE(m2.ashmem_handle().IsValid());
+  EXPECT_TRUE(m3.ashmem_handle().IsValid());
+
+  EXPECT_EQ(m2.user_metadata_size(), 0U);
+  EXPECT_EQ(m3.user_metadata_size(), sizeof(int));
+}
+
+}  // namespace dvr
+}  // namespace android