Add DaydreamVR native libraries and services

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

Bug: None
Test: `m -j32` succeeds. Sailfish boots and basic_vr sample app works
Change-Id: I853015872afc443aecee10411ef2d6b79184d051
diff --git a/libs/vr/.clang-format b/libs/vr/.clang-format
new file mode 100644
index 0000000..04d7970
--- /dev/null
+++ b/libs/vr/.clang-format
@@ -0,0 +1,5 @@
+BasedOnStyle: Google
+DerivePointerAlignment: false
+PointerAlignment: Left
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
diff --git a/libs/vr/Android.bp b/libs/vr/Android.bp
new file mode 100644
index 0000000..e8176cf
--- /dev/null
+++ b/libs/vr/Android.bp
@@ -0,0 +1,3 @@
+subdirs = [
+    "*",
+]
diff --git a/libs/vr/CPPLINT.cfg b/libs/vr/CPPLINT.cfg
new file mode 100644
index 0000000..87fb641
--- /dev/null
+++ b/libs/vr/CPPLINT.cfg
@@ -0,0 +1,2 @@
+set noparent
+filter=-build/include_order,-legal/copyright,-build/include,-build/c++11,+build/include_alpha
diff --git a/libs/vr/libbufferhub/Android.mk b/libs/vr/libbufferhub/Android.mk
new file mode 100644
index 0000000..467f69f
--- /dev/null
+++ b/libs/vr/libbufferhub/Android.mk
@@ -0,0 +1,57 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	buffer_hub_client.cpp \
+	buffer_hub_rpc.cpp \
+	ion_buffer.cpp
+
+includeFiles := \
+	$(LOCAL_PATH)/include
+
+staticLibraries := \
+	libchrome \
+	libdvrcommon \
+	libpdx_default_transport \
+
+sharedLibraries := \
+	libbase \
+	libcutils \
+	libhardware \
+	liblog \
+	libui \
+	libutils
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+LOCAL_CFLAGS := -DLOG_TAG=\"libbufferhub\"
+LOCAL_CFLAGS += -DTRACE=0
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := libbufferhub
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := bufferhub_tests.cpp
+LOCAL_STATIC_LIBRARIES := libbufferhub $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := bufferhub_tests
+include $(BUILD_NATIVE_TEST)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/libs/vr/libbufferhub/buffer_hub_client.cpp b/libs/vr/libbufferhub/buffer_hub_client.cpp
new file mode 100644
index 0000000..146780e
--- /dev/null
+++ b/libs/vr/libbufferhub/buffer_hub_client.cpp
@@ -0,0 +1,417 @@
+#include <private/dvr/buffer_hub_client.h>
+
+#include <cutils/log.h>
+#include <poll.h>
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Trace.h>
+
+#include <mutex>
+
+#include <pdx/default_transport/client_channel.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <private/dvr/platform_defines.h>
+
+#include "include/private/dvr/bufferhub_rpc.h"
+
+using android::pdx::LocalHandle;
+using android::pdx::LocalChannelHandle;
+using android::pdx::rpc::WrapBuffer;
+using android::pdx::Status;
+
+namespace {
+
+constexpr int kUncachedBlobUsageFlags = GRALLOC_USAGE_SW_READ_RARELY |
+                                        GRALLOC_USAGE_SW_WRITE_RARELY |
+                                        GRALLOC_USAGE_PRIVATE_UNCACHED;
+
+}  // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+BufferHubBuffer::BufferHubBuffer(LocalChannelHandle channel_handle)
+    : Client{pdx::default_transport::ClientChannel::Create(
+          std::move(channel_handle))},
+      id_(-1) {}
+BufferHubBuffer::BufferHubBuffer(const std::string& endpoint_path)
+    : Client{pdx::default_transport::ClientChannelFactory::Create(
+          endpoint_path)},
+      id_(-1) {}
+
+BufferHubBuffer::~BufferHubBuffer() {}
+
+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");
+  if (!IonBuffer::GetGrallocModule())
+    return -EIO;
+
+  Status<std::vector<NativeBufferHandle<LocalHandle>>> status =
+      InvokeRemoteMethod<BufferHubRPC::GetBuffers>();
+  if (!status) {
+    ALOGE("BufferHubBuffer::ImportBuffer: Failed to get buffers: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  } else if (status.get().empty()) {
+    ALOGE(
+        "BufferHubBuffer::ImportBuffer: Expected to receive at least one "
+        "buffer handle but got zero!");
+    return -EIO;
+  }
+
+  auto buffer_handles = status.take();
+
+  // Stash the buffer id to replace the value in id_. All sub-buffers of a
+  // buffer hub buffer have the same id.
+  const int new_id = buffer_handles[0].id();
+
+  // Import all of the buffers.
+  std::vector<IonBuffer> ion_buffers;
+  for (auto& handle : buffer_handles) {
+    const size_t i = &handle - buffer_handles.data();
+    ALOGD_IF(
+        TRACE,
+        "BufferHubBuffer::ImportBuffer: i=%zu id=%d FdCount=%zu IntCount=%zu",
+        i, handle.id(), handle.FdCount(), handle.IntCount());
+
+    IonBuffer buffer;
+    const int ret = handle.Import(&buffer);
+    if (ret < 0)
+      return ret;
+
+    ion_buffers.emplace_back(std::move(buffer));
+  }
+
+  // If all imports succeed, replace the previous buffers and id.
+  slices_ = std::move(ion_buffers);
+  id_ = new_id;
+  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, size_t index) {
+  return slices_[index].Lock(usage, x, y, width, height, address);
+}
+
+int BufferHubBuffer::Unlock(size_t index) { return slices_[index].Unlock(); }
+
+int BufferHubBuffer::GetBlobReadWritePointer(size_t size, void** addr) {
+  int width = static_cast<int>(size);
+  int height = 1;
+  constexpr int usage = GRALLOC_USAGE_SW_READ_RARELY |
+                        GRALLOC_USAGE_SW_WRITE_RARELY |
+                        GRALLOC_USAGE_PRIVATE_UNCACHED;
+  int ret = Lock(usage, 0, 0, width, height, addr);
+  if (ret == 0)
+    Unlock();
+  return ret;
+}
+
+int BufferHubBuffer::GetBlobReadOnlyPointer(size_t size, void** addr) {
+  int width = static_cast<int>(size);
+  int height = 1;
+  constexpr int usage =
+      GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_PRIVATE_UNCACHED;
+  int ret = Lock(usage, 0, 0, width, height, addr);
+  if (ret == 0)
+    Unlock();
+  return ret;
+}
+
+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::Acquire(LocalHandle* ready_fence) {
+  return Acquire(ready_fence, nullptr, 0);
+}
+
+int BufferConsumer::Acquire(LocalHandle* ready_fence, void* meta,
+                            size_t meta_size_bytes) {
+  ATRACE_NAME("BufferConsumer::Acquire");
+  LocalFence fence;
+  auto return_value =
+      std::make_pair(std::ref(fence), WrapBuffer(meta, meta_size_bytes));
+  auto status = InvokeRemoteMethodInPlace<BufferHubRPC::ConsumerAcquire>(
+      &return_value, meta_size_bytes);
+  if (status && ready_fence)
+    *ready_fence = fence.take();
+  return status ? 0 : -status.error();
+}
+
+int BufferConsumer::Release(const LocalHandle& release_fence) {
+  ATRACE_NAME("BufferConsumer::Release");
+  return ReturnStatusOrError(InvokeRemoteMethod<BufferHubRPC::ConsumerRelease>(
+      BorrowedFence(release_fence.Borrow())));
+}
+
+int BufferConsumer::ReleaseAsync() {
+  ATRACE_NAME("BufferConsumer::ReleaseAsync");
+  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(int width, int height, int format, int usage,
+                               size_t metadata_size, size_t slice_count)
+    : BASE(BufferHubRPC::kClientPath) {
+  ATRACE_NAME("BufferProducer::BufferProducer");
+  ALOGD_IF(TRACE,
+           "BufferProducer::BufferProducer: fd=%d width=%d height=%d format=%d "
+           "usage=%d, metadata_size=%zu, slice_count=%zu",
+           event_fd(), width, height, format, usage, metadata_size,
+           slice_count);
+
+  auto status = InvokeRemoteMethod<BufferHubRPC::CreateBuffer>(
+      width, height, format, usage, metadata_size, slice_count);
+  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(const std::string& name, int user_id,
+                               int group_id, int width, int height, int format,
+                               int usage, size_t meta_size_bytes,
+                               size_t slice_count)
+    : BASE(BufferHubRPC::kClientPath) {
+  ATRACE_NAME("BufferProducer::BufferProducer");
+  ALOGD_IF(TRACE,
+           "BufferProducer::BufferProducer: fd=%d name=%s user_id=%d "
+           "group_id=%d width=%d height=%d format=%d usage=%d, "
+           "meta_size_bytes=%zu, slice_count=%zu",
+           event_fd(), name.c_str(), user_id, group_id, width, height, format,
+           usage, meta_size_bytes, slice_count);
+
+  auto status = InvokeRemoteMethod<BufferHubRPC::CreatePersistentBuffer>(
+      name, user_id, group_id, width, height, format, usage, meta_size_bytes,
+      slice_count);
+  if (!status) {
+    ALOGE(
+        "BufferProducer::BufferProducer: Failed to create/get persistent "
+        "buffer \"%s\": %s",
+        name.c_str(), status.GetErrorMessage().c_str());
+    Close(-status.error());
+    return;
+  }
+
+  const int ret = ImportBuffer();
+  if (ret < 0) {
+    ALOGE(
+        "BufferProducer::BufferProducer: Failed to import producer buffer "
+        "\"%s\": %s",
+        name.c_str(), strerror(-ret));
+    Close(ret);
+  }
+}
+
+BufferProducer::BufferProducer(int usage, size_t size)
+    : BASE(BufferHubRPC::kClientPath) {
+  ATRACE_NAME("BufferProducer::BufferProducer");
+  ALOGD_IF(TRACE, "BufferProducer::BufferProducer: usage=%d 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 meta_size_bytes = 0;
+  const size_t slice_count = 1;
+  auto status = InvokeRemoteMethod<BufferHubRPC::CreateBuffer>(
+      width, height, format, usage, meta_size_bytes, slice_count);
+  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(const std::string& name, int user_id,
+                               int group_id, int usage, size_t size)
+    : BASE(BufferHubRPC::kClientPath) {
+  ATRACE_NAME("BufferProducer::BufferProducer");
+  ALOGD_IF(TRACE,
+           "BufferProducer::BufferProducer: name=%s user_id=%d group=%d "
+           "usage=%d size=%zu",
+           name.c_str(), user_id, group_id, usage, size);
+  const int width = static_cast<int>(size);
+  const int height = 1;
+  const int format = HAL_PIXEL_FORMAT_BLOB;
+  const size_t meta_size_bytes = 0;
+  const size_t slice_count = 1;
+  auto status = InvokeRemoteMethod<BufferHubRPC::CreatePersistentBuffer>(
+      name, user_id, group_id, width, height, format, usage, meta_size_bytes,
+      slice_count);
+  if (!status) {
+    ALOGE(
+        "BufferProducer::BufferProducer: Failed to create persistent "
+        "buffer \"%s\": %s",
+        name.c_str(), status.GetErrorMessage().c_str());
+    Close(-status.error());
+    return;
+  }
+
+  const int ret = ImportBuffer();
+  if (ret < 0) {
+    ALOGE(
+        "BufferProducer::BufferProducer: Failed to import producer buffer "
+        "\"%s\": %s",
+        name.c_str(), strerror(-ret));
+    Close(ret);
+  }
+}
+
+BufferProducer::BufferProducer(const std::string& name)
+    : BASE(BufferHubRPC::kClientPath) {
+  ATRACE_NAME("BufferProducer::BufferProducer");
+  ALOGD_IF(TRACE, "BufferProducer::BufferProducer: name=%s", name.c_str());
+
+  auto status = InvokeRemoteMethod<BufferHubRPC::GetPersistentBuffer>(name);
+  if (!status) {
+    ALOGE(
+        "BufferProducer::BufferProducer: Failed to get producer buffer by name "
+        "\"%s\": %s",
+        name.c_str(), status.GetErrorMessage().c_str());
+    Close(-status.error());
+    return;
+  }
+
+  const int ret = ImportBuffer();
+  if (ret < 0) {
+    ALOGE(
+        "BufferProducer::BufferProducer: Failed to import producer buffer "
+        "\"%s\": %s",
+        name.c_str(), 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::Post(const LocalHandle& ready_fence, const void* meta,
+                         size_t meta_size_bytes) {
+  ATRACE_NAME("BufferProducer::Post");
+  return ReturnStatusOrError(InvokeRemoteMethod<BufferHubRPC::ProducerPost>(
+      BorrowedFence(ready_fence.Borrow()), WrapBuffer(meta, meta_size_bytes)));
+}
+
+int BufferProducer::Gain(LocalHandle* release_fence) {
+  ATRACE_NAME("BufferProducer::Gain");
+  auto status = InvokeRemoteMethod<BufferHubRPC::ProducerGain>();
+  if (!status)
+    return -status.error();
+  if (release_fence)
+    *release_fence = status.take().take();
+  return 0;
+}
+
+int BufferProducer::GainAsync() {
+  ATRACE_NAME("BufferProducer::GainAsync");
+  return ReturnStatusOrError(SendImpulse(BufferHubRPC::ProducerGain::Opcode));
+}
+
+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()});
+}
+
+int BufferProducer::MakePersistent(const std::string& name, int user_id,
+                                   int group_id) {
+  ATRACE_NAME("BufferProducer::MakePersistent");
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<BufferHubRPC::ProducerMakePersistent>(name, user_id,
+                                                               group_id));
+}
+
+int BufferProducer::RemovePersistence() {
+  ATRACE_NAME("BufferProducer::RemovePersistence");
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<BufferHubRPC::ProducerRemovePersistence>());
+}
+
+std::unique_ptr<BufferProducer> BufferProducer::CreateUncachedBlob(
+    size_t size) {
+  return BufferProducer::Create(kUncachedBlobUsageFlags, size);
+}
+
+std::unique_ptr<BufferProducer> BufferProducer::CreatePersistentUncachedBlob(
+    const std::string& name, int user_id, int group_id, size_t size) {
+  return BufferProducer::Create(name, user_id, group_id,
+                                kUncachedBlobUsageFlags, size);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhub/buffer_hub_rpc.cpp b/libs/vr/libbufferhub/buffer_hub_rpc.cpp
new file mode 100644
index 0000000..9a67faa
--- /dev/null
+++ b/libs/vr/libbufferhub/buffer_hub_rpc.cpp
@@ -0,0 +1,9 @@
+#include "include/private/dvr/bufferhub_rpc.h"
+
+namespace android {
+namespace dvr {
+
+constexpr char BufferHubRPC::kClientPath[];
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhub/bufferhub_tests.cpp b/libs/vr/libbufferhub/bufferhub_tests.cpp
new file mode 100644
index 0000000..cb45dbe
--- /dev/null
+++ b/libs/vr/libbufferhub/bufferhub_tests.cpp
@@ -0,0 +1,219 @@
+#include <android/native_window.h>
+#include <base/posix/eintr_wrapper.h>
+#include <gtest/gtest.h>
+#include <private/dvr/buffer_hub_client.h>
+
+#include <mutex>
+#include <thread>
+
+using android::dvr::BufferProducer;
+using android::dvr::BufferConsumer;
+using android::pdx::LocalHandle;
+
+const int kWidth = 640;
+const int kHeight = 480;
+const int kFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+const int kUsage = 0;
+const uint64_t kContext = 42;
+
+using LibBufferHubTest = ::testing::Test;
+
+TEST_F(LibBufferHubTest, TestBasicUsage) {
+  std::unique_ptr<BufferProducer> p = BufferProducer::Create(
+      kWidth, kHeight, kFormat, kUsage, sizeof(uint64_t));
+  ASSERT_TRUE(p.get() != nullptr);
+  std::unique_ptr<BufferConsumer> c =
+      BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_TRUE(c.get() != nullptr);
+  // Check that consumers can spawn other consumers.
+  std::unique_ptr<BufferConsumer> c2 =
+      BufferConsumer::Import(c->CreateConsumer());
+  ASSERT_TRUE(c2.get() != nullptr);
+
+  EXPECT_EQ(0, p->Post(LocalHandle(), kContext));
+  // Both consumers should be triggered.
+  EXPECT_GE(0, HANDLE_EINTR(p->Poll(0)));
+  EXPECT_LT(0, HANDLE_EINTR(c->Poll(10)));
+  EXPECT_LT(0, HANDLE_EINTR(c2->Poll(10)));
+
+  uint64_t context;
+  LocalHandle fence;
+  EXPECT_LE(0, c->Acquire(&fence, &context));
+  EXPECT_EQ(kContext, context);
+  EXPECT_GE(0, HANDLE_EINTR(c->Poll(0)));
+
+  EXPECT_LE(0, c2->Acquire(&fence, &context));
+  EXPECT_EQ(kContext, context);
+  EXPECT_GE(0, HANDLE_EINTR(c2->Poll(0)));
+
+  EXPECT_EQ(0, c->Release(LocalHandle()));
+  EXPECT_GE(0, HANDLE_EINTR(p->Poll(0)));
+  EXPECT_EQ(0, c2->Discard());
+
+  EXPECT_LE(0, HANDLE_EINTR(p->Poll(0)));
+  EXPECT_EQ(0, p->Gain(&fence));
+  EXPECT_GE(0, HANDLE_EINTR(p->Poll(0)));
+}
+
+TEST_F(LibBufferHubTest, TestWithCustomMetadata) {
+  struct Metadata {
+    int64_t field1;
+    int64_t field2;
+  };
+  std::unique_ptr<BufferProducer> p = BufferProducer::Create(
+      kWidth, kHeight, kFormat, kUsage, sizeof(Metadata));
+  ASSERT_TRUE(p.get() != nullptr);
+  std::unique_ptr<BufferConsumer> c =
+      BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_TRUE(c.get() != nullptr);
+
+  Metadata m = {1, 3};
+  EXPECT_EQ(0, p->Post(LocalHandle(), m));
+  EXPECT_LE(0, HANDLE_EINTR(c->Poll(10)));
+
+  LocalHandle fence;
+  Metadata m2 = {};
+  EXPECT_EQ(0, c->Acquire(&fence, &m2));
+  EXPECT_EQ(m.field1, m2.field1);
+  EXPECT_EQ(m.field2, m2.field2);
+
+  EXPECT_EQ(0, c->Release(LocalHandle()));
+  EXPECT_LT(0, HANDLE_EINTR(p->Poll(0)));
+}
+
+TEST_F(LibBufferHubTest, TestPostWithWrongMetaSize) {
+  struct Metadata {
+    int64_t field1;
+    int64_t field2;
+  };
+  std::unique_ptr<BufferProducer> p = BufferProducer::Create(
+      kWidth, kHeight, kFormat, kUsage, sizeof(Metadata));
+  ASSERT_TRUE(p.get() != nullptr);
+  std::unique_ptr<BufferConsumer> c =
+      BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_TRUE(c.get() != nullptr);
+
+  int64_t sequence = 3;
+  EXPECT_NE(0, p->Post(LocalHandle(), sequence));
+  EXPECT_GE(0, HANDLE_EINTR(c->Poll(10)));
+}
+
+TEST_F(LibBufferHubTest, TestAcquireWithWrongMetaSize) {
+  struct Metadata {
+    int64_t field1;
+    int64_t field2;
+  };
+  std::unique_ptr<BufferProducer> p = BufferProducer::Create(
+      kWidth, kHeight, kFormat, kUsage, sizeof(Metadata));
+  ASSERT_TRUE(p.get() != nullptr);
+  std::unique_ptr<BufferConsumer> c =
+      BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_TRUE(c.get() != nullptr);
+
+  Metadata m = {1, 3};
+  EXPECT_EQ(0, p->Post(LocalHandle(), m));
+
+  LocalHandle fence;
+  int64_t sequence;
+  EXPECT_NE(0, c->Acquire(&fence, &sequence));
+}
+
+TEST_F(LibBufferHubTest, TestAcquireWithNoMeta) {
+  std::unique_ptr<BufferProducer> p = BufferProducer::Create(
+      kWidth, kHeight, kFormat, kUsage, sizeof(uint64_t));
+  ASSERT_TRUE(p.get() != nullptr);
+  std::unique_ptr<BufferConsumer> c =
+      BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_TRUE(c.get() != nullptr);
+
+  int64_t sequence = 3;
+  EXPECT_EQ(0, p->Post(LocalHandle(), sequence));
+
+  LocalHandle fence;
+  EXPECT_EQ(0, c->Acquire(&fence));
+}
+
+TEST_F(LibBufferHubTest, TestWithNoMeta) {
+  std::unique_ptr<BufferProducer> p =
+      BufferProducer::Create(kWidth, kHeight, kFormat, kUsage);
+  ASSERT_TRUE(p.get() != nullptr);
+  std::unique_ptr<BufferConsumer> c =
+      BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_TRUE(c.get() != nullptr);
+
+  LocalHandle fence;
+
+  EXPECT_EQ(0, p->Post<void>(LocalHandle()));
+  EXPECT_EQ(0, c->Acquire(&fence));
+}
+
+TEST_F(LibBufferHubTest, TestFailureToPostMetaFromABufferWithoutMeta) {
+  std::unique_ptr<BufferProducer> p =
+      BufferProducer::Create(kWidth, kHeight, kFormat, kUsage);
+  ASSERT_TRUE(p.get() != nullptr);
+  std::unique_ptr<BufferConsumer> c =
+      BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_TRUE(c.get() != nullptr);
+
+  int64_t sequence = 3;
+  EXPECT_NE(0, p->Post(LocalHandle(), sequence));
+}
+
+TEST_F(LibBufferHubTest, TestPersistentBufferPersistence) {
+  auto p = BufferProducer::Create("TestPersistentBuffer", -1, -1, kWidth,
+                                  kHeight, kFormat, kUsage);
+  ASSERT_NE(nullptr, p);
+
+  // Record the original buffer id for later comparison.
+  const int buffer_id = p->id();
+
+  auto c = BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_NE(nullptr, c);
+
+  EXPECT_EQ(0, p->Post<void>(LocalHandle()));
+
+  // Close the connection to the producer. This should not affect the consumer.
+  p = nullptr;
+
+  LocalHandle fence;
+  EXPECT_EQ(0, c->Acquire(&fence));
+  EXPECT_EQ(0, c->Release(LocalHandle()));
+
+  // Attempt to reconnect to the persistent buffer.
+  p = BufferProducer::Create("TestPersistentBuffer");
+  ASSERT_NE(nullptr, p);
+  EXPECT_EQ(buffer_id, p->id());
+  EXPECT_EQ(0, p->Gain(&fence));
+}
+
+TEST_F(LibBufferHubTest, TestPersistentBufferMismatchParams) {
+  auto p = BufferProducer::Create("TestPersistentBuffer", -1, -1, kWidth,
+                                  kHeight, kFormat, kUsage);
+  ASSERT_NE(nullptr, p);
+
+  // Close the connection to the producer.
+  p = nullptr;
+
+  // Mismatch the params.
+  p = BufferProducer::Create("TestPersistentBuffer", -1, -1, kWidth * 2,
+                             kHeight, kFormat, kUsage);
+  ASSERT_EQ(nullptr, p);
+}
+
+TEST_F(LibBufferHubTest, TestRemovePersistentBuffer) {
+  auto p = BufferProducer::Create("TestPersistentBuffer", -1, -1, kWidth,
+                                  kHeight, kFormat, kUsage);
+  ASSERT_NE(nullptr, p);
+
+  LocalHandle fence;
+  auto c = BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_NE(nullptr, c);
+  EXPECT_NE(-EPIPE, c->Acquire(&fence));
+
+  // Test that removing persistence and closing the producer orphans the
+  // consumer.
+  EXPECT_EQ(0, p->RemovePersistence());
+  p = nullptr;
+
+  EXPECT_EQ(-EPIPE, c->Release(LocalHandle()));
+}
diff --git a/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
new file mode 100644
index 0000000..b6ff5b6
--- /dev/null
+++ b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
@@ -0,0 +1,313 @@
+#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>
+
+namespace android {
+namespace dvr {
+
+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,
+           size_t index);
+
+  // Must be called after Lock() when the caller has finished changing the
+  // buffer.
+  int Unlock(size_t index);
+
+  // Helper for when index is 0.
+  int Lock(int usage, int x, int y, int width, int height, void** addr) {
+    return Lock(usage, x, y, width, height, addr, 0);
+  }
+
+  // Helper for when index is 0.
+  int Unlock() { return Unlock(0); }
+
+  // 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]));
+  }
+
+  using Client::event_fd;
+  native_handle_t* native_handle() const {
+    return const_cast<native_handle_t*>(slices_[0].handle());
+  }
+  // If index is greater than or equal to slice_count(), the result is
+  // undefined.
+  native_handle_t* native_handle(size_t index) const {
+    return const_cast<native_handle_t*>(slices_[index].handle());
+  }
+
+  IonBuffer* buffer() { return &slices_[0]; }
+  // If index is greater than or equal to slice_count(), the result is
+  // undefined.
+  IonBuffer* slice(size_t index) { return &slices_[index]; }
+
+  int slice_count() const { return static_cast<int>(slices_.size()); }
+  int id() const { return id_; }
+
+  // The following methods return settings of the first buffer. Currently,
+  // it is only possible to create multi-buffer BufferHubBuffers with the same
+  // settings.
+  int width() const { return slices_[0].width(); }
+  int height() const { return slices_[0].height(); }
+  int stride() const { return slices_[0].stride(); }
+  int format() const { return slices_[0].format(); }
+  int usage() const { return slices_[0].usage(); }
+
+ protected:
+  explicit BufferHubBuffer(LocalChannelHandle channel);
+  explicit BufferHubBuffer(const std::string& endpoint_path);
+  virtual ~BufferHubBuffer();
+
+  // Initialization helper.
+  int ImportBuffer();
+
+ 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_;
+
+  // A BufferHubBuffer may contain multiple slices of IonBuffers with same
+  // configurations.
+  std::vector<IonBuffer> slices_;
+};
+
+// 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:
+  // Create a buffer designed to hold arbitrary bytes that can be read and
+  // written from CPU, GPU and DSP. The buffer is mapped uncached so that CPU
+  // reads and writes are predictable.
+  static std::unique_ptr<BufferProducer> CreateUncachedBlob(size_t size);
+
+  // Creates a persistent uncached buffer with the given name and access.
+  static std::unique_ptr<BufferProducer> CreatePersistentUncachedBlob(
+      const std::string& name, int user_id, int group_id, size_t size);
+
+  // 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);
+
+  // 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 meta_size_bytes);
+
+  template <typename Meta,
+            typename = typename std::enable_if<std::is_void<Meta>::value>::type>
+  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);
+
+  // 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, nor does it transfer a release fence to
+  // the client. This version may be used in situations where a release fence is
+  // not needed. 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 otherwise.
+  int GainAsync();
+
+  // Attaches the producer to |name| so that it becomes a persistent buffer that
+  // may be retrieved by name at a later time. This may be used in cases where a
+  // shared memory buffer should persist across the life of the producer process
+  // (i.e. the buffer may be held by clients across a service restart). The
+  // buffer may be associated with a user and/or group id to restrict access to
+  // the buffer. If user_id or group_id is -1 then checks for the respective id
+  // are disabled. If user_id or group_id is 0 then the respective id of the
+  // calling process is used instead.
+  int MakePersistent(const std::string& name, int user_id, int group_id);
+
+  // Removes the persistence of the producer.
+  int RemovePersistence();
+
+ 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(int width, int height, int format, int usage,
+                 size_t metadata_size = 0, size_t slice_count = 1);
+
+  // Constructs a persistent buffer with the given geometry and parameters and
+  // binds it to |name| in one shot. If a persistent buffer with the same name
+  // and settings already exists and matches the given geometry and parameters,
+  // that buffer is connected to this client instead of creating a new buffer.
+  // If the name matches but the geometry or settings do not match then
+  // construction fails and BufferProducer::Create() returns nullptr.
+  //
+  // Access to the persistent buffer may be restricted by |user_id| and/or
+  // |group_id|; these settings are established only when the buffer is first
+  // created and cannot be changed. A user or group id of -1 disables checks for
+  // that respective id. A user or group id of 0 is substituted with the
+  // effective user or group id of the calling process.
+  BufferProducer(const std::string& name, int user_id, int group_id, int width,
+                 int height, int format, int usage, size_t metadata_size = 0,
+                 size_t slice_count = 1);
+
+  // Constructs a blob (flat) buffer with the given usage flags.
+  BufferProducer(int usage, size_t size);
+
+  // Constructs a persistent blob (flat) buffer and binds it to |name|.
+  BufferProducer(const std::string& name, int user_id, int group_id, int usage,
+                 size_t size);
+
+  // Constructs a channel to persistent buffer by name only. The buffer must
+  // have been previously created or made persistent.
+  explicit BufferProducer(const std::string& name);
+
+  // Imports the given file handle to a producer channel, taking ownership.
+  explicit BufferProducer(LocalChannelHandle channel);
+};
+
+// 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 meta_size_bytes);
+
+  // 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));
+  }
+
+  // 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);
+
+  // Asynchronously releases a buffer. Similar to the synchronous version above,
+  // except that it does not wait for BufferHub to reply with success or error,
+  // nor does it transfer a release fence. This version may be used in
+  // situations where a release fence is not needed. 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 otherwise.
+  int ReleaseAsync();
+
+  // 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);
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_CLIENT_H_
diff --git a/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h b/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
new file mode 100644
index 0000000..ed11551
--- /dev/null
+++ b/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
@@ -0,0 +1,214 @@
+#ifndef ANDROID_DVR_BUFFERHUB_RPC_H_
+#define ANDROID_DVR_BUFFERHUB_RPC_H_
+
+#include <cutils/native_handle.h>
+#include <gui/BufferQueueDefs.h>
+#include <sys/types.h>
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+#include <pdx/rpc/remote_method.h>
+#include <pdx/rpc/serializable.h>
+#include <private/dvr/ion_buffer.h>
+
+namespace android {
+namespace dvr {
+
+template <typename FileHandleType>
+class NativeBufferHandle {
+ public:
+  NativeBufferHandle() { Clear(); }
+  NativeBufferHandle(const IonBuffer& buffer, int id)
+      : id_(id),
+        stride_(buffer.stride()),
+        width_(buffer.width()),
+        height_(buffer.height()),
+        format_(buffer.format()),
+        usage_(buffer.usage()) {
+    // Populate the fd and int vectors: native_handle->data[] is an array of fds
+    // followed by an array of opaque ints.
+    const int fd_count = buffer.handle()->numFds;
+    const int int_count = buffer.handle()->numInts;
+    for (int i = 0; i < fd_count; i++) {
+      fds_.emplace_back(FileHandleType::AsDuplicate(buffer.handle()->data[i]));
+    }
+    for (int i = 0; i < int_count; i++) {
+      opaque_ints_.push_back(buffer.handle()->data[fd_count + i]);
+    }
+  }
+  NativeBufferHandle(NativeBufferHandle&& other) = default;
+
+  // Imports the native handle into the given IonBuffer instance.
+  int Import(IonBuffer* buffer) {
+    // This is annoying, but we need to convert the vector of FileHandles into a
+    // vector of ints for the Import API.
+    std::vector<int> fd_ints;
+    for (const auto& fd : fds_)
+      fd_ints.push_back(fd.Get());
+
+    const int ret = buffer->Import(fd_ints.data(), fd_ints.size(),
+                                   opaque_ints_.data(), opaque_ints_.size(),
+                                   width_, height_, stride_, format_, usage_);
+    if (ret < 0)
+      return ret;
+
+    // Import succeeded, release the file handles which are now owned by the
+    // IonBuffer and clear members.
+    for (auto& fd : fds_)
+      fd.Release();
+    opaque_ints_.clear();
+    Clear();
+
+    return 0;
+  }
+
+  int id() const { return id_; }
+  size_t IntCount() const { return opaque_ints_.size(); }
+  size_t FdCount() const { return fds_.size(); }
+
+ private:
+  int id_;
+  int stride_;
+  int width_;
+  int height_;
+  int format_;
+  int usage_;
+  std::vector<int> opaque_ints_;
+  std::vector<FileHandleType> fds_;
+
+  void Clear() { id_ = stride_ = width_ = height_ = format_ = usage_ = -1; }
+
+  PDX_SERIALIZABLE_MEMBERS(NativeBufferHandle<FileHandleType>, id_, stride_,
+                           width_, height_, format_, usage_, opaque_ints_,
+                           fds_);
+
+  NativeBufferHandle(const NativeBufferHandle&) = delete;
+  void operator=(const NativeBufferHandle&) = delete;
+};
+
+template <typename FileHandleType>
+class FenceHandle {
+ public:
+  FenceHandle() = default;
+  explicit FenceHandle(int fence) : fence_{fence} {}
+  explicit FenceHandle(FileHandleType&& fence) : fence_{std::move(fence)} {}
+  FenceHandle(FenceHandle&&) = default;
+  FenceHandle& operator=(FenceHandle&&) = default;
+
+  explicit operator bool() const { return fence_.IsValid(); }
+
+  const FileHandleType& get() const { fence_; }
+  FileHandleType&& take() { return std::move(fence_); }
+
+  int get_fd() const { return fence_.Get(); }
+  void close() { fence_.Close(); }
+
+  FenceHandle<pdx::BorrowedHandle> borrow() const {
+    return FenceHandle<pdx::BorrowedHandle>(fence_.Borrow());
+  }
+
+ private:
+  FileHandleType fence_;
+
+  PDX_SERIALIZABLE_MEMBERS(FenceHandle<FileHandleType>, fence_);
+
+  FenceHandle(const FenceHandle&) = delete;
+  void operator=(const FenceHandle&) = delete;
+};
+
+using LocalFence = FenceHandle<pdx::LocalHandle>;
+using BorrowedFence = FenceHandle<pdx::BorrowedHandle>;
+
+// BufferHub Service RPC interface. Defines the endpoints, op codes, and method
+// type signatures supported by bufferhubd.
+struct BufferHubRPC {
+  // Service path.
+  static constexpr char kClientPath[] = "system/buffer_hub/client";
+
+  // |BufferHubQueue| will keep track of at most this value of buffers.
+  // Attempts at runtime to increase the number of buffers past this
+  // will fail. Note that the value is in sync with |android::BufferQueue|, so
+  // that slot id can be shared between |android::dvr::BufferHubQueueProducer|
+  // and |android::BufferQueueProducer| which both implements the same
+  // interface: |android::IGraphicBufferProducer|.
+  static constexpr size_t kMaxQueueCapacity =
+      android::BufferQueueDefs::NUM_BUFFER_SLOTS;
+
+  // Op codes.
+  enum {
+    kOpCreateBuffer = 0,
+    kOpCreatePersistentBuffer,
+    kOpGetPersistentBuffer,
+    kOpGetBuffer,
+    kOpGetBuffers,
+    kOpNewConsumer,
+    kOpProducerMakePersistent,
+    kOpProducerRemovePersistence,
+    kOpProducerPost,
+    kOpProducerGain,
+    kOpConsumerAcquire,
+    kOpConsumerRelease,
+    kOpConsumerSetIgnore,
+    kOpCreateProducerQueue,
+    kOpCreateConsumerQueue,
+    kOpProducerQueueAllocateBuffers,
+    kOpProducerQueueDetachBuffer,
+    kOpConsumerQueueImportBuffers,
+  };
+
+  // Aliases.
+  using MetaData = pdx::rpc::BufferWrapper<std::uint8_t*>;
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+  using LocalHandle = pdx::LocalHandle;
+  using Void = pdx::rpc::Void;
+
+  // Methods.
+  PDX_REMOTE_METHOD(CreateBuffer, kOpCreateBuffer,
+                    int(int width, int height, int format, int usage,
+                        size_t meta_size_bytes, size_t slice_count));
+  PDX_REMOTE_METHOD(CreatePersistentBuffer, kOpCreatePersistentBuffer,
+                    int(const std::string& name, int user_id, int group_id,
+                        int width, int height, int format, int usage,
+                        size_t meta_size_bytes, size_t slice_count));
+  PDX_REMOTE_METHOD(GetPersistentBuffer, kOpGetPersistentBuffer,
+                    int(const std::string& name));
+  PDX_REMOTE_METHOD(GetBuffer, kOpGetBuffer,
+                    NativeBufferHandle<LocalHandle>(unsigned index));
+  PDX_REMOTE_METHOD(GetBuffers, kOpGetBuffers,
+                    std::vector<NativeBufferHandle<LocalHandle>>(Void));
+  PDX_REMOTE_METHOD(NewConsumer, kOpNewConsumer, LocalChannelHandle(Void));
+  PDX_REMOTE_METHOD(ProducerMakePersistent, kOpProducerMakePersistent,
+                    int(const std::string& name, int user_id, int group_id));
+  PDX_REMOTE_METHOD(ProducerRemovePersistence, kOpProducerRemovePersistence,
+                    int(Void));
+  PDX_REMOTE_METHOD(ProducerPost, kOpProducerPost,
+                    int(LocalFence acquire_fence, MetaData));
+  PDX_REMOTE_METHOD(ProducerGain, kOpProducerGain, LocalFence(Void));
+  PDX_REMOTE_METHOD(ConsumerAcquire, kOpConsumerAcquire,
+                    std::pair<LocalFence, MetaData>(std::size_t metadata_size));
+  PDX_REMOTE_METHOD(ConsumerRelease, kOpConsumerRelease,
+                    int(LocalFence release_fence));
+  PDX_REMOTE_METHOD(ConsumerSetIgnore, kOpConsumerSetIgnore, int(bool ignore));
+
+  // Buffer Queue Methods.
+  PDX_REMOTE_METHOD(CreateProducerQueue, kOpCreateProducerQueue,
+                    int(size_t meta_size_bytes, int usage_set_mask,
+                        int usage_clear_mask, int usage_deny_set_mask,
+                        int usage_deny_clear_mask));
+  PDX_REMOTE_METHOD(CreateConsumerQueue, kOpCreateConsumerQueue,
+                    std::pair<LocalChannelHandle, size_t>(Void));
+  PDX_REMOTE_METHOD(ProducerQueueAllocateBuffers,
+                    kOpProducerQueueAllocateBuffers,
+                    std::vector<std::pair<LocalChannelHandle, size_t>>(
+                        int width, int height, int format, int usage,
+                        size_t slice_count, size_t buffer_count));
+  PDX_REMOTE_METHOD(ProducerQueueDetachBuffer, kOpProducerQueueDetachBuffer,
+                    int(size_t slot));
+  PDX_REMOTE_METHOD(ConsumerQueueImportBuffers, kOpConsumerQueueImportBuffers,
+                    std::vector<std::pair<LocalChannelHandle, size_t>>(Void));
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFERHUB_RPC_H_
diff --git a/libs/vr/libbufferhub/include/private/dvr/ion_buffer.h b/libs/vr/libbufferhub/include/private/dvr/ion_buffer.h
new file mode 100644
index 0000000..8125c54
--- /dev/null
+++ b/libs/vr/libbufferhub/include/private/dvr/ion_buffer.h
@@ -0,0 +1,108 @@
+#ifndef ANDROID_DVR_ION_BUFFER_H_
+#define ANDROID_DVR_ION_BUFFER_H_
+
+#include <hardware/gralloc.h>
+
+namespace android {
+namespace dvr {
+
+// IonBuffer is an abstraction of Ion/Gralloc buffers.
+class IonBuffer {
+ public:
+  IonBuffer();
+  IonBuffer(int width, int height, int format, int usage);
+  IonBuffer(buffer_handle_t handle, int width, int height, int stride,
+            int format, int usage);
+  IonBuffer(buffer_handle_t handle, int width, int height, int layer_count,
+            int stride, int layer_stride, int format, int usage);
+  ~IonBuffer();
+
+  IonBuffer(IonBuffer&& other);
+  IonBuffer& operator=(IonBuffer&& other);
+
+  // Frees the underlying native handle and leaves the instance initialized to
+  // empty.
+  void FreeHandle();
+
+  // Allocates a new native handle with the given parameters, freeing the
+  // previous native handle if necessary. Returns 0 on success or a negative
+  // errno code otherwise. If allocation fails the previous native handle is
+  // left intact.
+  int Alloc(int width, int height, int format, int usage);
+
+  // Resets the underlying native handle and parameters, freeing the previous
+  // native handle if necessary.
+  void Reset(buffer_handle_t handle, int width, int height, int stride,
+             int format, int usage);
+
+  // Like Reset but also registers the native handle, which is necessary for
+  // native handles received over IPC. Returns 0 on success or a negative errno
+  // code otherwise. If import fails the previous native handle is left intact.
+  int Import(buffer_handle_t handle, int width, int height, int stride,
+             int format, int usage);
+
+  // Like Reset but imports a native handle from raw fd and int arrays. Returns
+  // 0 on success or a negative errno code otherwise. If import fails the
+  // previous native handle is left intact.
+  int Import(const int* fd_array, int fd_count, const int* int_array,
+             int int_count, int width, int height, int stride, int format,
+             int usage);
+
+  // Duplicates the native handle underlying |other| and then imports it. This
+  // is useful for creating multiple, independent views of the same Ion/Gralloc
+  // buffer. Returns 0 on success or a negative errno code otherwise. If
+  // duplication or import fail the previous native handle is left intact.
+  int Duplicate(const IonBuffer* other);
+
+  int Lock(int usage, int x, int y, int width, int height, void** address);
+  int LockYUV(int usage, int x, int y, int width, int height,
+              struct android_ycbcr* yuv);
+  int Unlock();
+
+  buffer_handle_t handle() const { return handle_; }
+  int width() const { return width_; }
+  int height() const { return height_; }
+  int layer_count() const { return layer_count_; }
+  int stride() const { return stride_; }
+  int layer_stride() const { return layer_stride_; }
+  int format() const { return format_; }
+  int usage() const { return usage_; }
+
+  static gralloc_module_t const* GetGrallocModule() {
+    GrallocInit();
+    return gralloc_module_;
+  }
+
+  static alloc_device_t* GetGrallocDevice() {
+    GrallocInit();
+    return gralloc_device_;
+  }
+
+ private:
+  buffer_handle_t handle_;
+  int width_;
+  int height_;
+  int layer_count_;
+  int stride_;
+  int layer_stride_;
+  int format_;
+  int usage_;
+  bool locked_;
+  bool needs_unregister_;
+
+  void Replace(buffer_handle_t handle, int width, int height, int layer_count,
+               int stride, int layer_stride, int format, int usage,
+               bool needs_unregister);
+
+  static void GrallocInit();
+  static gralloc_module_t const* gralloc_module_;
+  static alloc_device_t* gralloc_device_;
+
+  IonBuffer(const IonBuffer&) = delete;
+  void operator=(const IonBuffer&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_ION_BUFFER_H_
diff --git a/libs/vr/libbufferhub/include/private/dvr/native_buffer.h b/libs/vr/libbufferhub/include/private/dvr/native_buffer.h
new file mode 100644
index 0000000..f6c24d9
--- /dev/null
+++ b/libs/vr/libbufferhub/include/private/dvr/native_buffer.h
@@ -0,0 +1,226 @@
+#ifndef ANDROID_DVR_NATIVE_BUFFER_H_
+#define ANDROID_DVR_NATIVE_BUFFER_H_
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <android/native_window.h>
+#include <base/logging.h>
+#include <cutils/log.h>
+#include <system/window.h>
+#include <ui/ANativeObjectBase.h>
+#include <utils/RefBase.h>
+
+#include <private/dvr/buffer_hub_client.h>
+
+namespace android {
+namespace dvr {
+
+// ANativeWindowBuffer is the abstraction Android HALs and frameworks use to
+// pass around hardware graphics buffers. The following classes implement this
+// abstraction with different DVR backing buffers, all of which provide
+// different semantics on top of ion/gralloc buffers.
+
+// An implementation of ANativeWindowBuffer backed by an IonBuffer.
+class NativeBuffer
+    : public android::ANativeObjectBase<ANativeWindowBuffer, NativeBuffer,
+                                        android::LightRefBase<NativeBuffer>> {
+ public:
+  static constexpr int kEmptyFence = -1;
+
+  explicit NativeBuffer(const std::shared_ptr<IonBuffer>& buffer)
+      : BASE(), buffer_(buffer), fence_(kEmptyFence) {
+    ANativeWindowBuffer::width = buffer->width();
+    ANativeWindowBuffer::height = buffer->height();
+    ANativeWindowBuffer::stride = buffer->stride();
+    ANativeWindowBuffer::format = buffer->format();
+    ANativeWindowBuffer::usage = buffer->usage();
+    handle = buffer_->handle();
+  }
+
+  virtual ~NativeBuffer() {}
+
+  std::shared_ptr<IonBuffer> buffer() { return buffer_; }
+  int fence() const { return fence_.Get(); }
+
+  void SetFence(int fence) { fence_.Reset(fence); }
+
+ private:
+  friend class android::LightRefBase<NativeBuffer>;
+
+  std::shared_ptr<IonBuffer> buffer_;
+  pdx::LocalHandle fence_;
+
+  NativeBuffer(const NativeBuffer&) = delete;
+  void operator=(NativeBuffer&) = delete;
+};
+
+// NativeBufferProducerSlice is an implementation of ANativeWindowBuffer backed
+// by a buffer slice of a BufferProducer.
+class NativeBufferProducerSlice
+    : public android::ANativeObjectBase<
+          ANativeWindowBuffer, NativeBufferProducerSlice,
+          android::LightRefBase<NativeBufferProducerSlice>> {
+ public:
+  NativeBufferProducerSlice(const std::shared_ptr<BufferProducer>& buffer,
+                            int buffer_index)
+      : BASE(), buffer_(buffer) {
+    ANativeWindowBuffer::width = buffer_->width();
+    ANativeWindowBuffer::height = buffer_->height();
+    ANativeWindowBuffer::stride = buffer_->stride();
+    ANativeWindowBuffer::format = buffer_->format();
+    ANativeWindowBuffer::usage = buffer_->usage();
+    handle = buffer_->native_handle(buffer_index);
+  }
+
+  virtual ~NativeBufferProducerSlice() {}
+
+ private:
+  friend class android::LightRefBase<NativeBufferProducerSlice>;
+
+  std::shared_ptr<BufferProducer> buffer_;
+
+  NativeBufferProducerSlice(const NativeBufferProducerSlice&) = delete;
+  void operator=(NativeBufferProducerSlice&) = delete;
+};
+
+// NativeBufferProducer is an implementation of ANativeWindowBuffer backed by a
+// BufferProducer.
+class NativeBufferProducer : public android::ANativeObjectBase<
+  ANativeWindowBuffer, NativeBufferProducer,
+  android::LightRefBase<NativeBufferProducer>> {
+ public:
+  static constexpr int kEmptyFence = -1;
+
+  NativeBufferProducer(const std::shared_ptr<BufferProducer>& buffer,
+                       EGLDisplay display, uint32_t surface_buffer_index)
+      : BASE(),
+        buffer_(buffer),
+        surface_buffer_index_(surface_buffer_index),
+        display_(display) {
+    ANativeWindowBuffer::width = buffer_->width();
+    ANativeWindowBuffer::height = buffer_->height();
+    ANativeWindowBuffer::stride = buffer_->stride();
+    ANativeWindowBuffer::format = buffer_->format();
+    ANativeWindowBuffer::usage = buffer_->usage();
+    handle = buffer_->native_handle();
+    for (int i = 0; i < buffer->slice_count(); ++i) {
+      // display == null means don't create an EGL image. This is used by our
+      // Vulkan code.
+      slices_.push_back(new NativeBufferProducerSlice(buffer, i));
+      if (display_ != nullptr) {
+        egl_images_.push_back(eglCreateImageKHR(
+            display_, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
+            static_cast<ANativeWindowBuffer*>(slices_.back().get()), nullptr));
+        if (egl_images_.back() == EGL_NO_IMAGE_KHR) {
+          ALOGE("NativeBufferProducer: eglCreateImageKHR failed");
+        }
+      }
+    }
+  }
+
+  explicit NativeBufferProducer(const std::shared_ptr<BufferProducer>& buffer)
+      : NativeBufferProducer(buffer, nullptr, 0) {}
+
+  virtual ~NativeBufferProducer() {
+    for (EGLImageKHR egl_image : egl_images_) {
+      if (egl_image != EGL_NO_IMAGE_KHR)
+        eglDestroyImageKHR(display_, egl_image);
+    }
+  }
+
+  EGLImageKHR image_khr(int index) const { return egl_images_[index]; }
+  std::shared_ptr<BufferProducer> buffer() const { return buffer_; }
+  int release_fence() const { return release_fence_.Get(); }
+  uint32_t surface_buffer_index() const { return surface_buffer_index_; }
+
+  // Return the release fence, passing ownership to the caller.
+  pdx::LocalHandle ClaimReleaseFence() { return std::move(release_fence_); }
+
+  // Post the buffer consumer, closing the acquire and release fences.
+  int Post(int acquire_fence, uint64_t sequence) {
+    release_fence_.Close();
+    return buffer_->Post(pdx::LocalHandle(acquire_fence), sequence);
+  }
+
+  // Gain the buffer producer, closing the previous release fence if valid.
+  int Gain() { return buffer_->Gain(&release_fence_); }
+
+  // Asynchronously gain the buffer, closing the previous release fence.
+  int GainAsync() {
+    release_fence_.Close();
+    return buffer_->GainAsync();
+  }
+
+ private:
+  friend class android::LightRefBase<NativeBufferProducer>;
+
+  std::shared_ptr<BufferProducer> buffer_;
+  pdx::LocalHandle release_fence_;
+  std::vector<android::sp<NativeBufferProducerSlice>> slices_;
+  std::vector<EGLImageKHR> egl_images_;
+  uint32_t surface_buffer_index_;
+  EGLDisplay display_;
+
+  NativeBufferProducer(const NativeBufferProducer&) = delete;
+  void operator=(NativeBufferProducer&) = delete;
+};
+
+// NativeBufferConsumer is an implementation of ANativeWindowBuffer backed by a
+// BufferConsumer.
+class NativeBufferConsumer : public android::ANativeObjectBase<
+                                 ANativeWindowBuffer, NativeBufferConsumer,
+                                 android::LightRefBase<NativeBufferConsumer>> {
+ public:
+  static constexpr int kEmptyFence = -1;
+
+  explicit NativeBufferConsumer(const std::shared_ptr<BufferConsumer>& buffer,
+                                int index)
+      : BASE(), buffer_(buffer), acquire_fence_(kEmptyFence), sequence_(0) {
+    ANativeWindowBuffer::width = buffer_->width();
+    ANativeWindowBuffer::height = buffer_->height();
+    ANativeWindowBuffer::stride = buffer_->stride();
+    ANativeWindowBuffer::format = buffer_->format();
+    ANativeWindowBuffer::usage = buffer_->usage();
+    CHECK(buffer_->slice_count() > index);
+    handle = buffer_->slice(index)->handle();
+  }
+
+  explicit NativeBufferConsumer(const std::shared_ptr<BufferConsumer>& buffer)
+      : NativeBufferConsumer(buffer, 0) {}
+
+  virtual ~NativeBufferConsumer() {}
+
+  std::shared_ptr<BufferConsumer> buffer() const { return buffer_; }
+  int acquire_fence() const { return acquire_fence_.Get(); }
+  uint64_t sequence() const { return sequence_; }
+
+  // Return the acquire fence, passing ownership to the caller.
+  pdx::LocalHandle ClaimAcquireFence() { return std::move(acquire_fence_); }
+
+  // Acquire the underlying buffer consumer, closing the previous acquire fence
+  // if valid.
+  int Acquire() { return buffer_->Acquire(&acquire_fence_, &sequence_); }
+
+  // Release the buffer consumer, closing the acquire and release fences if
+  // valid.
+  int Release(int release_fence) {
+    acquire_fence_.Close();
+    sequence_ = 0;
+    return buffer_->Release(pdx::LocalHandle(release_fence));
+  }
+
+ private:
+  friend class android::LightRefBase<NativeBufferConsumer>;
+
+  std::shared_ptr<BufferConsumer> buffer_;
+  pdx::LocalHandle acquire_fence_;
+  uint64_t sequence_;
+
+  NativeBufferConsumer(const NativeBufferConsumer&) = delete;
+  void operator=(NativeBufferConsumer&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_NATIVE_BUFFER_H_
diff --git a/libs/vr/libbufferhub/ion_buffer.cpp b/libs/vr/libbufferhub/ion_buffer.cpp
new file mode 100644
index 0000000..7d20049
--- /dev/null
+++ b/libs/vr/libbufferhub/ion_buffer.cpp
@@ -0,0 +1,322 @@
+#include <private/dvr/ion_buffer.h>
+
+#include <cutils/log.h>
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Trace.h>
+
+#include <mutex>
+
+namespace android {
+namespace dvr {
+
+gralloc_module_t const* IonBuffer::gralloc_module_ = nullptr;
+alloc_device_t* IonBuffer::gralloc_device_ = nullptr;
+
+IonBuffer::IonBuffer() : IonBuffer(nullptr, 0, 0, 0, 0, 0, 0, 0) {}
+
+IonBuffer::IonBuffer(int width, int height, int format, int usage)
+    : IonBuffer() {
+  Alloc(width, height, format, usage);
+}
+
+IonBuffer::IonBuffer(buffer_handle_t handle, int width, int height, int stride,
+                     int format, int usage)
+    : IonBuffer(handle, width, height, 1, stride, 0, format, usage) {}
+
+IonBuffer::IonBuffer(buffer_handle_t handle, int width, int height,
+                     int layer_count, int stride, int layer_stride, int format,
+                     int usage)
+    : handle_(handle),
+      width_(width),
+      height_(height),
+      layer_count_(layer_count),
+      stride_(stride),
+      layer_stride_(layer_stride),
+      format_(format),
+      usage_(usage),
+      locked_(false),
+      needs_unregister_(false) {
+  ALOGD_IF(TRACE,
+           "IonBuffer::IonBuffer: handle=%p width=%d height=%d layer_count=%d "
+           "stride=%d layer stride=%d format=%d usage=%d",
+           handle_, width_, height_, layer_count_, stride_, layer_stride_,
+           format_, usage_);
+  GrallocInit();
+}
+
+IonBuffer::~IonBuffer() {
+  ALOGD_IF(TRACE,
+           "IonBuffer::~IonBuffer: handle=%p width=%d height=%d stride=%d "
+           "format=%d usage=%d",
+           handle_, width_, height_, stride_, format_, usage_);
+
+  FreeHandle();
+}
+
+IonBuffer::IonBuffer(IonBuffer&& other) : IonBuffer() {
+  *this = std::move(other);
+}
+
+IonBuffer& IonBuffer::operator=(IonBuffer&& other) {
+  ALOGD_IF(TRACE, "IonBuffer::operator=: handle_=%p other.handle_=%p", handle_,
+           other.handle_);
+
+  if (this != &other) {
+    Replace(other.handle_, other.width_, other.height_, other.layer_count_,
+            other.stride_, other.layer_stride_, other.format_, other.usage_,
+            other.needs_unregister_);
+    locked_ = other.locked_;
+    other.handle_ = nullptr;
+    other.FreeHandle();
+  }
+
+  return *this;
+}
+
+void IonBuffer::FreeHandle() {
+  if (handle_) {
+    // Lock/Unlock don't need to be balanced, but one Unlock is needed to
+    // clean/unmap the buffer. Warn if this didn't happen before freeing the
+    // native handle.
+    ALOGW_IF(locked_,
+             "IonBuffer::FreeHandle: freeing a locked handle!!! handle=%p",
+             handle_);
+
+    if (needs_unregister_) {
+      int ret = gralloc_module_->unregisterBuffer(gralloc_module_, handle_);
+      ALOGE_IF(ret < 0,
+               "IonBuffer::FreeHandle: Failed to unregister handle: %s",
+               strerror(-ret));
+
+      native_handle_close(const_cast<native_handle_t*>(handle_));
+      native_handle_delete(const_cast<native_handle_t*>(handle_));
+    } else {
+      int ret = gralloc_device_->free(gralloc_device_, handle_);
+      if (ret < 0) {
+        ALOGE("IonBuffer::FreeHandle: failed to free buffer: %s",
+              strerror(-ret));
+
+        // Not sure if this is the right thing to do. Attempting to prevent a
+        // memory leak of the native handle.
+        native_handle_close(const_cast<native_handle_t*>(handle_));
+        native_handle_delete(const_cast<native_handle_t*>(handle_));
+      }
+    }
+  }
+
+  // Always re-initialize these members, even if handle_ was nullptr, in case
+  // someone was dumb enough to pass a nullptr handle to the constructor or
+  // Reset.
+  handle_ = nullptr;
+  width_ = 0;
+  height_ = 0;
+  layer_count_ = 0;
+  stride_ = 0;
+  layer_stride_ = 0;
+  format_ = 0;
+  usage_ = 0;
+  locked_ = false;
+  needs_unregister_ = false;
+}
+
+int IonBuffer::Alloc(int width, int height, int format, int usage) {
+  ATRACE_NAME("IonBuffer::Alloc");
+  ALOGD_IF(TRACE, "IonBuffer::Alloc: width=%d height=%d format=%d usage=%d",
+           width, height, format, usage);
+
+  int stride;
+  buffer_handle_t handle;
+
+  int ret = gralloc_device_->alloc(gralloc_device_, width, height, format,
+                                   usage, &handle, &stride);
+  if (ret < 0) {
+    ALOGE("IonBuffer::Alloc: failed to allocate gralloc buffer: %s",
+          strerror(-ret));
+    return ret;
+  }
+
+  Replace(handle, width, height, 1, stride, 0, format, usage, false);
+  return 0;
+}
+
+void IonBuffer::Replace(buffer_handle_t handle, int width, int height,
+                        int layer_count, int stride, int layer_stride,
+                        int format, int usage, bool needs_unregister) {
+  FreeHandle();
+
+  handle_ = handle;
+  width_ = width;
+  height_ = height;
+  layer_count_ = layer_count;
+  stride_ = stride;
+  layer_stride_ = layer_stride;
+  format_ = format;
+  usage_ = usage;
+  needs_unregister_ = needs_unregister;
+}
+
+void IonBuffer::Reset(buffer_handle_t handle, int width, int height, int stride,
+                      int format, int usage) {
+  ALOGD_IF(TRACE,
+           "IonBuffer::Reset: handle=%p width=%d height=%d stride=%d format=%d "
+           "usage=%d",
+           handle, width, height, stride, format, usage);
+
+  Replace(handle, width, height, 1, stride, 0, format, usage, false);
+}
+
+int IonBuffer::Import(buffer_handle_t handle, int width, int height, int stride,
+                      int format, int usage) {
+  ATRACE_NAME("IonBuffer::Import1");
+  ALOGD_IF(
+      TRACE,
+      "IonBuffer::Import: handle=%p width=%d height=%d stride=%d format=%d "
+      "usage=%d",
+      handle, width, height, stride, format, usage);
+
+  int ret = gralloc_module_->registerBuffer(gralloc_module_, handle);
+  if (ret < 0) {
+    ALOGE("IonBuffer::Import: failed to import handle: %s", strerror(-ret));
+    return ret;
+  }
+
+  Replace(handle, width, height, 1, stride, 0, format, usage, true);
+  return 0;
+}
+
+int IonBuffer::Import(const int* fd_array, int fd_count, const int* int_array,
+                      int int_count, int width, int height, int stride,
+                      int format, int usage) {
+  ATRACE_NAME("IonBuffer::Import2");
+  ALOGD_IF(TRACE,
+           "IonBuffer::Import: fd_count=%d int_count=%d width=%d height=%d "
+           "stride=%d format=%d usage=%d",
+           fd_count, int_count, width, height, stride, format, usage);
+
+  if (fd_count < 0 || int_count < 0) {
+    ALOGE("IonBuffer::Import: invalid arguments.");
+    return -EINVAL;
+  }
+
+  native_handle_t* handle = native_handle_create(fd_count, int_count);
+  if (!handle) {
+    ALOGE("IonBuffer::Import: failed to create new native handle.");
+    return -ENOMEM;
+  }
+
+  // Copy fd_array into the first part of handle->data and int_array right
+  // after it.
+  memcpy(handle->data, fd_array, sizeof(int) * fd_count);
+  memcpy(handle->data + fd_count, int_array, sizeof(int) * int_count);
+
+  int ret = Import(handle, width, height, stride, format, usage);
+  if (ret < 0) {
+    ALOGE("IonBuffer::Import: failed to import raw native handle: %s",
+          strerror(-ret));
+    native_handle_close(handle);
+    native_handle_delete(handle);
+  }
+
+  return ret;
+}
+
+int IonBuffer::Duplicate(const IonBuffer* other) {
+  if (!other->handle())
+    return -EINVAL;
+
+  const int fd_count = other->handle()->numFds;
+  const int int_count = other->handle()->numInts;
+
+  if (fd_count < 0 || int_count < 0)
+    return -EINVAL;
+
+  native_handle_t* handle = native_handle_create(fd_count, int_count);
+  if (!handle) {
+    ALOGE("IonBuffer::Duplicate: Failed to create new native handle.");
+    return -ENOMEM;
+  }
+
+  // Duplicate the file descriptors from the other native handle.
+  for (int i = 0; i < fd_count; i++)
+    handle->data[i] = dup(other->handle()->data[i]);
+
+  // Copy the ints after the file descriptors.
+  memcpy(handle->data + fd_count, other->handle()->data + fd_count,
+         sizeof(int) * int_count);
+
+  const int ret = Import(handle, other->width(), other->height(),
+                         other->stride(), other->format(), other->usage());
+  if (ret < 0) {
+    ALOGE("IonBuffer::Duplicate: Failed to import duplicate native handle: %s",
+          strerror(-ret));
+    native_handle_close(handle);
+    native_handle_delete(handle);
+  }
+
+  return ret;
+}
+
+int IonBuffer::Lock(int usage, int x, int y, int width, int height,
+                    void** address) {
+  ATRACE_NAME("IonBuffer::Lock");
+  ALOGD_IF(TRACE,
+           "IonBuffer::Lock: handle=%p usage=%d x=%d y=%d width=%d height=%d "
+           "address=%p",
+           handle_, usage, x, y, width, height, address);
+
+  // Lock may be called multiple times; but only one Unlock is required.
+  const int err = gralloc_module_->lock(gralloc_module_, handle_, usage, x, y,
+                                        width, height, address);
+  if (!err)
+    locked_ = true;
+
+  return err;
+}
+
+int IonBuffer::LockYUV(int usage, int x, int y, int width, int height,
+                       struct android_ycbcr* yuv) {
+  ATRACE_NAME("IonBuffer::LockYUV");
+  ALOGD_IF(TRACE,
+           "IonBuffer::Lock: handle=%p usage=%d x=%d y=%d width=%d height=%d",
+           handle_, usage, x, y, width, height);
+  const int err = gralloc_module_->lock_ycbcr(gralloc_module_, handle_, usage,
+                                              x, y, width, height, yuv);
+  if (!err)
+    locked_ = true;
+
+  return err;
+}
+
+int IonBuffer::Unlock() {
+  ATRACE_NAME("IonBuffer::Unlock");
+  ALOGD_IF(TRACE, "IonBuffer::Unlock: handle=%p", handle_);
+
+  // Lock may be called multiple times; but only one Unlock is required.
+  const int err = gralloc_module_->unlock(gralloc_module_, handle_);
+  if (!err)
+    locked_ = false;
+
+  return err;
+}
+
+void IonBuffer::GrallocInit() {
+  static std::once_flag gralloc_flag;
+  std::call_once(gralloc_flag, []() {
+    hw_module_t const* module = nullptr;
+    alloc_device_t* device = nullptr;
+
+    int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
+    ALOGE_IF(err, "IonBuffer::GrallocInit: failed to find the %s module: %s",
+             GRALLOC_HARDWARE_MODULE_ID, strerror(-err));
+
+    err = gralloc_open(module, &device);
+    ALOGE_IF(err, "IonBuffer::GrallocInit: failed to open gralloc device: %s",
+             strerror(-err));
+
+    gralloc_module_ = reinterpret_cast<gralloc_module_t const*>(module);
+    gralloc_device_ = device;
+  });
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhub/mocks/client/private/dvr/buffer_hub_client.h b/libs/vr/libbufferhub/mocks/client/private/dvr/buffer_hub_client.h
new file mode 100644
index 0000000..33816fa
--- /dev/null
+++ b/libs/vr/libbufferhub/mocks/client/private/dvr/buffer_hub_client.h
@@ -0,0 +1,57 @@
+#ifndef LIB_LIBBUFFERHUB_PRIVATE_DVR_BUFFER_HUB_CLIENT_H_  // NOLINT
+#define LIB_LIBBUFFERHUB_PRIVATE_DVR_BUFFER_HUB_CLIENT_H_
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+// TODO(jwcai) mock not need for now
+class native_handle_t;
+
+namespace android {
+namespace dvr {
+
+// TODO(jwcai) mock not need for now
+class IonBuffer;
+
+class BufferHubBuffer {
+ public:
+  MOCK_METHOD1(Poll, int(int timeout_ms));
+  MOCK_METHOD6(Lock, bool(int usage, int x, int y, int width, int height,
+                          void** addr));
+  MOCK_METHOD0(Unlock, int());
+
+  MOCK_METHOD0(native_handle, native_handle_t*());
+  MOCK_METHOD0(buffer, IonBuffer*());
+  MOCK_METHOD0(event_fd, int());
+
+  MOCK_METHOD0(id, int());
+  MOCK_METHOD0(width, int());
+  MOCK_METHOD0(height, int());
+  MOCK_METHOD0(stride, int());
+  MOCK_METHOD0(format, int());
+  MOCK_METHOD0(usage, int());
+};
+
+class BufferProducer : public BufferHubBuffer {
+ public:
+  // Note that static method |CreateBuffer| and |Import| are not mocked
+  // here, they are just implementation details and thus not needed.
+  MOCK_METHOD2(Post, int(int ready_fence, uint64_t sequence));
+  MOCK_METHOD1(Gain, int(int* release_fence));
+
+  static BufferProducer* staticObject;
+};
+
+class BufferConsumer : public BufferHubBuffer {
+ public:
+  MOCK_METHOD2(Acquire, int(int* ready_fence, uint64_t* sequence));
+  MOCK_METHOD1(Release, int(int release_fence));
+  MOCK_METHOD0(Discard, int());
+  MOCK_METHOD3(DoAcquire,
+               int(int* ready_fence, void* meta, size_t meta_size_bytes));
+
+  static BufferConsumer* staticObject;
+};
+
+}  // namespace dvr
+}  // namespace android
+#endif  // LIB_LIBBUFFERHUB_PRIVATE_DVR_BUFFER_HUB_CLIENT_H_  //NOLINT
diff --git a/libs/vr/libbufferhub/mocks/gralloc/BUILD.gn b/libs/vr/libbufferhub/mocks/gralloc/BUILD.gn
new file mode 100644
index 0000000..9674c7c
--- /dev/null
+++ b/libs/vr/libbufferhub/mocks/gralloc/BUILD.gn
@@ -0,0 +1,23 @@
+config("gralloc_config") {
+  include_dirs = [ "." ]
+}
+
+static_library("gralloc") {
+  testonly = true
+
+  sources = [
+    "gralloc.cpp",
+    "gralloc.h",
+  ]
+
+  include_dirs = [
+    "//system/core/include",
+    "//hardware/libhardware/include",
+  ]
+
+  public_deps = [
+    "//dreamos/external/gmock",
+  ]
+
+  public_configs = [ ":gralloc_config" ]
+}
diff --git a/libs/vr/libbufferhub/mocks/gralloc/gralloc.cpp b/libs/vr/libbufferhub/mocks/gralloc/gralloc.cpp
new file mode 100644
index 0000000..4a923ec
--- /dev/null
+++ b/libs/vr/libbufferhub/mocks/gralloc/gralloc.cpp
@@ -0,0 +1,68 @@
+#include <gralloc_mock.h>
+#include <hardware/gralloc.h>
+
+static alloc_device_t sdevice;
+
+static int local_registerBuffer(struct gralloc_module_t const*,
+                                buffer_handle_t handle) {
+  return GrallocMock::staticObject->registerBuffer(handle);
+}
+
+static int local_unregisterBuffer(struct gralloc_module_t const*,
+                                  buffer_handle_t handle) {
+  return GrallocMock::staticObject->unregisterBuffer(handle);
+}
+
+static int local_unlock(struct gralloc_module_t const*,
+                        buffer_handle_t handle) {
+  return GrallocMock::staticObject->unlock(handle);
+}
+
+static int local_lock(struct gralloc_module_t const*, buffer_handle_t handle,
+                      int usage, int l, int t, int w, int h, void** vaddr) {
+  return GrallocMock::staticObject->lock(handle, usage, l, t, w, h, vaddr);
+}
+
+static int local_alloc(struct alloc_device_t*, int w, int h, int format,
+                       int usage, buffer_handle_t* handle, int* stride) {
+  return GrallocMock::staticObject->alloc(w, h, format, usage, handle, stride);
+}
+
+static int local_free(struct alloc_device_t*, buffer_handle_t handle) {
+  return GrallocMock::staticObject->free(handle);
+}
+
+static int local_open(const struct hw_module_t*, const char*,
+                      struct hw_device_t** device) {
+  sdevice.alloc = local_alloc;
+  sdevice.free = local_free;
+  *device = reinterpret_cast<hw_device_t*>(&sdevice);
+  return 0;
+}
+
+static hw_module_methods_t smethods;
+
+static gralloc_module_t smodule;
+
+int hw_get_module(const char*, const struct hw_module_t** module) {
+  smodule.registerBuffer = local_registerBuffer;
+  smodule.unregisterBuffer = local_unregisterBuffer;
+  smodule.lock = local_lock;
+  smodule.unlock = local_unlock;
+  smethods.open = local_open;
+  smodule.common.methods = &smethods;
+  *module = reinterpret_cast<hw_module_t*>(&smodule);
+  return 0;
+}
+
+int native_handle_close(const native_handle_t* handle) {
+  return GrallocMock::staticObject->native_handle_close(handle);
+}
+
+int native_handle_delete(native_handle_t* handle) {
+  return GrallocMock::staticObject->native_handle_delete(handle);
+}
+
+native_handle_t* native_handle_create(int numFds, int numInts) {
+  return GrallocMock::staticObject->native_handle_create(numFds, numInts);
+}
diff --git a/libs/vr/libbufferhub/mocks/gralloc/gralloc_mock.h b/libs/vr/libbufferhub/mocks/gralloc/gralloc_mock.h
new file mode 100644
index 0000000..f62f579
--- /dev/null
+++ b/libs/vr/libbufferhub/mocks/gralloc/gralloc_mock.h
@@ -0,0 +1,23 @@
+#ifndef LIB_LIBBUFFERHUB_MOCKS_GRALLOC_GRALLOC_MOCK_H_
+#define LIB_LIBBUFFERHUB_MOCKS_GRALLOC_GRALLOC_MOCK_H_
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hardware/gralloc.h>
+
+// IonBuffer is an abstraction of Ion/Gralloc buffers.
+class GrallocMock {
+ public:
+  // Add methods here.
+  MOCK_METHOD1(native_handle_close, int(const native_handle_t*));
+  MOCK_METHOD1(native_handle_delete, int(native_handle_t*));
+  MOCK_METHOD2(native_handle_create, native_handle_t*(int, int));
+  MOCK_METHOD1(registerBuffer, int(buffer_handle_t));
+  MOCK_METHOD1(unregisterBuffer, int(buffer_handle_t));
+  MOCK_METHOD7(lock, int(buffer_handle_t, int, int, int, int, int, void**));
+  MOCK_METHOD1(unlock, int(buffer_handle_t));
+  MOCK_METHOD6(alloc, int(int, int, int, int, buffer_handle_t*, int*));
+  MOCK_METHOD1(free, int(buffer_handle_t));
+  static GrallocMock* staticObject;
+};
+
+#endif  // LIB_LIBBUFFERHUB_MOCKS_GRALLOC_GRALLOC_MOCK_H_
diff --git a/libs/vr/libbufferhub/mocks/ion_buffer/private/dvr/ion_buffer.h b/libs/vr/libbufferhub/mocks/ion_buffer/private/dvr/ion_buffer.h
new file mode 100644
index 0000000..fac6db0
--- /dev/null
+++ b/libs/vr/libbufferhub/mocks/ion_buffer/private/dvr/ion_buffer.h
@@ -0,0 +1,78 @@
+// This file has a big hack, it "mocks" the actual IonBuffer by redefining
+// it with mock methods and using the same header guard to prevent the original
+// definition from being included in the same context.
+#ifndef LIB_LIBBUFFERHUB_PRIVATE_DVR_ION_BUFFER_H_  // NOLINT
+#define LIB_LIBBUFFERHUB_PRIVATE_DVR_ION_BUFFER_H_
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <hardware/gralloc.h>
+
+namespace android {
+namespace dvr {
+
+// IonBuffer is an abstraction of Ion/Gralloc buffers.
+class IonBufferMock {
+ public:
+  IonBufferMock() {}
+  MOCK_METHOD0(GetGrallocModuleImpl, gralloc_module_t const*());
+  MOCK_METHOD6(Import, int(buffer_handle_t handle, int width, int height,
+                           int stride, int format, int usage));
+  MOCK_METHOD9(Import, int(const int* fd_array, int fd_count,
+                           const int* int_array, int int_count, int width,
+                           int height, int stride, int format, int usage));
+  MOCK_METHOD6(Lock, int(int usage, int x, int y, int width, int height,
+                         void** address));
+  MOCK_METHOD0(Unlock, int());
+  MOCK_CONST_METHOD0(handle, buffer_handle_t());
+  MOCK_CONST_METHOD0(width, int());
+  MOCK_CONST_METHOD0(height, int());
+  MOCK_CONST_METHOD0(layer_count, int());
+  MOCK_CONST_METHOD0(stride, int());
+  MOCK_CONST_METHOD0(layer_stride, int());
+  MOCK_CONST_METHOD0(format, int());
+  MOCK_CONST_METHOD0(usage, int());
+};
+
+// IonBuffer is an abstraction of Ion/Gralloc buffers.
+class IonBuffer {
+ public:
+  IonBuffer() : mock_(new IonBufferMock) {
+    if (initializer) {
+      initializer(mock_.get());
+    }
+  }
+  IonBuffer(IonBuffer&& other) = default;
+  static gralloc_module_t const* GetGrallocModule() {
+    return staticObject->GetGrallocModuleImpl();
+  }
+  int Import(buffer_handle_t handle, int width, int height, int stride,
+             int format, int usage) {
+    return mock_->Import(handle, width, height, stride, format, usage);
+  }
+  int Import(const int* fd_array, int fd_count, const int* int_array,
+             int int_count, int width, int height, int stride, int format,
+             int usage) {
+    return mock_->Import(fd_array, fd_count, int_array, int_count, width,
+                         height, stride, format, usage);
+  }
+  int Lock(int usage, int x, int y, int width, int height, void** address) {
+    return mock_->Lock(usage, x, y, width, height, address);
+  }
+  int Unlock() { return mock_->Unlock(); }
+  buffer_handle_t handle() const { return mock_->handle(); }
+  int width() const { return mock_->width(); }
+  int height() const { return mock_->height(); }
+  int layer_count() const { return mock_->layer_count(); }
+  int stride() const { return mock_->stride(); }
+  int layer_stride() const { return mock_->layer_stride(); }
+  int format() const { return mock_->format(); }
+  int usage() const { return mock_->usage(); }
+  std::unique_ptr<IonBufferMock> mock_;
+  static IonBufferMock* staticObject;
+  static void (*initializer)(IonBufferMock* target);
+};
+
+}  // namespace dvr
+}  // namespace android
+#endif  // LIB_LIBBUFFERHUB_PRIVATE_DVR_ION_BUFFER_H_ - NOLINT
diff --git a/libs/vr/libbufferhub/tests/Android.mk b/libs/vr/libbufferhub/tests/Android.mk
new file mode 100644
index 0000000..5053e7d
--- /dev/null
+++ b/libs/vr/libbufferhub/tests/Android.mk
@@ -0,0 +1 @@
+include $(call all-subdir-makefiles)
diff --git a/libs/vr/libbufferhub/tests/ion_buffer/Android.mk b/libs/vr/libbufferhub/tests/ion_buffer/Android.mk
new file mode 100644
index 0000000..3bfdb7b
--- /dev/null
+++ b/libs/vr/libbufferhub/tests/ion_buffer/Android.mk
@@ -0,0 +1,74 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+COMPONENT_TOP := ${LOCAL_PATH}/../..
+
+LOCAL_SRC_FILES := \
+        ion_buffer-test.cpp \
+        ../../ion_buffer.cpp \
+        ../../mocks/gralloc/gralloc.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+        libc \
+        libcutils \
+        libutils \
+        liblog
+
+LOCAL_STATIC_LIBRARIES := \
+        libgmock
+
+LOCAL_C_INCLUDES := \
+        ${COMPONENT_TOP}/mocks/gralloc \
+        ${COMPONENT_TOP}/include \
+        $(TOP)/system/core/base/include
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := ${LOCAL_C_INCLUDES}
+
+LOCAL_NATIVE_COVERAGE := true
+
+LOCAL_CFLAGS := -DTRACE=0 -g
+
+LOCAL_MODULE := ion_buffer-test
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+        ion_buffer-test.cpp \
+        ../../ion_buffer.cpp \
+        ../../mocks/gralloc/gralloc.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+        liblog
+
+LOCAL_STATIC_LIBRARIES := \
+        libgmock_host
+
+LOCAL_C_INCLUDES := \
+        ${COMPONENT_TOP}/mocks/gralloc \
+        ${COMPONENT_TOP}/include \
+        $(TOP)/system/core/base/include
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := ${LOCAL_C_INCLUDES}
+
+LOCAL_NATIVE_COVERAGE := true
+
+LOCAL_CFLAGS := -DTRACE=0
+
+LOCAL_MODULE := ion_buffer-host_test
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_HOST_NATIVE_TEST)
+
+.PHONY: dvr_host_native_unit_tests
+dvr_host_native_unit_tests: ion_buffer-host_test
+ifeq (true,$(NATIVE_COVERAGE))
+  ion_buffer-host_test: llvm-cov
+  ion_buffer-test: llvm-cov
+  # This shouldn't be necessary, but the default build with
+  # NATIVE_COVERAGE=true manages to ion_buffer-test without
+  # building llvm-cov (droid is the default target).
+  droid: llvm-cov
+endif
diff --git a/libs/vr/libbufferhub/tests/ion_buffer/ion_buffer-test.cpp b/libs/vr/libbufferhub/tests/ion_buffer/ion_buffer-test.cpp
new file mode 100644
index 0000000..68f82d7
--- /dev/null
+++ b/libs/vr/libbufferhub/tests/ion_buffer/ion_buffer-test.cpp
@@ -0,0 +1,375 @@
+#include <gmock/gmock.h>
+#include <gralloc_mock.h>
+#include <gtest/gtest.h>
+#include <private/dvr/ion_buffer.h>
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using android::dvr::IonBuffer;
+
+GrallocMock* GrallocMock::staticObject = nullptr;
+
+namespace {
+
+const int w1 = 100;
+const int h1 = 200;
+const int d1 = 2;
+const int f1 = 1;
+const int u1 = 3;
+const int stride1 = 8;
+const int layer_stride1 = 8;
+native_handle_t handle1;
+const int w2 = 150;
+const int h2 = 300;
+const int d2 = 4;
+const int f2 = 2;
+const int u2 = 5;
+const int stride2 = 4;
+const int layer_stride2 = 4;
+native_handle_t handle2;
+const int kMaxFd = 10;
+const int kMaxInt = 10;
+char handleData[sizeof(native_handle_t) + (kMaxFd + kMaxInt) * sizeof(int)];
+native_handle_t* const dataHandle =
+    reinterpret_cast<native_handle_t*>(handleData);
+char refData[sizeof(native_handle_t) + (kMaxFd + kMaxInt) * sizeof(int)];
+native_handle_t* const refHandle = reinterpret_cast<native_handle_t*>(refData);
+
+class IonBufferUnitTest : public ::testing::Test {
+ protected:
+  // You can remove any or all of the following functions if its body
+  // is empty.
+
+  IonBufferUnitTest() {
+    GrallocMock::staticObject = new GrallocMock;
+    // You can do set-up work for each test here.
+    // most ServicefsClients will use this initializer. Use as the
+    // default.
+  }
+
+  virtual ~IonBufferUnitTest() {
+    delete GrallocMock::staticObject;
+    GrallocMock::staticObject = nullptr;
+    // You can do clean-up work that doesn't throw exceptions here.
+  }
+
+  // If the constructor and destructor are not enough for setting up
+  // and cleaning up each test, you can define the following methods:
+
+  void SetUp() override {
+    // Code here will be called immediately after the constructor (right
+    // before each test).
+  }
+
+  void TearDown() override {
+    // Code here will be called immediately after each test (right
+    // before the destructor).
+  }
+};
+
+void TestIonBufferState(const IonBuffer& buffer, int w, int h, int d, int f,
+                        int u, native_handle_t* handle, int stride,
+                        int layer_stride) {
+  EXPECT_EQ(buffer.width(), w);
+  EXPECT_EQ(buffer.height(), h);
+  EXPECT_EQ(buffer.layer_count(), d);
+  EXPECT_EQ(buffer.format(), f);
+  EXPECT_EQ(buffer.usage(), u);
+  EXPECT_EQ(buffer.handle(), handle);
+  EXPECT_EQ(buffer.stride(), stride);
+  EXPECT_EQ(buffer.layer_stride(), layer_stride);
+}
+
+TEST_F(IonBufferUnitTest, TestFreeOnDestruction) {
+  // Set up |alloc|(|w1...|) to succeed once and fail on others calls.
+  EXPECT_CALL(*GrallocMock::staticObject, alloc(w1, h1, f1, u1, _, _))
+      .Times(1)
+      .WillOnce(DoAll(SetArgPointee<4>(&handle1), SetArgPointee<5>(stride1),
+                      Return(0)));
+  // Set up |free| to be called once.
+  EXPECT_CALL(*GrallocMock::staticObject, free(&handle1))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+
+  IonBuffer buffer;
+  // First call to |alloc| with |w1...| set up to succeed.
+  int ret = buffer.Alloc(w1, h1, f1, u1);
+  EXPECT_EQ(ret, 0);
+  TestIonBufferState(buffer, w1, h1, 1, f1, u1, &handle1, stride1, 0);
+
+  // Scoped destructor will be called, calling |free| on |handle1|.
+}
+
+TEST_F(IonBufferUnitTest, TestAlloc) {
+  IonBuffer buffer;
+  // Set up |alloc|(|w1...|) to succeed once and fail on others calls.
+  EXPECT_CALL(*GrallocMock::staticObject, alloc(w1, h1, f1, u1, _, _))
+      .Times(2)
+      .WillOnce(DoAll(SetArgPointee<4>(&handle1), SetArgPointee<5>(stride1),
+                      Return(0)))
+      .WillRepeatedly(Return(-1));
+
+  // Set up |alloc|(|w2...|)  to succeed once and fail on others calls.
+  EXPECT_CALL(*GrallocMock::staticObject, alloc(w2, h2, f2, u2, _, _))
+      .Times(2)
+      .WillOnce(DoAll(SetArgPointee<4>(&handle2), SetArgPointee<5>(stride2),
+                      Return(0)))
+      .WillRepeatedly(Return(-1));
+  EXPECT_CALL(*GrallocMock::staticObject, free(&handle1))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+  EXPECT_CALL(*GrallocMock::staticObject, free(&handle2))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+
+  // First call to |alloc| with |w1...| set up to succeed.
+  int ret = buffer.Alloc(w1, h1, f1, u1);
+  EXPECT_EQ(ret, 0);
+  TestIonBufferState(buffer, w1, h1, 1, f1, u1, &handle1, stride1, 0);
+
+  // First call to |alloc| with |w2...| set up to succeed, |free| should be
+  // called once on |handle1|.
+  ret = buffer.Alloc(w2, h2, f2, u2);
+  EXPECT_EQ(ret, 0);
+  TestIonBufferState(buffer, w2, h2, 1, f2, u2, &handle2, stride2, 0);
+
+  // Second call to |alloc| with |w1| is set up to fail.
+  ret = buffer.Alloc(w1, h1, f1, u1);
+  EXPECT_LT(ret, 0);
+  TestIonBufferState(buffer, w2, h2, 1, f2, u2, &handle2, stride2, 0);
+
+  // |free| on |handle2| should be called here.
+  buffer.FreeHandle();
+  TestIonBufferState(buffer, 0, 0, 0, 0, 0, nullptr, 0, 0);
+
+  // |alloc| is set up to fail.
+  ret = buffer.Alloc(w2, h2, f2, u2);
+  EXPECT_LT(ret, 0);
+  TestIonBufferState(buffer, 0, 0, 0, 0, 0, nullptr, 0, 0);
+}
+
+TEST_F(IonBufferUnitTest, TestReset) {
+  IonBuffer buffer;
+  EXPECT_CALL(*GrallocMock::staticObject, free(&handle1))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+  EXPECT_CALL(*GrallocMock::staticObject, free(&handle2))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+  buffer.Reset(&handle1, w1, h1, stride1, f1, u1);
+  TestIonBufferState(buffer, w1, h1, 1, f1, u1, &handle1, stride1, 0);
+  buffer.Reset(&handle2, w2, h2, stride2, f2, u2);
+  TestIonBufferState(buffer, w2, h2, 1, f2, u2, &handle2, stride2, 0);
+  buffer.FreeHandle();
+}
+
+TEST_F(IonBufferUnitTest, TestImport1) {
+  IonBuffer buffer;
+  EXPECT_CALL(*GrallocMock::staticObject, registerBuffer(&handle1))
+      .Times(3)
+      .WillOnce(Return(0))
+      .WillRepeatedly(Return(-1));
+  EXPECT_CALL(*GrallocMock::staticObject, registerBuffer(&handle2))
+      .Times(3)
+      .WillOnce(Return(0))
+      .WillOnce(Return(-1))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*GrallocMock::staticObject, unregisterBuffer(&handle1))
+      .Times(1)
+      .WillOnce(Return(0));
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_close(&handle1))
+      .Times(1);
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_delete(&handle1))
+      .Times(1);
+  EXPECT_CALL(*GrallocMock::staticObject, alloc(w1, h1, f1, u1, _, _))
+      .Times(1)
+      .WillRepeatedly(DoAll(SetArgPointee<4>(&handle1),
+                            SetArgPointee<5>(stride1), Return(0)));
+  EXPECT_CALL(*GrallocMock::staticObject, unregisterBuffer(&handle2))
+      .Times(2)
+      .WillRepeatedly(Return(0));
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_close(&handle2))
+      .Times(2);
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_delete(&handle2))
+      .Times(2);
+  EXPECT_CALL(*GrallocMock::staticObject, free(&handle1))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+
+  int ret = buffer.Import(&handle1, w1, h1, stride1, f1, u1);
+  EXPECT_EQ(ret, 0);
+  TestIonBufferState(buffer, w1, h1, 1, f1, u1, &handle1, stride1, 0);
+  ret = buffer.Import(&handle2, w2, h2, stride2, f2, u2);
+  EXPECT_EQ(ret, 0);
+  TestIonBufferState(buffer, w2, h2, 1, f2, u2, &handle2, stride2, 0);
+  ret = buffer.Alloc(w1, h1, f1, u1);
+  EXPECT_EQ(ret, 0);
+  ret = buffer.Import(&handle2, w2, h2, stride2, f2, u2);
+  EXPECT_LT(ret, 0);
+  TestIonBufferState(buffer, w1, h1, 1, f1, u1, &handle1, stride1, 0);
+  ret = buffer.Import(&handle2, w2, h2, stride2, f2, u2);
+  EXPECT_EQ(ret, 0);
+  TestIonBufferState(buffer, w2, h2, 1, f2, u2, &handle2, stride2, 0);
+  ret = buffer.Import(&handle1, w1, h1, stride1, f1, u1);
+  EXPECT_LT(ret, 0);
+  TestIonBufferState(buffer, w2, h2, 1, f2, u2, &handle2, stride2, 0);
+  buffer.FreeHandle();
+  ret = buffer.Import(&handle1, w1, h1, stride1, f1, u1);
+  EXPECT_LT(ret, 0);
+  TestIonBufferState(buffer, 0, 0, 0, 0, 0, nullptr, 0, 0);
+}
+
+native_handle_t* native_handle_create_impl(int nFds, int nInts) {
+  if ((nFds + nInts) > (kMaxFd + kMaxInt))
+    return nullptr;
+  dataHandle->version = sizeof(native_handle_t);
+  dataHandle->numFds = nFds;
+  dataHandle->numInts = nInts;
+  for (int i = 0; i < nFds + nInts; i++)
+    dataHandle->data[i] = 0;
+  return dataHandle;
+}
+
+TEST_F(IonBufferUnitTest, TestImport2) {
+  IonBuffer buffer;
+  int ints[] = {211, 313, 444};
+  int fds[] = {-1, -1};
+  int ni = sizeof(ints) / sizeof(ints[0]);
+  int nfd = sizeof(fds) / sizeof(fds[0]);
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_create(nfd, ni))
+      .Times(3)
+      .WillOnce(Return(nullptr))
+      .WillRepeatedly(Invoke(native_handle_create_impl));
+  EXPECT_CALL(*GrallocMock::staticObject, registerBuffer(dataHandle))
+      .Times(2)
+      .WillOnce(Return(-1))
+      .WillRepeatedly(Return(0));
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_close(dataHandle))
+      .Times(2);
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_delete(dataHandle))
+      .Times(2);
+  EXPECT_CALL(*GrallocMock::staticObject, unregisterBuffer(dataHandle))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+
+  int ret = buffer.Import(fds, -1, ints, ni, w1, h1, stride1, f1, u1);
+  EXPECT_LT(ret, 0);
+  ret = buffer.Import(fds, nfd, ints, -1, w1, h1, stride1, f1, u1);
+  EXPECT_LT(ret, 0);
+  ret = buffer.Import(fds, nfd, ints, ni, w1, h1, stride1, f1, u1);
+  EXPECT_LT(ret, 0);
+  ret = buffer.Import(fds, nfd, ints, ni, w1, h1, stride1, f1, u1);
+  EXPECT_LT(ret, 0);
+  ret = buffer.Import(fds, nfd, ints, ni, w1, h1, stride1, f1, u1);
+  EXPECT_EQ(ret, 0);
+  TestIonBufferState(buffer, w1, h1, 1, f1, u1, dataHandle, stride1, 0);
+  EXPECT_EQ(dataHandle->numFds, nfd);
+  EXPECT_EQ(dataHandle->numInts, ni);
+  for (int i = 0; i < nfd; i++)
+    EXPECT_EQ(dataHandle->data[i], fds[i]);
+  for (int i = 0; i < ni; i++)
+    EXPECT_EQ(dataHandle->data[nfd + i], ints[i]);
+  buffer.FreeHandle();
+}
+
+TEST_F(IonBufferUnitTest, TestDuplicate) {
+  IonBuffer buffer;
+  IonBuffer ref;
+  int ints[] = {211, 313, 444};
+  int fds[] = {-1, -1};
+  int ni = sizeof(ints) / sizeof(ints[0]);
+  int nfd = sizeof(fds) / sizeof(fds[0]);
+
+  for (int i = 0; i < nfd; i++) {
+    refHandle->data[i] = fds[i];
+  }
+  for (int i = 0; i < ni; i++) {
+    refHandle->data[i + nfd] = ints[i];
+  }
+
+  EXPECT_CALL(*GrallocMock::staticObject, alloc(w1, h1, f1, u1, _, _))
+      .Times(1)
+      .WillRepeatedly(DoAll(SetArgPointee<4>(refHandle),
+                            SetArgPointee<5>(stride1), Return(0)));
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_create(nfd, ni))
+      .Times(3)
+      .WillOnce(Return(nullptr))
+      .WillRepeatedly(Invoke(native_handle_create_impl));
+  EXPECT_CALL(*GrallocMock::staticObject, registerBuffer(dataHandle))
+      .Times(2)
+      .WillOnce(Return(-1))
+      .WillRepeatedly(Return(0));
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_close(dataHandle))
+      .Times(2);
+  EXPECT_CALL(*GrallocMock::staticObject, native_handle_delete(dataHandle))
+      .Times(2);
+  EXPECT_CALL(*GrallocMock::staticObject, unregisterBuffer(dataHandle))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+  EXPECT_CALL(*GrallocMock::staticObject, free(refHandle))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+
+  int ret = buffer.Duplicate(&ref);
+  EXPECT_LT(ret, 0);
+  ret = ref.Alloc(w1, h1, f1, u1);
+  EXPECT_EQ(ret, 0);
+  refHandle->numFds = -1;
+  refHandle->numInts = 0;
+  ret = buffer.Duplicate(&ref);
+  EXPECT_LT(ret, 0);
+  refHandle->numFds = nfd;
+  refHandle->numInts = ni;
+  ret = buffer.Duplicate(&ref);
+  EXPECT_LT(ret, 0);
+  ret = buffer.Duplicate(&ref);
+  EXPECT_LT(ret, 0);
+  ret = buffer.Duplicate(&ref);
+  EXPECT_EQ(ret, 0);
+  TestIonBufferState(buffer, w1, h1, 1, f1, u1, dataHandle, stride1, 0);
+  EXPECT_EQ(dataHandle->numFds, nfd);
+  EXPECT_EQ(dataHandle->numInts, ni);
+  for (int i = 0; i < nfd; i++)
+    EXPECT_LT(dataHandle->data[i], 0);
+  for (int i = 0; i < ni; i++)
+    EXPECT_EQ(dataHandle->data[nfd + i], ints[i]);
+  buffer.FreeHandle();
+  ref.FreeHandle();
+}
+
+TEST_F(IonBufferUnitTest, TestLockUnlock) {
+  IonBuffer buffer;
+  const int x = 12;
+  const int y = 24;
+  const int value1 = 17;
+  const int value2 = 25;
+  void* addr1;
+  void** addr = &addr1;
+
+  EXPECT_CALL(*GrallocMock::staticObject, alloc(w1, h1, f1, u1, _, _))
+      .Times(1)
+      .WillRepeatedly(DoAll(SetArgPointee<4>(&handle1),
+                            SetArgPointee<5>(stride1), Return(0)));
+  EXPECT_CALL(*GrallocMock::staticObject,
+              lock(&handle1, u2, x, y, w2, h2, addr))
+      .Times(1)
+      .WillRepeatedly(Return(value1));
+  EXPECT_CALL(*GrallocMock::staticObject, unlock(&handle1))
+      .Times(1)
+      .WillRepeatedly(Return(value2));
+  EXPECT_CALL(*GrallocMock::staticObject, free(&handle1))
+      .Times(1)
+      .WillRepeatedly(Return(0));
+
+  int ret = buffer.Alloc(w1, h1, f1, u1);
+  EXPECT_EQ(ret, 0);
+  ret = buffer.Lock(u2, x, y, w2, h2, addr);
+  EXPECT_EQ(ret, value1);
+  ret = buffer.Unlock();
+  EXPECT_EQ(ret, value2);
+  buffer.FreeHandle();
+}
+
+}  // namespace
diff --git a/libs/vr/libbufferhubqueue/Android.mk b/libs/vr/libbufferhubqueue/Android.mk
new file mode 100644
index 0000000..46b83e7
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/Android.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	buffer_hub_queue_client.cpp \
+	buffer_hub_queue_core.cpp \
+	buffer_hub_queue_consumer.cpp \
+	buffer_hub_queue_producer.cpp \
+
+includeFiles := \
+	$(LOCAL_PATH)/include
+
+staticLibraries := \
+	libbufferhub \
+	libchrome \
+	libdvrcommon \
+	libpdx_default_transport \
+
+sharedLibraries := \
+	libbase \
+	libbinder \
+	libcutils \
+	libhardware \
+	liblog \
+	libui \
+	libutils \
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := libbufferhubqueue
+include $(BUILD_STATIC_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
new file mode 100644
index 0000000..4fbfcf6
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
@@ -0,0 +1,414 @@
+#include "include/private/dvr/buffer_hub_queue_client.h"
+
+#include <base/logging.h>
+#include <sys/epoll.h>
+
+#include <array>
+
+#include <pdx/default_transport/client_channel.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <pdx/file_handle.h>
+#include <private/dvr/bufferhub_rpc.h>
+
+using android::pdx::LocalHandle;
+using android::pdx::LocalChannelHandle;
+
+namespace android {
+namespace dvr {
+
+BufferHubQueue::BufferHubQueue(LocalChannelHandle channel_handle,
+                               size_t meta_size)
+    : Client{pdx::default_transport::ClientChannel::Create(
+          std::move(channel_handle))},
+      meta_size_(meta_size),
+      meta_buffer_tmp_(meta_size ? new uint8_t[meta_size] : nullptr),
+      buffers_(BufferHubQueue::kMaxQueueCapacity),
+      available_buffers_(BufferHubQueue::kMaxQueueCapacity),
+      capacity_(0) {
+  Initialize();
+}
+
+BufferHubQueue::BufferHubQueue(const std::string& endpoint_path,
+                               size_t meta_size)
+    : Client{pdx::default_transport::ClientChannelFactory::Create(
+          endpoint_path)},
+      meta_size_(meta_size),
+      meta_buffer_tmp_(meta_size ? new uint8_t[meta_size] : nullptr),
+      buffers_(BufferHubQueue::kMaxQueueCapacity),
+      available_buffers_(BufferHubQueue::kMaxQueueCapacity),
+      capacity_(0) {
+  Initialize();
+}
+
+void BufferHubQueue::Initialize() {
+  int ret = epoll_fd_.Create();
+  if (ret < 0) {
+    LOG(ERROR) << "BufferHubQueue::BufferHubQueue: Failed to create epoll fd:"
+               << strerror(-ret);
+    return;
+  }
+
+  epoll_event event = {.events = EPOLLIN | EPOLLET,
+                       .data = {.u64 = static_cast<uint64_t>(
+                                    BufferHubQueue::kEpollQueueEventIndex)}};
+  ret = epoll_fd_.Control(EPOLL_CTL_ADD, event_fd(), &event);
+  if (ret < 0) {
+    LOG(ERROR) << "Failed to register ConsumerQueue into epoll event: "
+               << strerror(-ret);
+  }
+}
+
+std::unique_ptr<ConsumerQueue> BufferHubQueue::CreateConsumerQueue() {
+  Status<std::pair<LocalChannelHandle, size_t>> status =
+      InvokeRemoteMethod<BufferHubRPC::CreateConsumerQueue>();
+
+  if (!status) {
+    LOG(ERROR) << "Cannot create ConsumerQueue: " << status.GetErrorMessage();
+    return nullptr;
+  }
+
+  auto return_value = status.take();
+
+  VLOG(1) << "CreateConsumerQueue: meta_size_bytes=" << return_value.second;
+  return ConsumerQueue::Create(std::move(return_value.first),
+                               return_value.second);
+}
+
+bool BufferHubQueue::WaitForBuffers(int timeout) {
+  std::array<epoll_event, kMaxEvents> events;
+
+  while (count() == 0) {
+    int ret = epoll_fd_.Wait(events.data(), events.size(), timeout);
+
+    if (ret == 0) {
+      VLOG(1) << "Wait on epoll returns nothing before timeout.";
+      return false;
+    }
+
+    if (ret < 0 && ret != -EINTR) {
+      LOG(ERROR) << "Failed to wait for buffers:" << strerror(-ret);
+      return false;
+    }
+
+    const int num_events = ret;
+
+    // A BufferQueue's epoll fd tracks N+1 events, where there are N events,
+    // one for each buffer, in the queue and one extra event for the queue
+    // client itself.
+    for (int i = 0; i < num_events; i++) {
+      int64_t index = static_cast<int64_t>(events[i].data.u64);
+
+      VLOG(1) << "New BufferHubQueue event " << i << ": index=" << index;
+
+      if (is_buffer_event_index(index) && (events[i].events & EPOLLIN)) {
+        auto buffer = buffers_[index];
+        ret = OnBufferReady(buffer);
+        if (ret < 0) {
+          LOG(ERROR) << "Failed to set buffer ready:" << strerror(-ret);
+          continue;
+        }
+        Enqueue(buffer, index);
+      } else if (is_buffer_event_index(index) &&
+                 (events[i].events & EPOLLHUP)) {
+        // This maybe caused by producer replacing an exising buffer slot.
+        // Currently the epoll FD is cleaned up when the replacement consumer
+        // client is imported.
+        LOG(WARNING) << "Receives EPOLLHUP at slot: " << index;
+      } else if (is_queue_event_index(index) && (events[i].events & EPOLLIN)) {
+        // Note that after buffer imports, if |count()| still returns 0, epoll
+        // wait will be tried again to acquire the newly imported buffer.
+        ret = OnBufferAllocated();
+        if (ret < 0) {
+          LOG(ERROR) << "Failed to import buffer:" << strerror(-ret);
+          continue;
+        }
+      } else {
+        LOG(WARNING) << "Unknown event " << i << ": u64=" << index
+                     << ": events=" << events[i].events;
+      }
+    }
+  }
+
+  return true;
+}
+
+int BufferHubQueue::AddBuffer(const std::shared_ptr<BufferHubBuffer>& buf,
+                              size_t slot) {
+  if (is_full()) {
+    // TODO(jwcai) Move the check into Producer's AllocateBuffer and consumer's
+    // import buffer.
+    LOG(ERROR) << "BufferHubQueue::AddBuffer queue is at maximum capacity: "
+               << capacity_;
+    return -E2BIG;
+  }
+
+  if (buffers_[slot] != nullptr) {
+    // Replace the buffer if the slot is preoccupied. This could happen when the
+    // producer side replaced the slot with a newly allocated buffer. Detach the
+    // buffer and set up with the new one.
+    DetachBuffer(slot);
+  }
+
+  epoll_event event = {.events = EPOLLIN | EPOLLET, .data = {.u64 = slot}};
+  const int ret = epoll_fd_.Control(EPOLL_CTL_ADD, buf->event_fd(), &event);
+  if (ret < 0) {
+    LOG(ERROR)
+        << "BufferHubQueue::AddBuffer: Failed to add buffer to epoll set:"
+        << strerror(-ret);
+    return ret;
+  }
+
+  buffers_[slot] = buf;
+  capacity_++;
+  return 0;
+}
+
+int BufferHubQueue::DetachBuffer(size_t slot) {
+  auto& buf = buffers_[slot];
+  if (buf == nullptr) {
+    LOG(ERROR) << "BufferHubQueue::DetachBuffer: Invalid slot: " << slot;
+    return -EINVAL;
+  }
+
+  const int ret = epoll_fd_.Control(EPOLL_CTL_DEL, buf->event_fd(), nullptr);
+  if (ret < 0) {
+    LOG(ERROR) << "BufferHubQueue::DetachBuffer: Failed to detach buffer from  "
+                  "epoll set:"
+               << strerror(-ret);
+    return ret;
+  }
+
+  buffers_[slot] = nullptr;
+  capacity_--;
+  return 0;
+}
+
+void BufferHubQueue::Enqueue(std::shared_ptr<BufferHubBuffer> buf,
+                             size_t slot) {
+  if (count() == capacity_) {
+    LOG(ERROR) << "Buffer queue is full!";
+    return;
+  }
+
+  // Set slot buffer back to vector.
+  // TODO(jwcai) Here have to dynamically allocate BufferInfo::metadata due to
+  // the limitation of the RingBuffer we are using. Would be better to refactor
+  // that.
+  BufferInfo buffer_info(slot, meta_size_);
+  // Swap buffer into vector.
+  std::swap(buffer_info.buffer, buf);
+  // Swap metadata loaded during onBufferReady into vector.
+  std::swap(buffer_info.metadata, meta_buffer_tmp_);
+
+  available_buffers_.Append(std::move(buffer_info));
+}
+
+std::shared_ptr<BufferHubBuffer> BufferHubQueue::Dequeue(int timeout,
+                                                         size_t* slot,
+                                                         void* meta) {
+  VLOG(1) << "Dequeue: count=" << count() << ", timeout=" << timeout;
+
+  if (count() == 0 && !WaitForBuffers(timeout))
+    return nullptr;
+
+  std::shared_ptr<BufferHubBuffer> buf;
+  BufferInfo& buffer_info = available_buffers_.Front();
+
+  // Report current pos as the output slot.
+  std::swap(buffer_info.slot, *slot);
+  // Swap buffer from vector to be returned later.
+  std::swap(buffer_info.buffer, buf);
+  // Swap metadata from vector into tmp so that we can write out to |meta|.
+  std::swap(buffer_info.metadata, meta_buffer_tmp_);
+
+  available_buffers_.PopFront();
+
+  if (!buf) {
+    LOG(ERROR) << "Dequeue: Buffer to be dequeued is nullptr";
+    return nullptr;
+  }
+
+  if (meta) {
+    std::copy(meta_buffer_tmp_.get(), meta_buffer_tmp_.get() + meta_size_,
+              reinterpret_cast<uint8_t*>(meta));
+  }
+
+  return buf;
+}
+
+ProducerQueue::ProducerQueue(size_t meta_size)
+    : ProducerQueue(meta_size, 0, 0, 0, 0) {}
+
+ProducerQueue::ProducerQueue(LocalChannelHandle handle, size_t meta_size)
+    : BASE(std::move(handle), meta_size) {}
+
+ProducerQueue::ProducerQueue(size_t meta_size, int usage_set_mask,
+                             int usage_clear_mask, int usage_deny_set_mask,
+                             int usage_deny_clear_mask)
+    : BASE(BufferHubRPC::kClientPath, meta_size) {
+  auto status = InvokeRemoteMethod<BufferHubRPC::CreateProducerQueue>(
+      meta_size_, usage_set_mask, usage_clear_mask, usage_deny_set_mask,
+      usage_deny_clear_mask);
+  if (!status) {
+    LOG(ERROR)
+        << "ProducerQueue::ProducerQueue: Failed to create producer queue: %s"
+        << status.GetErrorMessage();
+    Close(-status.error());
+    return;
+  }
+}
+
+int ProducerQueue::AllocateBuffer(int width, int height, int format, int usage,
+                                  size_t slice_count, size_t* out_slot) {
+  if (out_slot == nullptr) {
+    LOG(ERROR) << "Parameter out_slot cannot be null.";
+    return -EINVAL;
+  }
+
+  if (is_full()) {
+    LOG(ERROR) << "ProducerQueue::AllocateBuffer queue is at maximum capacity: "
+               << capacity();
+    return -E2BIG;
+  }
+
+  const size_t kBufferCount = 1U;
+
+  Status<std::vector<std::pair<LocalChannelHandle, size_t>>> status =
+      InvokeRemoteMethod<BufferHubRPC::ProducerQueueAllocateBuffers>(
+          width, height, format, usage, slice_count, kBufferCount);
+  if (!status) {
+    LOG(ERROR) << "ProducerQueue::AllocateBuffer failed to create producer "
+                  "buffer through BufferHub.";
+    return -status.error();
+  }
+
+  auto buffer_handle_slots = status.take();
+  CHECK_EQ(buffer_handle_slots.size(), kBufferCount)
+      << "BufferHubRPC::ProducerQueueAllocateBuffers should return one and "
+         "only one buffer handle.";
+
+  // We only allocate one buffer at a time.
+  auto& buffer_handle = buffer_handle_slots[0].first;
+  size_t buffer_slot = buffer_handle_slots[0].second;
+  VLOG(1) << "ProducerQueue::AllocateBuffer, new buffer, channel_handle: "
+          << buffer_handle.value();
+
+  *out_slot = buffer_slot;
+  return AddBuffer(BufferProducer::Import(std::move(buffer_handle)),
+                   buffer_slot);
+}
+
+int ProducerQueue::AddBuffer(const std::shared_ptr<BufferProducer>& buf,
+                             size_t slot) {
+  // For producer buffer, we need to enqueue the newly added buffer
+  // immediately. Producer queue starts with all buffers in available state.
+  const int ret = BufferHubQueue::AddBuffer(buf, slot);
+  if (ret < 0)
+    return ret;
+
+  Enqueue(buf, slot);
+  return 0;
+}
+
+int ProducerQueue::DetachBuffer(size_t slot) {
+  Status<int> status =
+      InvokeRemoteMethod<BufferHubRPC::ProducerQueueDetachBuffer>(slot);
+  if (!status) {
+    LOG(ERROR) << "ProducerQueue::DetachBuffer failed to detach producer "
+                  "buffer through BufferHub, error: "
+               << status.GetErrorMessage();
+    return -status.error();
+  }
+
+  return BufferHubQueue::DetachBuffer(slot);
+}
+
+std::shared_ptr<BufferProducer> ProducerQueue::Dequeue(int timeout,
+                                                       size_t* slot) {
+  auto buf = BufferHubQueue::Dequeue(timeout, slot, nullptr);
+  return std::static_pointer_cast<BufferProducer>(buf);
+}
+
+int ProducerQueue::OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) {
+  auto buffer = std::static_pointer_cast<BufferProducer>(buf);
+  return buffer->GainAsync();
+}
+
+ConsumerQueue::ConsumerQueue(LocalChannelHandle handle, size_t meta_size)
+    : BASE(std::move(handle), meta_size) {
+  // TODO(b/34387835) Import consumer queue in case the ProducerQueue we are
+  // based on was not empty.
+}
+
+int ConsumerQueue::ImportBuffers() {
+  Status<std::vector<std::pair<LocalChannelHandle, size_t>>> status =
+      InvokeRemoteMethod<BufferHubRPC::ConsumerQueueImportBuffers>();
+  if (!status) {
+    LOG(ERROR) << "ConsumerQueue::ImportBuffers failed to import consumer "
+                  "buffer through BufferBub, error: "
+               << status.GetErrorMessage();
+    return -status.error();
+  }
+
+  int last_error = 0;
+  int imported_buffers = 0;
+
+  auto buffer_handle_slots = status.take();
+  for (auto& buffer_handle_slot : buffer_handle_slots) {
+    VLOG(1) << "ConsumerQueue::ImportBuffers, new buffer, buffer_handle: "
+            << buffer_handle_slot.first.value();
+
+    std::unique_ptr<BufferConsumer> buffer_consumer =
+        BufferConsumer::Import(std::move(buffer_handle_slot.first));
+    int ret = AddBuffer(std::move(buffer_consumer), buffer_handle_slot.second);
+    if (ret < 0) {
+      LOG(ERROR) << "ConsumerQueue::ImportBuffers failed to add buffer, ret: "
+                 << strerror(-ret);
+      last_error = ret;
+      continue;
+    } else {
+      imported_buffers++;
+    }
+  }
+
+  return imported_buffers > 0 ? imported_buffers : last_error;
+}
+
+int ConsumerQueue::AddBuffer(const std::shared_ptr<BufferConsumer>& buf,
+                             size_t slot) {
+  // Consumer queue starts with all buffers in unavailable state.
+  return BufferHubQueue::AddBuffer(buf, slot);
+}
+
+std::shared_ptr<BufferConsumer> ConsumerQueue::Dequeue(int timeout,
+                                                       size_t* slot, void* meta,
+                                                       size_t meta_size) {
+  if (meta_size != meta_size_) {
+    LOG(ERROR) << "metadata size (" << meta_size
+               << ") for the dequeuing buffer does not match metadata size ("
+               << meta_size_ << ") for the queue.";
+    return nullptr;
+  }
+  auto buf = BufferHubQueue::Dequeue(timeout, slot, meta);
+  return std::static_pointer_cast<BufferConsumer>(buf);
+}
+
+int ConsumerQueue::OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) {
+  auto buffer = std::static_pointer_cast<BufferConsumer>(buf);
+  LocalHandle fence;
+  return buffer->Acquire(&fence, meta_buffer_tmp_.get(), meta_size_);
+}
+
+int ConsumerQueue::OnBufferAllocated() {
+  const int ret = ImportBuffers();
+  if (ret == 0) {
+    LOG(WARNING) << "No new buffer can be imported on buffer allocated event.";
+  } else if (ret < 0) {
+    LOG(ERROR) << "Failed to import buffers on buffer allocated event.";
+  }
+  VLOG(1) << "Imported " << ret << " consumer buffers.";
+  return ret;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_consumer.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_consumer.cpp
new file mode 100644
index 0000000..1ea3994
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_consumer.cpp
@@ -0,0 +1,11 @@
+#include "include/private/dvr/buffer_hub_queue_consumer.h"
+
+namespace android {
+namespace dvr {
+
+BufferHubQueueConsumer::BufferHubQueueConsumer(
+    const std::shared_ptr<BufferHubQueueCore>& core)
+    : core_(core) {}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_core.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_core.cpp
new file mode 100644
index 0000000..3fc0600
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_core.cpp
@@ -0,0 +1,93 @@
+#include "include/private/dvr/buffer_hub_queue_core.h"
+
+namespace android {
+namespace dvr {
+
+/* static */
+std::shared_ptr<BufferHubQueueCore> BufferHubQueueCore::Create() {
+  auto core = std::shared_ptr<BufferHubQueueCore>(new BufferHubQueueCore());
+  core->producer_ = ProducerQueue::Create<BufferMetadata>();
+  return core;
+}
+
+/* static */
+std::shared_ptr<BufferHubQueueCore> BufferHubQueueCore::Create(
+    const std::shared_ptr<ProducerQueue>& producer) {
+  if (producer->metadata_size() != sizeof(BufferMetadata)) {
+    LOG(ERROR)
+        << "BufferHubQueueCore::Create producer's metadata size is "
+        << "different than the size of BufferHubQueueCore::BufferMetadata";
+    return nullptr;
+  }
+
+  auto core = std::shared_ptr<BufferHubQueueCore>(new BufferHubQueueCore());
+  core->producer_ = producer;
+  return core;
+}
+
+BufferHubQueueCore::BufferHubQueueCore()
+    : generation_number_(0),
+      dequeue_timeout_ms_(BufferHubQueue::kNoTimeOut),
+      unique_id_(getUniqueId()) {}
+
+status_t BufferHubQueueCore::AllocateBuffer(uint32_t width, uint32_t height,
+                                            PixelFormat format, uint32_t usage,
+                                            size_t slice_count) {
+  size_t slot;
+
+  // Allocate new buffer through BufferHub and add it into |producer_| queue for
+  // bookkeeping.
+  if (producer_->AllocateBuffer(width, height, format, usage, slice_count,
+                                &slot) < 0) {
+    LOG(ERROR) << "Failed to allocate new buffer in BufferHub.";
+    return NO_MEMORY;
+  }
+
+  auto buffer_producer = producer_->GetBuffer(slot);
+
+  CHECK(buffer_producer != nullptr) << "Failed to get buffer producer at slot: "
+                                    << slot;
+
+  // Allocating a new buffer, |buffers_[slot]| should be in initial state.
+  CHECK(buffers_[slot].mGraphicBuffer == nullptr) << "AllocateBuffer: slot "
+                                                  << slot << " is not empty.";
+
+  // Create new GraphicBuffer based on the newly created |buffer_producer|. Here
+  // we have to cast |buffer_handle_t| to |native_handle_t|, it's OK because
+  // internally, GraphicBuffer is still an |ANativeWindowBuffer| and |handle|
+  // is still type of |buffer_handle_t| and bears const property.
+  sp<GraphicBuffer> graphic_buffer(new GraphicBuffer(
+      buffer_producer->width(), buffer_producer->height(),
+      buffer_producer->format(),
+      1, /* layer count */
+      buffer_producer->usage(),
+      buffer_producer->stride(),
+      const_cast<native_handle_t*>(buffer_producer->buffer()->handle()),
+      false));
+
+  CHECK_EQ(NO_ERROR, graphic_buffer->initCheck())
+      << "Failed to init GraphicBuffer.";
+  buffers_[slot].mBufferProducer = buffer_producer;
+  buffers_[slot].mGraphicBuffer = graphic_buffer;
+
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueCore::DetachBuffer(size_t slot) {
+  // Detach the buffer producer via BufferHubRPC.
+  int ret = producer_->DetachBuffer(slot);
+  if (ret < 0) {
+    LOG(ERROR) << "BufferHubQueueCore::DetachBuffer failed through RPC, ret="
+               << strerror(-ret);
+    return ret;
+  }
+
+  // Reset in memory objects related the the buffer.
+  buffers_[slot].mBufferProducer = nullptr;
+  buffers_[slot].mGraphicBuffer = nullptr;
+  buffers_[slot].mBufferState.detachProducer();
+  return NO_ERROR;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_producer.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_producer.cpp
new file mode 100644
index 0000000..93d7307
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_producer.cpp
@@ -0,0 +1,390 @@
+#include "include/private/dvr/buffer_hub_queue_producer.h"
+
+namespace android {
+namespace dvr {
+
+BufferHubQueueProducer::BufferHubQueueProducer(
+    const std::shared_ptr<BufferHubQueueCore>& core)
+    : core_(core), req_buffer_count_(kInvalidBufferCount) {}
+
+status_t BufferHubQueueProducer::requestBuffer(int slot,
+                                               sp<GraphicBuffer>* buf) {
+  VLOG(1) << "requestBuffer: slot=" << slot;;
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (slot < 0 || slot >= req_buffer_count_) {
+    LOG(ERROR) << "requestBuffer: slot index " << slot << " out of range [0, "
+               << req_buffer_count_ << ")";
+    return BAD_VALUE;
+  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
+    LOG(ERROR) << "requestBuffer: slot " << slot
+               << " is not owned by the producer (state = "
+               << core_->buffers_[slot].mBufferState.string() << " )";
+    return BAD_VALUE;
+  }
+
+  core_->buffers_[slot].mRequestBufferCalled = true;
+  *buf = core_->buffers_[slot].mGraphicBuffer;
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::setMaxDequeuedBufferCount(
+    int max_dequeued_buffers) {
+  VLOG(1) << "setMaxDequeuedBufferCount: max_dequeued_buffers="
+          << max_dequeued_buffers;
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (max_dequeued_buffers <= 0 ||
+      max_dequeued_buffers >
+          static_cast<int>(BufferHubQueue::kMaxQueueCapacity)) {
+    LOG(ERROR) << "setMaxDequeuedBufferCount: " << max_dequeued_buffers
+               << " out of range (0, " << BufferHubQueue::kMaxQueueCapacity
+               << "]";
+    return BAD_VALUE;
+  }
+
+  req_buffer_count_ = max_dequeued_buffers;
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::setAsyncMode(bool /* async */) {
+  LOG(ERROR) << "BufferHubQueueProducer::setAsyncMode not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::dequeueBuffer(int* out_slot,
+                                               sp<Fence>* out_fence,
+                                               uint32_t width, uint32_t height,
+                                               PixelFormat format,
+                                               uint32_t usage,
+                                               FrameEventHistoryDelta* /* outTimestamps */) {
+  VLOG(1) << "dequeueBuffer: w=" << width << ", h=" << height
+          << " format=" << format << ", usage=" << usage;
+
+  status_t ret;
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (static_cast<int32_t>(core_->producer_->capacity()) < req_buffer_count_) {
+    // Lazy allocation. When the capacity of |core_->producer_| has not reach
+    // |req_buffer_count_|, allocate new buffer.
+    // TODO(jwcai) To save memory, the really reasonable thing to do is to go
+    // over existing slots and find first existing one to dequeue.
+    ret = core_->AllocateBuffer(width, height, format, usage, 1);
+    if (ret < 0)
+      return ret;
+  }
+
+  size_t slot;
+  std::shared_ptr<BufferProducer> buffer_producer;
+
+  for (size_t retry = 0; retry < BufferHubQueue::kMaxQueueCapacity; retry++) {
+    buffer_producer =
+        core_->producer_->Dequeue(core_->dequeue_timeout_ms_, &slot);
+    if (!buffer_producer)
+      return NO_MEMORY;
+
+    if (static_cast<int>(width) == buffer_producer->width() &&
+        static_cast<int>(height) == buffer_producer->height() &&
+        static_cast<int>(format) == buffer_producer->format()) {
+      // The producer queue returns a buffer producer matches the request.
+      break;
+    }
+
+    // Needs reallocation.
+    // TODO(jwcai) Consider use VLOG instead if we find this log is not useful.
+    LOG(INFO) << "dequeueBuffer,: requested buffer (w=" << width
+              << ", h=" << height << ", format=" << format
+              << ") is different from the buffer returned at slot: " << slot
+              << " (w=" << buffer_producer->width()
+              << ", h=" << buffer_producer->height()
+              << ", format=" << buffer_producer->format()
+              << "). Need re-allocattion.";
+    // Mark the slot as reallocating, so that later we can set
+    // BUFFER_NEEDS_REALLOCATION when the buffer actually get dequeued.
+    core_->buffers_[slot].mIsReallocating = true;
+
+    // Detach the old buffer once the allocation before allocating its
+    // replacement.
+    core_->DetachBuffer(slot);
+
+    // Allocate a new producer buffer with new buffer configs. Note that if
+    // there are already multiple buffers in the queue, the next one returned
+    // from |core_->producer_->Dequeue| may not be the new buffer we just
+    // reallocated. Retry up to BufferHubQueue::kMaxQueueCapacity times.
+    ret = core_->AllocateBuffer(width, height, format, usage, 1);
+    if (ret < 0)
+      return ret;
+  }
+
+  // With the BufferHub backed solution. Buffer slot returned from
+  // |core_->producer_->Dequeue| is guaranteed to avaiable for producer's use.
+  // It's either in free state (if the buffer has never been used before) or
+  // in queued state (if the buffer has been dequeued and queued back to
+  // BufferHubQueue).
+  // TODO(jwcai) Clean this up, make mBufferState compatible with BufferHub's
+  // model.
+  CHECK(core_->buffers_[slot].mBufferState.isFree() ||
+        core_->buffers_[slot].mBufferState.isQueued())
+      << "dequeueBuffer: slot " << slot << " is not free or queued.";
+
+  core_->buffers_[slot].mBufferState.freeQueued();
+  core_->buffers_[slot].mBufferState.dequeue();
+  VLOG(1) << "dequeueBuffer: slot=" << slot;
+
+  // TODO(jwcai) Handle fence properly. |BufferHub| has full fence support, we
+  // just need to exopose that through |BufferHubQueue| once we need fence.
+  *out_fence = Fence::NO_FENCE;
+  *out_slot = slot;
+  ret = NO_ERROR;
+
+  if (core_->buffers_[slot].mIsReallocating) {
+    ret |= BUFFER_NEEDS_REALLOCATION;
+    core_->buffers_[slot].mIsReallocating = false;
+  }
+
+  return ret;
+}
+
+status_t BufferHubQueueProducer::detachBuffer(int /* slot */) {
+  LOG(ERROR) << "BufferHubQueueProducer::detachBuffer not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::detachNextBuffer(
+    sp<GraphicBuffer>* /* out_buffer */, sp<Fence>* /* out_fence */) {
+  LOG(ERROR) << "BufferHubQueueProducer::detachNextBuffer not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::attachBuffer(
+    int* /* out_slot */, const sp<GraphicBuffer>& /* buffer */) {
+  // With this BufferHub backed implementation, we assume (for now) all buffers
+  // are allocated and owned by the BufferHub. Thus the attempt of transfering
+  // ownership of a buffer to the buffer queue is intentionally unsupported.
+  LOG(FATAL) << "BufferHubQueueProducer::attachBuffer not supported.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::queueBuffer(int slot,
+                                             const QueueBufferInput& input,
+                                             QueueBufferOutput* /* output */) {
+  VLOG(1) << "queueBuffer: slot " << slot;
+
+  int64_t timestamp;
+  sp<Fence> fence;
+
+  // TODO(jwcai) The following attributes are ignored.
+  bool is_auto_timestamp;
+  android_dataspace data_space;
+  Rect crop(Rect::EMPTY_RECT);
+  int scaling_mode;
+  uint32_t transform;
+
+  input.deflate(&timestamp, &is_auto_timestamp, &data_space, &crop,
+                &scaling_mode, &transform, &fence);
+
+  if (fence == nullptr) {
+    LOG(ERROR) << "queueBuffer: fence is NULL";
+    return BAD_VALUE;
+  }
+
+  status_t ret;
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (slot < 0 || slot >= req_buffer_count_) {
+    LOG(ERROR) << "queueBuffer: slot index " << slot << " out of range [0, "
+               << req_buffer_count_ << ")";
+    return BAD_VALUE;
+  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
+    LOG(ERROR) << "queueBuffer: slot " << slot
+               << " is not owned by the producer (state = "
+               << core_->buffers_[slot].mBufferState.string() << " )";
+    return BAD_VALUE;
+  }
+
+  // Post the buffer producer with timestamp in the metadata.
+  auto buffer_producer = core_->buffers_[slot].mBufferProducer;
+  LocalHandle fence_fd(fence->isValid() ? fence->dup() : -1);
+
+  BufferHubQueueCore::BufferMetadata meta_data = {.timestamp = timestamp};
+  buffer_producer->Post(fence_fd, &meta_data, sizeof(meta_data));
+  core_->buffers_[slot].mBufferState.queue();
+
+  // TODO(jwcai) check how to fill in output properly.
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::cancelBuffer(int slot,
+                                              const sp<Fence>& fence) {
+  VLOG(1) << (__FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (slot < 0 || slot >= req_buffer_count_) {
+    LOG(ERROR) << "cancelBuffer: slot index " << slot << " out of range [0, "
+               << req_buffer_count_ << ")";
+    return BAD_VALUE;
+  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
+    LOG(ERROR) << "cancelBuffer: slot " << slot
+               << " is not owned by the producer (state = "
+               << core_->buffers_[slot].mBufferState.string() << " )";
+    return BAD_VALUE;
+  } else if (fence == NULL) {
+    LOG(ERROR) << "cancelBuffer: fence is NULL";
+    return BAD_VALUE;
+  }
+
+  auto buffer_producer = core_->buffers_[slot].mBufferProducer;
+  core_->producer_->Enqueue(buffer_producer, slot);
+  core_->buffers_[slot].mBufferState.cancel();
+  core_->buffers_[slot].mFence = fence;
+  VLOG(1) << "cancelBuffer: slot " << slot;
+
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::query(int what, int* out_value) {
+  VLOG(1) << (__FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+
+  if (out_value == NULL) {
+    LOG(ERROR) << "query: out_value was NULL";
+    return BAD_VALUE;
+  }
+
+  int value = 0;
+  switch (what) {
+    case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+      value = 0;
+      break;
+    case NATIVE_WINDOW_BUFFER_AGE:
+      value = 0;
+      break;
+    // The following queries are currently considered as unsupported.
+    // TODO(jwcai) Need to carefully check the whether they should be
+    // supported after all.
+    case NATIVE_WINDOW_WIDTH:
+    case NATIVE_WINDOW_HEIGHT:
+    case NATIVE_WINDOW_FORMAT:
+    case NATIVE_WINDOW_STICKY_TRANSFORM:
+    case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+    case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
+    case NATIVE_WINDOW_DEFAULT_DATASPACE:
+    default:
+      return BAD_VALUE;
+  }
+
+  VLOG(1) << "query: key=" << what << ", v=" << value;
+  *out_value = value;
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::connect(
+    const sp<IProducerListener>& /* listener */, int /* api */,
+    bool /* producer_controlled_by_app */, QueueBufferOutput* /* output */) {
+  // Consumer interaction are actually handled by buffer hub, and we need
+  // to maintain consumer operations here. Hence |connect| is a NO-OP.
+  VLOG(1) << (__FUNCTION__);
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::disconnect(int /* api */, DisconnectMode /* mode */) {
+  // Consumer interaction are actually handled by buffer hub, and we need
+  // to maintain consumer operations here. Hence |disconnect| is a NO-OP.
+  VLOG(1) << (__FUNCTION__);
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::setSidebandStream(
+    const sp<NativeHandle>& stream) {
+  if (stream != NULL) {
+    // TODO(jwcai) Investigate how is is used, maybe use BufferHubBuffer's
+    // metadata.
+    LOG(ERROR) << "SidebandStream is not currently supported.";
+    return INVALID_OPERATION;
+  }
+  return NO_ERROR;
+}
+
+void BufferHubQueueProducer::allocateBuffers(uint32_t /* width */,
+                                             uint32_t /* height */,
+                                             PixelFormat /* format */,
+                                             uint32_t /* usage */) {
+  // TODO(jwcai) |allocateBuffers| aims to preallocate up to the maximum number
+  // of buffers permitted by the current BufferQueue configuration (aka
+  // |req_buffer_count_|).
+  LOG(ERROR) << "BufferHubQueueProducer::allocateBuffers not implemented.";
+}
+
+status_t BufferHubQueueProducer::allowAllocation(bool /* allow */) {
+  LOG(ERROR) << "BufferHubQueueProducer::allowAllocation not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::setGenerationNumber(
+    uint32_t generation_number) {
+  VLOG(1) << (__FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+  core_->generation_number_ = generation_number;
+  return NO_ERROR;
+}
+
+String8 BufferHubQueueProducer::getConsumerName() const {
+  // BufferHub based implementation could have one to many producer/consumer
+  // relationship, thus |getConsumerName| from the producer side does not
+  // make any sense.
+  LOG(ERROR) << "BufferHubQueueProducer::getConsumerName not supported.";
+  return String8("BufferHubQueue::DummyConsumer");
+}
+
+status_t BufferHubQueueProducer::setSharedBufferMode(
+    bool /* shared_buffer_mode */) {
+  LOG(ERROR) << "BufferHubQueueProducer::setSharedBufferMode not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::setAutoRefresh(bool /* auto_refresh */) {
+  LOG(ERROR) << "BufferHubQueueProducer::setAutoRefresh not implemented.";
+  return INVALID_OPERATION;
+}
+
+status_t BufferHubQueueProducer::setDequeueTimeout(nsecs_t timeout) {
+  VLOG(1) << (__FUNCTION__);
+
+  std::unique_lock<std::mutex> lock(core_->mutex_);
+  core_->dequeue_timeout_ms_ = static_cast<int>(timeout / (1000 * 1000));
+  return NO_ERROR;
+}
+
+status_t BufferHubQueueProducer::getLastQueuedBuffer(
+    sp<GraphicBuffer>* /* out_buffer */, sp<Fence>* /* out_fence */,
+    float /*out_transform_matrix*/[16]) {
+  LOG(ERROR) << "BufferHubQueueProducer::getLastQueuedBuffer not implemented.";
+  return INVALID_OPERATION;
+}
+
+void BufferHubQueueProducer::getFrameTimestamps(
+    FrameEventHistoryDelta* /*outDelta*/) {
+  LOG(ERROR) << "BufferHubQueueProducer::getFrameTimestamps not implemented.";
+}
+
+status_t BufferHubQueueProducer::getUniqueId(uint64_t* out_id) const {
+  VLOG(1) << (__FUNCTION__);
+
+  *out_id = core_->unique_id_;
+  return NO_ERROR;
+}
+
+IBinder* BufferHubQueueProducer::onAsBinder() {
+  // BufferHubQueueProducer is a non-binder implementation of
+  // IGraphicBufferProducer.
+  LOG(WARNING) << "BufferHubQueueProducer::onAsBinder is not supported.";
+  return nullptr;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
new file mode 100644
index 0000000..83e77d4
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
@@ -0,0 +1,311 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
+#define ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
+
+#include <gui/BufferQueueDefs.h>
+
+#include <pdx/client.h>
+#include <private/dvr/buffer_hub_client.h>
+#include <private/dvr/epoll_file_descriptor.h>
+#include <private/dvr/ring_buffer.h>
+
+#include <memory>
+#include <vector>
+
+namespace android {
+namespace dvr {
+
+class ConsumerQueue;
+
+// |BufferHubQueue| manages a queue of |BufferHubBuffer|s. Buffers are
+// automatically re-requeued when released by the remote side.
+class BufferHubQueue : public pdx::Client {
+ public:
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+  template <typename T>
+  using Status = pdx::Status<T>;
+
+  virtual ~BufferHubQueue() {}
+  void Initialize();
+
+  // Create a new consumer queue that is attached to the producer. Returns
+  // a new consumer queue client or nullptr on failure.
+  std::unique_ptr<ConsumerQueue> CreateConsumerQueue();
+
+  // Return the number of buffers avaiable for dequeue.
+  size_t count() const { return available_buffers_.GetSize(); }
+
+  // Return the total number of buffers that the queue is tracking.
+  size_t capacity() const { return capacity_; }
+
+  // Return the size of metadata structure associated with this BufferBubQueue.
+  size_t metadata_size() const { return meta_size_; }
+
+  // Return whether the buffer queue is alrady full.
+  bool is_full() const { return  available_buffers_.IsFull(); }
+
+  explicit operator bool() const { return epoll_fd_.IsValid(); }
+
+  std::shared_ptr<BufferHubBuffer> GetBuffer(size_t slot) const {
+    return buffers_[slot];
+  }
+
+  // Enqueue a buffer marks buffer to be available (|Gain|'ed for producer
+  // and |Acquire|'ed for consumer. This is only used for internal bookkeeping.
+  void Enqueue(std::shared_ptr<BufferHubBuffer> buf, size_t slot);
+
+  // |BufferHubQueue| will keep track of at most this value of buffers.
+  static constexpr size_t kMaxQueueCapacity =
+      android::BufferQueueDefs::NUM_BUFFER_SLOTS;
+
+  // Special epoll data field indicating that the epoll event refers to the
+  // queue.
+  static constexpr int64_t kEpollQueueEventIndex = -1;
+
+  // When pass |kNoTimeout| to |Dequeue|, it will block indefinitely without a
+  // timeout.
+  static constexpr int kNoTimeOut = -1;
+
+ protected:
+  BufferHubQueue(LocalChannelHandle channel, size_t meta_size);
+  BufferHubQueue(const std::string& endpoint_path, size_t meta_size);
+
+  // Called by ProducerQueue::AddBuffer and ConsumerQueue::AddBuffer only. to
+  // register a buffer for epoll and internal bookkeeping.
+  int AddBuffer(const std::shared_ptr<BufferHubBuffer>& buf, size_t slot);
+
+  // Called by ProducerQueue::DetachBuffer and ConsumerQueue::DetachBuffer only.
+  // to deregister a buffer for epoll and internal bookkeeping.
+  virtual int DetachBuffer(size_t slot);
+
+  // Dequeue a buffer from the free queue, blocking until one is available. The
+  // timeout argument specifies the number of milliseconds that |Dequeue()| will
+  // block. Specifying a timeout of -1 causes |Dequeue()| to block indefinitely,
+  // while specifying a timeout equal to zero cause |Dequeue()| to return
+  // immediately, even if no buffers are available.
+  std::shared_ptr<BufferHubBuffer> Dequeue(int timeout, size_t* slot,
+                                           void* meta);
+
+  // Wait for buffers to be released and re-add them to the queue.
+  bool WaitForBuffers(int timeout);
+  virtual int OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) = 0;
+
+  // Called when a buffer is allocated remotely.
+  virtual int OnBufferAllocated() = 0;
+
+  // Data members to handle arbitrary metadata passed through BufferHub. It is
+  // fair to enforce that all buffers in the same queue share the same metadata
+  // type. |meta_size_| is used to store the size of metadata on queue creation;
+  // and |meta_buffer_tmp_| is allocated and resized to |meta_size_| on queue
+  // creation to be later used as temporary space so that we can avoid
+  // additional dynamic memory allocation in each |Enqueue| and |Dequeue| call.
+  size_t meta_size_;
+
+  // Here we intentionally choose |unique_ptr<uint8_t[]>| over vector<uint8_t>
+  // to disallow dynamic resizing for stability reasons.
+  std::unique_ptr<uint8_t[]> meta_buffer_tmp_;
+
+ private:
+  static constexpr size_t kMaxEvents = 128;
+
+  // The |u64| data field of an epoll event is interpreted as int64_t:
+  // When |index| >= 0 and |index| < kMaxQueueCapacity it refers to a specific
+  // element of |buffers_| as a direct index;
+  static bool is_buffer_event_index(int64_t index) {
+    return index >= 0 &&
+           index < static_cast<int64_t>(BufferHubQueue::kMaxQueueCapacity);
+  }
+
+  // When |index| == kEpollQueueEventIndex, it refers to the queue itself.
+  static bool is_queue_event_index(int64_t index) {
+    return index == BufferHubQueue::kEpollQueueEventIndex;
+  }
+
+  struct BufferInfo {
+    // A logical slot number that is assigned to a buffer at allocation time.
+    // The slot number remains unchanged during the entire life cycle of the
+    // buffer and should not be confused with the enqueue and dequeue order.
+    size_t slot;
+
+    // A BufferHubBuffer client.
+    std::shared_ptr<BufferHubBuffer> buffer;
+
+    // Metadata associated with the buffer.
+    std::unique_ptr<uint8_t[]> metadata;
+
+    BufferInfo() : BufferInfo(-1, 0) {}
+
+    BufferInfo(size_t slot, size_t metadata_size)
+        : slot(slot),
+          buffer(nullptr),
+          metadata(metadata_size ? new uint8_t[metadata_size] : nullptr) {}
+
+    BufferInfo(BufferInfo&& other)
+        : slot(other.slot),
+          buffer(std::move(other.buffer)),
+          metadata(std::move(other.metadata)) {}
+
+    BufferInfo& operator=(BufferInfo&& other) {
+      slot = other.slot;
+      buffer = std::move(other.buffer);
+      metadata = std::move(other.metadata);
+      return *this;
+    }
+
+   private:
+    BufferInfo(const BufferInfo&) = delete;
+    void operator=(BufferInfo&) = delete;
+  };
+
+  // Buffer queue:
+  // |buffers_| tracks all |BufferHubBuffer|s created by this |BufferHubQueue|.
+  std::vector<std::shared_ptr<BufferHubBuffer>> buffers_;
+
+  // |available_buffers_| uses |dvr::RingBuffer| to implementation queue
+  // sematics. When |Dequeue|, we pop the front element from
+  // |available_buffers_|, and  that buffer's reference count will decrease by
+  // one, while another reference in |buffers_| keeps the last reference to
+  // prevent the buffer from being deleted.
+  RingBuffer<BufferInfo> available_buffers_;
+
+  // Keep track with how many buffers have been added into the queue.
+  size_t capacity_;
+
+  // Epoll fd used to wait for BufferHub events.
+  EpollFileDescriptor epoll_fd_;
+
+  BufferHubQueue(const BufferHubQueue&) = delete;
+  void operator=(BufferHubQueue&) = delete;
+};
+
+class ProducerQueue : public pdx::ClientBase<ProducerQueue, BufferHubQueue> {
+ public:
+  template <typename Meta>
+  static std::unique_ptr<ProducerQueue> Create() {
+    return BASE::Create(sizeof(Meta));
+  }
+
+  // Usage bits in |usage_set_mask| will be automatically masked on. Usage bits
+  // in |usage_clear_mask| will be automatically masked off. Note that
+  // |usage_set_mask| and |usage_clear_mask| may conflict with each other, but
+  // |usage_set_mask| takes precedence over |usage_clear_mask|. All buffer
+  // allocation through this producer queue shall not have any of the usage bits
+  // in |usage_deny_set_mask| set. Allocation calls violating this will be
+  // rejected. All buffer allocation through this producer queue must have all
+  // the usage bits in |usage_deny_clear_mask| set. Allocation calls violating
+  // this will be rejected. Note that |usage_deny_set_mask| and
+  // |usage_deny_clear_mask| shall not conflict with each other. Such
+  // configuration will be treated as invalid input on creation.
+  template <typename Meta>
+  static std::unique_ptr<ProducerQueue> Create(int usage_set_mask,
+                                               int usage_clear_mask,
+                                               int usage_deny_set_mask,
+                                               int usage_deny_clear_mask) {
+    return BASE::Create(sizeof(Meta), usage_set_mask, usage_clear_mask,
+                        usage_deny_set_mask, usage_deny_clear_mask);
+  }
+
+  // Import a |ProducerQueue| from a channel handle.
+  template <typename Meta>
+  static std::unique_ptr<ProducerQueue> Import(LocalChannelHandle handle) {
+    return BASE::Create(std::move(handle), sizeof(Meta));
+  }
+
+  // Get a buffer producer. Note that the method doesn't check whether the
+  // buffer slot has a valid buffer that has been allocated already. When no
+  // buffer has been imported before it returns |nullptr|; otherwise it returns
+  // a shared pointer to a |BufferProducer|.
+  std::shared_ptr<BufferProducer> GetBuffer(size_t slot) const {
+    return std::static_pointer_cast<BufferProducer>(
+        BufferHubQueue::GetBuffer(slot));
+  }
+
+  // Allocate producer buffer to populate the queue. Once allocated, a producer
+  // buffer is automatically enqueue'd into the ProducerQueue and available to
+  // use (i.e. in |Gain|'ed mode).
+  // Returns Zero on success and negative error code when buffer allocation
+  // fails.
+  int AllocateBuffer(int width, int height, int format, int usage,
+                     size_t buffer_count, size_t* out_slot);
+
+  // Add a producer buffer to populate the queue. Once added, a producer buffer
+  // is available to use (i.e. in |Gain|'ed mode).
+  int AddBuffer(const std::shared_ptr<BufferProducer>& buf, size_t slot);
+
+  // Detach producer buffer from the queue.
+  // Returns Zero on success and negative error code when buffer detach
+  // fails.
+  int DetachBuffer(size_t slot) override;
+
+  // Dequeue a producer buffer to write. The returned buffer in |Gain|'ed mode,
+  // and caller should call Post() once it's done writing to release the buffer
+  // to the consumer side.
+  std::shared_ptr<BufferProducer> Dequeue(int timeout, size_t* slot);
+
+ private:
+  friend BASE;
+
+  // Constructors are automatically exposed through ProducerQueue::Create(...)
+  // static template methods inherited from ClientBase, which take the same
+  // arguments as the constructors.
+  explicit ProducerQueue(size_t meta_size);
+  ProducerQueue(LocalChannelHandle handle, size_t meta_size);
+  ProducerQueue(size_t meta_size, int usage_set_mask, int usage_clear_mask,
+                int usage_deny_set_mask, int usage_deny_clear_mask);
+
+  int OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) override;
+
+  // Producer buffer is always allocated from the client (i.e. local) side.
+  int OnBufferAllocated() override { return 0; }
+};
+
+class ConsumerQueue : public pdx::ClientBase<ConsumerQueue, BufferHubQueue> {
+ public:
+  // Get a buffer consumer. Note that the method doesn't check whether the
+  // buffer slot has a valid buffer that has been imported already. When no
+  // buffer has been imported before it returns |nullptr|; otherwise it returns
+  // a shared pointer to a |BufferConsumer|.
+  std::shared_ptr<BufferConsumer> GetBuffer(size_t slot) const {
+    return std::static_pointer_cast<BufferConsumer>(
+        BufferHubQueue::GetBuffer(slot));
+  }
+
+  // Import newly created buffers from the service side.
+  // Returns number of buffers successfully imported; or negative error code
+  // when buffer import fails.
+  int ImportBuffers();
+
+  // Dequeue a consumer buffer to read. The returned buffer in |Acquired|'ed
+  // mode, and caller should call Releasse() once it's done writing to release
+  // the buffer to the producer side. |meta| is passed along from BufferHub,
+  // The user of BufferProducer is responsible with making sure that the
+  // Dequeue() is done with the corect metadata type and size with those used
+  // when the buffer is orignally created.
+  template <typename Meta>
+  std::shared_ptr<BufferConsumer> Dequeue(int timeout, size_t* slot,
+                                          Meta* meta) {
+    return Dequeue(timeout, slot, meta, sizeof(*meta));
+  }
+
+  std::shared_ptr<BufferConsumer> Dequeue(int timeout, size_t* slot, void* meta,
+                                          size_t meta_size);
+
+ private:
+  friend BASE;
+
+  ConsumerQueue(LocalChannelHandle handle, size_t meta_size);
+
+  // Add a consumer buffer to populate the queue. Once added, a consumer buffer
+  // is NOT available to use until the producer side |Post| it. |WaitForBuffers|
+  // will catch the |Post| and |Acquire| the buffer to make it available for
+  // consumer.
+  int AddBuffer(const std::shared_ptr<BufferConsumer>& buf, size_t slot);
+
+  int OnBufferReady(std::shared_ptr<BufferHubBuffer> buf) override;
+
+  int OnBufferAllocated() override;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_consumer.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_consumer.h
new file mode 100644
index 0000000..8d7bfcc
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_consumer.h
@@ -0,0 +1,22 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_CONSUMER_H_
+#define ANDROID_DVR_BUFFER_HUB_QUEUE_CONSUMER_H_
+
+#include <private/dvr/buffer_hub_queue_core.h>
+
+#include <gui/IGraphicBufferConsumer.h>
+
+namespace android {
+namespace dvr {
+
+class BufferHubQueueConsumer : public IGraphicBufferConsumer {
+ public:
+  BufferHubQueueConsumer(const std::shared_ptr<BufferHubQueueCore>& core);
+
+ private:
+  std::shared_ptr<BufferHubQueueCore> core_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_CONSUMER_H_
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_core.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_core.h
new file mode 100644
index 0000000..ba0c0c5
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_core.h
@@ -0,0 +1,116 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_CORE_H_
+#define ANDROID_DVR_BUFFER_HUB_QUEUE_CORE_H_
+
+#include <private/dvr/buffer_hub_queue_client.h>
+
+#include <gui/BufferSlot.h>
+#include <utils/Atomic.h>
+#include <utils/String8.h>
+
+#include <mutex>
+
+namespace android {
+namespace dvr {
+
+class BufferHubQueueCore {
+ private:
+  friend class BufferHubQueueProducer;
+
+ public:
+  // Create a BufferHubQueueCore instance by creating a new producer queue.
+  static std::shared_ptr<BufferHubQueueCore> Create();
+
+  // Create a BufferHubQueueCore instance by importing an existing prodcuer queue.
+  static std::shared_ptr<BufferHubQueueCore> Create(
+      const std::shared_ptr<ProducerQueue>& producer);
+
+  struct BufferMetadata {
+    // Timestamp of the frame.
+    int64_t timestamp;
+  };
+
+  class NativeBuffer
+      : public ANativeObjectBase<ANativeWindowBuffer, NativeBuffer, RefBase> {
+   public:
+    explicit NativeBuffer(const std::shared_ptr<BufferHubBuffer>& buffer)
+        : buffer_(buffer) {
+      ANativeWindowBuffer::width = buffer_->width();
+      ANativeWindowBuffer::height = buffer_->height();
+      ANativeWindowBuffer::stride = buffer_->stride();
+      ANativeWindowBuffer::format = buffer_->format();
+      ANativeWindowBuffer::usage = buffer_->usage();
+      ANativeWindowBuffer::handle = buffer_->buffer()->handle();
+    }
+
+    std::shared_ptr<BufferHubBuffer> buffer() { return buffer_; }
+
+   private:
+    std::shared_ptr<BufferHubBuffer> buffer_;
+  };
+
+  // Get the unique buffer producer queue backing this BufferHubQueue.
+  std::shared_ptr<ProducerQueue> GetProducerQueue() { return producer_; }
+
+ private:
+  using LocalHandle = pdx::LocalHandle;
+
+  struct BufferHubSlot : public BufferSlot {
+    BufferHubSlot() : mBufferProducer(nullptr), mIsReallocating(false) {}
+    // BufferSlot comes from android framework, using m prefix to comply with
+    // the name convention with the reset of data fields from BufferSlot.
+    std::shared_ptr<BufferProducer> mBufferProducer;
+    bool mIsReallocating;
+  };
+
+  static String8 getUniqueName() {
+    static volatile int32_t counter = 0;
+    return String8::format("unnamed-%d-%d", getpid(),
+                           android_atomic_inc(&counter));
+  }
+
+  static uint64_t getUniqueId() {
+    static std::atomic<uint32_t> counter{0};
+    static uint64_t id = static_cast<uint64_t>(getpid()) << 32;
+    return id | counter++;
+  }
+
+  // Private constructor to force use of |Create|.
+  BufferHubQueueCore();
+
+  // Allocate a new buffer producer through BufferHub.
+  int AllocateBuffer(uint32_t width, uint32_t height, PixelFormat format,
+                     uint32_t usage, size_t slice_count);
+
+  // Detach a buffer producer through BufferHub.
+  int DetachBuffer(size_t slot);
+
+  // Mutex for thread safety.
+  std::mutex mutex_;
+
+  // |buffers_| stores the buffers that have been dequeued from
+  // |dvr::BufferHubQueue|, It is initialized to invalid buffers, and gets
+  // filled in with the result of |Dequeue|.
+  // TODO(jwcai) The buffer allocated to a slot will also be replaced if the
+  // requested buffer usage or geometry differs from that of the buffer
+  // allocated to a slot.
+  BufferHubSlot buffers_[BufferHubQueue::kMaxQueueCapacity];
+
+  // Concreate implementation backed by BufferHubBuffer.
+  std::shared_ptr<ProducerQueue> producer_;
+
+  // |generation_number_| stores the current generation number of the attached
+  // producer. Any attempt to attach a buffer with a different generation
+  // number will fail.
+  uint32_t generation_number_;
+
+  // Sets how long dequeueBuffer or attachBuffer will block if a buffer or
+  // slot is not yet available. The timeout is stored in milliseconds.
+  int dequeue_timeout_ms_;
+
+  const uint64_t unique_id_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_CORE_H_
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_producer.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_producer.h
new file mode 100644
index 0000000..5b1a7e0
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_producer.h
@@ -0,0 +1,118 @@
+#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_PRODUCER_H_
+#define ANDROID_DVR_BUFFER_HUB_QUEUE_PRODUCER_H_
+
+#include <private/dvr/buffer_hub_queue_core.h>
+
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+namespace dvr {
+
+class BufferHubQueueProducer : public IGraphicBufferProducer {
+ public:
+  BufferHubQueueProducer(const std::shared_ptr<BufferHubQueueCore>& core);
+
+  // See |IGraphicBufferProducer::requestBuffer|
+  status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) override;
+
+  // For the BufferHub based implementation. All buffers in the queue are
+  // allowed to be dequeued from the consumer side. It call always returns
+  // 0 for |NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS| query. Thus setting
+  // |max_dequeued_buffers| here can be considered the same as setting queue
+  // capacity.
+  //
+  // See |IGraphicBufferProducer::setMaxDequeuedBufferCount| for more info
+  status_t setMaxDequeuedBufferCount(int max_dequeued_buffers) override;
+
+  // See |IGraphicBufferProducer::setAsyncMode|
+  status_t setAsyncMode(bool async) override;
+
+  // See |IGraphicBufferProducer::dequeueBuffer|
+  status_t dequeueBuffer(int* out_slot, sp<Fence>* out_fence, uint32_t width,
+                         uint32_t height, PixelFormat format,
+                         uint32_t usage,
+                         FrameEventHistoryDelta* outTimestamps) override;
+
+  // See |IGraphicBufferProducer::detachBuffer|
+  status_t detachBuffer(int slot) override;
+
+  // See |IGraphicBufferProducer::detachNextBuffer|
+  status_t detachNextBuffer(sp<GraphicBuffer>* out_buffer,
+                            sp<Fence>* out_fence) override;
+
+  // See |IGraphicBufferProducer::attachBuffer|
+  status_t attachBuffer(int* out_slot, const sp<GraphicBuffer>& buffer) override;
+
+  // See |IGraphicBufferProducer::queueBuffer|
+  status_t queueBuffer(int slot, const QueueBufferInput& input,
+                       QueueBufferOutput* output) override;
+
+  // See |IGraphicBufferProducer::cancelBuffer|
+  status_t cancelBuffer(int slot, const sp<Fence>& fence) override;
+
+  // See |IGraphicBufferProducer::query|
+  status_t query(int what, int* out_value) override;
+
+  // See |IGraphicBufferProducer::connect|
+  status_t connect(const sp<IProducerListener>& listener, int api,
+                   bool producer_controlled_by_app,
+                   QueueBufferOutput* output) override;
+
+  // See |IGraphicBufferProducer::disconnect|
+  status_t disconnect(int api, DisconnectMode mode = DisconnectMode::Api) override;
+
+  // See |IGraphicBufferProducer::setSidebandStream|
+  status_t setSidebandStream(const sp<NativeHandle>& stream) override;
+
+  // See |IGraphicBufferProducer::allocateBuffers|
+  void allocateBuffers(uint32_t width, uint32_t height, PixelFormat format,
+                       uint32_t usage) override;
+
+  // See |IGraphicBufferProducer::allowAllocation|
+  status_t allowAllocation(bool allow) override;
+
+  // See |IGraphicBufferProducer::setGenerationNumber|
+  status_t setGenerationNumber(uint32_t generation_number) override;
+
+  // See |IGraphicBufferProducer::getConsumerName|
+  String8 getConsumerName() const override;
+
+  // See |IGraphicBufferProducer::setSharedBufferMode|
+  status_t setSharedBufferMode(bool shared_buffer_mode) override;
+
+  // See |IGraphicBufferProducer::setAutoRefresh|
+  status_t setAutoRefresh(bool auto_refresh) override;
+
+  // See |IGraphicBufferProducer::setDequeueTimeout|
+  status_t setDequeueTimeout(nsecs_t timeout) override;
+
+  // See |IGraphicBufferProducer::getLastQueuedBuffer|
+  status_t getLastQueuedBuffer(sp<GraphicBuffer>* out_buffer,
+                               sp<Fence>* out_fence,
+                               float out_transform_matrix[16]) override;
+
+  // See |IGraphicBufferProducer::getFrameTimestamps|
+  void getFrameTimestamps(FrameEventHistoryDelta* /*outDelta*/) override;
+
+  // See |IGraphicBufferProducer::getUniqueId|
+  status_t getUniqueId(uint64_t* out_id) const override;
+
+ protected:
+  IBinder* onAsBinder() override;
+
+ private:
+  using LocalHandle = pdx::LocalHandle;
+
+  static constexpr int kInvalidBufferCount = -1;
+
+  // |core_| holds the actually buffer slots.
+  std::shared_ptr<BufferHubQueueCore> core_;
+
+  // |req_buffer_count_| sets the capacity of the underlying buffer queue.
+  int32_t req_buffer_count_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_PRODUCER_H_
diff --git a/libs/vr/libbufferhubqueue/tests/Android.mk b/libs/vr/libbufferhubqueue/tests/Android.mk
new file mode 100644
index 0000000..59061e6
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/tests/Android.mk
@@ -0,0 +1,38 @@
+LOCAL_PATH := $(call my-dir)
+
+shared_libraries := \
+	libbase \
+	libbinder \
+	libcutils \
+	libgui \
+	liblog \
+	libhardware \
+	libui \
+	libutils \
+
+static_libraries := \
+	libbufferhubqueue \
+	libbufferhub \
+	libchrome \
+	libdvrcommon \
+	libpdx_default_transport \
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := buffer_hub_queue-test.cpp
+LOCAL_STATIC_LIBRARIES := $(static_libraries)
+LOCAL_SHARED_LIBRARIES := $(shared_libraries)
+LOCAL_EXPORT_C_INCLUDE_DIRS := ${LOCAL_C_INCLUDES}
+LOCAL_CFLAGS := -DTRACE=0 -O0 -g
+LOCAL_MODULE := buffer_hub_queue-test
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := buffer_hub_queue_producer-test.cpp
+LOCAL_STATIC_LIBRARIES := $(static_libraries)
+LOCAL_SHARED_LIBRARIES := $(shared_libraries)
+LOCAL_EXPORT_C_INCLUDE_DIRS := ${LOCAL_C_INCLUDES}
+LOCAL_CFLAGS := -DTRACE=0 -O0 -g
+LOCAL_MODULE := buffer_hub_queue_producer-test
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
new file mode 100644
index 0000000..841554e
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
@@ -0,0 +1,292 @@
+#include <base/logging.h>
+#include <private/dvr/buffer_hub_client.h>
+#include <private/dvr/buffer_hub_queue_client.h>
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+namespace android {
+namespace dvr {
+
+using pdx::LocalHandle;
+
+namespace {
+
+constexpr int kBufferWidth = 100;
+constexpr int kBufferHeight = 1;
+constexpr int kBufferFormat = HAL_PIXEL_FORMAT_BLOB;
+constexpr int kBufferUsage = GRALLOC_USAGE_SW_READ_RARELY;
+constexpr int kBufferSliceCount = 1;  // number of slices in each buffer
+
+class BufferHubQueueTest : public ::testing::Test {
+ public:
+  template <typename Meta>
+  void CreateQueues(int usage_set_mask = 0, int usage_clear_mask = 0,
+                    int usage_deny_set_mask = 0,
+                    int usage_deny_clear_mask = 0) {
+    producer_queue_ =
+        ProducerQueue::Create<Meta>(usage_set_mask, usage_clear_mask,
+                                    usage_deny_set_mask, usage_deny_clear_mask);
+    ASSERT_NE(nullptr, producer_queue_);
+
+    consumer_queue_ = producer_queue_->CreateConsumerQueue();
+    ASSERT_NE(nullptr, consumer_queue_);
+  }
+
+  void AllocateBuffer() {
+    // Create producer buffer.
+    size_t slot;
+    int ret = producer_queue_->AllocateBuffer(kBufferWidth, kBufferHeight,
+                                              kBufferFormat, kBufferUsage,
+                                              kBufferSliceCount, &slot);
+    ASSERT_EQ(ret, 0);
+  }
+
+ protected:
+  std::unique_ptr<ProducerQueue> producer_queue_;
+  std::unique_ptr<ConsumerQueue> consumer_queue_;
+};
+
+TEST_F(BufferHubQueueTest, TestDequeue) {
+  const size_t nb_dequeue_times = 16;
+
+  CreateQueues<size_t>();
+
+  // Allocate only one buffer.
+  AllocateBuffer();
+
+  // But dequeue multiple times.
+  for (size_t i = 0; i < nb_dequeue_times; i++) {
+    size_t slot;
+    auto p1 = producer_queue_->Dequeue(0, &slot);
+    ASSERT_NE(nullptr, p1);
+    size_t mi = i;
+    ASSERT_EQ(p1->Post(LocalHandle(), &mi, sizeof(mi)), 0);
+    size_t mo;
+    auto c1 = consumer_queue_->Dequeue(100, &slot, &mo);
+    ASSERT_NE(nullptr, c1);
+    ASSERT_EQ(mi, mo);
+    c1->Release(LocalHandle());
+  }
+}
+
+TEST_F(BufferHubQueueTest, TestProducerConsumer) {
+  const size_t nb_buffer = 16;
+  size_t slot;
+  uint64_t seq;
+
+  CreateQueues<uint64_t>();
+
+  for (size_t i = 0; i < nb_buffer; i++) {
+    AllocateBuffer();
+
+    // Producer queue has all the available buffers on initialize.
+    ASSERT_EQ(producer_queue_->count(), i + 1);
+    ASSERT_EQ(producer_queue_->capacity(), i + 1);
+
+    // Consumer queue has no avaiable buffer on initialize.
+    ASSERT_EQ(consumer_queue_->count(), 0U);
+    // Consumer queue does not import buffers until a dequeue is issued.
+    ASSERT_EQ(consumer_queue_->capacity(), i);
+    // Dequeue returns nullptr since no buffer is ready to consumer, but
+    // this implicitly triggers buffer import and bump up |capacity|.
+    auto consumer_null = consumer_queue_->Dequeue(0, &slot, &seq);
+    ASSERT_EQ(nullptr, consumer_null);
+    ASSERT_EQ(consumer_queue_->capacity(), i + 1);
+  }
+
+  for (size_t i = 0; i < nb_buffer; i++) {
+    // First time, there is no buffer available to dequeue.
+    auto buffer_null = consumer_queue_->Dequeue(0, &slot, &seq);
+    ASSERT_EQ(nullptr, buffer_null);
+
+    // Make sure Producer buffer is Post()'ed so that it's ready to Accquire
+    // in the consumer's Dequeue() function.
+    auto producer = producer_queue_->Dequeue(0, &slot);
+    ASSERT_NE(nullptr, producer);
+
+    uint64_t seq_in = static_cast<uint64_t>(i);
+    ASSERT_EQ(producer->Post({}, &seq_in, sizeof(seq_in)), 0);
+
+    // Second time, the just |Post()|'ed buffer should be dequeued.
+    uint64_t seq_out = 0;
+    auto consumer = consumer_queue_->Dequeue(0, &slot, &seq_out);
+    ASSERT_NE(nullptr, consumer);
+    ASSERT_EQ(seq_in, seq_out);
+  }
+}
+
+struct TestMetadata {
+  char a;
+  int32_t b;
+  int64_t c;
+};
+
+TEST_F(BufferHubQueueTest, TestMetadata) {
+  CreateQueues<TestMetadata>();
+  AllocateBuffer();
+
+  std::vector<TestMetadata> ms = {
+      {'0', 0, 0}, {'1', 10, 3333}, {'@', 123, 1000000000}};
+
+  for (auto mi : ms) {
+    size_t slot;
+    auto p1 = producer_queue_->Dequeue(0, &slot);
+    ASSERT_NE(nullptr, p1);
+    ASSERT_EQ(p1->Post(LocalHandle(-1), &mi, sizeof(mi)), 0);
+    TestMetadata mo;
+    auto c1 = consumer_queue_->Dequeue(0, &slot, &mo);
+    ASSERT_EQ(mi.a, mo.a);
+    ASSERT_EQ(mi.b, mo.b);
+    ASSERT_EQ(mi.c, mo.c);
+    c1->Release(LocalHandle(-1));
+  }
+}
+
+TEST_F(BufferHubQueueTest, TestMetadataMismatch) {
+  CreateQueues<int64_t>();
+  AllocateBuffer();
+
+  int64_t mi = 3;
+  size_t slot;
+  auto p1 = producer_queue_->Dequeue(0, &slot);
+  ASSERT_NE(nullptr, p1);
+  ASSERT_EQ(p1->Post(LocalHandle(-1), &mi, sizeof(mi)), 0);
+
+  int32_t mo;
+  // Acquire a buffer with mismatched metadata is not OK.
+  auto c1 = consumer_queue_->Dequeue(0, &slot, &mo);
+  ASSERT_EQ(nullptr, c1);
+}
+
+TEST_F(BufferHubQueueTest, TestEnqueue) {
+  CreateQueues<int64_t>();
+  AllocateBuffer();
+
+  size_t slot;
+  auto p1 = producer_queue_->Dequeue(0, &slot);
+  ASSERT_NE(nullptr, p1);
+
+  int64_t mo;
+  producer_queue_->Enqueue(p1, slot);
+  auto c1 = consumer_queue_->Dequeue(0, &slot, &mo);
+  ASSERT_EQ(nullptr, c1);
+}
+
+TEST_F(BufferHubQueueTest, TestAllocateBuffer) {
+  CreateQueues<int64_t>();
+
+  size_t s1;
+  AllocateBuffer();
+  auto p1 = producer_queue_->Dequeue(0, &s1);
+  ASSERT_NE(nullptr, p1);
+
+  // producer queue is exhausted
+  size_t s2;
+  auto p2_null = producer_queue_->Dequeue(0, &s2);
+  ASSERT_EQ(nullptr, p2_null);
+
+  // dynamically add buffer.
+  AllocateBuffer();
+  ASSERT_EQ(producer_queue_->count(), 1U);
+  ASSERT_EQ(producer_queue_->capacity(), 2U);
+
+  // now we can dequeue again
+  auto p2 = producer_queue_->Dequeue(0, &s2);
+  ASSERT_NE(nullptr, p2);
+  ASSERT_EQ(producer_queue_->count(), 0U);
+  // p1 and p2 should have different slot number
+  ASSERT_NE(s1, s2);
+
+  // Consumer queue does not import buffers until |Dequeue| or |ImportBuffers|
+  // are called. So far consumer_queue_ should be empty.
+  ASSERT_EQ(consumer_queue_->count(), 0U);
+
+  int64_t seq = 1;
+  ASSERT_EQ(p1->Post(LocalHandle(), seq), 0);
+  size_t cs1, cs2;
+  auto c1 = consumer_queue_->Dequeue(0, &cs1, &seq);
+  ASSERT_NE(nullptr, c1);
+  ASSERT_EQ(consumer_queue_->count(), 0U);
+  ASSERT_EQ(consumer_queue_->capacity(), 2U);
+  ASSERT_EQ(cs1, s1);
+
+  ASSERT_EQ(p2->Post(LocalHandle(), seq), 0);
+  auto c2 = consumer_queue_->Dequeue(0, &cs2, &seq);
+  ASSERT_NE(nullptr, c2);
+  ASSERT_EQ(cs2, s2);
+}
+
+TEST_F(BufferHubQueueTest, TestUsageSetMask) {
+  const int set_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
+  CreateQueues<int64_t>(set_mask, 0, 0, 0);
+
+  // When allocation, leave out |set_mask| from usage bits on purpose.
+  size_t slot;
+  int ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage & ~set_mask,
+      kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, 0);
+
+  auto p1 = producer_queue_->Dequeue(0, &slot);
+  ASSERT_EQ(p1->usage() & set_mask, set_mask);
+}
+
+TEST_F(BufferHubQueueTest, TestUsageClearMask) {
+  const int clear_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
+  CreateQueues<int64_t>(0, clear_mask, 0, 0);
+
+  // When allocation, add |clear_mask| into usage bits on purpose.
+  size_t slot;
+  int ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage | clear_mask,
+      kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, 0);
+
+  auto p1 = producer_queue_->Dequeue(0, &slot);
+  ASSERT_EQ(p1->usage() & clear_mask, 0);
+}
+
+TEST_F(BufferHubQueueTest, TestUsageDenySetMask) {
+  const int deny_set_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
+  CreateQueues<int64_t>(0, 0, deny_set_mask, 0);
+
+  // Now that |deny_set_mask| is illegal, allocation without those bits should
+  // be able to succeed.
+  size_t slot;
+  int ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage & ~deny_set_mask,
+      kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, 0);
+
+  // While allocation with those bits should fail.
+  ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage | deny_set_mask,
+      kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, -EINVAL);
+}
+
+TEST_F(BufferHubQueueTest, TestUsageDenyClearMask) {
+  const int deny_clear_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
+  CreateQueues<int64_t>(0, 0, 0, deny_clear_mask);
+
+  // Now that clearing |deny_clear_mask| is illegal (i.e. setting these bits are
+  // mandatory), allocation with those bits should be able to succeed.
+  size_t slot;
+  int ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat,
+      kBufferUsage | deny_clear_mask, kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, 0);
+
+  // While allocation without those bits should fail.
+  ret = producer_queue_->AllocateBuffer(
+      kBufferWidth, kBufferHeight, kBufferFormat,
+      kBufferUsage & ~deny_clear_mask, kBufferSliceCount, &slot);
+  ASSERT_EQ(ret, -EINVAL);
+}
+
+}  // namespace
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
new file mode 100644
index 0000000..5bb121a
--- /dev/null
+++ b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
@@ -0,0 +1,23 @@
+#include <private/dvr/buffer_hub_queue_producer.h>
+
+#include <base/logging.h>
+#include <gui/Surface.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace dvr {
+
+namespace {
+
+class BufferHubQueueProducerTest : public ::testing::Test {};
+
+TEST_F(BufferHubQueueProducerTest, TempTestBufferHubQueueProducer) {
+  auto core = BufferHubQueueCore::Create();
+  sp<BufferHubQueueProducer> producer = new BufferHubQueueProducer(core);
+  sp<Surface> surface = new Surface(producer, true);
+}
+
+}  // namespace
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/Android.mk b/libs/vr/libdisplay/Android.mk
new file mode 100644
index 0000000..670bdcd
--- /dev/null
+++ b/libs/vr/libdisplay/Android.mk
@@ -0,0 +1,96 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	native_window.cpp \
+	native_buffer_queue.cpp \
+	display_client.cpp \
+	display_manager_client.cpp \
+	display_manager_client_impl.cpp \
+	display_rpc.cpp \
+	dummy_native_window.cpp \
+	gl_fenced_flush.cpp \
+	graphics.cpp \
+	late_latch.cpp \
+	video_mesh_surface_client.cpp \
+	vsync_client.cpp \
+	vsync_client_api.cpp \
+	screenshot_client.cpp \
+	frame_history.cpp
+
+includeFiles := \
+	$(LOCAL_PATH)/include \
+	frameworks/native/vulkan/include
+
+sharedLibraries := \
+	libbase \
+	libcutils \
+	liblog \
+	libutils \
+	libEGL \
+	libGLESv2 \
+	libvulkan \
+	libui \
+	libgui \
+	libhardware \
+	libsync
+
+staticLibraries := \
+	libchrome \
+	libbufferhub \
+	libbufferhubqueue \
+	libdvrcommon \
+	libdvrgraphics \
+	libsensor \
+	libpdx_default_transport \
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+#LOCAL_CPPFLAGS := -UNDEBUG -DDEBUG -O0 -g
+LOCAL_CFLAGS += -DLOG_TAG=\"libdisplay\"
+LOCAL_CFLAGS += -DTRACE=0
+LOCAL_CFLAGS += -DATRACE_TAG=ATRACE_TAG_GRAPHICS
+LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_MODULE := libdisplay
+include $(BUILD_STATIC_LIBRARY)
+
+
+testFiles := \
+  tests/graphics_app_tests.cpp
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := graphics_app_tests
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+  $(testFiles) \
+
+LOCAL_C_INCLUDES := \
+  $(includeFiles) \
+
+LOCAL_SHARED_LIBRARIES := \
+  $(sharedLibraries) \
+
+LOCAL_STATIC_LIBRARIES := \
+  libdisplay \
+  $(staticLibraries) \
+
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/vr/libdisplay/display_client.cpp b/libs/vr/libdisplay/display_client.cpp
new file mode 100644
index 0000000..cfb346d
--- /dev/null
+++ b/libs/vr/libdisplay/display_client.cpp
@@ -0,0 +1,276 @@
+#include "include/private/dvr/display_client.h"
+
+#include <cutils/log.h>
+#include <cutils/native_handle.h>
+#include <pdx/default_transport/client_channel.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <pdx/status.h>
+
+#include <mutex>
+
+#include <private/dvr/display_rpc.h>
+#include <private/dvr/late_latch.h>
+#include <private/dvr/native_buffer.h>
+
+using android::pdx::LocalHandle;
+using android::pdx::LocalChannelHandle;
+using android::pdx::Status;
+using android::pdx::Transaction;
+using android::pdx::rpc::IfAnyOf;
+
+namespace android {
+namespace dvr {
+
+SurfaceClient::SurfaceClient(LocalChannelHandle channel_handle,
+                             SurfaceType type)
+    : Client{pdx::default_transport::ClientChannel::Create(
+          std::move(channel_handle))},
+      type_(type) {}
+
+SurfaceClient::SurfaceClient(const std::string& endpoint_path, SurfaceType type)
+    : Client{pdx::default_transport::ClientChannelFactory::Create(
+                 endpoint_path),
+             kInfiniteTimeout},
+      type_(type) {}
+
+int SurfaceClient::GetMetadataBufferFd(LocalHandle* out_fd) {
+  auto buffer_producer = GetMetadataBuffer();
+  if (!buffer_producer)
+    return -ENOMEM;
+
+  *out_fd = buffer_producer->GetBlobFd();
+  return 0;
+}
+
+std::shared_ptr<BufferProducer> SurfaceClient::GetMetadataBuffer() {
+  if (!metadata_buffer_) {
+    auto status = InvokeRemoteMethod<DisplayRPC::GetMetadataBuffer>();
+    if (!status) {
+      ALOGE(
+          "SurfaceClient::AllocateMetadataBuffer: Failed to allocate buffer: "
+          "%s",
+          status.GetErrorMessage().c_str());
+      return nullptr;
+    }
+
+    metadata_buffer_ = BufferProducer::Import(status.take());
+  }
+
+  return metadata_buffer_;
+}
+
+DisplaySurfaceClient::DisplaySurfaceClient(int width, int height, int format,
+                                           int usage, int flags)
+    : BASE(DisplayRPC::kClientPath, SurfaceTypeEnum::Normal),
+      width_(width),
+      height_(height),
+      format_(format),
+      usage_(usage),
+      flags_(flags),
+      z_order_(0),
+      visible_(true),
+      exclude_from_blur_(false),
+      blur_behind_(true),
+      mapped_metadata_buffer_(nullptr) {
+  auto status = InvokeRemoteMethod<DisplayRPC::CreateSurface>(
+      width, height, format, usage, flags);
+  if (!status) {
+    ALOGE(
+        "DisplaySurfaceClient::DisplaySurfaceClient: Failed to create display "
+        "surface: %s",
+        status.GetErrorMessage().c_str());
+    Close(status.error());
+  }
+}
+
+void DisplaySurfaceClient::SetVisible(bool visible) {
+  SetAttributes({{DisplaySurfaceAttributeEnum::Visible,
+                  DisplaySurfaceAttributeValue{visible}}});
+}
+
+void DisplaySurfaceClient::SetZOrder(int z_order) {
+  SetAttributes({{DisplaySurfaceAttributeEnum::ZOrder,
+                  DisplaySurfaceAttributeValue{z_order}}});
+}
+
+void DisplaySurfaceClient::SetExcludeFromBlur(bool exclude_from_blur) {
+  SetAttributes({{DisplaySurfaceAttributeEnum::ExcludeFromBlur,
+                  DisplaySurfaceAttributeValue{exclude_from_blur}}});
+}
+
+void DisplaySurfaceClient::SetBlurBehind(bool blur_behind) {
+  SetAttributes({{DisplaySurfaceAttributeEnum::BlurBehind,
+                  DisplaySurfaceAttributeValue{blur_behind}}});
+}
+
+void DisplaySurfaceClient::SetAttributes(
+    const DisplaySurfaceAttributes& attributes) {
+  Status<int> status =
+      InvokeRemoteMethod<DisplayRPC::SetAttributes>(attributes);
+  if (!status) {
+    ALOGE(
+        "DisplaySurfaceClient::SetAttributes: Failed to set display surface "
+        "attributes: %s",
+        status.GetErrorMessage().c_str());
+    return;
+  }
+
+  // Set the local cached copies of the attributes we care about from the full
+  // set of attributes sent to the display service.
+  for (const auto& attribute : attributes) {
+    const auto& key = attribute.first;
+    const auto* variant = &attribute.second;
+    bool invalid_value = false;
+    switch (key) {
+      case DisplaySurfaceAttributeEnum::Visible:
+        invalid_value =
+            !IfAnyOf<int32_t, int64_t, bool>::Get(variant, &visible_);
+        break;
+      case DisplaySurfaceAttributeEnum::ZOrder:
+        invalid_value = !IfAnyOf<int32_t>::Get(variant, &z_order_);
+        break;
+      case DisplaySurfaceAttributeEnum::ExcludeFromBlur:
+        invalid_value =
+            !IfAnyOf<int32_t, int64_t, bool>::Get(variant, &exclude_from_blur_);
+        break;
+      case DisplaySurfaceAttributeEnum::BlurBehind:
+        invalid_value =
+            !IfAnyOf<int32_t, int64_t, bool>::Get(variant, &blur_behind_);
+        break;
+    }
+
+    if (invalid_value) {
+      ALOGW(
+          "DisplaySurfaceClient::SetAttributes: Failed to set display "
+          "surface attribute '%s' because of incompatible type: %d",
+          DisplaySurfaceAttributeEnum::ToString(key).c_str(), variant->index());
+    }
+  }
+}
+
+std::shared_ptr<BufferProducer> DisplaySurfaceClient::AllocateBuffer(
+    uint32_t* buffer_index) {
+  auto status = InvokeRemoteMethod<DisplayRPC::AllocateBuffer>();
+  if (!status) {
+    ALOGE("DisplaySurfaceClient::AllocateBuffer: Failed to allocate buffer: %s",
+          status.GetErrorMessage().c_str());
+    return nullptr;
+  }
+
+  if (buffer_index)
+    *buffer_index = status.get().first;
+  return BufferProducer::Import(status.take().second);
+}
+
+volatile DisplaySurfaceMetadata* DisplaySurfaceClient::GetMetadataBufferPtr() {
+  if (!mapped_metadata_buffer_) {
+    if (auto buffer_producer = GetMetadataBuffer()) {
+      void* addr = nullptr;
+      const int ret = buffer_producer->GetBlobReadWritePointer(
+          sizeof(DisplaySurfaceMetadata), &addr);
+      if (ret < 0) {
+        ALOGE(
+            "DisplaySurfaceClient::GetMetadataBufferPtr: Failed to map surface "
+            "metadata: %s",
+            strerror(-ret));
+        return nullptr;
+      }
+      mapped_metadata_buffer_ = static_cast<DisplaySurfaceMetadata*>(addr);
+    }
+  }
+
+  return mapped_metadata_buffer_;
+}
+
+LocalChannelHandle DisplaySurfaceClient::CreateVideoMeshSurface() {
+  auto status = InvokeRemoteMethod<DisplayRPC::CreateVideoMeshSurface>();
+  if (!status) {
+    ALOGE(
+        "DisplaySurfaceClient::CreateVideoMeshSurface: Failed to create "
+        "video mesh surface: %s",
+        status.GetErrorMessage().c_str());
+  }
+  return status.take();
+}
+
+DisplayClient::DisplayClient(int* error)
+    : BASE(pdx::default_transport::ClientChannelFactory::Create(
+               DisplayRPC::kClientPath),
+           kInfiniteTimeout) {
+  if (error)
+    *error = Client::error();
+}
+
+int DisplayClient::GetDisplayMetrics(SystemDisplayMetrics* metrics) {
+  auto status = InvokeRemoteMethod<DisplayRPC::GetMetrics>();
+  if (!status) {
+    ALOGE("DisplayClient::GetDisplayMetrics: Failed to get metrics: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+
+  *metrics = status.get();
+  return 0;
+}
+
+pdx::Status<void> DisplayClient::SetViewerParams(const ViewerParams& viewer_params) {
+  auto status = InvokeRemoteMethod<DisplayRPC::SetViewerParams>(viewer_params);
+  if (!status) {
+    ALOGE("DisplayClient::SetViewerParams: Failed to set viewer params: %s",
+          status.GetErrorMessage().c_str());
+  }
+  return status;
+}
+
+int DisplayClient::GetLastFrameEdsTransform(LateLatchOutput* ll_out) {
+  auto status = InvokeRemoteMethod<DisplayRPC::GetEdsCapture>();
+  if (!status) {
+    ALOGE(
+        "DisplayClient::GetLastFrameLateLatch: Failed to get most recent late"
+        " latch: %s",
+        status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+
+  if (status.get().size() != sizeof(LateLatchOutput)) {
+    ALOGE(
+        "DisplayClient::GetLastFrameLateLatch: Error expected to receive %zu "
+        "bytes but received %zu",
+        sizeof(LateLatchOutput), status.get().size());
+    return -EIO;
+  }
+
+  *ll_out = *reinterpret_cast<const LateLatchOutput*>(status.get().data());
+  return 0;
+}
+
+int DisplayClient::EnterVrMode() {
+  auto status = InvokeRemoteMethod<DisplayRPC::EnterVrMode>();
+  if (!status) {
+    ALOGE(
+        "DisplayClient::EnterVrMode: Failed to set display service to Vr mode");
+    return -status.error();
+  }
+
+  return 0;
+}
+
+int DisplayClient::ExitVrMode() {
+  auto status = InvokeRemoteMethod<DisplayRPC::ExitVrMode>();
+  if (!status) {
+    ALOGE(
+        "DisplayClient::ExitVrMode: Failed to revert display service from Vr "
+        "mode");
+    return -status.error();
+  }
+
+  return 0;
+}
+
+std::unique_ptr<DisplaySurfaceClient> DisplayClient::CreateDisplaySurface(
+    int width, int height, int format, int usage, int flags) {
+  return DisplaySurfaceClient::Create(width, height, format, usage, flags);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/display_manager_client.cpp b/libs/vr/libdisplay/display_manager_client.cpp
new file mode 100644
index 0000000..f454b08
--- /dev/null
+++ b/libs/vr/libdisplay/display_manager_client.cpp
@@ -0,0 +1,109 @@
+#include "include/private/dvr/display_manager_client.h"
+
+#include <private/dvr/buffer_hub_client.h>
+#include <private/dvr/display_manager_client_impl.h>
+
+using android::dvr::DisplaySurfaceAttributeEnum;
+
+extern "C" {
+
+struct DvrDisplayManagerClient {
+  DvrDisplayManagerClient()
+      : client(android::dvr::DisplayManagerClient::Create()) {}
+  ~DvrDisplayManagerClient() {}
+
+  std::unique_ptr<android::dvr::DisplayManagerClient> client;
+};
+
+struct DvrDisplayManagerClientSurfaceList {
+  DvrDisplayManagerClientSurfaceList(
+      std::vector<android::dvr::DisplaySurfaceInfo> surface_list)
+      : list(std::move(surface_list)) {}
+  ~DvrDisplayManagerClientSurfaceList() {}
+
+  std::vector<android::dvr::DisplaySurfaceInfo> list;
+};
+
+struct DvrDisplayManagerClientSurfaceBuffers {
+  DvrDisplayManagerClientSurfaceBuffers(
+      std::vector<std::unique_ptr<android::dvr::BufferConsumer>> buffer_list)
+      : list(std::move(buffer_list)) {}
+  ~DvrDisplayManagerClientSurfaceBuffers() {}
+
+  std::vector<std::unique_ptr<android::dvr::BufferConsumer>> list;
+};
+
+DvrDisplayManagerClient* dvrDisplayManagerClientCreate() {
+  return new DvrDisplayManagerClient();
+}
+
+void dvrDisplayManagerClientDestroy(DvrDisplayManagerClient* client) {
+  delete client;
+}
+
+int dvrDisplayManagerClientGetSurfaceList(
+    DvrDisplayManagerClient* client,
+    DvrDisplayManagerClientSurfaceList** surface_list) {
+  std::vector<android::dvr::DisplaySurfaceInfo> list;
+  int ret = client->client->GetSurfaceList(&list);
+  if (ret < 0)
+    return ret;
+
+  *surface_list = new DvrDisplayManagerClientSurfaceList(std::move(list));
+  return ret;
+}
+
+void dvrDisplayManagerClientSurfaceListDestroy(
+    DvrDisplayManagerClientSurfaceList* surface_list) {
+  delete surface_list;
+}
+
+size_t dvrDisplayManagerClientSurfaceListGetSize(
+    DvrDisplayManagerClientSurfaceList* surface_list) {
+  return surface_list->list.size();
+}
+
+int dvrDisplayManagerClientSurfaceListGetSurfaceId(
+    DvrDisplayManagerClientSurfaceList* surface_list, size_t index) {
+  return surface_list->list[index].surface_id;
+}
+
+int dvrDisplayManagerClientSurfaceListGetClientZOrder(
+    DvrDisplayManagerClientSurfaceList* surface_list, size_t index) {
+  return surface_list->list[index].ClientZOrder();
+}
+
+bool dvrDisplayManagerClientSurfaceListGetClientIsVisible(
+    DvrDisplayManagerClientSurfaceList* surface_list, size_t index) {
+  return surface_list->list[index].IsClientVisible();
+}
+
+int dvrDisplayManagerClientGetSurfaceBuffers(
+    DvrDisplayManagerClient* client, int surface_id,
+    DvrDisplayManagerClientSurfaceBuffers** surface_buffers) {
+  std::vector<std::unique_ptr<android::dvr::BufferConsumer>> buffer_list;
+  int ret = client->client->GetSurfaceBuffers(surface_id, &buffer_list);
+  if (ret < 0)
+    return ret;
+
+  *surface_buffers =
+      new DvrDisplayManagerClientSurfaceBuffers(std::move(buffer_list));
+  return ret;
+}
+
+void dvrDisplayManagerClientSurfaceBuffersDestroy(
+    DvrDisplayManagerClientSurfaceBuffers* surface_buffers) {
+  delete surface_buffers;
+}
+
+size_t dvrDisplayManagerClientSurfaceBuffersGetSize(
+    DvrDisplayManagerClientSurfaceBuffers* surface_buffers) {
+  return surface_buffers->list.size();
+}
+
+int dvrDisplayManagerClientSurfaceBuffersGetFd(
+    DvrDisplayManagerClientSurfaceBuffers* surface_buffers, size_t index) {
+  return surface_buffers->list[index]->event_fd();
+}
+
+}  // extern "C"
diff --git a/libs/vr/libdisplay/display_manager_client_impl.cpp b/libs/vr/libdisplay/display_manager_client_impl.cpp
new file mode 100644
index 0000000..82198b9
--- /dev/null
+++ b/libs/vr/libdisplay/display_manager_client_impl.cpp
@@ -0,0 +1,57 @@
+#include "include/private/dvr/display_manager_client_impl.h"
+
+#include <pdx/default_transport/client_channel_factory.h>
+#include <private/dvr/buffer_hub_client.h>
+#include <private/dvr/display_rpc.h>
+#include <utils/Log.h>
+
+using android::pdx::LocalChannelHandle;
+using android::pdx::Transaction;
+
+namespace android {
+namespace dvr {
+
+DisplayManagerClient::DisplayManagerClient()
+    : BASE(pdx::default_transport::ClientChannelFactory::Create(
+          DisplayManagerRPC::kClientPath)) {}
+
+DisplayManagerClient::~DisplayManagerClient() {}
+
+int DisplayManagerClient::GetSurfaceList(
+    std::vector<DisplaySurfaceInfo>* surface_list) {
+  auto status = InvokeRemoteMethod<DisplayManagerRPC::GetSurfaceList>();
+  if (!status) {
+    ALOGE(
+        "DisplayManagerClient::GetSurfaceList: Failed to get surface info: %s",
+        status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+
+  *surface_list = status.take();
+  return 0;
+}
+
+int DisplayManagerClient::GetSurfaceBuffers(
+    int surface_id, std::vector<std::unique_ptr<BufferConsumer>>* consumers) {
+  auto status =
+      InvokeRemoteMethod<DisplayManagerRPC::GetSurfaceBuffers>(surface_id);
+  if (!status) {
+    ALOGE(
+        "DisplayManagerClient::GetSurfaceBuffers: Failed to get buffers for "
+        "surface_id=%d: %s",
+        surface_id, status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+
+  std::vector<std::unique_ptr<BufferConsumer>> consumer_buffers;
+  std::vector<LocalChannelHandle> channel_handles = status.take();
+  for (auto&& handle : channel_handles) {
+    consumer_buffers.push_back(BufferConsumer::Import(std::move(handle)));
+  }
+
+  *consumers = std::move(consumer_buffers);
+  return 0;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/display_rpc.cpp b/libs/vr/libdisplay/display_rpc.cpp
new file mode 100644
index 0000000..f5693bd
--- /dev/null
+++ b/libs/vr/libdisplay/display_rpc.cpp
@@ -0,0 +1,12 @@
+#include "include/private/dvr/display_rpc.h"
+
+namespace android {
+namespace dvr {
+
+constexpr char DisplayRPC::kClientPath[];
+constexpr char DisplayManagerRPC::kClientPath[];
+constexpr char DisplayScreenshotRPC::kClientPath[];
+constexpr char DisplayVSyncRPC::kClientPath[];
+
+} // namespace dvr
+} // namespace android
diff --git a/libs/vr/libdisplay/dummy_native_window.cpp b/libs/vr/libdisplay/dummy_native_window.cpp
new file mode 100644
index 0000000..5547f53
--- /dev/null
+++ b/libs/vr/libdisplay/dummy_native_window.cpp
@@ -0,0 +1,75 @@
+#include "include/private/dvr/dummy_native_window.h"
+
+#include <utils/Errors.h>
+
+namespace {
+// Dummy functions required for an ANativeWindow Implementation.
+int F1(struct ANativeWindow*, int) { return 0; }
+int F2(struct ANativeWindow*, struct ANativeWindowBuffer**) { return 0; }
+int F3(struct ANativeWindow*, struct ANativeWindowBuffer*) { return 0; }
+int F4(struct ANativeWindow*, struct ANativeWindowBuffer**, int*) { return 0; }
+int F5(struct ANativeWindow*, struct ANativeWindowBuffer*, int) { return 0; }
+}  // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+DummyNativeWindow::DummyNativeWindow() {
+  ANativeWindow::setSwapInterval = F1;
+  ANativeWindow::dequeueBuffer = F4;
+  ANativeWindow::cancelBuffer = F5;
+  ANativeWindow::queueBuffer = F5;
+  ANativeWindow::query = Query;
+  ANativeWindow::perform = Perform;
+
+  ANativeWindow::dequeueBuffer_DEPRECATED = F2;
+  ANativeWindow::cancelBuffer_DEPRECATED = F3;
+  ANativeWindow::lockBuffer_DEPRECATED = F3;
+  ANativeWindow::queueBuffer_DEPRECATED = F3;
+}
+
+int DummyNativeWindow::Query(const ANativeWindow*, int what, int* value) {
+  switch (what) {
+    case NATIVE_WINDOW_WIDTH:
+    case NATIVE_WINDOW_HEIGHT:
+    case NATIVE_WINDOW_FORMAT:
+    case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+    case NATIVE_WINDOW_CONCRETE_TYPE:
+    case NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER:
+    case NATIVE_WINDOW_DEFAULT_WIDTH:
+    case NATIVE_WINDOW_DEFAULT_HEIGHT:
+    case NATIVE_WINDOW_TRANSFORM_HINT:
+      *value = 0;
+      return NO_ERROR;
+  }
+
+  *value = 0;
+  return BAD_VALUE;
+}
+
+int DummyNativeWindow::Perform(ANativeWindow*, int operation, ...) {
+  switch (operation) {
+    case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS:
+    case NATIVE_WINDOW_SET_BUFFERS_FORMAT:
+    case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM:
+    case NATIVE_WINDOW_SET_USAGE:
+    case NATIVE_WINDOW_CONNECT:
+    case NATIVE_WINDOW_DISCONNECT:
+    case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY:
+    case NATIVE_WINDOW_API_CONNECT:
+    case NATIVE_WINDOW_API_DISCONNECT:
+    case NATIVE_WINDOW_SET_BUFFER_COUNT:
+    case NATIVE_WINDOW_SET_BUFFERS_DATASPACE:
+    case NATIVE_WINDOW_SET_SCALING_MODE:
+      return NO_ERROR;
+    case NATIVE_WINDOW_LOCK:
+    case NATIVE_WINDOW_UNLOCK_AND_POST:
+    case NATIVE_WINDOW_SET_CROP:
+    case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP:
+      return INVALID_OPERATION;
+  }
+  return NAME_NOT_FOUND;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/frame_history.cpp b/libs/vr/libdisplay/frame_history.cpp
new file mode 100644
index 0000000..67e4a09
--- /dev/null
+++ b/libs/vr/libdisplay/frame_history.cpp
@@ -0,0 +1,147 @@
+#include <private/dvr/frame_history.h>
+
+#include <cutils/log.h>
+#include <errno.h>
+#include <sync/sync.h>
+
+#include <pdx/file_handle.h>
+#include <private/dvr/clock_ns.h>
+#include <private/dvr/sync_util.h>
+
+using android::pdx::LocalHandle;
+
+constexpr int kNumFramesToUseForSchedulePrediction = 10;
+constexpr int kDefaultVsyncIntervalPrediction = 1;
+constexpr int kMaxVsyncIntervalPrediction = 4;
+constexpr int kDefaultPendingFrameBufferSize = 10;
+
+namespace android {
+namespace dvr {
+
+FrameHistory::PendingFrame::PendingFrame()
+    : start_ns(0), scheduled_vsync(0), scheduled_finish_ns(0) {}
+
+FrameHistory::PendingFrame::PendingFrame(int64_t start_ns,
+                                         uint32_t scheduled_vsync,
+                                         int64_t scheduled_finish_ns,
+                                         LocalHandle&& fence)
+    : start_ns(start_ns), scheduled_vsync(scheduled_vsync),
+      scheduled_finish_ns(scheduled_finish_ns), fence(std::move(fence)) {}
+
+FrameHistory::FrameHistory() : FrameHistory(kDefaultPendingFrameBufferSize) {}
+
+FrameHistory::FrameHistory(int pending_frame_buffer_size)
+    : pending_frames_(pending_frame_buffer_size),
+      finished_frames_(pending_frame_buffer_size),
+      frame_duration_history_(kNumFramesToUseForSchedulePrediction) {}
+
+void FrameHistory::Reset(int pending_frame_buffer_size) {
+  pending_frames_.Reset(pending_frame_buffer_size);
+  finished_frames_.Reset(pending_frame_buffer_size);
+  frame_duration_history_.Clear();
+}
+
+void FrameHistory::OnFrameStart(uint32_t scheduled_vsync,
+                                int64_t scheduled_finish_ns) {
+  if (!pending_frames_.IsEmpty() && !pending_frames_.Back().fence) {
+    // If we don't have a fence set for the previous frame it's because
+    // OnFrameStart() was called twice in a row with no OnFrameSubmit() call. In
+    // that case throw out the pending frame data for the last frame.
+    pending_frames_.PopBack();
+  }
+
+  if (pending_frames_.IsFull()) {
+    ALOGW("Pending frames buffer is full. Discarding pending frame data.");
+  }
+
+  pending_frames_.Append(PendingFrame(GetSystemClockNs(), scheduled_vsync,
+                                      scheduled_finish_ns, LocalHandle()));
+}
+
+void FrameHistory::OnFrameSubmit(LocalHandle&& fence) {
+  // Add the fence to the previous frame data in pending_frames so we can
+  // track when it finishes.
+  if (!pending_frames_.IsEmpty() && !pending_frames_.Back().fence) {
+    if (fence && pending_frames_.Back().scheduled_vsync != UINT32_MAX)
+      pending_frames_.Back().fence = std::move(fence);
+    else
+      pending_frames_.PopBack();
+  }
+}
+
+void FrameHistory::CheckForFinishedFrames() {
+  if (pending_frames_.IsEmpty())
+    return;
+
+  android::dvr::FenceInfoBuffer fence_info_buffer;
+  while (!pending_frames_.IsEmpty()) {
+    const auto& pending_frame = pending_frames_.Front();
+    if (!pending_frame.fence) {
+      // The frame hasn't been submitted yet, so there's nothing more to do
+      break;
+    }
+
+    int64_t fence_signaled_time = -1;
+    int fence = pending_frame.fence.Get();
+    int sync_result = sync_wait(fence, 0);
+    if (sync_result == 0) {
+      int fence_signaled_result =
+          GetFenceSignaledTimestamp(fence, &fence_info_buffer,
+                                    &fence_signaled_time);
+      if (fence_signaled_result < 0) {
+        ALOGE("Failed getting signaled timestamp from fence");
+      } else {
+        // The frame is finished. Record the duration and move the frame data
+        // from pending_frames_ to finished_frames_.
+        DvrFrameScheduleResult schedule_result = {};
+        schedule_result.vsync_count = pending_frame.scheduled_vsync;
+        schedule_result.scheduled_frame_finish_ns =
+            pending_frame.scheduled_finish_ns;
+        schedule_result.frame_finish_offset_ns =
+            fence_signaled_time - pending_frame.scheduled_finish_ns;
+        finished_frames_.Append(schedule_result);
+        frame_duration_history_.Append(
+            fence_signaled_time - pending_frame.start_ns);
+      }
+      pending_frames_.PopFront();
+    } else {
+      if (errno != ETIME) {
+        ALOGE("sync_wait on frame fence failed. fence=%d errno=%d (%s).",
+              fence, errno, strerror(errno));
+      }
+      break;
+    }
+  }
+}
+
+int FrameHistory::PredictNextFrameVsyncInterval(int64_t vsync_period_ns) const {
+  if (frame_duration_history_.IsEmpty())
+    return kDefaultVsyncIntervalPrediction;
+
+  double total = 0;
+  for (size_t i = 0; i < frame_duration_history_.GetSize(); ++i)
+    total += frame_duration_history_.Get(i);
+  double avg_duration = total / frame_duration_history_.GetSize();
+
+  return std::min(kMaxVsyncIntervalPrediction,
+                  static_cast<int>(avg_duration / vsync_period_ns) + 1);
+}
+
+int FrameHistory::GetPreviousFrameResults(DvrFrameScheduleResult* results,
+                                          int in_result_count) {
+  int out_result_count =
+      std::min(in_result_count, static_cast<int>(finished_frames_.GetSize()));
+  for (int i = 0; i < out_result_count; ++i) {
+    results[i] = finished_frames_.Get(0);
+    finished_frames_.PopFront();
+  }
+  return out_result_count;
+}
+
+uint32_t FrameHistory::GetCurrentFrameVsync() const {
+  return pending_frames_.IsEmpty() ?
+      UINT32_MAX : pending_frames_.Back().scheduled_vsync;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/gl_fenced_flush.cpp b/libs/vr/libdisplay/gl_fenced_flush.cpp
new file mode 100644
index 0000000..64b2e99
--- /dev/null
+++ b/libs/vr/libdisplay/gl_fenced_flush.cpp
@@ -0,0 +1,39 @@
+#include "include/private/dvr/gl_fenced_flush.h"
+
+#include <EGL/eglext.h>
+#include <GLES3/gl31.h>
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Trace.h>
+
+#include <base/logging.h>
+
+using android::pdx::LocalHandle;
+
+namespace android {
+namespace dvr {
+
+LocalHandle CreateGLSyncAndFlush(EGLDisplay display) {
+  ATRACE_NAME("CreateGLSyncAndFlush");
+
+  EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID,
+                      EGL_NO_NATIVE_FENCE_FD_ANDROID, EGL_NONE};
+  EGLSyncKHR sync_point =
+      eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
+  glFlush();
+  if (sync_point == EGL_NO_SYNC_KHR) {
+    LOG(ERROR) << "sync_point == EGL_NO_SYNC_KHR";
+    return LocalHandle();
+  }
+  EGLint fence_fd = eglDupNativeFenceFDANDROID(display, sync_point);
+  eglDestroySyncKHR(display, sync_point);
+
+  if (fence_fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
+    LOG(ERROR) << "fence_fd == EGL_NO_NATIVE_FENCE_FD_ANDROID";
+    return LocalHandle();
+  }
+  return LocalHandle(fence_fd);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/graphics.cpp b/libs/vr/libdisplay/graphics.cpp
new file mode 100644
index 0000000..d599616
--- /dev/null
+++ b/libs/vr/libdisplay/graphics.cpp
@@ -0,0 +1,1587 @@
+#include <dvr/graphics.h>
+
+#include <sys/timerfd.h>
+#include <array>
+#include <vector>
+
+#include <cutils/log.h>
+#include <utils/Trace.h>
+
+#ifndef VK_USE_PLATFORM_ANDROID_KHR
+#define VK_USE_PLATFORM_ANDROID_KHR 1
+#endif
+#include <vulkan/vulkan.h>
+
+#include <pdx/file_handle.h>
+#include <private/dvr/clock_ns.h>
+#include <private/dvr/debug.h>
+#include <private/dvr/display_types.h>
+#include <private/dvr/frame_history.h>
+#include <private/dvr/gl_fenced_flush.h>
+#include <private/dvr/graphics/vr_gl_extensions.h>
+#include <private/dvr/graphics_private.h>
+#include <private/dvr/late_latch.h>
+#include <private/dvr/native_buffer_queue.h>
+#include <private/dvr/sensor_constants.h>
+#include <private/dvr/video_mesh_surface_client.h>
+#include <private/dvr/vsync_client.h>
+
+#include <android/native_window.h>
+
+#ifndef EGL_CONTEXT_MAJOR_VERSION
+#define EGL_CONTEXT_MAJOR_VERSION 0x3098
+#define EGL_CONTEXT_MINOR_VERSION 0x30FB
+#endif
+
+using android::pdx::LocalHandle;
+using android::pdx::LocalChannelHandle;
+
+using android::dvr::DisplaySurfaceAttributeEnum;
+using android::dvr::DisplaySurfaceAttributeValue;
+
+namespace {
+
+constexpr int kDefaultDisplaySurfaceUsage =
+    GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
+constexpr int kDefaultDisplaySurfaceFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+// TODO(alexst): revisit this count when HW encode is available for casting.
+constexpr int kDefaultBufferCount = 4;
+
+// Use with dvrBeginRenderFrame to disable EDS for the current frame.
+constexpr float32x4_t DVR_POSE_NO_EDS = {10.0f, 0.0f, 0.0f, 0.0f};
+
+// Use with dvrBeginRenderFrame to indicate that GPU late-latching is being used
+// for determining the render pose.
+constexpr float32x4_t DVR_POSE_LATE_LATCH = {20.0f, 0.0f, 0.0f, 0.0f};
+
+#ifndef NDEBUG
+
+static const char* GetGlCallbackType(GLenum type) {
+  switch (type) {
+    case GL_DEBUG_TYPE_ERROR_KHR:
+      return "ERROR";
+    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR:
+      return "DEPRECATED_BEHAVIOR";
+    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR:
+      return "UNDEFINED_BEHAVIOR";
+    case GL_DEBUG_TYPE_PORTABILITY_KHR:
+      return "PORTABILITY";
+    case GL_DEBUG_TYPE_PERFORMANCE_KHR:
+      return "PERFORMANCE";
+    case GL_DEBUG_TYPE_OTHER_KHR:
+      return "OTHER";
+    default:
+      return "UNKNOWN";
+  }
+}
+
+static void on_gl_error(GLenum /*source*/, GLenum type, GLuint /*id*/,
+                        GLenum severity, GLsizei /*length*/,
+                        const char* message, const void* /*user_param*/) {
+  char msg[400];
+  snprintf(msg, sizeof(msg), "[" __FILE__ ":%u] GL %s: %s", __LINE__,
+           GetGlCallbackType(type), message);
+  switch (severity) {
+    case GL_DEBUG_SEVERITY_LOW_KHR:
+      ALOGI("%s", msg);
+      break;
+    case GL_DEBUG_SEVERITY_MEDIUM_KHR:
+      ALOGW("%s", msg);
+      break;
+    case GL_DEBUG_SEVERITY_HIGH_KHR:
+      ALOGE("%s", msg);
+      break;
+  }
+  fprintf(stderr, "%s\n", msg);
+}
+
+#endif
+
+int DvrToHalSurfaceFormat(int dvr_surface_format) {
+  switch (dvr_surface_format) {
+    case DVR_SURFACE_FORMAT_RGBA_8888:
+      return HAL_PIXEL_FORMAT_RGBA_8888;
+    case DVR_SURFACE_FORMAT_RGB_565:
+      return HAL_PIXEL_FORMAT_RGB_565;
+    default:
+      return HAL_PIXEL_FORMAT_RGBA_8888;
+  }
+}
+
+int SelectEGLConfig(EGLDisplay dpy, EGLint* attr, unsigned format,
+                    EGLConfig* config) {
+  std::array<EGLint, 4> desired_rgba;
+  switch (format) {
+    case HAL_PIXEL_FORMAT_RGBA_8888:
+    case HAL_PIXEL_FORMAT_BGRA_8888:
+      desired_rgba = {{8, 8, 8, 8}};
+      break;
+    case HAL_PIXEL_FORMAT_RGB_565:
+      desired_rgba = {{5, 6, 5, 0}};
+      break;
+    default:
+      ALOGE("Unsupported framebuffer pixel format %d", format);
+      return -1;
+  }
+
+  EGLint max_configs = 0;
+  if (eglGetConfigs(dpy, NULL, 0, &max_configs) == EGL_FALSE) {
+    ALOGE("No EGL configurations available?!");
+    return -1;
+  }
+
+  std::vector<EGLConfig> configs(max_configs);
+
+  EGLint num_configs;
+  if (eglChooseConfig(dpy, attr, &configs[0], max_configs, &num_configs) ==
+      EGL_FALSE) {
+    ALOGE("eglChooseConfig failed");
+    return -1;
+  }
+
+  std::array<EGLint, 4> config_rgba;
+  for (int i = 0; i < num_configs; i++) {
+    eglGetConfigAttrib(dpy, configs[i], EGL_RED_SIZE, &config_rgba[0]);
+    eglGetConfigAttrib(dpy, configs[i], EGL_GREEN_SIZE, &config_rgba[1]);
+    eglGetConfigAttrib(dpy, configs[i], EGL_BLUE_SIZE, &config_rgba[2]);
+    eglGetConfigAttrib(dpy, configs[i], EGL_ALPHA_SIZE, &config_rgba[3]);
+    if (config_rgba == desired_rgba) {
+      *config = configs[i];
+      return 0;
+    }
+  }
+
+  ALOGE("Cannot find a matching EGL config");
+  return -1;
+}
+
+void DestroyEglContext(EGLDisplay egl_display, EGLContext* egl_context) {
+  if (*egl_context != EGL_NO_CONTEXT) {
+    eglDestroyContext(egl_display, *egl_context);
+    *egl_context = EGL_NO_CONTEXT;
+  }
+}
+
+// Perform internal initialization. A GL context must be bound to the current
+// thread.
+// @param internally_created_context True if we created and own the GL context,
+//        false if it was supplied by the application.
+// @return 0 if init was successful, or a negative error code on failure.
+int InitGl(bool internally_created_context) {
+  EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+  if (egl_display == EGL_NO_DISPLAY) {
+    ALOGE("eglGetDisplay failed");
+    return -EINVAL;
+  }
+
+  EGLContext egl_context = eglGetCurrentContext();
+  if (egl_context == EGL_NO_CONTEXT) {
+    ALOGE("No GL context bound");
+    return -EINVAL;
+  }
+
+  glGetError();  // Clear the error state
+  GLint major_version, minor_version;
+  glGetIntegerv(GL_MAJOR_VERSION, &major_version);
+  glGetIntegerv(GL_MINOR_VERSION, &minor_version);
+  if (glGetError() != GL_NO_ERROR) {
+    // GL_MAJOR_VERSION and GL_MINOR_VERSION were added in GLES 3. If we get an
+    // error querying them it's almost certainly because it's GLES 1 or 2.
+    ALOGE("Error getting GL version. Must be GLES 3.2 or greater.");
+    return -EINVAL;
+  }
+
+  if (major_version < 3 || (major_version == 3 && minor_version < 2)) {
+    ALOGE("Invalid GL version: %d.%d. Must be GLES 3.2 or greater.",
+          major_version, minor_version);
+    return -EINVAL;
+  }
+
+#ifndef NDEBUG
+  if (internally_created_context) {
+    // Enable verbose GL debug output.
+    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
+    glDebugMessageCallbackKHR(on_gl_error, NULL);
+    GLuint unused_ids = 0;
+    glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0,
+                             &unused_ids, GL_TRUE);
+  }
+#else
+  (void)internally_created_context;
+#endif
+
+  load_gl_extensions();
+  return 0;
+}
+
+int CreateEglContext(EGLDisplay egl_display, DvrSurfaceParameter* parameters,
+                     EGLContext* egl_context) {
+  *egl_context = EGL_NO_CONTEXT;
+
+  EGLint major, minor;
+  if (!eglInitialize(egl_display, &major, &minor)) {
+    ALOGE("Failed to initialize EGL");
+    return -ENXIO;
+  }
+
+  ALOGI("EGL version: %d.%d\n", major, minor);
+
+  int buffer_format = kDefaultDisplaySurfaceFormat;
+
+  for (auto p = parameters; p && p->key != DVR_SURFACE_PARAMETER_NONE; ++p) {
+    switch (p->key) {
+      case DVR_SURFACE_PARAMETER_FORMAT_IN:
+        buffer_format = DvrToHalSurfaceFormat(p->value);
+        break;
+    }
+  }
+
+  EGLint config_attrs[] = {EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+                           EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE};
+  EGLConfig config = {0};
+
+  int ret = SelectEGLConfig(egl_display, config_attrs, buffer_format, &config);
+  if (ret < 0)
+    return ret;
+
+  ALOGI("EGL SelectEGLConfig ok.\n");
+
+  EGLint context_attrs[] = {EGL_CONTEXT_MAJOR_VERSION,
+                            3,
+                            EGL_CONTEXT_MINOR_VERSION,
+                            2,
+#ifndef NDEBUG
+                            EGL_CONTEXT_FLAGS_KHR,
+                            EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
+#endif
+                            EGL_NONE};
+
+  *egl_context =
+      eglCreateContext(egl_display, config, EGL_NO_CONTEXT, context_attrs);
+  if (*egl_context == EGL_NO_CONTEXT) {
+    ALOGE("eglCreateContext failed");
+    return -ENXIO;
+  }
+
+  ALOGI("eglCreateContext ok.\n");
+
+  if (!eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                      *egl_context)) {
+    ALOGE("eglMakeCurrent failed");
+    DestroyEglContext(egl_display, egl_context);
+    return -EINVAL;
+  }
+
+  return 0;
+}
+
+}  // anonymous namespace
+
+// TODO(hendrikw): When we remove the calls to this in native_window.cpp, move
+// this back into the anonymous namespace
+std::shared_ptr<android::dvr::DisplaySurfaceClient> CreateDisplaySurfaceClient(
+    struct DvrSurfaceParameter* parameters,
+    /*out*/ android::dvr::SystemDisplayMetrics* metrics) {
+  auto client = android::dvr::DisplayClient::Create();
+  if (!client) {
+    ALOGE("Failed to create display client!");
+    return nullptr;
+  }
+
+  const int ret = client->GetDisplayMetrics(metrics);
+  if (ret < 0) {
+    ALOGE("Failed to get display metrics: %s", strerror(-ret));
+    return nullptr;
+  }
+
+  // Parameters that may be modified by the parameters array. Some of these are
+  // here for future expansion.
+  int request_width = -1;
+  int request_height = -1;
+  int request_flags = 0;
+  bool disable_distortion = false;
+  bool disable_stabilization = false;
+  bool disable_cac = false;
+  bool request_visible = true;
+  bool vertical_flip = false;
+  int request_z_order = 0;
+  bool request_exclude_from_blur = false;
+  bool request_blur_behind = true;
+  int request_format = kDefaultDisplaySurfaceFormat;
+  int request_usage = kDefaultDisplaySurfaceUsage;
+  int geometry_type = DVR_SURFACE_GEOMETRY_SINGLE;
+
+  // Handle parameter inputs.
+  for (auto p = parameters; p && p->key != DVR_SURFACE_PARAMETER_NONE; ++p) {
+    switch (p->key) {
+      case DVR_SURFACE_PARAMETER_WIDTH_IN:
+        request_width = p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_HEIGHT_IN:
+        request_height = p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_DISABLE_DISTORTION_IN:
+        disable_distortion = !!p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_DISABLE_STABILIZATION_IN:
+        disable_stabilization = !!p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_DISABLE_CAC_IN:
+        disable_cac = !!p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_VISIBLE_IN:
+        request_visible = !!p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_Z_ORDER_IN:
+        request_z_order = p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_EXCLUDE_FROM_BLUR_IN:
+        request_exclude_from_blur = !!p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_BLUR_BEHIND_IN:
+        request_blur_behind = !!p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_VERTICAL_FLIP_IN:
+        vertical_flip = !!p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_GEOMETRY_IN:
+        geometry_type = p->value;
+        break;
+      case DVR_SURFACE_PARAMETER_FORMAT_IN:
+        request_format = DvrToHalSurfaceFormat(p->value);
+        break;
+      case DVR_SURFACE_PARAMETER_ENABLE_LATE_LATCH_IN:
+      case DVR_SURFACE_PARAMETER_CREATE_GL_CONTEXT_IN:
+      case DVR_SURFACE_PARAMETER_DISPLAY_WIDTH_OUT:
+      case DVR_SURFACE_PARAMETER_DISPLAY_HEIGHT_OUT:
+      case DVR_SURFACE_PARAMETER_SURFACE_WIDTH_OUT:
+      case DVR_SURFACE_PARAMETER_SURFACE_HEIGHT_OUT:
+      case DVR_SURFACE_PARAMETER_INTER_LENS_METERS_OUT:
+      case DVR_SURFACE_PARAMETER_LEFT_FOV_LRBT_OUT:
+      case DVR_SURFACE_PARAMETER_RIGHT_FOV_LRBT_OUT:
+      case DVR_SURFACE_PARAMETER_VSYNC_PERIOD_OUT:
+      case DVR_SURFACE_PARAMETER_SURFACE_TEXTURE_TARGET_TYPE_OUT:
+      case DVR_SURFACE_PARAMETER_SURFACE_TEXTURE_TARGET_ID_OUT:
+      case DVR_SURFACE_PARAMETER_GRAPHICS_API_IN:
+      case DVR_SURFACE_PARAMETER_VK_INSTANCE_IN:
+      case DVR_SURFACE_PARAMETER_VK_PHYSICAL_DEVICE_IN:
+      case DVR_SURFACE_PARAMETER_VK_DEVICE_IN:
+      case DVR_SURFACE_PARAMETER_VK_PRESENT_QUEUE_IN:
+      case DVR_SURFACE_PARAMETER_VK_PRESENT_QUEUE_FAMILY_IN:
+      case DVR_SURFACE_PARAMETER_VK_SWAPCHAIN_IMAGE_COUNT_OUT:
+      case DVR_SURFACE_PARAMETER_VK_SWAPCHAIN_IMAGE_FORMAT_OUT:
+        break;
+      default:
+        ALOGE("Invalid display surface parameter: key=%d value=%ld", p->key,
+              p->value);
+        return nullptr;
+    }
+  }
+
+  request_flags |= disable_distortion
+                       ? DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_DISTORTION
+                       : 0;
+  request_flags |=
+      disable_stabilization ? DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_EDS : 0;
+  request_flags |=
+      disable_cac ? DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_CAC : 0;
+  request_flags |= vertical_flip ? DVR_DISPLAY_SURFACE_FLAGS_VERTICAL_FLIP : 0;
+  request_flags |= (geometry_type == DVR_SURFACE_GEOMETRY_SEPARATE_2)
+                       ? DVR_DISPLAY_SURFACE_FLAGS_GEOMETRY_SEPARATE_2
+                       : 0;
+
+  if (request_width == -1) {
+    request_width = disable_distortion ? metrics->display_native_width
+                                       : metrics->distorted_width;
+    if (!disable_distortion &&
+        geometry_type == DVR_SURFACE_GEOMETRY_SEPARATE_2) {
+      // The metrics always return the single wide buffer resolution.
+      // When split between eyes, we need to halve the width of the surface.
+      request_width /= 2;
+    }
+  }
+  if (request_height == -1) {
+    request_height = disable_distortion ? metrics->display_native_height
+                                        : metrics->distorted_height;
+  }
+
+  std::shared_ptr<android::dvr::DisplaySurfaceClient> surface =
+      client->CreateDisplaySurface(request_width, request_height,
+                                   request_format, request_usage,
+                                   request_flags);
+  surface->SetAttributes(
+      {{DisplaySurfaceAttributeEnum::Visible,
+        DisplaySurfaceAttributeValue{request_visible}},
+       {DisplaySurfaceAttributeEnum::ZOrder,
+        DisplaySurfaceAttributeValue{request_z_order}},
+       {DisplaySurfaceAttributeEnum::ExcludeFromBlur,
+        DisplaySurfaceAttributeValue{request_exclude_from_blur}},
+       {DisplaySurfaceAttributeEnum::BlurBehind,
+        DisplaySurfaceAttributeValue{request_blur_behind}}});
+
+  // Handle parameter output requests down here so we can return surface info.
+  for (auto p = parameters; p && p->key != DVR_SURFACE_PARAMETER_NONE; ++p) {
+    switch (p->key) {
+      case DVR_SURFACE_PARAMETER_DISPLAY_WIDTH_OUT:
+        *static_cast<int32_t*>(p->value_out) = metrics->display_native_width;
+        break;
+      case DVR_SURFACE_PARAMETER_DISPLAY_HEIGHT_OUT:
+        *static_cast<int32_t*>(p->value_out) = metrics->display_native_height;
+        break;
+      case DVR_SURFACE_PARAMETER_SURFACE_WIDTH_OUT:
+        *static_cast<int32_t*>(p->value_out) = surface->width();
+        break;
+      case DVR_SURFACE_PARAMETER_SURFACE_HEIGHT_OUT:
+        *static_cast<int32_t*>(p->value_out) = surface->height();
+        break;
+      case DVR_SURFACE_PARAMETER_INTER_LENS_METERS_OUT:
+        *static_cast<float*>(p->value_out) = metrics->inter_lens_distance_m;
+        break;
+      case DVR_SURFACE_PARAMETER_LEFT_FOV_LRBT_OUT:
+        for (int i = 0; i < 4; ++i) {
+          float* float_values_out = static_cast<float*>(p->value_out);
+          float_values_out[i] = metrics->left_fov_lrbt[i];
+        }
+        break;
+      case DVR_SURFACE_PARAMETER_RIGHT_FOV_LRBT_OUT:
+        for (int i = 0; i < 4; ++i) {
+          float* float_values_out = static_cast<float*>(p->value_out);
+          float_values_out[i] = metrics->right_fov_lrbt[i];
+        }
+        break;
+      case DVR_SURFACE_PARAMETER_VSYNC_PERIOD_OUT:
+        *static_cast<uint64_t*>(p->value_out) = metrics->vsync_period_ns;
+        break;
+      default:
+        break;
+    }
+  }
+
+  return surface;
+}
+
+extern "C" int dvrGetNativeDisplayDimensions(int* native_width,
+                                             int* native_height) {
+  int error = 0;
+  auto client = android::dvr::DisplayClient::Create(&error);
+  if (!client) {
+    ALOGE("Failed to create display client!");
+    return error;
+  }
+
+  android::dvr::SystemDisplayMetrics metrics;
+  const int ret = client->GetDisplayMetrics(&metrics);
+
+  if (ret != 0) {
+    ALOGE("Failed to get display metrics!");
+    return ret;
+  }
+
+  *native_width = static_cast<int>(metrics.display_native_width);
+  *native_height = static_cast<int>(metrics.display_native_height);
+  return 0;
+}
+
+extern "C" int dvrGetDisplaySurfaceInfo(EGLNativeWindowType win, int* width,
+                                        int* height, int* format) {
+  ANativeWindow* nwin = reinterpret_cast<ANativeWindow*>(win);
+  int w, h, f;
+
+  nwin->query(nwin, NATIVE_WINDOW_DEFAULT_WIDTH, &w);
+  nwin->query(nwin, NATIVE_WINDOW_DEFAULT_HEIGHT, &h);
+  nwin->query(nwin, NATIVE_WINDOW_FORMAT, &f);
+
+  if (width)
+    *width = w;
+  if (height)
+    *height = h;
+  if (format)
+    *format = f;
+
+  return 0;
+}
+
+struct DvrGraphicsContext : public android::ANativeObjectBase<
+                                ANativeWindow, DvrGraphicsContext,
+                                android::LightRefBase<DvrGraphicsContext>> {
+ public:
+  DvrGraphicsContext();
+  ~DvrGraphicsContext();
+
+  int graphics_api;  // DVR_SURFACE_GRAPHICS_API_*
+
+  // GL specific members.
+  struct {
+    EGLDisplay egl_display;
+    EGLContext egl_context;
+    bool owns_egl_context;
+    GLuint texture_id[kSurfaceViewMaxCount];
+    int texture_count;
+    GLenum texture_target_type;
+  } gl;
+
+  // VK specific members
+  struct {
+    // These objects are passed in by the application, and are NOT owned
+    // by the context.
+    VkInstance instance;
+    VkPhysicalDevice physical_device;
+    VkDevice device;
+    VkQueue present_queue;
+    uint32_t present_queue_family;
+    const VkAllocationCallbacks* allocation_callbacks;
+    // These objects are owned by the context.
+    ANativeWindow* window;
+    VkSurfaceKHR surface;
+    VkSwapchainKHR swapchain;
+    std::vector<VkImage> swapchain_images;
+    std::vector<VkImageView> swapchain_image_views;
+  } vk;
+
+  // Display surface, metrics, and buffer management members.
+  std::shared_ptr<android::dvr::DisplaySurfaceClient> display_surface;
+  android::dvr::SystemDisplayMetrics display_metrics;
+  std::unique_ptr<android::dvr::NativeBufferQueue> buffer_queue;
+  android::dvr::NativeBufferProducer* current_buffer;
+  bool buffer_already_posted;
+
+  // Synchronization members.
+  std::unique_ptr<android::dvr::VSyncClient> vsync_client;
+  LocalHandle timerfd;
+
+  android::dvr::FrameHistory frame_history;
+
+  // Mapped surface metadata (ie: for pose delivery with presented frames).
+  volatile android::dvr::DisplaySurfaceMetadata* surface_metadata;
+
+  // LateLatch support.
+  std::unique_ptr<android::dvr::LateLatch> late_latch;
+
+  // Video mesh support.
+  std::vector<std::shared_ptr<android::dvr::VideoMeshSurfaceClient>>
+      video_mesh_surfaces;
+
+ private:
+  // ANativeWindow function implementations
+  std::mutex lock_;
+  int Post(android::dvr::NativeBufferProducer* buffer, int fence_fd);
+  static int SetSwapInterval(ANativeWindow* window, int interval);
+  static int DequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer,
+                           int* fence_fd);
+  static int QueueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer,
+                         int fence_fd);
+  static int CancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer,
+                          int fence_fd);
+  static int Query(const ANativeWindow* window, int what, int* value);
+  static int Perform(ANativeWindow* window, int operation, ...);
+  static int DequeueBuffer_DEPRECATED(ANativeWindow* window,
+                                      ANativeWindowBuffer** buffer);
+  static int CancelBuffer_DEPRECATED(ANativeWindow* window,
+                                     ANativeWindowBuffer* buffer);
+  static int QueueBuffer_DEPRECATED(ANativeWindow* window,
+                                    ANativeWindowBuffer* buffer);
+  static int LockBuffer_DEPRECATED(ANativeWindow* window,
+                                   ANativeWindowBuffer* buffer);
+
+  DISALLOW_COPY_AND_ASSIGN(DvrGraphicsContext);
+};
+
+DvrGraphicsContext::DvrGraphicsContext()
+    : graphics_api(DVR_GRAPHICS_API_GLES),
+      gl{},
+      vk{},
+      current_buffer(nullptr),
+      buffer_already_posted(false),
+      surface_metadata(nullptr) {
+  gl.egl_display = EGL_NO_DISPLAY;
+  gl.egl_context = EGL_NO_CONTEXT;
+  gl.owns_egl_context = true;
+  gl.texture_target_type = GL_TEXTURE_2D;
+
+  ANativeWindow::setSwapInterval = SetSwapInterval;
+  ANativeWindow::dequeueBuffer = DequeueBuffer;
+  ANativeWindow::cancelBuffer = CancelBuffer;
+  ANativeWindow::queueBuffer = QueueBuffer;
+  ANativeWindow::query = Query;
+  ANativeWindow::perform = Perform;
+
+  ANativeWindow::dequeueBuffer_DEPRECATED = DequeueBuffer_DEPRECATED;
+  ANativeWindow::cancelBuffer_DEPRECATED = CancelBuffer_DEPRECATED;
+  ANativeWindow::lockBuffer_DEPRECATED = LockBuffer_DEPRECATED;
+  ANativeWindow::queueBuffer_DEPRECATED = QueueBuffer_DEPRECATED;
+}
+
+DvrGraphicsContext::~DvrGraphicsContext() {
+  if (graphics_api == DVR_GRAPHICS_API_GLES) {
+    glDeleteTextures(gl.texture_count, gl.texture_id);
+    if (gl.owns_egl_context)
+      DestroyEglContext(gl.egl_display, &gl.egl_context);
+  } else if (graphics_api == DVR_GRAPHICS_API_VULKAN) {
+    if (vk.swapchain != VK_NULL_HANDLE) {
+      for (auto view : vk.swapchain_image_views) {
+        vkDestroyImageView(vk.device, view, vk.allocation_callbacks);
+      }
+      vkDestroySwapchainKHR(vk.device, vk.swapchain, vk.allocation_callbacks);
+      vkDestroySurfaceKHR(vk.instance, vk.surface, vk.allocation_callbacks);
+      delete vk.window;
+    }
+  }
+}
+
+int dvrGraphicsContextCreate(struct DvrSurfaceParameter* parameters,
+                             DvrGraphicsContext** return_graphics_context) {
+  std::unique_ptr<DvrGraphicsContext> context(new DvrGraphicsContext);
+
+  // See whether we're using GL or Vulkan
+  for (auto p = parameters; p && p->key != DVR_SURFACE_PARAMETER_NONE; ++p) {
+    switch (p->key) {
+      case DVR_SURFACE_PARAMETER_GRAPHICS_API_IN:
+        context->graphics_api = p->value;
+        break;
+    }
+  }
+
+  if (context->graphics_api == DVR_GRAPHICS_API_GLES) {
+    context->gl.egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    if (context->gl.egl_display == EGL_NO_DISPLAY) {
+      ALOGE("eglGetDisplay failed");
+      return -ENXIO;
+    }
+
+    // See if we should create a GL context
+    for (auto p = parameters; p && p->key != DVR_SURFACE_PARAMETER_NONE; ++p) {
+      switch (p->key) {
+        case DVR_SURFACE_PARAMETER_CREATE_GL_CONTEXT_IN:
+          context->gl.owns_egl_context = p->value != 0;
+          break;
+      }
+    }
+
+    if (context->gl.owns_egl_context) {
+      int ret = CreateEglContext(context->gl.egl_display, parameters,
+                                 &context->gl.egl_context);
+      if (ret < 0)
+        return ret;
+    } else {
+      context->gl.egl_context = eglGetCurrentContext();
+    }
+
+    int ret = InitGl(context->gl.owns_egl_context);
+    if (ret < 0)
+      return ret;
+  } else if (context->graphics_api == DVR_GRAPHICS_API_VULKAN) {
+    for (auto p = parameters; p && p->key != DVR_SURFACE_PARAMETER_NONE; ++p) {
+      switch (p->key) {
+        case DVR_SURFACE_PARAMETER_VK_INSTANCE_IN:
+          context->vk.instance = reinterpret_cast<VkInstance>(p->value);
+          break;
+        case DVR_SURFACE_PARAMETER_VK_PHYSICAL_DEVICE_IN:
+          context->vk.physical_device =
+              reinterpret_cast<VkPhysicalDevice>(p->value);
+          break;
+        case DVR_SURFACE_PARAMETER_VK_DEVICE_IN:
+          context->vk.device = reinterpret_cast<VkDevice>(p->value);
+          break;
+        case DVR_SURFACE_PARAMETER_VK_PRESENT_QUEUE_IN:
+          context->vk.present_queue = reinterpret_cast<VkQueue>(p->value);
+          break;
+        case DVR_SURFACE_PARAMETER_VK_PRESENT_QUEUE_FAMILY_IN:
+          context->vk.present_queue_family = static_cast<uint32_t>(p->value);
+          break;
+      }
+    }
+  } else {
+    ALOGE("Error: invalid graphics API type");
+    return -EINVAL;
+  }
+
+  context->display_surface =
+      CreateDisplaySurfaceClient(parameters, &context->display_metrics);
+  if (!context->display_surface) {
+    ALOGE("Error: failed to create display surface client");
+    return -ECOMM;
+  }
+
+  context->buffer_queue.reset(new android::dvr::NativeBufferQueue(
+      context->gl.egl_display, context->display_surface, kDefaultBufferCount));
+
+  // The way the call sequence works we need 1 more than the buffer queue
+  // capacity to store data for all pending frames
+  context->frame_history.Reset(context->buffer_queue->GetQueueCapacity() + 1);
+
+  context->vsync_client = android::dvr::VSyncClient::Create();
+  if (!context->vsync_client) {
+    ALOGE("Error: failed to create vsync client");
+    return -ECOMM;
+  }
+
+  context->timerfd.Reset(timerfd_create(CLOCK_MONOTONIC, 0));
+  if (!context->timerfd) {
+    ALOGE("Error: timerfd_create failed because: %s", strerror(errno));
+    return -EPERM;
+  }
+
+  context->surface_metadata = context->display_surface->GetMetadataBufferPtr();
+  if (!context->surface_metadata) {
+    ALOGE("Error: surface metadata allocation failed");
+    return -ENOMEM;
+  }
+
+  ALOGI("buffer: %d x %d\n", context->display_surface->width(),
+        context->display_surface->height());
+
+  if (context->graphics_api == DVR_GRAPHICS_API_GLES) {
+    context->gl.texture_count = (context->display_surface->flags() &
+                                 DVR_DISPLAY_SURFACE_FLAGS_GEOMETRY_SEPARATE_2)
+                                    ? 2
+                                    : 1;
+
+    // Create the GL textures.
+    glGenTextures(context->gl.texture_count, context->gl.texture_id);
+
+    // We must make sure that we have at least one buffer allocated at this time
+    // so that anyone who tries to bind an FBO to context->texture_id
+    // will not get an incomplete buffer.
+    context->current_buffer = context->buffer_queue->Dequeue();
+    CHECK(context->gl.texture_count ==
+          context->current_buffer->buffer()->slice_count());
+    for (int i = 0; i < context->gl.texture_count; ++i) {
+      glBindTexture(context->gl.texture_target_type, context->gl.texture_id[i]);
+      glEGLImageTargetTexture2DOES(context->gl.texture_target_type,
+                                   context->current_buffer->image_khr(i));
+    }
+    glBindTexture(context->gl.texture_target_type, 0);
+    CHECK_GL();
+
+    bool is_late_latch = false;
+
+    // Pass back the texture target type and id.
+    for (auto p = parameters; p && p->key != DVR_SURFACE_PARAMETER_NONE; ++p) {
+      switch (p->key) {
+        case DVR_SURFACE_PARAMETER_ENABLE_LATE_LATCH_IN:
+          is_late_latch = !!p->value;
+          break;
+        case DVR_SURFACE_PARAMETER_SURFACE_TEXTURE_TARGET_TYPE_OUT:
+          *static_cast<GLenum*>(p->value_out) = context->gl.texture_target_type;
+          break;
+        case DVR_SURFACE_PARAMETER_SURFACE_TEXTURE_TARGET_ID_OUT:
+          for (int i = 0; i < context->gl.texture_count; ++i) {
+            *(static_cast<GLuint*>(p->value_out) + i) =
+                context->gl.texture_id[i];
+          }
+          break;
+      }
+    }
+
+    // Initialize late latch.
+    if (is_late_latch) {
+      LocalHandle fd;
+      int ret = context->display_surface->GetMetadataBufferFd(&fd);
+      if (ret == 0) {
+        context->late_latch.reset(
+            new android::dvr::LateLatch(true, std::move(fd)));
+      } else {
+        ALOGE("Error: failed to get surface metadata buffer fd for late latch");
+      }
+    }
+  } else if (context->graphics_api == DVR_GRAPHICS_API_VULKAN) {
+    VkResult result = VK_SUCCESS;
+    // Create a VkSurfaceKHR from the ANativeWindow.
+    VkAndroidSurfaceCreateInfoKHR android_surface_ci = {};
+    android_surface_ci.sType =
+        VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
+    android_surface_ci.window = context.get();
+    result = vkCreateAndroidSurfaceKHR(
+        context->vk.instance, &android_surface_ci,
+        context->vk.allocation_callbacks, &context->vk.surface);
+    CHECK_EQ(result, VK_SUCCESS);
+    VkBool32 surface_supports_present = VK_FALSE;
+    result = vkGetPhysicalDeviceSurfaceSupportKHR(
+        context->vk.physical_device, context->vk.present_queue_family,
+        context->vk.surface, &surface_supports_present);
+    CHECK_EQ(result, VK_SUCCESS);
+    if (!surface_supports_present) {
+      ALOGE("Error: provided queue family (%u) does not support presentation",
+            context->vk.present_queue_family);
+      return -EPERM;
+    }
+    VkSurfaceCapabilitiesKHR surface_capabilities = {};
+    result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
+        context->vk.physical_device, context->vk.surface,
+        &surface_capabilities);
+    CHECK_EQ(result, VK_SUCCESS);
+    // Determine the swapchain image format.
+    uint32_t device_surface_format_count = 0;
+    result = vkGetPhysicalDeviceSurfaceFormatsKHR(
+        context->vk.physical_device, context->vk.surface,
+        &device_surface_format_count, nullptr);
+    CHECK_EQ(result, VK_SUCCESS);
+    std::vector<VkSurfaceFormatKHR> device_surface_formats(
+        device_surface_format_count);
+    result = vkGetPhysicalDeviceSurfaceFormatsKHR(
+        context->vk.physical_device, context->vk.surface,
+        &device_surface_format_count, device_surface_formats.data());
+    CHECK_EQ(result, VK_SUCCESS);
+    CHECK_GT(device_surface_format_count, 0U);
+    CHECK_NE(device_surface_formats[0].format, VK_FORMAT_UNDEFINED);
+    VkSurfaceFormatKHR present_surface_format = device_surface_formats[0];
+    // Determine the swapchain present mode.
+    // TODO(cort): query device_present_modes to make sure MAILBOX is supported.
+    // But according to libvulkan, it is.
+    uint32_t device_present_mode_count = 0;
+    result = vkGetPhysicalDeviceSurfacePresentModesKHR(
+        context->vk.physical_device, context->vk.surface,
+        &device_present_mode_count, nullptr);
+    CHECK_EQ(result, VK_SUCCESS);
+    std::vector<VkPresentModeKHR> device_present_modes(
+        device_present_mode_count);
+    result = vkGetPhysicalDeviceSurfacePresentModesKHR(
+        context->vk.physical_device, context->vk.surface,
+        &device_present_mode_count, device_present_modes.data());
+    CHECK_EQ(result, VK_SUCCESS);
+    VkPresentModeKHR present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
+    // Extract presentation surface extents, image count, transform, usages,
+    // etc.
+    LOG_ASSERT(
+        static_cast<int>(surface_capabilities.currentExtent.width) != -1 &&
+        static_cast<int>(surface_capabilities.currentExtent.height) != -1);
+    VkExtent2D swapchain_extent = surface_capabilities.currentExtent;
+
+    uint32_t desired_image_count = surface_capabilities.minImageCount;
+    if (surface_capabilities.maxImageCount > 0 &&
+        desired_image_count > surface_capabilities.maxImageCount) {
+      desired_image_count = surface_capabilities.maxImageCount;
+    }
+    VkSurfaceTransformFlagBitsKHR surface_transform =
+        surface_capabilities.currentTransform;
+    VkImageUsageFlags image_usage_flags =
+        surface_capabilities.supportedUsageFlags;
+    CHECK_NE(surface_capabilities.supportedCompositeAlpha,
+             static_cast<VkFlags>(0));
+    VkCompositeAlphaFlagBitsKHR composite_alpha =
+        VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+    if (!(surface_capabilities.supportedCompositeAlpha &
+          VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)) {
+      composite_alpha = VkCompositeAlphaFlagBitsKHR(
+          static_cast<int>(surface_capabilities.supportedCompositeAlpha) &
+          -static_cast<int>(surface_capabilities.supportedCompositeAlpha));
+    }
+    // Create VkSwapchainKHR
+    VkSwapchainCreateInfoKHR swapchain_ci = {};
+    swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+    swapchain_ci.pNext = nullptr;
+    swapchain_ci.surface = context->vk.surface;
+    swapchain_ci.minImageCount = desired_image_count;
+    swapchain_ci.imageFormat = present_surface_format.format;
+    swapchain_ci.imageColorSpace = present_surface_format.colorSpace;
+    swapchain_ci.imageExtent.width = swapchain_extent.width;
+    swapchain_ci.imageExtent.height = swapchain_extent.height;
+    swapchain_ci.imageUsage = image_usage_flags;
+    swapchain_ci.preTransform = surface_transform;
+    swapchain_ci.compositeAlpha = composite_alpha;
+    swapchain_ci.imageArrayLayers = 1;
+    swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    swapchain_ci.queueFamilyIndexCount = 0;
+    swapchain_ci.pQueueFamilyIndices = nullptr;
+    swapchain_ci.presentMode = present_mode;
+    swapchain_ci.clipped = VK_TRUE;
+    swapchain_ci.oldSwapchain = VK_NULL_HANDLE;
+    result = vkCreateSwapchainKHR(context->vk.device, &swapchain_ci,
+                                  context->vk.allocation_callbacks,
+                                  &context->vk.swapchain);
+    CHECK_EQ(result, VK_SUCCESS);
+    // Create swapchain image views
+    uint32_t image_count = 0;
+    result = vkGetSwapchainImagesKHR(context->vk.device, context->vk.swapchain,
+                                     &image_count, nullptr);
+    CHECK_EQ(result, VK_SUCCESS);
+    CHECK_GT(image_count, 0U);
+    context->vk.swapchain_images.resize(image_count);
+    result = vkGetSwapchainImagesKHR(context->vk.device, context->vk.swapchain,
+                                     &image_count,
+                                     context->vk.swapchain_images.data());
+    CHECK_EQ(result, VK_SUCCESS);
+    context->vk.swapchain_image_views.resize(image_count);
+    VkImageViewCreateInfo image_view_ci = {};
+    image_view_ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    image_view_ci.pNext = nullptr;
+    image_view_ci.flags = 0;
+    image_view_ci.format = swapchain_ci.imageFormat;
+    image_view_ci.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_ci.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_ci.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_ci.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_ci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    image_view_ci.subresourceRange.baseMipLevel = 0;
+    image_view_ci.subresourceRange.levelCount = 1;
+    image_view_ci.subresourceRange.baseArrayLayer = 0;
+    image_view_ci.subresourceRange.layerCount = 1;
+    image_view_ci.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    image_view_ci.image = VK_NULL_HANDLE;  // filled in below
+    for (uint32_t i = 0; i < image_count; ++i) {
+      image_view_ci.image = context->vk.swapchain_images[i];
+      result = vkCreateImageView(context->vk.device, &image_view_ci,
+                                 context->vk.allocation_callbacks,
+                                 &context->vk.swapchain_image_views[i]);
+      CHECK_EQ(result, VK_SUCCESS);
+    }
+    // Fill in any requested output parameters.
+    for (auto p = parameters; p && p->key != DVR_SURFACE_PARAMETER_NONE; ++p) {
+      switch (p->key) {
+        case DVR_SURFACE_PARAMETER_VK_SWAPCHAIN_IMAGE_COUNT_OUT:
+          *static_cast<uint32_t*>(p->value_out) = image_count;
+          break;
+        case DVR_SURFACE_PARAMETER_VK_SWAPCHAIN_IMAGE_FORMAT_OUT:
+          *static_cast<VkFormat*>(p->value_out) = swapchain_ci.imageFormat;
+          break;
+      }
+    }
+  }
+
+  *return_graphics_context = context.release();
+  return 0;
+}
+
+void dvrGraphicsContextDestroy(DvrGraphicsContext* graphics_context) {
+  delete graphics_context;
+}
+
+// ANativeWindow function implementations. These should only be used
+// by the Vulkan path.
+int DvrGraphicsContext::Post(android::dvr::NativeBufferProducer* buffer,
+                             int fence_fd) {
+  LOG_ASSERT(graphics_api == DVR_GRAPHICS_API_VULKAN);
+  ATRACE_NAME(__PRETTY_FUNCTION__);
+  ALOGI_IF(TRACE, "DvrGraphicsContext::Post: buffer_id=%d, fence_fd=%d",
+           buffer->buffer()->id(), fence_fd);
+  ALOGW_IF(!display_surface->visible(),
+           "DvrGraphicsContext::Post: Posting buffer on invisible surface!!!");
+  // The NativeBufferProducer closes the fence fd, so dup it for tracking in the
+  // frame history.
+  frame_history.OnFrameSubmit(LocalHandle::AsDuplicate(fence_fd));
+  int result = buffer->Post(fence_fd, 0);
+  return result;
+}
+
+int DvrGraphicsContext::SetSwapInterval(ANativeWindow* window, int interval) {
+  ALOGI_IF(TRACE, "SetSwapInterval: window=%p interval=%d", window, interval);
+  DvrGraphicsContext* self = getSelf(window);
+  (void)self;
+  LOG_ASSERT(self->graphics_api == DVR_GRAPHICS_API_VULKAN);
+  return android::NO_ERROR;
+}
+
+int DvrGraphicsContext::DequeueBuffer(ANativeWindow* window,
+                                      ANativeWindowBuffer** buffer,
+                                      int* fence_fd) {
+  ATRACE_NAME(__PRETTY_FUNCTION__);
+
+  DvrGraphicsContext* self = getSelf(window);
+  LOG_ASSERT(self->graphics_api == DVR_GRAPHICS_API_VULKAN);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  if (!self->current_buffer) {
+    self->current_buffer = self->buffer_queue.get()->Dequeue();
+  }
+  ATRACE_ASYNC_BEGIN("BufferDraw", self->current_buffer->buffer()->id());
+  *fence_fd = self->current_buffer->ClaimReleaseFence().Release();
+  *buffer = self->current_buffer;
+
+  ALOGI_IF(TRACE, "DvrGraphicsContext::DequeueBuffer: fence_fd=%d", *fence_fd);
+  return android::NO_ERROR;
+}
+
+int DvrGraphicsContext::QueueBuffer(ANativeWindow* window,
+                                    ANativeWindowBuffer* buffer, int fence_fd) {
+  ATRACE_NAME("NativeWindow::QueueBuffer");
+  ALOGI_IF(TRACE, "NativeWindow::QueueBuffer: fence_fd=%d", fence_fd);
+
+  DvrGraphicsContext* self = getSelf(window);
+  LOG_ASSERT(self->graphics_api == DVR_GRAPHICS_API_VULKAN);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  android::dvr::NativeBufferProducer* native_buffer =
+      static_cast<android::dvr::NativeBufferProducer*>(buffer);
+  ATRACE_ASYNC_END("BufferDraw", native_buffer->buffer()->id());
+  bool do_post = true;
+  if (self->buffer_already_posted) {
+    // Check that the buffer is the one we expect, but handle it if this happens
+    // in production by allowing this buffer to post on top of the previous one.
+    DCHECK(native_buffer == self->current_buffer);
+    if (native_buffer == self->current_buffer) {
+      do_post = false;
+      if (fence_fd >= 0)
+        close(fence_fd);
+    }
+  }
+  if (do_post) {
+    ATRACE_ASYNC_BEGIN("BufferPost", native_buffer->buffer()->id());
+    self->Post(native_buffer, fence_fd);
+  }
+  self->buffer_already_posted = false;
+  self->current_buffer = nullptr;
+
+  return android::NO_ERROR;
+}
+
+int DvrGraphicsContext::CancelBuffer(ANativeWindow* window,
+                                     ANativeWindowBuffer* buffer,
+                                     int fence_fd) {
+  ATRACE_NAME("DvrGraphicsContext::CancelBuffer");
+  ALOGI_IF(TRACE, "DvrGraphicsContext::CancelBuffer: fence_fd: %d", fence_fd);
+
+  DvrGraphicsContext* self = getSelf(window);
+  LOG_ASSERT(self->graphics_api == DVR_GRAPHICS_API_VULKAN);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  android::dvr::NativeBufferProducer* native_buffer =
+      static_cast<android::dvr::NativeBufferProducer*>(buffer);
+  ATRACE_ASYNC_END("BufferDraw", native_buffer->buffer()->id());
+  ATRACE_INT("CancelBuffer", native_buffer->buffer()->id());
+  bool do_enqueue = true;
+  if (self->buffer_already_posted) {
+    // Check that the buffer is the one we expect, but handle it if this happens
+    // in production by returning this buffer to the buffer queue.
+    DCHECK(native_buffer == self->current_buffer);
+    if (native_buffer == self->current_buffer) {
+      do_enqueue = false;
+    }
+  }
+  if (do_enqueue) {
+    self->buffer_queue.get()->Enqueue(native_buffer);
+  }
+  if (fence_fd >= 0)
+    close(fence_fd);
+  self->buffer_already_posted = false;
+  self->current_buffer = nullptr;
+
+  return android::NO_ERROR;
+}
+
+int DvrGraphicsContext::Query(const ANativeWindow* window, int what,
+                              int* value) {
+  DvrGraphicsContext* self = getSelf(const_cast<ANativeWindow*>(window));
+  LOG_ASSERT(self->graphics_api == DVR_GRAPHICS_API_VULKAN);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  switch (what) {
+    case NATIVE_WINDOW_WIDTH:
+      *value = self->display_surface->width();
+      return android::NO_ERROR;
+    case NATIVE_WINDOW_HEIGHT:
+      *value = self->display_surface->height();
+      return android::NO_ERROR;
+    case NATIVE_WINDOW_FORMAT:
+      *value = self->display_surface->format();
+      return android::NO_ERROR;
+    case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+      *value = 1;
+      return android::NO_ERROR;
+    case NATIVE_WINDOW_CONCRETE_TYPE:
+      *value = NATIVE_WINDOW_SURFACE;
+      return android::NO_ERROR;
+    case NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER:
+      *value = 1;
+      return android::NO_ERROR;
+    case NATIVE_WINDOW_DEFAULT_WIDTH:
+      *value = self->display_surface->width();
+      return android::NO_ERROR;
+    case NATIVE_WINDOW_DEFAULT_HEIGHT:
+      *value = self->display_surface->height();
+      return android::NO_ERROR;
+    case NATIVE_WINDOW_TRANSFORM_HINT:
+      *value = 0;
+      return android::NO_ERROR;
+  }
+
+  *value = 0;
+  return android::BAD_VALUE;
+}
+
+int DvrGraphicsContext::Perform(ANativeWindow* window, int operation, ...) {
+  DvrGraphicsContext* self = getSelf(window);
+  LOG_ASSERT(self->graphics_api == DVR_GRAPHICS_API_VULKAN);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  va_list args;
+  va_start(args, operation);
+
+  // TODO(eieio): The following operations are not used at this time. They are
+  // included here to help document which operations may be useful and what
+  // parameters they take.
+  switch (operation) {
+    case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: {
+      int w = va_arg(args, int);
+      int h = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: w=%d h=%d", w, h);
+      return android::NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_SET_BUFFERS_FORMAT: {
+      int format = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFERS_FORMAT: format=%d", format);
+      return android::NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: {
+      int transform = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: transform=%d",
+               transform);
+      return android::NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_SET_USAGE: {
+      int usage = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_USAGE: usage=%d", usage);
+      return android::NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_CONNECT:
+    case NATIVE_WINDOW_DISCONNECT:
+    case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY:
+    case NATIVE_WINDOW_API_CONNECT:
+    case NATIVE_WINDOW_API_DISCONNECT:
+      // TODO(eieio): we should implement these
+      return android::NO_ERROR;
+
+    case NATIVE_WINDOW_SET_BUFFER_COUNT: {
+      int buffer_count = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFER_COUNT: bufferCount=%d",
+               buffer_count);
+      return android::NO_ERROR;
+    }
+    case NATIVE_WINDOW_SET_BUFFERS_DATASPACE: {
+      android_dataspace_t data_space =
+          static_cast<android_dataspace_t>(va_arg(args, int));
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFERS_DATASPACE: dataSpace=%d",
+               data_space);
+      return android::NO_ERROR;
+    }
+    case NATIVE_WINDOW_SET_SCALING_MODE: {
+      int mode = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_SCALING_MODE: mode=%d", mode);
+      return android::NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_LOCK:
+    case NATIVE_WINDOW_UNLOCK_AND_POST:
+    case NATIVE_WINDOW_SET_CROP:
+    case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP:
+      return android::INVALID_OPERATION;
+  }
+
+  return android::NAME_NOT_FOUND;
+}
+
+int DvrGraphicsContext::DequeueBuffer_DEPRECATED(ANativeWindow* window,
+                                                 ANativeWindowBuffer** buffer) {
+  int fence_fd = -1;
+  int ret = DequeueBuffer(window, buffer, &fence_fd);
+
+  // wait for fence
+  if (ret == android::NO_ERROR && fence_fd != -1)
+    close(fence_fd);
+
+  return ret;
+}
+
+int DvrGraphicsContext::CancelBuffer_DEPRECATED(ANativeWindow* window,
+                                                ANativeWindowBuffer* buffer) {
+  return CancelBuffer(window, buffer, -1);
+}
+
+int DvrGraphicsContext::QueueBuffer_DEPRECATED(ANativeWindow* window,
+                                               ANativeWindowBuffer* buffer) {
+  return QueueBuffer(window, buffer, -1);
+}
+
+int DvrGraphicsContext::LockBuffer_DEPRECATED(ANativeWindow* /*window*/,
+                                              ANativeWindowBuffer* /*buffer*/) {
+  return android::NO_ERROR;
+}
+// End ANativeWindow implementation
+
+int dvrSetEdsPose(DvrGraphicsContext* graphics_context,
+                  float32x4_t render_pose_orientation,
+                  float32x4_t render_pose_translation) {
+  ATRACE_NAME("dvrSetEdsPose");
+  if (!graphics_context->current_buffer) {
+    ALOGE("dvrBeginRenderFrame must be called before dvrSetEdsPose");
+    return -EPERM;
+  }
+
+  // When late-latching is enabled, the pose buffer is written by the GPU, so
+  // we don't touch it here.
+  float32x4_t is_late_latch = DVR_POSE_LATE_LATCH;
+  if (render_pose_orientation[0] != is_late_latch[0]) {
+    volatile android::dvr::DisplaySurfaceMetadata* data =
+        graphics_context->surface_metadata;
+    uint32_t buffer_index =
+        graphics_context->current_buffer->surface_buffer_index();
+    ALOGE_IF(TRACE, "write pose index %d %f %f", buffer_index,
+             render_pose_orientation[0], render_pose_orientation[1]);
+    data->orientation[buffer_index] = render_pose_orientation;
+    data->translation[buffer_index] = render_pose_translation;
+  }
+
+  return 0;
+}
+
+int dvrBeginRenderFrameEds(DvrGraphicsContext* graphics_context,
+                           float32x4_t render_pose_orientation,
+                           float32x4_t render_pose_translation) {
+  ATRACE_NAME("dvrBeginRenderFrameEds");
+  LOG_ASSERT(graphics_context->graphics_api == DVR_GRAPHICS_API_GLES);
+  CHECK_GL();
+  // Grab a buffer from the queue and set its pose.
+  if (!graphics_context->current_buffer) {
+    graphics_context->current_buffer =
+        graphics_context->buffer_queue->Dequeue();
+  }
+
+  int ret = dvrSetEdsPose(graphics_context, render_pose_orientation,
+                          render_pose_translation);
+  if (ret < 0)
+    return ret;
+
+  ATRACE_ASYNC_BEGIN("BufferDraw",
+                     graphics_context->current_buffer->buffer()->id());
+
+  {
+    ATRACE_NAME("glEGLImageTargetTexture2DOES");
+    // Bind the texture to the latest buffer in the queue.
+    for (int i = 0; i < graphics_context->gl.texture_count; ++i) {
+      glBindTexture(graphics_context->gl.texture_target_type,
+                    graphics_context->gl.texture_id[i]);
+      glEGLImageTargetTexture2DOES(
+          graphics_context->gl.texture_target_type,
+          graphics_context->current_buffer->image_khr(i));
+    }
+    glBindTexture(graphics_context->gl.texture_target_type, 0);
+  }
+  CHECK_GL();
+  return 0;
+}
+int dvrBeginRenderFrameEdsVk(DvrGraphicsContext* graphics_context,
+                             float32x4_t render_pose_orientation,
+                             float32x4_t render_pose_translation,
+                             VkSemaphore acquire_semaphore,
+                             VkFence acquire_fence,
+                             uint32_t* swapchain_image_index,
+                             VkImageView* swapchain_image_view) {
+  ATRACE_NAME("dvrBeginRenderFrameEds");
+  LOG_ASSERT(graphics_context->graphics_api == DVR_GRAPHICS_API_VULKAN);
+
+  // Acquire a swapchain image. This calls Dequeue() internally.
+  VkResult result = vkAcquireNextImageKHR(
+      graphics_context->vk.device, graphics_context->vk.swapchain, UINT64_MAX,
+      acquire_semaphore, acquire_fence, swapchain_image_index);
+  if (result != VK_SUCCESS)
+    return -EINVAL;
+
+  // Set the pose pose.
+  int ret = dvrSetEdsPose(graphics_context, render_pose_orientation,
+                          render_pose_translation);
+  if (ret < 0)
+    return ret;
+  *swapchain_image_view =
+      graphics_context->vk.swapchain_image_views[*swapchain_image_index];
+  return 0;
+}
+
+int dvrBeginRenderFrame(DvrGraphicsContext* graphics_context) {
+  return dvrBeginRenderFrameEds(graphics_context, DVR_POSE_NO_EDS,
+                                DVR_POSE_NO_EDS);
+}
+int dvrBeginRenderFrameVk(DvrGraphicsContext* graphics_context,
+                          VkSemaphore acquire_semaphore, VkFence acquire_fence,
+                          uint32_t* swapchain_image_index,
+                          VkImageView* swapchain_image_view) {
+  return dvrBeginRenderFrameEdsVk(
+      graphics_context, DVR_POSE_NO_EDS, DVR_POSE_NO_EDS, acquire_semaphore,
+      acquire_fence, swapchain_image_index, swapchain_image_view);
+}
+
+int dvrBeginRenderFrameLateLatch(DvrGraphicsContext* graphics_context,
+                                 uint32_t /*flags*/,
+                                 uint32_t target_vsync_count, int num_views,
+                                 const float** projection_matrices,
+                                 const float** eye_from_head_matrices,
+                                 const float** pose_offset_matrices,
+                                 uint32_t* out_late_latch_buffer_id) {
+  if (!graphics_context->late_latch) {
+    return -EPERM;
+  }
+  if (num_views > DVR_GRAPHICS_SURFACE_MAX_VIEWS) {
+    LOG(ERROR) << "dvrBeginRenderFrameLateLatch called with too many views.";
+    return -EINVAL;
+  }
+  dvrBeginRenderFrameEds(graphics_context, DVR_POSE_LATE_LATCH,
+                         DVR_POSE_LATE_LATCH);
+  auto& ll = graphics_context->late_latch;
+  // TODO(jbates) Need to change this shader so that it dumps the single
+  // captured pose for both eyes into the display surface metadata buffer at
+  // the right index.
+  android::dvr::LateLatchInput input;
+  memset(&input, 0, sizeof(input));
+  for (int i = 0; i < num_views; ++i) {
+    memcpy(input.proj_mat + i, *(projection_matrices + i), 16 * sizeof(float));
+    memcpy(input.eye_from_head_mat + i, *(eye_from_head_matrices + i),
+           16 * sizeof(float));
+    memcpy(input.pose_offset + i, *(pose_offset_matrices + i),
+           16 * sizeof(float));
+  }
+  input.pose_index =
+      target_vsync_count & android::dvr::kPoseAsyncBufferIndexMask;
+  input.render_pose_index =
+      graphics_context->current_buffer->surface_buffer_index();
+  ll->AddLateLatch(input);
+  *out_late_latch_buffer_id = ll->output_buffer_id();
+  return 0;
+}
+
+extern "C" int dvrGraphicsWaitNextFrame(
+    DvrGraphicsContext* graphics_context, int64_t start_delay_ns,
+    DvrFrameSchedule* out_next_frame_schedule) {
+  start_delay_ns = std::max(start_delay_ns, static_cast<int64_t>(0));
+
+  // We only do one-shot timers:
+  int64_t wake_time_ns = 0;
+
+  uint32_t current_frame_vsync;
+  int64_t current_frame_scheduled_finish_ns;
+  int64_t vsync_period_ns;
+
+  int fetch_schedule_result = graphics_context->vsync_client->GetSchedInfo(
+      &vsync_period_ns, &current_frame_scheduled_finish_ns,
+      &current_frame_vsync);
+  if (fetch_schedule_result == 0) {
+    wake_time_ns = current_frame_scheduled_finish_ns + start_delay_ns;
+    // If the last wakeup time is still in the future, use it instead to avoid
+    // major schedule jumps when applications call WaitNextFrame with
+    // aggressive offsets.
+    int64_t now = android::dvr::GetSystemClockNs();
+    if (android::dvr::TimestampGT(wake_time_ns - vsync_period_ns, now)) {
+      wake_time_ns -= vsync_period_ns;
+      --current_frame_vsync;
+    }
+    // If the next wakeup time is in the past, add a vsync period to keep the
+    // application on schedule.
+    if (android::dvr::TimestampLT(wake_time_ns, now)) {
+      wake_time_ns += vsync_period_ns;
+      ++current_frame_vsync;
+    }
+  } else {
+    ALOGE("Error getting frame schedule because: %s",
+          strerror(-fetch_schedule_result));
+    // Sleep for a vsync period to avoid cascading failure.
+    wake_time_ns = android::dvr::GetSystemClockNs() +
+                   graphics_context->display_metrics.vsync_period_ns;
+  }
+
+  // Adjust nsec to [0..999,999,999].
+  struct itimerspec wake_time;
+  wake_time.it_interval.tv_sec = 0;
+  wake_time.it_interval.tv_nsec = 0;
+  wake_time.it_value = android::dvr::NsToTimespec(wake_time_ns);
+  bool sleep_result =
+      timerfd_settime(graphics_context->timerfd.Get(), TFD_TIMER_ABSTIME,
+                      &wake_time, nullptr) == 0;
+  if (sleep_result) {
+    ATRACE_NAME("sleep");
+    uint64_t expirations = 0;
+    sleep_result = read(graphics_context->timerfd.Get(), &expirations,
+                        sizeof(uint64_t)) == sizeof(uint64_t);
+    if (!sleep_result) {
+      ALOGE("Error: timerfd read failed");
+    }
+  } else {
+    ALOGE("Error: timerfd_settime failed because: %s", strerror(errno));
+  }
+
+  auto& frame_history = graphics_context->frame_history;
+  frame_history.CheckForFinishedFrames();
+  if (fetch_schedule_result == 0) {
+    uint32_t next_frame_vsync =
+        current_frame_vsync +
+        frame_history.PredictNextFrameVsyncInterval(vsync_period_ns);
+    int64_t next_frame_scheduled_finish =
+        (wake_time_ns - start_delay_ns) + vsync_period_ns;
+    frame_history.OnFrameStart(next_frame_vsync, next_frame_scheduled_finish);
+    if (out_next_frame_schedule) {
+      out_next_frame_schedule->vsync_count = next_frame_vsync;
+      out_next_frame_schedule->scheduled_frame_finish_ns =
+          next_frame_scheduled_finish;
+    }
+  } else {
+    frame_history.OnFrameStart(UINT32_MAX, -1);
+  }
+
+  return (fetch_schedule_result == 0 && sleep_result) ? 0 : -1;
+}
+
+extern "C" void dvrGraphicsPostEarly(DvrGraphicsContext* graphics_context) {
+  ATRACE_NAME("dvrGraphicsPostEarly");
+  ALOGI_IF(TRACE, "dvrGraphicsPostEarly");
+
+  LOG_ASSERT(graphics_context->graphics_api == DVR_GRAPHICS_API_GLES);
+
+  // Note that this function can be called before or after
+  // dvrBeginRenderFrame.
+  if (!graphics_context->buffer_already_posted) {
+    graphics_context->buffer_already_posted = true;
+
+    if (!graphics_context->current_buffer) {
+      graphics_context->current_buffer =
+          graphics_context->buffer_queue->Dequeue();
+    }
+
+    auto buffer = graphics_context->current_buffer->buffer().get();
+    ATRACE_ASYNC_BEGIN("BufferPost", buffer->id());
+    int result = buffer->Post<uint64_t>(LocalHandle(), 0);
+    if (result < 0)
+      ALOGE("Buffer post failed: %d (%s)", result, strerror(-result));
+  }
+}
+
+int dvrPresent(DvrGraphicsContext* graphics_context) {
+  LOG_ASSERT(graphics_context->graphics_api == DVR_GRAPHICS_API_GLES);
+
+  std::array<char, 128> buf;
+  snprintf(buf.data(), buf.size(), "dvrPresent|vsync=%d|",
+           graphics_context->frame_history.GetCurrentFrameVsync());
+  ATRACE_NAME(buf.data());
+
+  if (!graphics_context->current_buffer) {
+    ALOGE("Error: dvrPresent called without dvrBeginRenderFrame");
+    return -EPERM;
+  }
+
+  LocalHandle fence_fd =
+      android::dvr::CreateGLSyncAndFlush(graphics_context->gl.egl_display);
+
+  ALOGI_IF(TRACE, "PostBuffer: buffer_id=%d, fence_fd=%d",
+           graphics_context->current_buffer->buffer()->id(), fence_fd.Get());
+  ALOGW_IF(!graphics_context->display_surface->visible(),
+           "PostBuffer: Posting buffer on invisible surface!!!");
+
+  auto buffer = graphics_context->current_buffer->buffer().get();
+  ATRACE_ASYNC_END("BufferDraw", buffer->id());
+  if (!graphics_context->buffer_already_posted) {
+    ATRACE_ASYNC_BEGIN("BufferPost", buffer->id());
+    int result = buffer->Post<uint64_t>(fence_fd, 0);
+    if (result < 0)
+      ALOGE("Buffer post failed: %d (%s)", result, strerror(-result));
+  }
+
+  graphics_context->frame_history.OnFrameSubmit(std::move(fence_fd));
+  graphics_context->buffer_already_posted = false;
+  graphics_context->current_buffer = nullptr;
+  return 0;
+}
+
+int dvrPresentVk(DvrGraphicsContext* graphics_context,
+                 VkSemaphore submit_semaphore, uint32_t swapchain_image_index) {
+  LOG_ASSERT(graphics_context->graphics_api == DVR_GRAPHICS_API_VULKAN);
+
+  std::array<char, 128> buf;
+  snprintf(buf.data(), buf.size(), "dvrPresent|vsync=%d|",
+           graphics_context->frame_history.GetCurrentFrameVsync());
+  ATRACE_NAME(buf.data());
+
+  if (!graphics_context->current_buffer) {
+    ALOGE("Error: dvrPresentVk called without dvrBeginRenderFrameVk");
+    return -EPERM;
+  }
+
+  // Present the specified image. Internally, this gets a fence from the
+  // Vulkan driver and passes it to DvrGraphicsContext::Post(),
+  // which in turn passes it to buffer->Post() and adds it to frame_history.
+  VkPresentInfoKHR present_info = {};
+  present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+  present_info.swapchainCount = 1;
+  present_info.pSwapchains = &graphics_context->vk.swapchain;
+  present_info.pImageIndices = &swapchain_image_index;
+  present_info.waitSemaphoreCount =
+      (submit_semaphore != VK_NULL_HANDLE) ? 1 : 0;
+  present_info.pWaitSemaphores = &submit_semaphore;
+  VkResult result =
+      vkQueuePresentKHR(graphics_context->vk.present_queue, &present_info);
+  if (result != VK_SUCCESS) {
+    return -EINVAL;
+  }
+
+  return 0;
+}
+
+extern "C" int dvrGetFrameScheduleResults(DvrGraphicsContext* context,
+                                          DvrFrameScheduleResult* results,
+                                          int in_result_count) {
+  if (!context || !results)
+    return -EINVAL;
+
+  return context->frame_history.GetPreviousFrameResults(results,
+                                                        in_result_count);
+}
+
+extern "C" void dvrGraphicsSurfaceSetVisible(
+    DvrGraphicsContext* graphics_context, int visible) {
+  graphics_context->display_surface->SetVisible(visible);
+}
+
+extern "C" int dvrGraphicsSurfaceGetVisible(
+    DvrGraphicsContext* graphics_context) {
+  return graphics_context->display_surface->visible() ? 1 : 0;
+}
+
+extern "C" void dvrGraphicsSurfaceSetZOrder(
+    DvrGraphicsContext* graphics_context, int z_order) {
+  graphics_context->display_surface->SetZOrder(z_order);
+}
+
+extern "C" int dvrGraphicsSurfaceGetZOrder(
+    DvrGraphicsContext* graphics_context) {
+  return graphics_context->display_surface->z_order();
+}
+
+extern "C" DvrVideoMeshSurface* dvrGraphicsVideoMeshSurfaceCreate(
+    DvrGraphicsContext* graphics_context) {
+  auto display_surface = graphics_context->display_surface;
+  // A DisplaySurface must be created prior to the creation of a
+  // VideoMeshSurface.
+  LOG_ASSERT(display_surface != nullptr);
+
+  LocalChannelHandle surface_handle = display_surface->CreateVideoMeshSurface();
+  if (!surface_handle.valid()) {
+    return nullptr;
+  }
+
+  std::unique_ptr<DvrVideoMeshSurface> surface(new DvrVideoMeshSurface);
+  surface->client =
+      android::dvr::VideoMeshSurfaceClient::Import(std::move(surface_handle));
+
+  // TODO(jwcai) The next line is not needed...
+  auto producer_queue = surface->client->GetProducerQueue();
+  return surface.release();
+}
+
+extern "C" void dvrGraphicsVideoMeshSurfaceDestroy(
+    DvrVideoMeshSurface* surface) {
+  delete surface;
+}
+
+extern "C" void dvrGraphicsVideoMeshSurfacePresent(
+    DvrGraphicsContext* graphics_context, DvrVideoMeshSurface* surface,
+    const int eye, const float* transform) {
+  volatile android::dvr::VideoMeshSurfaceMetadata* metadata =
+      surface->client->GetMetadataBufferPtr();
+
+  const uint32_t graphics_buffer_index =
+      graphics_context->current_buffer->surface_buffer_index();
+
+  for (int i = 0; i < 4; ++i) {
+    metadata->transform[graphics_buffer_index][eye].val[i] = {
+        transform[i + 0], transform[i + 4], transform[i + 8], transform[i + 12],
+    };
+  }
+}
diff --git a/libs/vr/libdisplay/include/CPPLINT.cfg b/libs/vr/libdisplay/include/CPPLINT.cfg
new file mode 100644
index 0000000..2f8a3c0
--- /dev/null
+++ b/libs/vr/libdisplay/include/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-build/header_guard
diff --git a/libs/vr/libdisplay/include/dvr/graphics.h b/libs/vr/libdisplay/include/dvr/graphics.h
new file mode 100644
index 0000000..50d2754
--- /dev/null
+++ b/libs/vr/libdisplay/include/dvr/graphics.h
@@ -0,0 +1,475 @@
+#ifndef DVR_GRAPHICS_H_
+#define DVR_GRAPHICS_H_
+
+#include <EGL/egl.h>
+#include <sys/cdefs.h>
+
+#ifdef __ARM_NEON
+#include <arm_neon.h>
+#else
+#ifndef __FLOAT32X4T_86
+#define __FLOAT32X4T_86
+typedef float float32x4_t __attribute__ ((__vector_size__ (16)));
+typedef struct float32x4x4_t { float32x4_t val[4]; };
+#endif
+#endif
+
+#ifndef VK_USE_PLATFORM_ANDROID_KHR
+#define VK_USE_PLATFORM_ANDROID_KHR 1
+#endif
+#include <vulkan/vulkan.h>
+
+__BEGIN_DECLS
+
+// Create a stereo surface that will be lens-warped by the system.
+EGLNativeWindowType dvrCreateWarpedDisplaySurface(int* display_width,
+                                                  int* display_height);
+EGLNativeWindowType dvrCreateDisplaySurface(void);
+
+// Display surface parameters used to specify display surface options.
+enum {
+  DVR_SURFACE_PARAMETER_NONE = 0,
+  // WIDTH
+  DVR_SURFACE_PARAMETER_WIDTH_IN,
+  // HEIGHT
+  DVR_SURFACE_PARAMETER_HEIGHT_IN,
+  // DISABLE_DISTORTION
+  DVR_SURFACE_PARAMETER_DISABLE_DISTORTION_IN,
+  // DISABLE_STABILIZATION
+  DVR_SURFACE_PARAMETER_DISABLE_STABILIZATION_IN,
+  // Disable chromatic aberration correction
+  DVR_SURFACE_PARAMETER_DISABLE_CAC_IN,
+  // ENABLE_LATE_LATCH: Enable late latching of pose data for application
+  // GPU shaders.
+  DVR_SURFACE_PARAMETER_ENABLE_LATE_LATCH_IN,
+  // VISIBLE
+  DVR_SURFACE_PARAMETER_VISIBLE_IN,
+  // Z_ORDER
+  DVR_SURFACE_PARAMETER_Z_ORDER_IN,
+  // EXCLUDE_FROM_BLUR
+  DVR_SURFACE_PARAMETER_EXCLUDE_FROM_BLUR_IN,
+  // BLUR_BEHIND
+  DVR_SURFACE_PARAMETER_BLUR_BEHIND_IN,
+  // DISPLAY_WIDTH
+  DVR_SURFACE_PARAMETER_DISPLAY_WIDTH_OUT,
+  // DISPLAY_HEIGHT
+  DVR_SURFACE_PARAMETER_DISPLAY_HEIGHT_OUT,
+  // SURFACE_WIDTH: Returns width of allocated surface buffer.
+  DVR_SURFACE_PARAMETER_SURFACE_WIDTH_OUT,
+  // SURFACE_HEIGHT: Returns height of allocated surface buffer.
+  DVR_SURFACE_PARAMETER_SURFACE_HEIGHT_OUT,
+  // INTER_LENS_METERS: Returns float value in meters, the distance between
+  // lenses.
+  DVR_SURFACE_PARAMETER_INTER_LENS_METERS_OUT,
+  // LEFT_FOV_LRBT: Return storage must have room for array of 4 floats (in
+  // radians). The layout is left, right, bottom, top as indicated by LRBT.
+  DVR_SURFACE_PARAMETER_LEFT_FOV_LRBT_OUT,
+  // RIGHT_FOV_LRBT: Return storage must have room for array of 4 floats (in
+  // radians). The layout is left, right, bottom, top as indicated by LRBT.
+  DVR_SURFACE_PARAMETER_RIGHT_FOV_LRBT_OUT,
+  // VSYNC_PERIOD: Returns the period of the display refresh (in
+  // nanoseconds per refresh), as a 64-bit unsigned integer.
+  DVR_SURFACE_PARAMETER_VSYNC_PERIOD_OUT,
+  // SURFACE_TEXTURE_TARGET_TYPE: Returns the type of texture used as the render
+  // target.
+  DVR_SURFACE_PARAMETER_SURFACE_TEXTURE_TARGET_TYPE_OUT,
+  // SURFACE_TEXTURE_TARGET_ID: Returns the texture ID used as the render
+  // target.
+  DVR_SURFACE_PARAMETER_SURFACE_TEXTURE_TARGET_ID_OUT,
+  // Whether the surface needs to be flipped vertically before display. Default
+  // is 0.
+  DVR_SURFACE_PARAMETER_VERTICAL_FLIP_IN,
+  // A bool indicating whether or not to create a GL context for the surface.
+  // 0: don't create a context
+  // Non-zero: create a context.
+  // Default is 1.
+  // If this value is 0, there must be a GLES 3.2 or greater context bound on
+  // the current thread at the time dvrGraphicsContextCreate is called.
+  DVR_SURFACE_PARAMETER_CREATE_GL_CONTEXT_IN,
+  // Specify one of DVR_SURFACE_GEOMETRY_*.
+  DVR_SURFACE_PARAMETER_GEOMETRY_IN,
+  // FORMAT: One of DVR_SURFACE_FORMAT_RGBA_8888 or DVR_SURFACE_FORMAT_RGB_565.
+  // Default is DVR_SURFACE_FORMAT_RGBA_8888.
+  DVR_SURFACE_PARAMETER_FORMAT_IN,
+  // GRAPHICS_API: One of DVR_SURFACE_GRAPHICS_API_GLES or
+  // DVR_SURFACE_GRAPHICS_API_VULKAN. Default is GLES.
+  DVR_SURFACE_PARAMETER_GRAPHICS_API_IN,
+  // VK_INSTANCE: In Vulkan mode, the application creates a VkInstance and
+  // passes it in.
+  DVR_SURFACE_PARAMETER_VK_INSTANCE_IN,
+  // VK_PHYSICAL_DEVICE: In Vulkan mode, the application passes in the
+  // PhysicalDevice handle corresponding to the logical device passed to
+  // VK_DEVICE.
+  DVR_SURFACE_PARAMETER_VK_PHYSICAL_DEVICE_IN,
+  // VK_DEVICE: In Vulkan mode, the application creates a VkDevice and
+  // passes it in.
+  DVR_SURFACE_PARAMETER_VK_DEVICE_IN,
+  // VK_PRESENT_QUEUE: In Vulkan mode, the application selects a
+  // presentation-compatible VkQueue and passes it in.
+  DVR_SURFACE_PARAMETER_VK_PRESENT_QUEUE_IN,
+  // VK_PRESENT_QUEUE_FAMILY: In Vulkan mode, the application passes in the
+  // index of the queue family containing the VkQueue passed to
+  // VK_PRESENT_QUEUE.
+  DVR_SURFACE_PARAMETER_VK_PRESENT_QUEUE_FAMILY_IN,
+  // VK_SWAPCHAIN_IMAGE_COUNT: In Vulkan mode, the number of swapchain images
+  // will be returned here.
+  DVR_SURFACE_PARAMETER_VK_SWAPCHAIN_IMAGE_COUNT_OUT,
+  // VK_SWAPCHAIN_IMAGE_FORMAT: In Vulkan mode, the VkFormat of the swapchain
+  // images will be returned here.
+  DVR_SURFACE_PARAMETER_VK_SWAPCHAIN_IMAGE_FORMAT_OUT,
+};
+
+enum {
+  // Default surface type. One wide buffer with the left eye view in the left
+  // half and the right eye view in the right half.
+  DVR_SURFACE_GEOMETRY_SINGLE,
+  // Separate buffers, one per eye. The width parameters still refer to the
+  // total width (2 * eye view width).
+  DVR_SURFACE_GEOMETRY_SEPARATE_2,
+};
+
+// Surface format. Gvr only supports RGBA_8888 and RGB_565 for now, so those are
+// the only formats we provide here.
+enum {
+  DVR_SURFACE_FORMAT_RGBA_8888,
+  DVR_SURFACE_FORMAT_RGB_565,
+};
+
+enum {
+  // Graphics contexts are created for OpenGL ES client applications by default.
+  DVR_GRAPHICS_API_GLES,
+  // Create the graphics context for Vulkan client applications.
+  DVR_GRAPHICS_API_VULKAN,
+};
+
+#define DVR_SURFACE_PARAMETER_IN(name, value) \
+  { DVR_SURFACE_PARAMETER_##name##_IN, (value), NULL }
+#define DVR_SURFACE_PARAMETER_OUT(name, value) \
+  { DVR_SURFACE_PARAMETER_##name##_OUT, 0, (value) }
+#define DVR_SURFACE_PARAMETER_LIST_END \
+  { DVR_SURFACE_PARAMETER_NONE, 0, NULL }
+
+struct DvrSurfaceParameter {
+  int32_t key;
+  int64_t value;
+  void* value_out;
+};
+
+// This is a convenience struct to hold the relevant information of the HMD
+// lenses.
+struct DvrLensInfo {
+  float inter_lens_meters;
+  float left_fov[4];
+  float right_fov[4];
+};
+
+// Creates a display surface with the given parameters. The list of parameters
+// is terminated with an entry where key == DVR_SURFACE_PARAMETER_NONE.
+// For example, the parameters array could be built as follows:
+//   int display_width = 0, display_height = 0;
+//   int surface_width = 0, surface_height = 0;
+//   float inter_lens_meters = 0.0f;
+//   float left_fov[4] = {0.0f};
+//   float right_fov[4] = {0.0f};
+//   int disable_warp = 0;
+//   DvrSurfaceParameter surface_params[] = {
+//       DVR_SURFACE_PARAMETER_IN(DISABLE_DISTORTION, disable_warp),
+//       DVR_SURFACE_PARAMETER_OUT(DISPLAY_WIDTH, &display_width),
+//       DVR_SURFACE_PARAMETER_OUT(DISPLAY_HEIGHT, &display_height),
+//       DVR_SURFACE_PARAMETER_OUT(SURFACE_WIDTH, &surface_width),
+//       DVR_SURFACE_PARAMETER_OUT(SURFACE_HEIGHT, &surface_height),
+//       DVR_SURFACE_PARAMETER_OUT(INTER_LENS_METERS, &inter_lens_meters),
+//       DVR_SURFACE_PARAMETER_OUT(LEFT_FOV_LRBT, left_fov),
+//       DVR_SURFACE_PARAMETER_OUT(RIGHT_FOV_LRBT, right_fov),
+//       DVR_SURFACE_PARAMETER_LIST_END,
+//   };
+EGLNativeWindowType dvrCreateDisplaySurfaceExtended(
+    struct DvrSurfaceParameter* parameters);
+
+int dvrGetNativeDisplayDimensions(int* native_width, int* native_height);
+
+int dvrGetDisplaySurfaceInfo(EGLNativeWindowType win, int* width, int* height,
+                             int* format);
+
+// NOTE: Only call the functions below on windows created with the API above.
+
+// Sets the display surface visible based on the boolean evaluation of
+// |visible|.
+void dvrDisplaySurfaceSetVisible(EGLNativeWindowType window, int visible);
+
+// Sets the application z-order of the display surface. Higher values display on
+// top of lower values.
+void dvrDisplaySurfaceSetZOrder(EGLNativeWindowType window, int z_order);
+
+// Post the next buffer early. This allows the application to race with either
+// the async EDS process or the scanline for applications that are not using
+// system distortion. When this is called, the next buffer in the queue is
+// posted for display. It is up to the application to kick its GPU rendering
+// work in time. If the rendering is incomplete there will be significant,
+// undesirable tearing artifacts.
+// It is not recommended to use this feature with system distortion.
+void dvrDisplayPostEarly(EGLNativeWindowType window);
+
+// Opaque struct that represents a graphics context, the texture swap chain,
+// and surfaces.
+typedef struct DvrGraphicsContext DvrGraphicsContext;
+
+// Create the graphics context.
+int dvrGraphicsContextCreate(struct DvrSurfaceParameter* parameters,
+                             DvrGraphicsContext** return_graphics_context);
+
+// Destroy the graphics context.
+void dvrGraphicsContextDestroy(DvrGraphicsContext* graphics_context);
+
+// For every frame a schedule is decided by the system compositor. A sample
+// schedule for two frames is shown below.
+//
+// |                        |                        |
+// |-----------------|------|-----------------|------|
+// |                        |                        |
+// V0                A1     V1                A2     V2
+//
+// V0, V1, and V2 are display vsync events. Vsync events are uniquely identified
+// throughout the DVR system by a vsync count maintained by the system
+// compositor.
+//
+// A1 and A2 indicate when the application should finish rendering its frame,
+// including all GPU work. Under normal circumstances the scheduled finish
+// finish time will be set a few milliseconds before the vsync time, to give the
+// compositor time to perform distortion and EDS on the app's buffer. For apps
+// that don't use system distortion the scheduled frame finish time will be
+// closer to the vsync time. Other factors can also effect the scheduled frame
+// finish time, e.g. whether or not the System UI is being displayed.
+typedef struct DvrFrameSchedule {
+  // vsync_count is used as a frame identifier.
+  uint32_t vsync_count;
+
+  // The time when the app should finish rendering its frame, including all GPU
+  // work.
+  int64_t scheduled_frame_finish_ns;
+} DvrFrameSchedule;
+
+// Sleep until it's time to render the next frame. This should be the first
+// function called as part of an app's render loop, which normally looks like
+// this:
+//
+// while (1) {
+//   DvrFrameSchedule schedule;
+//   dvrGraphicsWaitNextFrame(..., &schedule); // Sleep until it's time to
+//                                             // render the next frame
+//   pose = dvrPoseGet(schedule.vsync_count);
+//   dvrBeginRenderFrame(...);
+//   <render a frame using the pose>
+//   dvrPresent(...); // Post the buffer
+// }
+//
+// |start_delay_ns| adjusts how long this function blocks the app from starting
+// its next frame. If |start_delay_ns| is 0, the function waits until the
+// scheduled frame finish time for the current frame, which gives the app one
+// full vsync period to render the next frame. If the app needs less than a full
+// vysnc period to render the frame, pass in a non-zero |start_delay_ns| to
+// delay the start of frame rendering further. For example, if the vsync period
+// is 11.1ms and the app takes 6ms to render a frame, consider setting this to
+// 5ms (note that the value is in nanoseconds, so 5,000,000ns) so that the app
+// finishes the frame closer to the scheduled frame finish time. Delaying the
+// start of rendering allows the app to use a more up-to-date pose for
+// rendering.
+// |start_delay_ns| must be a positive value or 0. If you're unsure what to set
+// for |start_delay_ns|, use 0.
+//
+// |out_next_frame_schedule| is an output parameter that will contain the
+// schedule for the next frame. It can be null. This function returns a negative
+// error code on failure.
+int dvrGraphicsWaitNextFrame(DvrGraphicsContext* graphics_context,
+                             int64_t start_delay_ns,
+                             DvrFrameSchedule* out_next_frame_schedule);
+
+// Prepares the graphics context's texture for rendering.  This function should
+// be called once for each frame, ideally immediately before the first GL call
+// on the framebuffer which wraps the surface texture.
+//
+// For GL contexts, GL states are modified as follows by this function:
+// glBindTexture(GL_TEXTURE_2D, 0);
+//
+// @param[in] graphics_context The DvrGraphicsContext.
+// @param[in] render_pose_orientation Head pose orientation that rendering for
+//            this frame will be based off of. This must be an unmodified value
+//            from DvrPoseAsync, returned by dvrPoseGet.
+// @param[in] render_pose_translation Head pose translation that rendering for
+//            this frame will be based off of. This must be an unmodified value
+//            from DvrPoseAsync, returned by dvrPoseGet.
+// @return 0 on success or a negative error code on failure.
+// Check GL errors with glGetError for other error conditions.
+int dvrBeginRenderFrameEds(DvrGraphicsContext* graphics_context,
+                           float32x4_t render_pose_orientation,
+                           float32x4_t render_pose_translation);
+int dvrBeginRenderFrameEdsVk(DvrGraphicsContext* graphics_context,
+                             float32x4_t render_pose_orientation,
+                             float32x4_t render_pose_translation,
+                             VkSemaphore acquire_semaphore,
+                             VkFence acquire_fence,
+                             uint32_t* swapchain_image_index,
+                             VkImageView* swapchain_image_view);
+// Same as dvrBeginRenderFrameEds, but with no EDS (asynchronous reprojection).
+//
+// For GL contexts, GL states are modified as follows by this function:
+// glBindTexture(GL_TEXTURE_2D, 0);
+//
+// @param[in] graphics_context The DvrGraphicsContext.
+// @return 0 on success or a negative error code on failure.
+// Check GL errors with glGetError for other error conditions.
+int dvrBeginRenderFrame(DvrGraphicsContext* graphics_context);
+int dvrBeginRenderFrameVk(DvrGraphicsContext* graphics_context,
+                          VkSemaphore acquire_semaphore, VkFence acquire_fence,
+                          uint32_t* swapchain_image_index,
+                          VkImageView* swapchain_image_view);
+
+// Maximum number of views per surface buffer (for multiview, multi-eye, etc).
+#define DVR_GRAPHICS_SURFACE_MAX_VIEWS 4
+
+// Output data format of late latch shader. The application can bind all or part
+// of this data with the buffer ID returned by dvrBeginRenderFrameLateLatch.
+// This struct is compatible with std140 layout for use from shaders.
+struct __attribute__((__packed__)) DvrGraphicsLateLatchData {
+  // Column-major order.
+  float view_proj_matrix[DVR_GRAPHICS_SURFACE_MAX_VIEWS][16];
+  // Column-major order.
+  float view_matrix[DVR_GRAPHICS_SURFACE_MAX_VIEWS][16];
+  // Quaternion for pose orientation from start space.
+  float pose_orientation[4];
+  // Pose translation from start space.
+  float pose_translation[4];
+};
+
+// Begin render frame with late latching of pose data. This kicks off a compute
+// shader that will read the latest head pose and then compute and output
+// matrices that can be used by application shaders.
+//
+// Matrices are computed with the following pseudo code.
+//   Pose pose = getLateLatchPose();
+//   out.pose_orientation = pose.orientation;
+//   out.pose_translation = pose.translation;
+//   mat4 head_from_center = ComputeInverseMatrix(pose);
+//   for each view:
+//     out.viewMatrix[view] =
+//         eye_from_head_matrices[view] * head_from_center *
+//         pose_offset_matrices[view];
+//     out.viewProjMatrix[view] =
+//         projection_matrices[view] * out.viewMatrix[view];
+//
+// For GL contexts, GL states are modified as follows by this function:
+// glBindTexture(GL_TEXTURE_2D, 0);
+// glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
+// glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
+// glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);
+// glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0);
+// glUseProgram(0);
+//
+// @param[in] graphics_context The DvrGraphicsContext.
+// @param[in] flags Specify 0.
+// @param[in] target_vsync_count The target vsync count that this frame will
+//            display at. This is used for pose prediction.
+// @param[in] num_views Number of matrices in each of the following matrix array
+//            parameters. Typically 2 for left and right eye views. Maximum is
+//            DVR_GRAPHICS_SURFACE_MAX_VIEWS.
+// @param[in] projection_matrices Array of pointers to |num_views| matrices with
+//            column-major layout. These are the application projection
+//            matrices.
+// @param[in] eye_from_head_matrices Array of pointers to |num_views| matrices
+//            with column-major layout. See pseudo code for how these are used.
+// @param[in] pose_offset_matrices Array of pointers to |num_views| matrices
+//            with column-major layout. See pseudo code for how these are used.
+// @param[out] out_late_latch_buffer_id The GL buffer ID of the output buffer of
+//             of type DvrGraphicsLateLatchData.
+// @return 0 on success or a negative error code on failure.
+// Check GL errors with glGetError for other error conditions.
+int dvrBeginRenderFrameLateLatch(DvrGraphicsContext* graphics_context,
+                                 uint32_t flags, uint32_t target_vsync_count,
+                                 int num_views,
+                                 const float** projection_matrices,
+                                 const float** eye_from_head_matrices,
+                                 const float** pose_offset_matrices,
+                                 uint32_t* out_late_latch_buffer_id);
+
+// Present a frame for display.
+// This call is normally non-blocking, unless the internal buffer queue is full.
+// @return 0 on success or a negative error code on failure.
+int dvrPresent(DvrGraphicsContext* graphics_context);
+int dvrPresentVk(DvrGraphicsContext* graphics_context,
+                 VkSemaphore submit_semaphore, uint32_t swapchain_image_index);
+
+// Post the next buffer early. This allows the application to race with either
+// the async EDS process or the scanline for applications that are not using
+// system distortion. When this is called, the next buffer in the queue is
+// posted for display. It is up to the application to kick its GPU rendering
+// work in time. If the rendering is incomplete there will be significant,
+// undesirable tearing artifacts.
+// It is not recommended to use this feature with system distortion.
+void dvrGraphicsPostEarly(DvrGraphicsContext* graphics_context);
+
+// Used to retrieve frame measurement timings from dvrGetFrameScheduleResults().
+typedef struct DvrFrameScheduleResult {
+  // vsync_count is used as a frame identifier.
+  uint32_t vsync_count;
+
+  // The app's scheduled frame finish time.
+  int64_t scheduled_frame_finish_ns;
+
+  // The difference (in nanoseconds) between the scheduled finish time and the
+  // actual finish time.
+  //
+  // A value of +2ms for frame_finish_offset_ns indicates the app's frame was
+  // late and may have been skipped by the compositor for that vsync. A value of
+  // -1ms indicates the app's frame finished just ahead of schedule, as
+  // desired. A value of -6ms indicates the app's frame finished well ahead of
+  // schedule for that vsync. In that case the app may have unnecessary visual
+  // latency. Consider using the start_delay_ns parameter in
+  // dvrGraphicsWaitNextFrame() to align the app's frame finish time closer to
+  // the scheduled finish time.
+  int64_t frame_finish_offset_ns;
+} DvrFrameScheduleResult;
+
+// Retrieve the latest frame schedule results for the app. To collect all the
+// results this should be called each frame. The results for each frame are
+// returned only once.
+// The number of results written to |results| is returned on success, or a
+// negative error code on failure.
+// |graphics_context| is the context to retrieve frame schedule results for.
+// |results| is an array that will contain the frame schedule results.
+// |result_count| is the size of the |results| array. It's recommended to pass
+// in an array with 2 elements to ensure results for all frames are collected.
+int dvrGetFrameScheduleResults(DvrGraphicsContext* graphics_context,
+                               DvrFrameScheduleResult* results,
+                               int result_count);
+
+// Make the surface visible or hidden based on |visible|.
+// 0: hidden, Non-zero: visible.
+void dvrGraphicsSurfaceSetVisible(DvrGraphicsContext* graphics_context,
+                                  int visible);
+
+// Returns surface visilibity last requested by the client.
+int dvrGraphicsSurfaceGetVisible(DvrGraphicsContext* graphics_context);
+
+// Returns surface z order last requested by the client.
+int dvrGraphicsSurfaceGetZOrder(DvrGraphicsContext* graphics_context);
+
+// Sets the compositor z-order of the surface. Higher values display on
+// top of lower values.
+void dvrGraphicsSurfaceSetZOrder(DvrGraphicsContext* graphics_context,
+                                 int z_order);
+
+typedef struct DvrVideoMeshSurface DvrVideoMeshSurface;
+
+DvrVideoMeshSurface* dvrGraphicsVideoMeshSurfaceCreate(
+    DvrGraphicsContext* graphics_context);
+void dvrGraphicsVideoMeshSurfaceDestroy(DvrVideoMeshSurface* surface);
+
+// Present a VideoMeshSurface with the current video mesh transfromation matrix.
+void dvrGraphicsVideoMeshSurfacePresent(DvrGraphicsContext* graphics_context,
+                                        DvrVideoMeshSurface* surface,
+                                        const int eye,
+                                        const float* transform);
+
+__END_DECLS
+
+#endif  // DVR_GRAPHICS_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/display_client.h b/libs/vr/libdisplay/include/private/dvr/display_client.h
new file mode 100644
index 0000000..034b7b4
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/display_client.h
@@ -0,0 +1,126 @@
+#ifndef ANDROID_DVR_DISPLAY_CLIENT_H_
+#define ANDROID_DVR_DISPLAY_CLIENT_H_
+
+#include <hardware/hwcomposer.h>
+#include <pdx/client.h>
+#include <pdx/file_handle.h>
+#include <private/dvr/buffer_hub_client.h>
+#include <private/dvr/display_rpc.h>
+
+namespace android {
+namespace dvr {
+
+struct LateLatchOutput;
+
+// Abstract base class for all surface types maintained in DVR's display
+// service.
+// TODO(jwcai) Explain more, surface is a channel...
+class SurfaceClient : public pdx::Client {
+ public:
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+  SurfaceType type() const { return type_; }
+
+  // Get the shared memory metadata buffer fd for this display surface. If it is
+  // not yet allocated, this will allocate it.
+  int GetMetadataBufferFd(pdx::LocalHandle* out_fd);
+
+  // Allocate the single metadata buffer for providing metadata associated with
+  // posted buffers for this surface. This can be used to provide rendered poses
+  // for EDS, for example. The buffer format is defined by the struct
+  // DisplaySurfaceMetadata.
+  // The first call to this method will allocate the buffer in via IPC to the
+  // display surface.
+  std::shared_ptr<BufferProducer> GetMetadataBuffer();
+
+ protected:
+  SurfaceClient(LocalChannelHandle channel_handle, SurfaceType type);
+  SurfaceClient(const std::string& endpoint_path, SurfaceType type);
+
+ private:
+  SurfaceType type_;
+  std::shared_ptr<BufferProducer> metadata_buffer_;
+};
+
+// DisplaySurfaceClient represents the client interface to a displayd display
+// surface.
+class DisplaySurfaceClient
+    : public pdx::ClientBase<DisplaySurfaceClient, SurfaceClient> {
+ public:
+  using LocalHandle = pdx::LocalHandle;
+
+  int width() const { return width_; }
+  int height() const { return height_; }
+  int format() const { return format_; }
+  int usage() const { return usage_; }
+  int flags() const { return flags_; }
+  int z_order() const { return z_order_; }
+  bool visible() const { return visible_; }
+
+  void SetVisible(bool visible);
+  void SetZOrder(int z_order);
+  void SetExcludeFromBlur(bool exclude_from_blur);
+  void SetBlurBehind(bool blur_behind);
+  void SetAttributes(const DisplaySurfaceAttributes& attributes);
+
+  // |out_buffer_index| will receive a unique index for this buffer within the
+  // surface. The first buffer gets 0, second gets 1, and so on. This index
+  // can be used to deliver metadata for buffers that are queued for display.
+  std::shared_ptr<BufferProducer> AllocateBuffer(uint32_t* out_buffer_index);
+  std::shared_ptr<BufferProducer> AllocateBuffer() {
+    return AllocateBuffer(nullptr);
+  }
+
+  // Get the shared memory metadata buffer for this display surface. If it is
+  // not yet allocated, this will allocate it.
+  volatile DisplaySurfaceMetadata* GetMetadataBufferPtr();
+
+  // Create a VideoMeshSurface that is attached to the display sruface.
+  LocalChannelHandle CreateVideoMeshSurface();
+
+ private:
+  friend BASE;
+
+  DisplaySurfaceClient(int width, int height, int format, int usage, int flags);
+
+  int width_;
+  int height_;
+  int format_;
+  int usage_;
+  int flags_;
+  int z_order_;
+  bool visible_;
+  bool exclude_from_blur_;
+  bool blur_behind_;
+  DisplaySurfaceMetadata* mapped_metadata_buffer_;
+
+  DisplaySurfaceClient(const DisplaySurfaceClient&) = delete;
+  void operator=(const DisplaySurfaceClient&) = delete;
+};
+
+class DisplayClient : public pdx::ClientBase<DisplayClient> {
+ public:
+  int GetDisplayMetrics(SystemDisplayMetrics* metrics);
+  pdx::Status<void> SetViewerParams(const ViewerParams& viewer_params);
+
+  // Pull the latest eds pose data from the display service renderer
+  int GetLastFrameEdsTransform(LateLatchOutput* ll_out);
+
+  int EnterVrMode();
+  int ExitVrMode();
+
+  std::unique_ptr<DisplaySurfaceClient> CreateDisplaySurface(
+      int width, int height, int format, int usage, int flags);
+
+ private:
+  friend BASE;
+
+  explicit DisplayClient(int* error = nullptr);
+
+  DisplayClient(const DisplayClient&) = delete;
+  void operator=(const DisplayClient&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_DISPLAY_CLIENT_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/display_manager_client.h b/libs/vr/libdisplay/include/private/dvr/display_manager_client.h
new file mode 100644
index 0000000..f28c1e4
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/display_manager_client.h
@@ -0,0 +1,73 @@
+#ifndef DVR_DISPLAY_MANAGER_CLIENT_H_
+#define DVR_DISPLAY_MANAGER_CLIENT_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DvrDisplayManagerClient DvrDisplayManagerClient;
+typedef struct DvrDisplayManagerClientSurfaceList
+    DvrDisplayManagerClientSurfaceList;
+typedef struct DvrDisplayManagerClientSurfaceBuffers
+    DvrDisplayManagerClientSurfaceBuffers;
+
+DvrDisplayManagerClient* dvrDisplayManagerClientCreate();
+
+void dvrDisplayManagerClientDestroy(DvrDisplayManagerClient* client);
+
+// If successful, populates |surface_list| with a list of application
+// surfaces the display is currently using.
+//
+// @return 0 on success. Otherwise it returns a negative error value.
+int dvrDisplayManagerClientGetSurfaceList(
+    DvrDisplayManagerClient* client,
+    DvrDisplayManagerClientSurfaceList** surface_list);
+
+void dvrDisplayManagerClientSurfaceListDestroy(
+    DvrDisplayManagerClientSurfaceList* surface_list);
+
+// @return Returns the number of surfaces in the list.
+size_t dvrDisplayManagerClientSurfaceListGetSize(
+    DvrDisplayManagerClientSurfaceList* surface_list);
+
+// @return Return a unique identifier for a client surface. The identifier can
+// be used to query for other surface properties.
+int dvrDisplayManagerClientSurfaceListGetSurfaceId(
+    DvrDisplayManagerClientSurfaceList* surface_list, size_t index);
+
+// @return Returns the stacking order of the client surface at |index|.
+int dvrDisplayManagerClientSurfaceListGetClientZOrder(
+    DvrDisplayManagerClientSurfaceList* surface_list, size_t index);
+
+// @return Returns true if the client surface is visible, false otherwise.
+bool dvrDisplayManagerClientSurfaceListGetClientIsVisible(
+    DvrDisplayManagerClientSurfaceList* surface_list, size_t index);
+
+// Populates |surface_buffers| with the list of buffers for |surface_id|.
+// |surface_id| should be a valid ID from the list of surfaces.
+//
+// @return Returns 0 on success. Otherwise it returns a negative error value.
+int dvrDisplayManagerClientGetSurfaceBuffers(
+    DvrDisplayManagerClient* client, int surface_id,
+    DvrDisplayManagerClientSurfaceBuffers** surface_buffers);
+
+void dvrDisplayManagerClientSurfaceBuffersDestroy(
+    DvrDisplayManagerClientSurfaceBuffers* surface_buffers);
+
+// @return Returns the number of buffers.
+size_t dvrDisplayManagerClientSurfaceBuffersGetSize(
+    DvrDisplayManagerClientSurfaceBuffers* surface_buffers);
+
+// @return Returns the file descriptor for the buffer consumer at |index|.
+int dvrDisplayManagerClientSurfaceBuffersGetFd(
+    DvrDisplayManagerClientSurfaceBuffers* surface_buffers, size_t index);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DVR_DISPLAY_MANAGER_CLIENT_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/display_manager_client_impl.h b/libs/vr/libdisplay/include/private/dvr/display_manager_client_impl.h
new file mode 100644
index 0000000..645ccce
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/display_manager_client_impl.h
@@ -0,0 +1,35 @@
+#ifndef ANDROID_DVR_DISPLAY_MANAGER_CLIENT_IMPL_H_
+#define ANDROID_DVR_DISPLAY_MANAGER_CLIENT_IMPL_H_
+
+#include <vector>
+
+#include <pdx/client.h>
+#include <private/dvr/display_rpc.h>
+
+namespace android {
+namespace dvr {
+
+class BufferConsumer;
+
+class DisplayManagerClient : public pdx::ClientBase<DisplayManagerClient> {
+ public:
+  ~DisplayManagerClient() override;
+
+  int GetSurfaceList(std::vector<DisplaySurfaceInfo>* surface_list);
+
+  int GetSurfaceBuffers(
+      int surface_id, std::vector<std::unique_ptr<BufferConsumer>>* consumers);
+
+ private:
+  friend BASE;
+
+  DisplayManagerClient();
+
+  DisplayManagerClient(const DisplayManagerClient&) = delete;
+  void operator=(const DisplayManagerClient&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_DISPLAY_MANAGER_CLIENT_IMPL_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/display_rpc.h b/libs/vr/libdisplay/include/private/dvr/display_rpc.h
new file mode 100644
index 0000000..6150b35
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/display_rpc.h
@@ -0,0 +1,342 @@
+#ifndef ANDROID_DVR_DISPLAY_RPC_H_
+#define ANDROID_DVR_DISPLAY_RPC_H_
+
+#include <sys/types.h>
+
+#include <array>
+#include <map>
+
+#include <pdx/rpc/remote_method.h>
+#include <pdx/rpc/serializable.h>
+#include <pdx/rpc/variant.h>
+#include <private/dvr/display_types.h>
+
+namespace android {
+namespace dvr {
+
+struct SystemDisplayMetrics {
+  uint32_t display_native_width;
+  uint32_t display_native_height;
+  uint32_t display_x_dpi;
+  uint32_t display_y_dpi;
+  uint32_t distorted_width;
+  uint32_t distorted_height;
+  uint32_t vsync_period_ns;
+  uint32_t hmd_ipd_mm;
+  float inter_lens_distance_m;
+  std::array<float, 4> left_fov_lrbt;
+  std::array<float, 4> right_fov_lrbt;
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(SystemDisplayMetrics, display_native_width,
+                           display_native_height, display_x_dpi, display_y_dpi,
+                           distorted_width, distorted_height, vsync_period_ns,
+                           hmd_ipd_mm, inter_lens_distance_m, left_fov_lrbt,
+                           right_fov_lrbt);
+};
+
+using SurfaceType = uint32_t;
+struct SurfaceTypeEnum {
+  enum : SurfaceType {
+    Normal = DVR_SURFACE_TYPE_NORMAL,
+    VideoMesh = DVR_SURFACE_TYPE_VIDEO_MESH,
+    Overlay = DVR_SURFACE_TYPE_OVERLAY,
+  };
+};
+
+using DisplaySurfaceFlags = uint32_t;
+enum class DisplaySurfaceFlagsEnum : DisplaySurfaceFlags {
+  DisableSystemEds = DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_EDS,
+  DisableSystemDistortion = DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_DISTORTION,
+  VerticalFlip = DVR_DISPLAY_SURFACE_FLAGS_VERTICAL_FLIP,
+  SeparateGeometry = DVR_DISPLAY_SURFACE_FLAGS_GEOMETRY_SEPARATE_2,
+  DisableSystemCac = DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_CAC,
+};
+
+using DisplaySurfaceInfoFlags = uint32_t;
+enum class DisplaySurfaceInfoFlagsEnum : DisplaySurfaceInfoFlags {
+  BuffersChanged = DVR_DISPLAY_SURFACE_ITEM_FLAGS_BUFFERS_CHANGED,
+};
+
+using DisplaySurfaceAttributeValue =
+    pdx::rpc::Variant<int32_t, int64_t, bool, float, std::array<float, 2>,
+                      std::array<float, 3>, std::array<float, 4>,
+                      std::array<float, 16>>;
+using DisplaySurfaceAttribute = uint32_t;
+struct DisplaySurfaceAttributeEnum {
+  enum : DisplaySurfaceAttribute {
+    ZOrder = DVR_DISPLAY_SURFACE_ATTRIBUTE_Z_ORDER,
+    Visible = DVR_DISPLAY_SURFACE_ATTRIBUTE_VISIBLE,
+    // Manager only.
+    Blur = DVR_DISPLAY_SURFACE_ATTRIBUTE_BLUR,
+    // Client only.
+    ExcludeFromBlur = DVR_DISPLAY_SURFACE_ATTRIBUTE_EXCLUDE_FROM_BLUR,
+    BlurBehind = DVR_DISPLAY_SURFACE_ATTRIBUTE_BLUR_BEHIND,
+  };
+
+  static std::string ToString(DisplaySurfaceAttribute attribute) {
+    switch (attribute) {
+      case ZOrder:
+        return "z-order";
+      case Visible:
+        return "visible";
+      case Blur:
+        return "blur";
+      case ExcludeFromBlur:
+        return "exclude-from-blur";
+      case BlurBehind:
+        return "blur-behind";
+      default:
+        return "unknown";
+    }
+  }
+};
+
+using DisplaySurfaceAttributes =
+    std::map<DisplaySurfaceAttribute, DisplaySurfaceAttributeValue>;
+
+struct DisplaySurfaceInfo {
+  int surface_id;
+  int process_id;
+  SurfaceType type;
+  DisplaySurfaceFlags flags;
+  DisplaySurfaceInfoFlags info_flags;
+  DisplaySurfaceAttributes client_attributes;
+  DisplaySurfaceAttributes manager_attributes;
+
+  // Convenience accessors.
+  bool IsClientVisible() const {
+    const auto* variant =
+        FindClientAttribute(DisplaySurfaceAttributeEnum::Visible);
+    bool bool_value;
+    if (variant && pdx::rpc::IfAnyOf<int32_t, int64_t, bool, float>::Get(
+                       variant, &bool_value))
+      return bool_value;
+    else
+      return false;
+  }
+
+  int ClientZOrder() const {
+    const auto* variant =
+        FindClientAttribute(DisplaySurfaceAttributeEnum::ZOrder);
+    int int_value;
+    if (variant &&
+        pdx::rpc::IfAnyOf<int32_t, int64_t, float>::Get(variant, &int_value))
+      return int_value;
+    else
+      return 0;
+  }
+
+ private:
+  const DisplaySurfaceAttributeValue* FindClientAttribute(
+      DisplaySurfaceAttribute key) const {
+    auto search = client_attributes.find(key);
+    return (search != client_attributes.end()) ? &search->second : nullptr;
+  }
+
+  PDX_SERIALIZABLE_MEMBERS(DisplaySurfaceInfo, surface_id, process_id, type,
+                           flags, info_flags, client_attributes,
+                           manager_attributes);
+};
+
+struct VideoMeshSurfaceBufferMetadata {
+  int64_t timestamp_ns;
+};
+
+struct AlignmentMarker {
+ public:
+  float horizontal;
+  float vertical;
+
+  PDX_SERIALIZABLE_MEMBERS(AlignmentMarker, horizontal, vertical);
+};
+
+struct DaydreamInternalParams {
+ public:
+  int32_t version;
+  std::vector<AlignmentMarker> alignment_markers;
+
+  PDX_SERIALIZABLE_MEMBERS(DaydreamInternalParams, version, alignment_markers);
+};
+
+struct ViewerParams {
+ public:
+  // TODO(hendrikw): Do we need viewer_vendor_name and viewer_model_name?
+  float screen_to_lens_distance;
+  float inter_lens_distance;
+  float screen_center_to_lens_distance;
+  std::vector<float> left_eye_field_of_view_angles;
+
+  enum VerticalAlignmentType : int32_t {
+    BOTTOM = 0,  // phone rests against a fixed bottom tray
+    CENTER = 1,  // phone screen assumed to be centered w.r.t. lenses
+    TOP = 2      // phone rests against a fixed top tray
+  };
+
+  enum EyeOrientation : int32_t {
+    kCCW0Degrees = 0,
+    kCCW90Degrees = 1,
+    kCCW180Degrees = 2,
+    kCCW270Degrees = 3,
+    kCCW0DegreesMirrored = 4,
+    kCCW90DegreesMirrored = 5,
+    kCCW180DegreesMirrored = 6,
+    kCCW270DegreesMirrored = 7
+  };
+
+  VerticalAlignmentType vertical_alignment;
+  std::vector<EyeOrientation> eye_orientations;
+
+  float tray_to_lens_distance;
+
+  std::vector<float> distortion_coefficients_r;
+  std::vector<float> distortion_coefficients_g;
+  std::vector<float> distortion_coefficients_b;
+
+  DaydreamInternalParams daydream_internal;
+
+  PDX_SERIALIZABLE_MEMBERS(ViewerParams, screen_to_lens_distance,
+                           inter_lens_distance, screen_center_to_lens_distance,
+                           left_eye_field_of_view_angles, vertical_alignment,
+                           eye_orientations, tray_to_lens_distance,
+                           distortion_coefficients_r, distortion_coefficients_g,
+                           distortion_coefficients_b, daydream_internal);
+};
+
+struct DisplayRPC {
+  // Service path.
+  static constexpr char kClientPath[] = "system/display/client";
+
+  // Op codes.
+  enum {
+    kOpGetMetrics = 0,
+    kOpGetEdsCapture,
+    kOpCreateSurface,
+    kOpAllocateBuffer,
+    kOpSetAttributes,
+    kOpGetMetadataBuffer,
+    kOpCreateVideoMeshSurface,
+    kOpVideoMeshSurfaceCreateProducerQueue,
+    kOpEnterVrMode,
+    kOpExitVrMode,
+    kOpSetViewerParams
+  };
+
+  // Aliases.
+  using ByteBuffer = pdx::rpc::BufferWrapper<std::vector<uint8_t>>;
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+  using Void = pdx::rpc::Void;
+
+  // Methods.
+  PDX_REMOTE_METHOD(GetMetrics, kOpGetMetrics, SystemDisplayMetrics(Void));
+  PDX_REMOTE_METHOD(GetEdsCapture, kOpGetEdsCapture, ByteBuffer(Void));
+  PDX_REMOTE_METHOD(CreateSurface, kOpCreateSurface,
+                    int(int width, int height, int format, int usage,
+                        DisplaySurfaceFlags flags));
+  PDX_REMOTE_METHOD(AllocateBuffer, kOpAllocateBuffer,
+                    std::pair<std::uint32_t, LocalChannelHandle>(Void));
+  PDX_REMOTE_METHOD(SetAttributes, kOpSetAttributes,
+                    int(const DisplaySurfaceAttributes& attributes));
+  PDX_REMOTE_METHOD(GetMetadataBuffer, kOpGetMetadataBuffer,
+                    LocalChannelHandle(Void));
+  // VideoMeshSurface methods
+  PDX_REMOTE_METHOD(CreateVideoMeshSurface, kOpCreateVideoMeshSurface,
+                    LocalChannelHandle(Void));
+  PDX_REMOTE_METHOD(VideoMeshSurfaceCreateProducerQueue,
+                    kOpVideoMeshSurfaceCreateProducerQueue,
+                    LocalChannelHandle(Void));
+  PDX_REMOTE_METHOD(EnterVrMode, kOpEnterVrMode, int(Void));
+  PDX_REMOTE_METHOD(ExitVrMode, kOpExitVrMode, int(Void));
+  PDX_REMOTE_METHOD(SetViewerParams, kOpSetViewerParams,
+                    void(const ViewerParams& viewer_params));
+};
+
+struct DisplayManagerRPC {
+  // Service path.
+  static constexpr char kClientPath[] = "system/display/manager";
+
+  // Op codes.
+  enum {
+    kOpGetSurfaceList = 0,
+    kOpGetSurfaceBuffers,
+    kOpUpdateSurfaces,
+  };
+
+  // Aliases.
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+  using Void = pdx::rpc::Void;
+
+  // Methods.
+  PDX_REMOTE_METHOD(GetSurfaceList, kOpGetSurfaceList,
+                    std::vector<DisplaySurfaceInfo>(Void));
+  PDX_REMOTE_METHOD(GetSurfaceBuffers, kOpGetSurfaceBuffers,
+                    std::vector<LocalChannelHandle>(int surface_id));
+  PDX_REMOTE_METHOD(
+      UpdateSurfaces, kOpUpdateSurfaces,
+      int(const std::map<int, DisplaySurfaceAttributes>& updates));
+};
+
+struct ScreenshotData {
+  int width;
+  int height;
+  std::vector<uint8_t> buffer;
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(ScreenshotData, width, height, buffer);
+};
+
+struct DisplayScreenshotRPC {
+  // Service path.
+  static constexpr char kClientPath[] = "system/display/screenshot";
+
+  // Op codes.
+  enum {
+    kOpGetFormat = 0,
+    kOpTakeScreenshot,
+  };
+
+  using Void = pdx::rpc::Void;
+
+  PDX_REMOTE_METHOD(GetFormat, kOpGetFormat, int(Void));
+  PDX_REMOTE_METHOD(TakeScreenshot, kOpTakeScreenshot,
+                    ScreenshotData(int layer_index));
+};
+
+struct VSyncSchedInfo {
+  int64_t vsync_period_ns;
+  int64_t timestamp_ns;
+  uint32_t next_vsync_count;
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(VSyncSchedInfo, vsync_period_ns, timestamp_ns,
+                           next_vsync_count);
+};
+
+struct DisplayVSyncRPC {
+  // Service path.
+  static constexpr char kClientPath[] = "system/display/vsync";
+
+  // Op codes.
+  enum {
+    kOpWait = 0,
+    kOpAck,
+    kOpGetLastTimestamp,
+    kOpGetSchedInfo,
+    kOpAcknowledge,
+  };
+
+  // Aliases.
+  using Void = pdx::rpc::Void;
+  using Timestamp = int64_t;
+
+  // Methods.
+  PDX_REMOTE_METHOD(Wait, kOpWait, Timestamp(Void));
+  PDX_REMOTE_METHOD(GetLastTimestamp, kOpGetLastTimestamp, Timestamp(Void));
+  PDX_REMOTE_METHOD(GetSchedInfo, kOpGetSchedInfo, VSyncSchedInfo(Void));
+  PDX_REMOTE_METHOD(Acknowledge, kOpAcknowledge, int(Void));
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_DISPLAY_RPC_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/display_types.h b/libs/vr/libdisplay/include/private/dvr/display_types.h
new file mode 100644
index 0000000..2bd02bd
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/display_types.h
@@ -0,0 +1,83 @@
+#ifndef ANDROID_DVR_DISPLAY_TYPES_H_
+#define ANDROID_DVR_DISPLAY_TYPES_H_
+
+#ifdef __ARM_NEON
+#include <arm_neon.h>
+#else
+#ifndef __FLOAT32X4T_86
+#define __FLOAT32X4T_86
+typedef float float32x4_t __attribute__ ((__vector_size__ (16)));
+typedef struct float32x4x4_t { float32x4_t val[4]; };
+#endif
+#endif
+
+#include <cutils/native_handle.h>
+
+// DVR display-related data types.
+
+enum dvr_display_surface_type {
+  // Normal display surface meant to be used by applications' GL context to
+  // render into.
+  DVR_SURFACE_TYPE_NORMAL = 0,
+
+  // VideoMeshSurface is used to composite video frames into the 3D world.
+  DVR_SURFACE_TYPE_VIDEO_MESH,
+
+  // System overlay surface type. This is not currently in use.
+  DVR_SURFACE_TYPE_OVERLAY,
+};
+
+enum dvr_display_surface_flags {
+  DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_EDS = (1 << 0),
+  DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_DISTORTION = (1 << 1),
+  DVR_DISPLAY_SURFACE_FLAGS_VERTICAL_FLIP = (1 << 2),
+  DVR_DISPLAY_SURFACE_FLAGS_GEOMETRY_SEPARATE_2 = (1 << 3),
+  DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_CAC = (1 << 4),
+};
+
+enum dvr_display_surface_item_flags {
+  DVR_DISPLAY_SURFACE_ITEM_FLAGS_BUFFERS_CHANGED = (1 << 0),
+};
+
+enum dvr_display_surface_attribute {
+  DVR_DISPLAY_SURFACE_ATTRIBUTE_Z_ORDER = (1<<0),
+  DVR_DISPLAY_SURFACE_ATTRIBUTE_VISIBLE = (1<<1),
+  DVR_DISPLAY_SURFACE_ATTRIBUTE_BLUR = (1<<2),
+  DVR_DISPLAY_SURFACE_ATTRIBUTE_EXCLUDE_FROM_BLUR = (1<<3),
+  DVR_DISPLAY_SURFACE_ATTRIBUTE_BLUR_BEHIND = (1<<4),
+};
+
+// Maximum number of buffers for a surface. Each buffer represents a single
+// frame and may actually be a buffer array if multiview rendering is in use.
+// Define so that it can be used in shader code.
+#define kSurfaceBufferMaxCount 4
+
+// Maximum number of views per surface. Each eye is a view, for example.
+#define kSurfaceViewMaxCount 4
+
+namespace android {
+namespace dvr {
+
+struct __attribute__((packed, aligned(16))) DisplaySurfaceMetadata {
+  // Array of orientations and translations corresponding with surface buffers.
+  // The index is associated with each allocated buffer by DisplaySurface and
+  // communicated to clients.
+  // The maximum number of buffers is hard coded here as 4 so that we can bind
+  // this data structure in GPU shaders.
+  float32x4_t orientation[kSurfaceBufferMaxCount];
+  float32x4_t translation[kSurfaceBufferMaxCount];
+};
+
+struct __attribute__((packed, aligned(16))) VideoMeshSurfaceMetadata {
+  // Array of transform matrices corresponding with surface buffers.
+  // Note that The index is associated with each allocated buffer by
+  // DisplaySurface instead of VideoMeshSurface due to the fact that the
+  // metadata here is interpreted as video mesh's transformation in each
+  // application's rendering frame.
+  float32x4x4_t transform[4][2];
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_DISPLAY_TYPES_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/dummy_native_window.h b/libs/vr/libdisplay/include/private/dvr/dummy_native_window.h
new file mode 100644
index 0000000..b03eeaa
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/dummy_native_window.h
@@ -0,0 +1,30 @@
+#ifndef ANDROID_DVR_DUMMY_NATIVE_WINDOW_H_
+#define ANDROID_DVR_DUMMY_NATIVE_WINDOW_H_
+
+#include <android/native_window.h>
+#include <ui/ANativeObjectBase.h>
+
+namespace android {
+namespace dvr {
+
+// DummyNativeWindow is an implementation of ANativeWindow that is
+// essentially empty and is used as a surface placeholder during context
+// creation for contexts that we don't intend to call eglSwapBuffers on.
+class DummyNativeWindow
+    : public ANativeObjectBase<ANativeWindow, DummyNativeWindow,
+                               LightRefBase<DummyNativeWindow> > {
+ public:
+  DummyNativeWindow();
+
+ private:
+  static int Query(const ANativeWindow* window, int what, int* value);
+  static int Perform(ANativeWindow* window, int operation, ...);
+
+  DummyNativeWindow(const DummyNativeWindow&) = delete;
+  void operator=(DummyNativeWindow&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_DUMMY_NATIVE_WINDOW_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/frame_history.h b/libs/vr/libdisplay/include/private/dvr/frame_history.h
new file mode 100644
index 0000000..53e0717
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/frame_history.h
@@ -0,0 +1,71 @@
+#ifndef ANDROID_DVR_FRAME_HISTORY_H_
+#define ANDROID_DVR_FRAME_HISTORY_H_
+
+#include <dvr/graphics.h>
+#include <pdx/file_handle.h>
+#include <private/dvr/ring_buffer.h>
+
+namespace android {
+namespace dvr {
+
+// FrameHistory tracks frame times from the start of rendering commands to when
+// the buffer is ready.
+class FrameHistory {
+ public:
+  FrameHistory();
+  explicit FrameHistory(int pending_frame_buffer_size);
+
+  void Reset(int pending_frame_buffer_size);
+
+  // Call when starting rendering commands (i.e. dvrBeginRenderFrame).
+  void OnFrameStart(uint32_t scheduled_vsync, int64_t scheduled_finish_ns);
+
+  // Call when rendering commands are finished (i.e. dvrPresent).
+  void OnFrameSubmit(android::pdx::LocalHandle&& fence);
+
+  // Call once per frame to see if any pending frames have finished.
+  void CheckForFinishedFrames();
+
+  // Uses the recently completed frame render times to predict how long the next
+  // frame will take, in vsync intervals. For example if the predicted frame
+  // time is 10ms and the vsync interval is 11ms, this will return 1. If the
+  // predicted frame time is 12ms and the vsync interval is 11ms, this will
+  // return 2.
+  int PredictNextFrameVsyncInterval(int64_t vsync_period_ns) const;
+
+  // Returns results for recently completed frames. Each frame's result is
+  // returned only once.
+  int GetPreviousFrameResults(DvrFrameScheduleResult* results,
+                              int result_count);
+
+  // Gets the vsync count for the most recently started frame. If there are no
+  // started frames this will return UINT32_MAX.
+  uint32_t GetCurrentFrameVsync() const;
+
+ private:
+  struct PendingFrame {
+    int64_t start_ns;
+    uint32_t scheduled_vsync;
+    int64_t scheduled_finish_ns;
+    android::pdx::LocalHandle fence;
+
+    PendingFrame();
+    PendingFrame(int64_t start_ns, uint32_t scheduled_vsync,
+                 int64_t scheduled_finish_ns,
+                 android::pdx::LocalHandle&& fence);
+
+    PendingFrame(PendingFrame&&) = default;
+    PendingFrame& operator=(PendingFrame&&) = default;
+    PendingFrame(const PendingFrame&) = delete;
+    PendingFrame& operator=(const PendingFrame&) = delete;
+  };
+
+  RingBuffer<PendingFrame> pending_frames_;
+  RingBuffer<DvrFrameScheduleResult> finished_frames_;
+  RingBuffer<int64_t> frame_duration_history_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_FRAME_HISTORY_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/gl_fenced_flush.h b/libs/vr/libdisplay/include/private/dvr/gl_fenced_flush.h
new file mode 100644
index 0000000..1d75335
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/gl_fenced_flush.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_DVR_GL_FENCED_FLUSH_H_
+#define ANDROID_DVR_GL_FENCED_FLUSH_H_
+
+#include <EGL/egl.h>
+#include <pdx/file_handle.h>
+
+namespace android {
+namespace dvr {
+
+// Creates a EGL_SYNC_NATIVE_FENCE_ANDROID and flushes. Returns the fence as a
+// file descriptor.
+pdx::LocalHandle CreateGLSyncAndFlush(EGLDisplay display);
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_GL_FENCED_FLUSH_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/graphics_private.h b/libs/vr/libdisplay/include/private/dvr/graphics_private.h
new file mode 100644
index 0000000..57c99da
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/graphics_private.h
@@ -0,0 +1,39 @@
+#ifndef ANDROID_DVR_GRAPHICS_PRIVATE_H_
+#define ANDROID_DVR_GRAPHICS_PRIVATE_H_
+
+#ifdef __ARM_NEON
+#include <arm_neon.h>
+#else
+#ifndef __FLOAT32X4T_86
+#define __FLOAT32X4T_86
+typedef float float32x4_t __attribute__ ((__vector_size__ (16)));
+typedef struct float32x4x4_t { float32x4_t val[4]; };
+#endif
+#endif
+
+#include <sys/cdefs.h>
+
+#include <dvr/graphics.h>
+
+__BEGIN_DECLS
+
+// Sets the pose used by the system for EDS. If dvrBeginRenderFrameEds() or
+// dvrBeginRenderFrameLateLatch() are called instead of dvrBeginRenderFrame()
+// it's not necessary to call this function. If this function is used, the call
+// must be made after dvrBeginRenderFrame() and before dvrPresent().
+//
+// @param[in] graphics_context The DvrGraphicsContext.
+// @param[in] render_pose_orientation Head pose orientation that rendering for
+//            this frame will be based off of. This must be an unmodified value
+//            from DvrPoseAsync, returned by dvrPoseGet.
+// @param[in] render_pose_translation Head pose translation that rendering for
+//            this frame will be based off of. This must be an unmodified value
+//            from DvrPoseAsync, returned by dvrPoseGet.
+// @return 0 on success or a negative error code on failure.
+int dvrSetEdsPose(DvrGraphicsContext* graphics_context,
+                  float32x4_t render_pose_orientation,
+                  float32x4_t render_pose_translation);
+
+__END_DECLS
+
+#endif  // ANDROID_DVR_GRAPHICS_PRIVATE_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/late_latch.h b/libs/vr/libdisplay/include/private/dvr/late_latch.h
new file mode 100644
index 0000000..d0eff51
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/late_latch.h
@@ -0,0 +1,186 @@
+#ifndef ANDROID_DVR_LATE_LATCH_H_
+#define ANDROID_DVR_LATE_LATCH_H_
+
+#include <atomic>
+#include <thread>
+#include <vector>
+
+#include <dvr/pose_client.h>
+#include <pdx/file_handle.h>
+#include <private/dvr/display_types.h>
+#include <private/dvr/graphics/shader_program.h>
+#include <private/dvr/graphics/vr_gl_extensions.h>
+#include <private/dvr/types.h>
+
+struct DvrPose;
+
+namespace android {
+namespace dvr {
+
+// Input data for late latch compute shader.
+struct LateLatchInput {
+  // For app late latch:
+  mat4 eye_from_head_mat[kSurfaceViewMaxCount];
+  mat4 proj_mat[kSurfaceViewMaxCount];
+  mat4 pose_offset[kSurfaceViewMaxCount];
+  // For EDS late latch only:
+  mat4 eds_mat1[kSurfaceViewMaxCount];
+  mat4 eds_mat2[kSurfaceViewMaxCount];
+  // For both app and EDS late latch:
+  uint32_t pose_index;
+  uint32_t render_pose_index;
+};
+
+// Output data for late latch shader. The application can use all or part of
+// this data by calling LateLatch::BindUniformBuffer.
+// This struct matches the layout of DvrGraphicsLateLatchData.
+struct LateLatchOutput {
+  mat4 view_proj_matrix[kSurfaceViewMaxCount];
+  mat4 view_matrix[kSurfaceViewMaxCount];
+  vec4 pose_quaternion;
+  vec4 pose_translation;
+};
+
+// LateLatch provides a facility for GL workloads to acquire a late-adjusted
+// model-view projection matrix, adjusted based on the position/quaternion pose
+// read from a buffer that is being written to asynchronously. The adjusted
+// MVP matrix is written to a GL buffer object via GL transform feedback.
+class LateLatch {
+ public:
+  enum BufferType {
+    kViewProjMatrix,
+    kViewMatrix,
+    kPoseQuaternion,
+    kPoseTranslation,
+    // Max transform feedback count is 4, so no more buffers can go here.
+    kNumBuffers,
+  };
+
+  static size_t GetBufferSize(BufferType type) {
+    switch (type) {
+      default:
+      case kViewProjMatrix:
+      case kViewMatrix:
+        return 4 * 4 * sizeof(float);
+      case kPoseQuaternion:
+      case kPoseTranslation:
+        return 4 * sizeof(float);
+    }
+  }
+
+  static size_t GetBufferOffset(BufferType type, int view) {
+    switch (type) {
+      default:
+      case kViewProjMatrix:
+        return offsetof(LateLatchOutput, view_proj_matrix) +
+               GetBufferSize(type) * view;
+      case kViewMatrix:
+        return offsetof(LateLatchOutput, view_matrix) +
+               GetBufferSize(type) * view;
+      case kPoseQuaternion:
+        return offsetof(LateLatchOutput, pose_quaternion);
+      case kPoseTranslation:
+        return offsetof(LateLatchOutput, pose_translation);
+    }
+  }
+
+  explicit LateLatch(bool is_app_late_latch);
+  LateLatch(bool is_app_late_latch, pdx::LocalHandle&& surface_metadata_fd);
+  ~LateLatch();
+
+  // Bind the late-latch output data as a GL_UNIFORM_BUFFER. For example,
+  // to bind just the view_matrix from the output:
+  // BindUniformBuffer(BINDING, offsetof(LateLatchOutput, view_matrix),
+  //                   sizeof(mat4));
+  // buffer_index is the index of one of the output buffers if more than 1 were
+  // requested in the constructor.
+  void BindUniformBuffer(GLuint ubo_binding, size_t offset, size_t size) const {
+    glBindBufferRange(GL_UNIFORM_BUFFER, ubo_binding, output_buffer_id_, offset,
+                      size);
+  }
+
+  void BindUniformBuffer(GLuint ubo_binding, BufferType type, int view) const {
+    glBindBufferRange(GL_UNIFORM_BUFFER, ubo_binding, output_buffer_id_,
+                      GetBufferOffset(type, view), GetBufferSize(type));
+  }
+
+  GLuint output_buffer_id() const { return output_buffer_id_; }
+
+  void UnbindUniformBuffer(GLuint ubo_binding) const {
+    glBindBufferBase(GL_UNIFORM_BUFFER, ubo_binding, 0);
+  }
+
+  void CaptureOutputData(LateLatchOutput* data) const;
+
+  // Add the late latch GL commands for this frame. This should be done just
+  // before the first application draw calls that are dependent on the head
+  // latest head pose.
+  //
+  // For efficiency, the application projection and eye_from_head matrices are
+  // passed through the late latch shader and output in various combinations to
+  // allow for both simple application vertex shaders that can take the view-
+  // projection matrix as-is and shaders that need to access the view matrix
+  // separately.
+  //
+  // GL state must be reset to default for this call.
+  void AddLateLatch(const LateLatchInput& data) const;
+
+  // After calling AddEdsLateLatch one or more times, this method must be called
+  // to add the necessary GL memory barrier to ensure late latch outputs are
+  // written before the EDS and warp shaders read them.
+  void PostEdsLateLatchBarrier() const {
+    // The transform feedback buffer is going to be read as a uniform by EDS,
+    // so we need a uniform memory barrier.
+    glMemoryBarrier(GL_UNIFORM_BARRIER_BIT);
+  }
+
+  // Typically not for use by application code. This method adds the EDS late
+  // latch that will adjust the application framebuffer with the latest head
+  // pose.
+  // buffer_index is the index of one of the output buffers if more than 1 were
+  // requested in the constructor.
+  void AddEdsLateLatch(const LateLatchInput& data,
+                       GLuint render_pose_buffer_object) const;
+
+  // For debugging purposes, capture the output during the next call to
+  // AddLateLatch. Set to NULL to reset.
+  void SetLateLatchDataCapture(LateLatchOutput* app_late_latch) {
+    app_late_latch_output_ = app_late_latch;
+  }
+
+  // For debugging purposes, capture the output during the next call to
+  // AddEdsLateLatch. Set to NULL to reset.
+  void SetEdsLateLatchDataCapture(LateLatchOutput* eds_late_latch) {
+    eds_late_latch_output_ = eds_late_latch;
+  }
+
+ private:
+  LateLatch(const LateLatch&) = delete;
+  LateLatch& operator=(const LateLatch&) = delete;
+
+  void LoadLateLatchShader();
+
+  // Late latch shader.
+  ShaderProgram late_latch_program_;
+
+  // Async pose ring buffer object.
+  GLuint pose_buffer_object_;
+
+  GLuint metadata_buffer_id_;
+
+  // Pose matrix buffers
+  GLuint input_buffer_id_;
+  GLuint output_buffer_id_;
+
+  bool is_app_late_latch_;
+  // During development, these can be used to capture the pose output data.
+  LateLatchOutput* app_late_latch_output_;
+  LateLatchOutput* eds_late_latch_output_;
+
+  DvrPose* pose_client_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_LATE_LATCH_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/native_buffer_queue.h b/libs/vr/libdisplay/include/private/dvr/native_buffer_queue.h
new file mode 100644
index 0000000..87e9c9f
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/native_buffer_queue.h
@@ -0,0 +1,73 @@
+#ifndef ANDROID_DVR_NATIVE_BUFFER_QUEUE_H_
+#define ANDROID_DVR_NATIVE_BUFFER_QUEUE_H_
+
+#include <semaphore.h>
+
+#include <mutex>
+#include <vector>
+
+#include <private/dvr/native_buffer.h>
+#include <private/dvr/ring_buffer.h>
+
+#include "display_client.h"
+
+namespace android {
+namespace dvr {
+
+// NativeBufferQueue manages a queue of NativeBufferProducers allocated from a
+// DisplaySurfaceClient. Buffers are automatically re-enqueued when released by
+// the consumer side.
+class NativeBufferQueue {
+ public:
+  // Create a queue with the given number of free buffers.
+  NativeBufferQueue(const std::shared_ptr<DisplaySurfaceClient>& surface,
+                    size_t capacity);
+  NativeBufferQueue(EGLDisplay display,
+                    const std::shared_ptr<DisplaySurfaceClient>& surface,
+                    size_t capacity);
+  ~NativeBufferQueue();
+
+  std::shared_ptr<DisplaySurfaceClient> surface() const { return surface_; }
+
+  // Dequeue a buffer from the free queue, blocking until one is available.
+  NativeBufferProducer* Dequeue();
+
+  // Enqueue a buffer at the end of the free queue.
+  void Enqueue(NativeBufferProducer* buf);
+
+  // Get the number of free buffers in the queue.
+  size_t GetFreeBufferCount() const;
+
+  // Get the total number of buffers managed by this queue.
+  size_t GetQueueCapacity() const;
+
+  // Accessors for display surface buffer attributes.
+  int width() const { return surface_->width(); }
+  int height() const { return surface_->height(); }
+  int format() const { return surface_->format(); }
+  int usage() const { return surface_->usage(); }
+
+ private:
+  // Wait for buffers to be released and enqueue them.
+  bool WaitForBuffers();
+
+  std::shared_ptr<DisplaySurfaceClient> surface_;
+
+  // A list of strong pointers to the buffers, used for managing buffer
+  // lifetime.
+  std::vector<android::sp<NativeBufferProducer>> buffers_;
+
+  // Used to implement queue semantics.
+  RingBuffer<NativeBufferProducer*> buffer_queue_;
+
+  // Epoll fd used to wait for BufferHub events.
+  int epoll_fd_;
+
+  NativeBufferQueue(const NativeBufferQueue&) = delete;
+  void operator=(NativeBufferQueue&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_NATIVE_BUFFER_QUEUE_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/screenshot_client.h b/libs/vr/libdisplay/include/private/dvr/screenshot_client.h
new file mode 100644
index 0000000..b6fc859
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/screenshot_client.h
@@ -0,0 +1,42 @@
+#ifndef ANDROID_DVR_SCREENSHOT_CLIENT_H_
+#define ANDROID_DVR_SCREENSHOT_CLIENT_H_
+
+#include <memory>
+#include <vector>
+
+#include <pdx/client.h>
+#include <private/dvr/display_rpc.h>
+#include <system/graphics.h>
+
+namespace android {
+namespace dvr {
+
+// Represents a connection to the screenshot service, which allows capturing an
+// upcoming frame as it is being rendered to the display.
+class ScreenshotClient : public pdx::ClientBase<ScreenshotClient> {
+ public:
+  int format() const { return format_; }
+
+  // Attempts to take a screenshot. If successful, sets *data to the contents
+  // of the screenshot and returns zero. Otherwise, returns a negative error
+  // code.
+  // |index| is used to match the requested buffer with various buffer layers.
+  int Take(std::vector<uint8_t>* data, int index, int* return_width,
+           int* return_height);
+
+ private:
+  friend BASE;
+
+  ScreenshotClient();
+
+  // Layout information for screenshots.
+  int format_;
+
+  ScreenshotClient(const ScreenshotClient&) = delete;
+  void operator=(const ScreenshotClient&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_SCREENSHOT_CLIENT_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/video_mesh_surface_client.h b/libs/vr/libdisplay/include/private/dvr/video_mesh_surface_client.h
new file mode 100644
index 0000000..a2659a6
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/video_mesh_surface_client.h
@@ -0,0 +1,42 @@
+#ifndef ANDROID_DVR_VIDEO_MESH_SURFACE_CLIENT_H_
+#define ANDROID_DVR_VIDEO_MESH_SURFACE_CLIENT_H_
+
+#include <base/macros.h>
+#include <private/dvr/buffer_hub_queue_client.h>
+#include <private/dvr/display_client.h>
+
+namespace android {
+namespace dvr {
+
+class VideoMeshSurfaceClient
+    : pdx::ClientBase<VideoMeshSurfaceClient, SurfaceClient> {
+ public:
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+
+  // This call assumes ownership of |handle|.
+  static std::unique_ptr<VideoMeshSurfaceClient> Import(
+      LocalChannelHandle handle);
+
+  std::shared_ptr<ProducerQueue> GetProducerQueue();
+
+  // Get the shared memory metadata buffer for this video mesh surface. If it is
+  // not yet allocated, this will allocate it.
+  volatile VideoMeshSurfaceMetadata* GetMetadataBufferPtr();
+
+ private:
+  friend BASE;
+
+  std::shared_ptr<android::dvr::ProducerQueue> producer_queue_;
+  VideoMeshSurfaceMetadata* mapped_metadata_buffer_;
+
+  explicit VideoMeshSurfaceClient(LocalChannelHandle handle);
+};
+
+}  // namespace dvr
+}  // namespace android
+
+struct DvrVideoMeshSurface {
+  std::shared_ptr<android::dvr::VideoMeshSurfaceClient> client;
+};
+
+#endif  // ANDROID_DVR_VIDEO_MESH_SURFACE_CLIENT_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/vsync_client.h b/libs/vr/libdisplay/include/private/dvr/vsync_client.h
new file mode 100644
index 0000000..32fa40f
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/vsync_client.h
@@ -0,0 +1,70 @@
+#ifndef ANDROID_DVR_VSYNC_CLIENT_H_
+#define ANDROID_DVR_VSYNC_CLIENT_H_
+
+#include <stdint.h>
+
+#include <pdx/client.h>
+#include <private/dvr/vsync_client_api.h>
+
+struct dvr_vsync_client {};
+
+namespace android {
+namespace dvr {
+
+/*
+ * VSyncClient is a remote interface to the vsync service in displayd.
+ * This class is used to wait for and retrieve information about the
+ * display vsync.
+ */
+class VSyncClient : public pdx::ClientBase<VSyncClient>,
+                    public dvr_vsync_client {
+ public:
+  /*
+   * Wait for the next vsync signal.
+   * The timestamp (in ns) is written into *ts when ts is non-NULL.
+   */
+  int Wait(int64_t* timestamp_ns);
+
+  /*
+   * Returns the file descriptor used to communicate with the vsync system
+   * service or -1 on error.
+   */
+  int GetFd();
+
+  /*
+   * Clears the select/poll/epoll event so that subsequent calls to
+   * these will not signal until the next vsync.
+   */
+  int Acknowledge();
+
+  /*
+   * Get the timestamp of the last vsync event in ns. This call has
+   * the same side effect on events as Acknowledge(), which saves
+   * an IPC message.
+   */
+  int GetLastTimestamp(int64_t* timestamp_ns);
+
+  /*
+   * Get vsync scheduling info.
+   * Get the estimated timestamp of the next GPU lens warp preemption event in
+   * ns. Also returns the corresponding vsync count that the next lens warp
+   * operation will target. This call has the same side effect on events as
+   * Acknowledge(), which saves an IPC message.
+   */
+  int GetSchedInfo(int64_t* vsync_period_ns, int64_t* next_timestamp_ns,
+                   uint32_t* next_vsync_count);
+
+ private:
+  friend BASE;
+
+  VSyncClient();
+  explicit VSyncClient(long timeout_ms);
+
+  VSyncClient(const VSyncClient&) = delete;
+  void operator=(const VSyncClient&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_VSYNC_CLIENT_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/vsync_client_api.h b/libs/vr/libdisplay/include/private/dvr/vsync_client_api.h
new file mode 100644
index 0000000..4cdbc71
--- /dev/null
+++ b/libs/vr/libdisplay/include/private/dvr/vsync_client_api.h
@@ -0,0 +1,44 @@
+#ifndef ANDROID_DVR_VSYNC_CLIENT_API_H_
+#define ANDROID_DVR_VSYNC_CLIENT_API_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// A client of the vsync service.
+//
+// The "dvr_vsync_client" structure wraps a client connection to the
+// system vsync service. It is used to synchronize application drawing
+// with the scanout of the display.
+typedef struct dvr_vsync_client dreamos_vsync_client;
+
+// Creates a new client to the system vsync service.
+dvr_vsync_client* dvr_vsync_client_create();
+
+// Destroys the vsync client.
+void dvr_vsync_client_destroy(dvr_vsync_client* client);
+
+// Blocks until the next vsync signal.
+// The timestamp (in ns) is written into |*timestamp_ns| when it is non-NULL.
+// Returns 0 upon success, or -errno.
+int dvr_vsync_client_wait(dvr_vsync_client* client, int64_t* timestamp_ns);
+
+// Returns the file descriptor used to communicate with the vsync service.
+int dvr_vsync_client_get_fd(dvr_vsync_client* client);
+
+// Clears the select/poll/epoll event so that subsequent calls to these
+// will not signal until the next vsync.
+int dvr_vsync_client_acknowledge(dvr_vsync_client* client);
+
+// Gets the timestamp of the last vsync signal in ns. This call has the
+// same side effects on events as acknowledge.
+int dvr_vsync_client_get_last_timestamp(dvr_vsync_client* client,
+                                        int64_t* timestamp_ns);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // ANDROID_DVR_VSYNC_CLIENT_API_H_
diff --git a/libs/vr/libdisplay/late_latch.cpp b/libs/vr/libdisplay/late_latch.cpp
new file mode 100644
index 0000000..3681e10
--- /dev/null
+++ b/libs/vr/libdisplay/late_latch.cpp
@@ -0,0 +1,461 @@
+#include "include/private/dvr/late_latch.h"
+
+#include <unistd.h>
+
+#include <fstream>
+#include <iostream>
+#include <string>
+
+#include <base/logging.h>
+#include <private/dvr/clock_ns.h>
+#include <private/dvr/debug.h>
+#include <private/dvr/graphics/gpu_profiler.h>
+#include <private/dvr/pose_client_internal.h>
+#include <private/dvr/sensor_constants.h>
+#include <private/dvr/types.h>
+
+#define PRINT_MATRIX 0
+
+#if PRINT_MATRIX
+#ifndef LOG_TAG
+#define LOG_TAG "latelatch"
+#endif
+#include <cutils/log.h>
+
+#define PE(str, ...)                                                  \
+  fprintf(stderr, "[%s:%d] " str, __FILE__, __LINE__, ##__VA_ARGS__); \
+  ALOGI("[%s:%d] " str, __FILE__, __LINE__, ##__VA_ARGS__)
+
+#define PV4(v) PE(#v "=%f,%f,%f,%f\n", v[0], v[1], v[2], v[3]);
+#define PM4(m)                                                               \
+  PE(#m ":\n %f,%f,%f,%f\n %f,%f,%f,%f\n %f,%f,%f,%f\n %f,%f,%f,%f\n",       \
+     m(0, 0), m(0, 1), m(0, 2), m(0, 3), m(1, 0), m(1, 1), m(1, 2), m(1, 3), \
+     m(2, 0), m(2, 1), m(2, 2), m(2, 3), m(3, 0), m(3, 1), m(3, 2), m(3, 3))
+#endif  // PRINT_MATRIX
+
+#define STRINGIFY2(s) #s
+#define STRINGIFY(s) STRINGIFY2(s)
+
+// Compute shader bindings.
+// GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS must be at least 8 for GLES 3.1.
+#define POSE_BINDING 0
+#define RENDER_POSE_BINDING 1
+#define INPUT_BINDING 2
+#define OUTPUT_BINDING 3
+
+using android::pdx::LocalHandle;
+
+namespace {
+
+static const std::string kShaderLateLatch = R"(  // NOLINT
+  struct Pose {
+    vec4 quat;
+    vec3 pos;
+  };
+
+  // Must match DvrPoseAsync C struct.
+  struct DvrPoseAsync {
+    vec4 orientation;
+    vec4 translation;
+    vec4 right_orientation;
+    vec4 right_translation;
+    vec4 angular_velocity;
+    vec4 velocity;
+    vec4 reserved[2];
+  };
+
+  // Must match LateLatchInputData C struct.
+  layout(binding = INPUT_BINDING, std140)
+  buffer InputData {
+    mat4 uEyeFromHeadMat[kSurfaceViewMaxCount];
+    mat4 uProjMat[kSurfaceViewMaxCount];
+    mat4 uPoseOffset[kSurfaceViewMaxCount];
+    mat4 uEdsMat1[kSurfaceViewMaxCount];
+    mat4 uEdsMat2[kSurfaceViewMaxCount];
+    uint uPoseIndex;
+    uint uRenderPoseIndex;
+  } bIn;
+
+  // std140 is to layout the structure in a consistent, standard way so we
+  // can access it from C++.
+  // This structure exactly matches the pose ring buffer in pose_client.h.
+  layout(binding = POSE_BINDING, std140)
+  buffer PoseBuffer {
+    DvrPoseAsync data[kPoseAsyncBufferTotalCount];
+  } bPose;
+
+  // Must stay in sync with DisplaySurfaceMetadata C struct.
+  // GPU thread 0 will exclusively read in a pose and capture it
+  // into this array.
+  layout(binding = RENDER_POSE_BINDING, std140)
+  buffer DisplaySurfaceMetadata {
+    vec4 orientation[kSurfaceBufferMaxCount];
+    vec4 translation[kSurfaceBufferMaxCount];
+  } bSurfaceData;
+
+  // Must stay in sync with DisplaySurfaceMetadata C struct.
+  // Each thread writes to a vertic
+  layout(binding = OUTPUT_BINDING, std140)
+  buffer Output {
+    mat4 viewProjMatrix[kSurfaceViewMaxCount];
+    mat4 viewMatrix[kSurfaceViewMaxCount];
+    vec4 quaternion;
+    vec4 translation;
+  } bOut;
+
+  // Thread 0 will also store the single quat/pos pair in shared variables
+  // for the other threads to use (left and right eye in this array).
+  shared Pose sharedPose[2];
+
+  // Rotate v1 by the given quaternion. This is based on mathfu's
+  // Quaternion::Rotate function. It is the typical implementation of this
+  // operation. Eigen has a similar method (Quaternion::_transformVector) that
+  // supposedly requires fewer operations, but I am skeptical of optimizing
+  // shader code without proper profiling first.
+  vec3 rotate(vec4 quat, vec3 v1) {
+    float ss = 2.0 * quat.w;
+    vec3 v = quat.xyz;
+    return ss * cross(v, v1) + (ss * quat.w - 1.0) * v1 +
+           2.0 * dot(v, v1) * v;
+  }
+
+  // See Eigen Quaternion::conjugate;
+  // Note that this isn't a true multiplicative inverse unless you can guarantee
+  // quat is also normalized, but that typically isn't an issue for our
+  // purposes.
+  vec4 quatInvert(vec4 quat) {
+    return vec4(-quat.xyz, quat.w);
+  }
+
+  // This is based on mathfu's Quaternion::operator*(Quaternion)
+  // Eigen's version is mathematically equivalent, just notationally different.
+  vec4 quatMul(vec4 q1, vec4 q2) {
+    return vec4(q1.w * q2.xyz + q2.w * q1.xyz + cross(q1.xyz, q2.xyz),
+                q1.w * q2.w - dot(q1.xyz, q2.xyz));
+  }
+
+  // Equivalent to pose.h GetObjectFromReferenceMatrix.
+  mat4 getInverseMatrix(Pose pose) {
+    // Invert quaternion and store fields the way Eigen does so we can
+    // keep in sync with Eigen methods easier.
+    vec4 quatInv = quatInvert(pose.quat);
+    vec3 v = quatInv.xyz;
+    float s = quatInv.w;
+    // Convert quaternion to matrix. See Eigen Quaternion::toRotationMatrix()
+    float x2 = v.x * v.x, y2 = v.y * v.y, z2 = v.z * v.z;
+    float sx = s * v.x, sy = s * v.y, sz = s * v.z;
+    float xz = v.x * v.z, yz = v.y * v.z, xy = v.x * v.y;
+    // Inverse translation.
+    vec3 point = -pose.pos;
+
+    return
+      mat4(1.0 - 2.0 * (y2 + z2), 2.0 * (xy + sz), 2.0 * (xz - sy), 0.0,
+           2.0 * (xy - sz), 1.0 - 2.0 * (x2 + z2), 2.0 * (sx + yz), 0.0,
+           2.0 * (sy + xz), 2.0 * (yz - sx), 1.0 - 2.0 * (x2 + y2), 0.0,
+           0.0, 0.0, 0.0, 1.0)*
+      mat4(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
+           point.x, point.y, point.z, 1.0);
+  }
+
+  void appLateLatch() {
+    uint poseIndex = (gl_LocalInvocationIndex & uint(1));
+    mat4 head_from_center = getInverseMatrix(sharedPose[poseIndex]);
+    bOut.viewMatrix[gl_LocalInvocationIndex] =
+        bIn.uEyeFromHeadMat[gl_LocalInvocationIndex] *
+        head_from_center * bIn.uPoseOffset[gl_LocalInvocationIndex];
+    bOut.viewProjMatrix[gl_LocalInvocationIndex] =
+        bIn.uProjMat[gl_LocalInvocationIndex] *
+        bOut.viewMatrix[gl_LocalInvocationIndex];
+  }
+
+  // Extract the app frame's pose.
+  Pose getPoseFromApp() {
+    Pose p;
+    p.quat = bSurfaceData.orientation[bIn.uRenderPoseIndex];
+    p.pos =  bSurfaceData.translation[bIn.uRenderPoseIndex].xyz;
+    return p;
+  }
+
+  // See Posef::GetPoseOffset.
+  Pose getPoseOffset(Pose p1, Pose p2) {
+    Pose p;
+    p.quat = quatMul(quatInvert(p2.quat), p1.quat);
+    // TODO(jbates) Consider enabling positional EDS when it is better
+    //              tested.
+    // p.pos = p2.pos - p1.pos;
+    p.pos = vec3(0.0);
+    return p;
+  }
+
+  void edsLateLatch() {
+    Pose pose1 = getPoseFromApp();
+    Pose correction;
+    // Ignore the texture pose if the quat is not unit-length.
+    float tex_quat_length = length(pose1.quat);
+    uint poseIndex = (gl_LocalInvocationIndex & uint(1));
+    if (abs(tex_quat_length - 1.0) < 0.001)
+      correction = getPoseOffset(pose1, sharedPose[poseIndex]);
+    else
+      correction = Pose(vec4(0, 0, 0, 1), vec3(0, 0, 0));
+    mat4 eye_old_from_eye_new_matrix = getInverseMatrix(correction);
+    bOut.viewProjMatrix[gl_LocalInvocationIndex] =
+        bIn.uEdsMat1[gl_LocalInvocationIndex] *
+        eye_old_from_eye_new_matrix * bIn.uEdsMat2[gl_LocalInvocationIndex];
+    // Currently unused, except for debugging:
+    bOut.viewMatrix[gl_LocalInvocationIndex] = eye_old_from_eye_new_matrix;
+  }
+
+  // One thread per surface view.
+  layout (local_size_x = kSurfaceViewMaxCount, local_size_y = 1,
+          local_size_z = 1) in;
+
+  void main() {
+    // First, thread 0 late latches pose and stores it into various places.
+    if (gl_LocalInvocationIndex == uint(0)) {
+      sharedPose[0].quat = bPose.data[bIn.uPoseIndex].orientation;
+      sharedPose[0].pos =  bPose.data[bIn.uPoseIndex].translation.xyz;
+      sharedPose[1].quat = bPose.data[bIn.uPoseIndex].right_orientation;
+      sharedPose[1].pos =  bPose.data[bIn.uPoseIndex].right_translation.xyz;
+      if (IS_APP_LATE_LATCH) {
+        bSurfaceData.orientation[bIn.uRenderPoseIndex] = sharedPose[0].quat;
+        bSurfaceData.translation[bIn.uRenderPoseIndex] = vec4(sharedPose[0].pos, 0.0);
+        // TODO(jbates) implement app late-latch support for separate eye poses.
+        // App late latch currently uses the same pose for both eye views.
+        sharedPose[1] = sharedPose[0];
+      }
+      bOut.quaternion = sharedPose[0].quat;
+      bOut.translation = vec4(sharedPose[0].pos, 0.0);
+    }
+
+    // Memory barrier to make sure all threads can see prior writes.
+    memoryBarrierShared();
+
+    // Execution barrier to block all threads here until all threads have
+    // reached this point -- ensures the late latching is done.
+    barrier();
+
+    if (IS_APP_LATE_LATCH)
+      appLateLatch();
+    else
+      edsLateLatch();
+  }
+)";
+
+}  // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+LateLatch::LateLatch(bool is_app_late_latch)
+    : LateLatch(is_app_late_latch, LocalHandle()) {}
+
+LateLatch::LateLatch(bool is_app_late_latch,
+                     LocalHandle&& surface_metadata_fd)
+    : is_app_late_latch_(is_app_late_latch),
+      app_late_latch_output_(NULL),
+      eds_late_latch_output_(NULL) {
+  CHECK_GL();
+  glGenBuffers(1, &input_buffer_id_);
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, input_buffer_id_);
+  glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(LateLatchInput), nullptr,
+               GL_DYNAMIC_DRAW);
+  glGenBuffers(1, &output_buffer_id_);
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, output_buffer_id_);
+  glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(LateLatchOutput), nullptr,
+               GL_DYNAMIC_COPY);
+  CHECK_GL();
+
+  LocalHandle pose_buffer_fd;
+  pose_client_ = dvrPoseCreate();
+  if (!pose_client_) {
+    LOG(ERROR) << "LateLatch Error: failed to create pose client";
+  } else {
+    int ret = privateDvrPoseGetRingBufferFd(pose_client_, &pose_buffer_fd);
+    if (ret < 0) {
+      LOG(ERROR) << "LateLatch Error: failed to get pose ring buffer";
+    }
+  }
+
+  glGenBuffers(1, &pose_buffer_object_);
+  glGenBuffers(1, &metadata_buffer_id_);
+  if (!glBindSharedBufferQCOM) {
+    LOG(ERROR) << "Error: Missing gralloc buffer extension, no pose data";
+  } else {
+    if (pose_buffer_fd) {
+      glBindBuffer(GL_SHADER_STORAGE_BUFFER, pose_buffer_object_);
+      glBindSharedBufferQCOM(GL_SHADER_STORAGE_BUFFER,
+                             kPoseAsyncBufferTotalCount * sizeof(DvrPoseAsync),
+                             pose_buffer_fd.Release());
+    }
+    CHECK_GL();
+  }
+
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, metadata_buffer_id_);
+  if (surface_metadata_fd && glBindSharedBufferQCOM) {
+    glBindSharedBufferQCOM(GL_SHADER_STORAGE_BUFFER,
+                           sizeof(DisplaySurfaceMetadata),
+                           surface_metadata_fd.Release());
+  } else {
+    // Fall back on internal metadata buffer when none provided, for example
+    // when distortion is done in the application process.
+    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(DisplaySurfaceMetadata),
+                 nullptr, GL_DYNAMIC_COPY);
+  }
+  CHECK_GL();
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+
+  CHECK_GL();
+  LoadLateLatchShader();
+}
+
+LateLatch::~LateLatch() {
+  glDeleteBuffers(1, &metadata_buffer_id_);
+  glDeleteBuffers(1, &input_buffer_id_);
+  glDeleteBuffers(1, &output_buffer_id_);
+  glDeleteBuffers(1, &pose_buffer_object_);
+  dvrPoseDestroy(pose_client_);
+}
+
+void LateLatch::LoadLateLatchShader() {
+  std::string str;
+  str += "\n#define POSE_BINDING " STRINGIFY(POSE_BINDING);
+  str += "\n#define RENDER_POSE_BINDING " STRINGIFY(RENDER_POSE_BINDING);
+  str += "\n#define INPUT_BINDING " STRINGIFY(INPUT_BINDING);
+  str += "\n#define OUTPUT_BINDING " STRINGIFY(OUTPUT_BINDING);
+  str += "\n#define kPoseAsyncBufferTotalCount " STRINGIFY(
+      kPoseAsyncBufferTotalCount);
+  str += "\n#define kSurfaceBufferMaxCount " STRINGIFY(kSurfaceBufferMaxCount);
+  str += "\n#define kSurfaceBufferMaxCount " STRINGIFY(kSurfaceBufferMaxCount);
+  str += "\n#define kSurfaceViewMaxCount " STRINGIFY(kSurfaceViewMaxCount);
+  str += "\n#define IS_APP_LATE_LATCH ";
+  str += is_app_late_latch_ ? "true" : "false";
+  str += "\n";
+  str += kShaderLateLatch;
+  late_latch_program_.Link(str);
+  CHECK_GL();
+}
+
+void LateLatch::CaptureOutputData(LateLatchOutput* data) const {
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, output_buffer_id_);
+  LateLatchOutput* out_data = static_cast<LateLatchOutput*>(glMapBufferRange(
+      GL_SHADER_STORAGE_BUFFER, 0, sizeof(LateLatchOutput), GL_MAP_READ_BIT));
+  *data = *out_data;
+  glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+  CHECK_GL();
+}
+
+void LateLatch::AddLateLatch(const LateLatchInput& data) const {
+  CHECK(is_app_late_latch_);
+  CHECK_GL();
+  late_latch_program_.Use();
+
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, RENDER_POSE_BINDING,
+                   metadata_buffer_id_);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, POSE_BINDING, pose_buffer_object_);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, OUTPUT_BINDING, output_buffer_id_);
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, input_buffer_id_);
+  LateLatchInput* adata = (LateLatchInput*)glMapBufferRange(
+      GL_SHADER_STORAGE_BUFFER, 0, sizeof(LateLatchInput),
+      GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
+  if (adata)
+    *adata = data;
+  else
+    LOG(ERROR) << "Error: LateLatchInput gl mapping is null";
+  glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, INPUT_BINDING, input_buffer_id_);
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+  CHECK_GL();
+
+  // The output buffer is going to be written but it may be read by
+  // earlier shaders, so we need a shader storage memory barrier.
+  glMemoryBarrier(GL_SHADER_STORAGE_BUFFER);
+
+  glDispatchCompute(1, 1, 1);
+  CHECK_GL();
+
+  // The transform feedback buffer is going to be read as a uniform by the app,
+  // so we need a uniform memory barrier.
+  glMemoryBarrier(GL_UNIFORM_BARRIER_BIT);
+
+  if (app_late_latch_output_) {
+    // Capture the output data:
+    CaptureOutputData(app_late_latch_output_);
+  }
+#if PRINT_MATRIX
+  // Print the composed matrix to stderr:
+  LateLatchOutput out_data;
+  CaptureOutputData(&out_data);
+  CHECK_GL();
+  PE("LL APP slot:%d\n", data.render_pose_index);
+  PM4(data.proj_mat[0]);
+  PM4(out_data.view_proj_matrix[0]);
+  PM4(out_data.view_proj_matrix[1]);
+  PM4(out_data.view_proj_matrix[2]);
+  PM4(out_data.view_proj_matrix[3]);
+  PM4(out_data.view_matrix[0]);
+  PM4(out_data.view_matrix[1]);
+  PM4(out_data.view_matrix[2]);
+  PM4(out_data.view_matrix[3]);
+  PV4(out_data.pose_quaternion);
+  PV4(out_data.pose_translation);
+#endif
+
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, RENDER_POSE_BINDING, 0);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, POSE_BINDING, 0);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, OUTPUT_BINDING, 0);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, INPUT_BINDING, 0);
+  glUseProgram(0);
+}
+
+void LateLatch::AddEdsLateLatch(const LateLatchInput& data,
+                                GLuint render_pose_buffer_object) const {
+  CHECK(!is_app_late_latch_);
+  late_latch_program_.Use();
+
+  // Fall back on internal buffer when none is provided.
+  if (!render_pose_buffer_object)
+    render_pose_buffer_object = metadata_buffer_id_;
+
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, RENDER_POSE_BINDING,
+                   render_pose_buffer_object);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, POSE_BINDING, pose_buffer_object_);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, OUTPUT_BINDING, output_buffer_id_);
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, input_buffer_id_);
+  LateLatchInput* adata = (LateLatchInput*)glMapBufferRange(
+      GL_SHADER_STORAGE_BUFFER, 0, sizeof(LateLatchInput),
+      GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
+  *adata = data;
+  glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
+  glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, INPUT_BINDING, input_buffer_id_);
+  CHECK_GL();
+
+  glDispatchCompute(1, 1, 1);
+  CHECK_GL();
+
+  if (eds_late_latch_output_) {
+    // Capture the output data:
+    CaptureOutputData(eds_late_latch_output_);
+  }
+#if PRINT_MATRIX
+  // Print the composed matrix to stderr:
+  LateLatchOutput out_data;
+  CaptureOutputData(&out_data);
+  CHECK_GL();
+  PE("LL EDS\n");
+  PM4(out_data.view_proj_matrix[0]);
+  PM4(out_data.view_matrix[0]);
+  PV4(out_data.pose_quaternion);
+  PV4(out_data.pose_translation);
+#endif
+
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, RENDER_POSE_BINDING, 0);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, POSE_BINDING, 0);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, OUTPUT_BINDING, 0);
+  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, INPUT_BINDING, 0);
+  glUseProgram(0);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/native_buffer_queue.cpp b/libs/vr/libdisplay/native_buffer_queue.cpp
new file mode 100644
index 0000000..2d1e23d
--- /dev/null
+++ b/libs/vr/libdisplay/native_buffer_queue.cpp
@@ -0,0 +1,152 @@
+#include "include/private/dvr/native_buffer_queue.h"
+
+#include <base/logging.h>
+#include <cutils/log.h>
+#include <sys/epoll.h>
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Trace.h>
+
+#include <array>
+
+#include <private/dvr/display_types.h>
+
+namespace android {
+namespace dvr {
+
+NativeBufferQueue::NativeBufferQueue(
+    const std::shared_ptr<DisplaySurfaceClient>& surface, size_t capacity)
+    : NativeBufferQueue(nullptr, surface, capacity) {}
+
+NativeBufferQueue::NativeBufferQueue(
+    EGLDisplay display, const std::shared_ptr<DisplaySurfaceClient>& surface,
+    size_t capacity)
+    : surface_(surface),
+      buffers_(capacity),
+      buffer_queue_(capacity) {
+  CHECK(surface);
+
+  epoll_fd_ = epoll_create(64);
+  if (epoll_fd_ < 0) {
+    ALOGE("NativeBufferQueue::NativeBufferQueue: Failed to create epoll fd: %s",
+          strerror(errno));
+    return;
+  }
+
+  // The kSurfaceBufferMaxCount must be >= the capacity so that shader code
+  // can bind surface buffer array data.
+  CHECK(kSurfaceBufferMaxCount >= capacity);
+
+  for (size_t i = 0; i < capacity; i++) {
+    uint32_t buffer_index = 0;
+    auto buffer = surface_->AllocateBuffer(&buffer_index);
+    if (!buffer) {
+      ALOGE("NativeBufferQueue::NativeBufferQueue: Failed to allocate buffer!");
+      return;
+    }
+
+    // TODO(jbates): store an index associated with each buffer so that we can
+    // determine which index in DisplaySurfaceMetadata it is associated
+    // with.
+    buffers_.push_back(new NativeBufferProducer(buffer, display, buffer_index));
+    NativeBufferProducer* native_buffer = buffers_.back().get();
+
+    epoll_event event = {.events = EPOLLIN | EPOLLET,
+                         .data = {.ptr = native_buffer}};
+    if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, buffer->event_fd(), &event) <
+        0) {
+      ALOGE(
+          "NativeBufferQueue::NativeBufferQueue: Failed to add buffer producer "
+          "to epoll set: %s",
+          strerror(errno));
+      return;
+    }
+
+    Enqueue(native_buffer);
+  }
+}
+
+NativeBufferQueue::~NativeBufferQueue() {
+  if (epoll_fd_ >= 0)
+    close(epoll_fd_);
+}
+
+bool NativeBufferQueue::WaitForBuffers() {
+  ATRACE_NAME("NativeBufferQueue::WaitForBuffers");
+  // Intentionally set this to one so that we don't waste time retrieving too
+  // many buffers.
+  constexpr size_t kMaxEvents = 1;
+  std::array<epoll_event, kMaxEvents> events;
+
+  while (buffer_queue_.IsEmpty()) {
+    int num_events = epoll_wait(epoll_fd_, events.data(), events.size(), -1);
+    if (num_events < 0 && errno != EINTR) {
+      ALOGE("NativeBufferQueue:WaitForBuffers: Failed to wait for buffers: %s",
+            strerror(errno));
+      return false;
+    }
+
+    ALOGD_IF(TRACE, "NativeBufferQueue::WaitForBuffers: num_events=%d",
+             num_events);
+
+    for (int i = 0; i < num_events; i++) {
+      NativeBufferProducer* buffer =
+          static_cast<NativeBufferProducer*>(events[i].data.ptr);
+      ALOGD_IF(TRACE,
+               "NativeBufferQueue::WaitForBuffers: event %d: buffer_id=%d "
+               "events=0x%x",
+               i, buffer->buffer()->id(), events[i].events);
+
+      if (events[i].events & EPOLLIN) {
+        const int ret = buffer->GainAsync();
+        if (ret < 0) {
+          ALOGE("NativeBufferQueue::WaitForBuffers: Failed to gain buffer: %s",
+                strerror(-ret));
+          continue;
+        }
+
+        Enqueue(buffer);
+      }
+    }
+  }
+
+  return true;
+}
+
+void NativeBufferQueue::Enqueue(NativeBufferProducer* buf) {
+  ATRACE_NAME("NativeBufferQueue::Enqueue");
+  if (buffer_queue_.IsFull()) {
+    ALOGE("NativeBufferQueue::Enqueue: Queue is full!");
+    return;
+  }
+
+  buffer_queue_.Append(buf);
+}
+
+NativeBufferProducer* NativeBufferQueue::Dequeue() {
+  ATRACE_NAME("NativeBufferQueue::Dequeue");
+  ALOGD_IF(TRACE, "NativeBufferQueue::Dequeue: count=%zd",
+           buffer_queue_.GetSize());
+
+  if (buffer_queue_.IsEmpty() && !WaitForBuffers())
+    return nullptr;
+
+  NativeBufferProducer* buf = buffer_queue_.Front();
+  buffer_queue_.PopFront();
+  if (buf == nullptr) {
+    ALOGE("NativeBufferQueue::Dequeue: Buffer at tail was nullptr!!!");
+    return nullptr;
+  }
+
+  return buf;
+}
+
+size_t NativeBufferQueue::GetFreeBufferCount() const {
+  return buffer_queue_.GetSize();
+}
+
+size_t NativeBufferQueue::GetQueueCapacity() const {
+  return buffer_queue_.GetCapacity();
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/native_window.cpp b/libs/vr/libdisplay/native_window.cpp
new file mode 100644
index 0000000..63c81ed
--- /dev/null
+++ b/libs/vr/libdisplay/native_window.cpp
@@ -0,0 +1,459 @@
+#include <EGL/egl.h>
+
+#include <android/native_window.h>
+#include <base/logging.h>
+#include <cutils/native_handle.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/timerfd.h>
+#include <system/window.h>
+#include <time.h>
+#include <ui/ANativeObjectBase.h>
+#include <utils/Errors.h>
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Trace.h>
+
+#include <cutils/log.h>
+
+#include <memory>
+#include <mutex>
+
+#include <dvr/graphics.h>
+#include <private/dvr/clock_ns.h>
+#include <private/dvr/display_client.h>
+#include <private/dvr/native_buffer.h>
+#include <private/dvr/native_buffer_queue.h>
+
+namespace {
+
+constexpr int kDefaultDisplaySurfaceUsage =
+    GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
+constexpr int kDefaultDisplaySurfaceFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+constexpr int kWarpedDisplaySurfaceFlags = 0;
+constexpr int kUnwarpedDisplaySurfaceFlags =
+    DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_EDS |
+    DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_DISTORTION |
+    DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_CAC;
+constexpr int kDefaultBufferCount = 4;
+
+}  // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+// NativeWindow is an implementation of ANativeWindow. This class interacts with
+// displayd through the DisplaySurfaceClient and NativeBufferQueue.
+class NativeWindow : public ANativeObjectBase<ANativeWindow, NativeWindow,
+                                              LightRefBase<NativeWindow> > {
+ public:
+  explicit NativeWindow(const std::shared_ptr<DisplaySurfaceClient>& surface);
+
+  void SetVisible(bool visible);
+  void SetZOrder(int z_order);
+  void PostEarly();
+
+ private:
+  friend class LightRefBase<NativeWindow>;
+
+  void Post(sp<NativeBufferProducer> buffer, int fence_fd);
+
+  static int SetSwapInterval(ANativeWindow* window, int interval);
+  static int DequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer,
+                           int* fence_fd);
+  static int QueueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer,
+                         int fence_fd);
+  static int CancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer,
+                          int fence_fd);
+  static int Query(const ANativeWindow* window, int what, int* value);
+  static int Perform(ANativeWindow* window, int operation, ...);
+
+  static int DequeueBuffer_DEPRECATED(ANativeWindow* window,
+                                      ANativeWindowBuffer** buffer);
+  static int CancelBuffer_DEPRECATED(ANativeWindow* window,
+                                     ANativeWindowBuffer* buffer);
+  static int QueueBuffer_DEPRECATED(ANativeWindow* window,
+                                    ANativeWindowBuffer* buffer);
+  static int LockBuffer_DEPRECATED(ANativeWindow* window,
+                                   ANativeWindowBuffer* buffer);
+
+  std::shared_ptr<DisplaySurfaceClient> surface_;
+
+  std::mutex lock_;
+  NativeBufferQueue buffer_queue_;
+  sp<NativeBufferProducer> next_post_buffer_;
+  bool next_buffer_already_posted_;
+
+  NativeWindow(const NativeWindow&) = delete;
+  void operator=(NativeWindow&) = delete;
+};
+
+NativeWindow::NativeWindow(const std::shared_ptr<DisplaySurfaceClient>& surface)
+    : surface_(surface),
+      buffer_queue_(surface, kDefaultBufferCount),
+      next_post_buffer_(nullptr),
+      next_buffer_already_posted_(false) {
+  ANativeWindow::setSwapInterval = SetSwapInterval;
+  ANativeWindow::dequeueBuffer = DequeueBuffer;
+  ANativeWindow::cancelBuffer = CancelBuffer;
+  ANativeWindow::queueBuffer = QueueBuffer;
+  ANativeWindow::query = Query;
+  ANativeWindow::perform = Perform;
+
+  ANativeWindow::dequeueBuffer_DEPRECATED = DequeueBuffer_DEPRECATED;
+  ANativeWindow::cancelBuffer_DEPRECATED = CancelBuffer_DEPRECATED;
+  ANativeWindow::lockBuffer_DEPRECATED = LockBuffer_DEPRECATED;
+  ANativeWindow::queueBuffer_DEPRECATED = QueueBuffer_DEPRECATED;
+}
+
+void NativeWindow::SetVisible(bool visible) { surface_->SetVisible(visible); }
+
+void NativeWindow::SetZOrder(int z_order) { surface_->SetZOrder(z_order); }
+
+void NativeWindow::PostEarly() {
+  ATRACE_NAME("NativeWindow::PostEarly");
+  ALOGI_IF(TRACE, "NativeWindow::PostEarly");
+
+  std::lock_guard<std::mutex> autolock(lock_);
+
+  if (!next_buffer_already_posted_) {
+    next_buffer_already_posted_ = true;
+
+    if (!next_post_buffer_.get()) {
+      next_post_buffer_ = buffer_queue_.Dequeue();
+    }
+    ATRACE_ASYNC_BEGIN("BufferPost", next_post_buffer_->buffer()->id());
+    Post(next_post_buffer_, -1);
+  }
+}
+
+void NativeWindow::Post(sp<NativeBufferProducer> buffer, int fence_fd) {
+  ATRACE_NAME(__PRETTY_FUNCTION__);
+  ALOGI_IF(TRACE, "NativeWindow::Post: buffer_id=%d, fence_fd=%d",
+           buffer->buffer()->id(), fence_fd);
+  ALOGW_IF(!surface_->visible(),
+           "NativeWindow::Post: Posting buffer on invisible surface!!!");
+  buffer->Post(fence_fd, 0);
+}
+
+int NativeWindow::SetSwapInterval(ANativeWindow* window, int interval) {
+  ALOGI_IF(TRACE, "SetSwapInterval: window=%p interval=%d", window, interval);
+  return 0;
+}
+
+int NativeWindow::DequeueBuffer(ANativeWindow* window,
+                                ANativeWindowBuffer** buffer, int* fence_fd) {
+  ATRACE_NAME(__PRETTY_FUNCTION__);
+
+  NativeWindow* self = getSelf(window);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  if (!self->next_post_buffer_.get()) {
+    self->next_post_buffer_ = self->buffer_queue_.Dequeue();
+  }
+  ATRACE_ASYNC_BEGIN("BufferDraw", self->next_post_buffer_->buffer()->id());
+  *fence_fd = self->next_post_buffer_->ClaimReleaseFence().Release();
+  *buffer = self->next_post_buffer_.get();
+
+  ALOGI_IF(TRACE, "NativeWindow::DequeueBuffer: fence_fd=%d", *fence_fd);
+  return 0;
+}
+
+int NativeWindow::QueueBuffer(ANativeWindow* window,
+                              ANativeWindowBuffer* buffer, int fence_fd) {
+  ATRACE_NAME("NativeWindow::QueueBuffer");
+  ALOGI_IF(TRACE, "NativeWindow::QueueBuffer: fence_fd=%d", fence_fd);
+
+  NativeWindow* self = getSelf(window);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  NativeBufferProducer* native_buffer =
+      static_cast<NativeBufferProducer*>(buffer);
+  ATRACE_ASYNC_END("BufferDraw", native_buffer->buffer()->id());
+  bool do_post = true;
+  if (self->next_buffer_already_posted_) {
+    // Check that the buffer is the one we expect, but handle it if this happens
+    // in production by allowing this buffer to post on top of the previous one.
+    DCHECK(native_buffer == self->next_post_buffer_.get());
+    if (native_buffer == self->next_post_buffer_.get()) {
+      do_post = false;
+      if (fence_fd >= 0)
+        close(fence_fd);
+    }
+  }
+  if (do_post) {
+    ATRACE_ASYNC_BEGIN("BufferPost", native_buffer->buffer()->id());
+    self->Post(native_buffer, fence_fd);
+  }
+  self->next_buffer_already_posted_ = false;
+  self->next_post_buffer_ = nullptr;
+
+  return NO_ERROR;
+}
+
+int NativeWindow::CancelBuffer(ANativeWindow* window,
+                               ANativeWindowBuffer* buffer, int fence_fd) {
+  ATRACE_NAME("NativeWindow::CancelBuffer");
+  ALOGI_IF(TRACE, "NativeWindow::CancelBuffer: fence_fd: %d", fence_fd);
+
+  NativeWindow* self = getSelf(window);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  NativeBufferProducer* native_buffer =
+      static_cast<NativeBufferProducer*>(buffer);
+  ATRACE_ASYNC_END("BufferDraw", native_buffer->buffer()->id());
+  ATRACE_INT("CancelBuffer", native_buffer->buffer()->id());
+  bool do_enqueue = true;
+  if (self->next_buffer_already_posted_) {
+    // Check that the buffer is the one we expect, but handle it if this happens
+    // in production by returning this buffer to the buffer queue.
+    DCHECK(native_buffer == self->next_post_buffer_.get());
+    if (native_buffer == self->next_post_buffer_.get()) {
+      do_enqueue = false;
+    }
+  }
+  if (do_enqueue) {
+    self->buffer_queue_.Enqueue(native_buffer);
+  }
+  if (fence_fd >= 0)
+    close(fence_fd);
+  self->next_buffer_already_posted_ = false;
+  self->next_post_buffer_ = nullptr;
+
+  return NO_ERROR;
+}
+
+int NativeWindow::Query(const ANativeWindow* window, int what, int* value) {
+  NativeWindow* self = getSelf(const_cast<ANativeWindow*>(window));
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  switch (what) {
+    case NATIVE_WINDOW_WIDTH:
+      *value = self->surface_->width();
+      return NO_ERROR;
+    case NATIVE_WINDOW_HEIGHT:
+      *value = self->surface_->height();
+      return NO_ERROR;
+    case NATIVE_WINDOW_FORMAT:
+      *value = self->surface_->format();
+      return NO_ERROR;
+    case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+      *value = 1;
+      return NO_ERROR;
+    case NATIVE_WINDOW_CONCRETE_TYPE:
+      *value = NATIVE_WINDOW_SURFACE;
+      return NO_ERROR;
+    case NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER:
+      *value = 1;
+      return NO_ERROR;
+    case NATIVE_WINDOW_DEFAULT_WIDTH:
+      *value = self->surface_->width();
+      return NO_ERROR;
+    case NATIVE_WINDOW_DEFAULT_HEIGHT:
+      *value = self->surface_->height();
+      return NO_ERROR;
+    case NATIVE_WINDOW_TRANSFORM_HINT:
+      *value = 0;
+      return NO_ERROR;
+  }
+
+  *value = 0;
+  return BAD_VALUE;
+}
+
+int NativeWindow::Perform(ANativeWindow* window, int operation, ...) {
+  NativeWindow* self = getSelf(window);
+  std::lock_guard<std::mutex> autolock(self->lock_);
+
+  va_list args;
+  va_start(args, operation);
+
+  // TODO(eieio): The following operations are not used at this time. They are
+  // included here to help document which operations may be useful and what
+  // parameters they take.
+  switch (operation) {
+    case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: {
+      int w = va_arg(args, int);
+      int h = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: w=%d h=%d", w, h);
+      return NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_SET_BUFFERS_FORMAT: {
+      int format = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFERS_FORMAT: format=%d", format);
+      return NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: {
+      int transform = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: transform=%d",
+               transform);
+      return NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_SET_USAGE: {
+      int usage = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_USAGE: usage=%d", usage);
+      return NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_CONNECT:
+    case NATIVE_WINDOW_DISCONNECT:
+    case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY:
+    case NATIVE_WINDOW_API_CONNECT:
+    case NATIVE_WINDOW_API_DISCONNECT:
+      // TODO(eieio): we should implement these
+      return NO_ERROR;
+
+    case NATIVE_WINDOW_SET_BUFFER_COUNT: {
+      int buffer_count = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFER_COUNT: bufferCount=%d",
+               buffer_count);
+      return NO_ERROR;
+    }
+    case NATIVE_WINDOW_SET_BUFFERS_DATASPACE: {
+      android_dataspace_t data_space =
+          static_cast<android_dataspace_t>(va_arg(args, int));
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_BUFFERS_DATASPACE: dataSpace=%d",
+               data_space);
+      return NO_ERROR;
+    }
+    case NATIVE_WINDOW_SET_SCALING_MODE: {
+      int mode = va_arg(args, int);
+      ALOGD_IF(TRACE, "NATIVE_WINDOW_SET_SCALING_MODE: mode=%d", mode);
+      return NO_ERROR;
+    }
+
+    case NATIVE_WINDOW_LOCK:
+    case NATIVE_WINDOW_UNLOCK_AND_POST:
+    case NATIVE_WINDOW_SET_CROP:
+    case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP:
+      return INVALID_OPERATION;
+  }
+
+  return NAME_NOT_FOUND;
+}
+
+int NativeWindow::DequeueBuffer_DEPRECATED(ANativeWindow* window,
+                                           ANativeWindowBuffer** buffer) {
+  int fence_fd = -1;
+  int ret = DequeueBuffer(window, buffer, &fence_fd);
+
+  // wait for fence
+  if (ret == NO_ERROR && fence_fd != -1)
+    close(fence_fd);
+
+  return ret;
+}
+
+int NativeWindow::CancelBuffer_DEPRECATED(ANativeWindow* window,
+                                          ANativeWindowBuffer* buffer) {
+  return CancelBuffer(window, buffer, -1);
+}
+
+int NativeWindow::QueueBuffer_DEPRECATED(ANativeWindow* window,
+                                         ANativeWindowBuffer* buffer) {
+  return QueueBuffer(window, buffer, -1);
+}
+
+int NativeWindow::LockBuffer_DEPRECATED(ANativeWindow* /*window*/,
+                                        ANativeWindowBuffer* /*buffer*/) {
+  return NO_ERROR;
+}
+
+}  // namespace dvr
+}  // namespace android
+
+static EGLNativeWindowType CreateDisplaySurface(int* display_width,
+                                                int* display_height, int format,
+                                                int usage, int flags) {
+  auto client = android::dvr::DisplayClient::Create();
+  if (!client) {
+    ALOGE("Failed to create display client!");
+    return nullptr;
+  }
+
+  // TODO(eieio,jbates): Consider passing flags and other parameters to get
+  // metrics based on specific surface requirements.
+  android::dvr::SystemDisplayMetrics metrics;
+  const int ret = client->GetDisplayMetrics(&metrics);
+  if (ret < 0) {
+    ALOGE("Failed to get display metrics: %s", strerror(-ret));
+    return nullptr;
+  }
+
+  int width, height;
+
+  if (flags & DVR_DISPLAY_SURFACE_FLAGS_DISABLE_SYSTEM_DISTORTION) {
+    width = metrics.display_native_width;
+    height = metrics.display_native_height;
+  } else {
+    width = metrics.distorted_width;
+    height = metrics.distorted_height;
+  }
+
+  std::shared_ptr<android::dvr::DisplaySurfaceClient> surface =
+      client->CreateDisplaySurface(width, height, format, usage, flags);
+
+  if (display_width)
+    *display_width = metrics.display_native_width;
+  if (display_height)
+    *display_height = metrics.display_native_height;
+
+  // Set the surface visible by default.
+  // TODO(eieio,jbates): Remove this from here and set visible somewhere closer
+  // to the application to account for situations where the application wants to
+  // create surfaces that will be used later or shouldn't be visible yet.
+  surface->SetVisible(true);
+
+  return new android::dvr::NativeWindow(surface);
+}
+
+std::shared_ptr<android::dvr::DisplaySurfaceClient> CreateDisplaySurfaceClient(
+    struct DvrSurfaceParameter* parameters,
+    /*out*/ android::dvr::SystemDisplayMetrics* metrics);
+
+extern "C" EGLNativeWindowType dvrCreateDisplaySurfaceExtended(
+    struct DvrSurfaceParameter* parameters) {
+  android::dvr::SystemDisplayMetrics metrics;
+  auto surface = CreateDisplaySurfaceClient(parameters, &metrics);
+  if (!surface) {
+    ALOGE("Failed to create display surface client");
+    return nullptr;
+  }
+  return new android::dvr::NativeWindow(surface);
+}
+
+extern "C" EGLNativeWindowType dvrCreateDisplaySurface() {
+  return CreateDisplaySurface(NULL, NULL, kDefaultDisplaySurfaceFormat,
+                              kDefaultDisplaySurfaceUsage,
+                              kUnwarpedDisplaySurfaceFlags);
+}
+
+extern "C" EGLNativeWindowType dvrCreateWarpedDisplaySurface(
+    int* display_width, int* display_height) {
+  return CreateDisplaySurface(
+      display_width, display_height, kDefaultDisplaySurfaceFormat,
+      kDefaultDisplaySurfaceUsage, kWarpedDisplaySurfaceFlags);
+}
+
+extern "C" void dvrDisplaySurfaceSetVisible(EGLNativeWindowType window,
+                                            int visible) {
+  auto native_window = reinterpret_cast<android::dvr::NativeWindow*>(window);
+  native_window->SetVisible(visible);
+}
+
+extern "C" void dvrDisplaySurfaceSetZOrder(EGLNativeWindowType window,
+                                           int z_order) {
+  auto native_window = reinterpret_cast<android::dvr::NativeWindow*>(window);
+  native_window->SetZOrder(z_order);
+}
+
+extern "C" void dvrDisplayPostEarly(EGLNativeWindowType window) {
+  auto native_window = reinterpret_cast<android::dvr::NativeWindow*>(window);
+  native_window->PostEarly();
+}
diff --git a/libs/vr/libdisplay/native_window_test.cpp b/libs/vr/libdisplay/native_window_test.cpp
new file mode 100644
index 0000000..2f3bc33
--- /dev/null
+++ b/libs/vr/libdisplay/native_window_test.cpp
@@ -0,0 +1,43 @@
+#include <iostream>
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <dvr/graphics.h>
+#include <private/dvr/display_client.h>
+
+#include <cpp_free_mock/cpp_free_mock.h>
+
+// Checks querying the VSync of the device on display surface creation.
+TEST(CreateDisplaySurface, QueryVSyncPeriod) {
+  using ::testing::_;
+
+  const uint64_t kExpectedVSync = 123456;
+
+  // We only care about the expected VSync value
+  android::dvr::DisplayMetrics metrics;
+  metrics.vsync_period_ns = kExpectedVSync;
+
+  uint64_t outPeriod;
+
+  DvrSurfaceParameter display_params[] = {
+      DVR_SURFACE_PARAMETER_IN(WIDTH, 256),
+      DVR_SURFACE_PARAMETER_IN(HEIGHT, 256),
+      DVR_SURFACE_PARAMETER_OUT(VSYNC_PERIOD, &outPeriod),
+      DVR_SURFACE_PARAMETER_LIST_END,
+  };
+
+  // inject the mocking code to the target method
+  auto mocked_function =
+      MOCKER(&android::dvr::DisplayClient::GetDisplayMetrics);
+
+  // instrument the mock function to return our custom metrics
+  EXPECT_CALL(*mocked_function, MOCK_FUNCTION(_, _))
+      .WillOnce(::testing::DoAll(::testing::SetArgPointee<1>(metrics),
+                                 ::testing::Return(0)));
+
+  ASSERT_NE(nullptr, dvrCreateDisplaySurfaceExtended(display_params));
+
+  EXPECT_EQ(kExpectedVSync, outPeriod);
+}
diff --git a/libs/vr/libdisplay/screenshot_client.cpp b/libs/vr/libdisplay/screenshot_client.cpp
new file mode 100644
index 0000000..78f5e0f
--- /dev/null
+++ b/libs/vr/libdisplay/screenshot_client.cpp
@@ -0,0 +1,66 @@
+#include "include/private/dvr/screenshot_client.h"
+
+#include <cutils/log.h>
+
+#include <mutex>
+
+#include <pdx/default_transport/client_channel_factory.h>
+#include <private/dvr/display_rpc.h>
+
+using android::pdx::Transaction;
+using android::pdx::rpc::ClientPayload;
+using android::pdx::rpc::MessageBuffer;
+using android::pdx::rpc::ReplyBuffer;
+
+namespace android {
+namespace dvr {
+
+namespace {
+// Maximum supported pixels for screenshot capture. If the actual target buffer
+// is more than this, an error will be reported.
+constexpr int kMaxScreenshotPixels = 6000 * 4000;
+}  // namespace
+
+int ScreenshotClient::Take(std::vector<uint8_t>* out_image, int index,
+                           int* return_width, int* return_height) {
+  if (format_ != HAL_PIXEL_FORMAT_RGB_888) {
+    ALOGE("ScreenshotClient::Take: Unsupported layout format: format=%d",
+          format_);
+    return -ENOSYS;
+  }
+
+  // TODO(eieio): Make a cleaner way to ensure enough capacity for send or
+  // receive buffers. This method assumes TLS buffers that will maintain
+  // capacity across calls within the same thread.
+  MessageBuffer<ReplyBuffer>::Reserve(kMaxScreenshotPixels * 3);
+  auto status = InvokeRemoteMethod<DisplayScreenshotRPC::TakeScreenshot>(index);
+  if (!status) {
+    ALOGE("ScreenshotClient::Take: Failed to take screenshot: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+
+  *return_width = status.get().width;
+  *return_height = status.get().height;
+  *out_image = std::move(status.take().buffer);
+  return 0;
+}
+
+ScreenshotClient::ScreenshotClient()
+    : BASE(pdx::default_transport::ClientChannelFactory::Create(
+          DisplayScreenshotRPC::kClientPath)) {
+  auto status = InvokeRemoteMethod<DisplayScreenshotRPC::GetFormat>();
+  if (!status) {
+    ALOGE(
+        "ScreenshotClient::ScreenshotClient: Failed to retrieve screenshot "
+        "layout: %s",
+        status.GetErrorMessage().c_str());
+
+    Close(status.error());
+  } else {
+    format_ = status.get();
+  }
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/system/CPPLINT.cfg b/libs/vr/libdisplay/system/CPPLINT.cfg
new file mode 100644
index 0000000..2f8a3c0
--- /dev/null
+++ b/libs/vr/libdisplay/system/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-build/header_guard
diff --git a/libs/vr/libdisplay/tests/graphics_app_tests.cpp b/libs/vr/libdisplay/tests/graphics_app_tests.cpp
new file mode 100644
index 0000000..7ea3952
--- /dev/null
+++ b/libs/vr/libdisplay/tests/graphics_app_tests.cpp
@@ -0,0 +1,177 @@
+#include <dvr/graphics.h>
+#include <gtest/gtest.h>
+
+TEST(GraphicsAppTests, CreateWarpedDisplaySurfaceParams) {
+  int width = 0, height = 0;
+  EGLNativeWindowType window = dvrCreateWarpedDisplaySurface(&width, &height);
+  EXPECT_GT(width, 0);
+  EXPECT_GT(height, 0);
+  EXPECT_NE(window, nullptr);
+}
+
+TEST(GraphicsAppTests, CreateDisplaySurface) {
+  EGLNativeWindowType window = dvrCreateDisplaySurface();
+  EXPECT_NE(window, nullptr);
+}
+
+TEST(GraphicsAppTests, CreateDisplaySurfaceExtended) {
+  int display_width = 0, display_height = 0;
+  int surface_width = 0, surface_height = 0;
+  float inter_lens_meters = 0.0f;
+  float left_fov[4] = {0.0f};
+  float right_fov[4] = {0.0f};
+  int disable_warp = 0;
+  DvrSurfaceParameter surface_params[] = {
+      DVR_SURFACE_PARAMETER_IN(DISABLE_DISTORTION, disable_warp),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_WIDTH, &display_width),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_HEIGHT, &display_height),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_WIDTH, &surface_width),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_HEIGHT, &surface_height),
+      DVR_SURFACE_PARAMETER_OUT(INTER_LENS_METERS, &inter_lens_meters),
+      DVR_SURFACE_PARAMETER_OUT(LEFT_FOV_LRBT, left_fov),
+      DVR_SURFACE_PARAMETER_OUT(RIGHT_FOV_LRBT, right_fov),
+      DVR_SURFACE_PARAMETER_LIST_END,
+  };
+
+  EGLNativeWindowType window = dvrCreateDisplaySurfaceExtended(surface_params);
+  EXPECT_NE(window, nullptr);
+  EXPECT_GT(display_width, 0);
+  EXPECT_GT(display_height, 0);
+  EXPECT_GT(surface_width, 0);
+  EXPECT_GT(surface_height, 0);
+  EXPECT_GT(inter_lens_meters, 0);
+  EXPECT_GT(left_fov[0], 0);
+  EXPECT_GT(left_fov[1], 0);
+  EXPECT_GT(left_fov[2], 0);
+  EXPECT_GT(left_fov[3], 0);
+  EXPECT_GT(right_fov[0], 0);
+  EXPECT_GT(right_fov[1], 0);
+  EXPECT_GT(right_fov[2], 0);
+  EXPECT_GT(right_fov[3], 0);
+}
+
+TEST(GraphicsAppTests, GetNativeDisplayDimensions) {
+  int width, height;
+  dvrGetNativeDisplayDimensions(&width, &height);
+  EXPECT_GT(width, 0);
+  EXPECT_GT(height, 0);
+}
+
+TEST(GraphicsAppTests, GetDisplaySurfaceInfo) {
+  int ret, width, height, format;
+  EGLNativeWindowType window = dvrCreateDisplaySurface();
+  ASSERT_NE(window, nullptr);
+  ret = dvrGetDisplaySurfaceInfo(window, &width, &height, &format);
+  ASSERT_EQ(0, ret);
+  ASSERT_GT(width, 0);
+  ASSERT_GT(height, 0);
+  ASSERT_NE(0, format);
+}
+
+// TODO(jpoichet) How to check it worked?
+TEST(GraphicsAppTests, GraphicsSurfaceSetVisible) {
+  DvrSurfaceParameter surface_params[] = {DVR_SURFACE_PARAMETER_LIST_END};
+  DvrGraphicsContext* context = nullptr;
+  int result = dvrGraphicsContextCreate(surface_params, &context);
+  ASSERT_GE(result, 0);
+  ASSERT_NE(context, nullptr);
+  dvrGraphicsSurfaceSetVisible(context, 0);
+  dvrGraphicsSurfaceSetVisible(context, 1);
+  dvrGraphicsSurfaceSetVisible(context, 2);
+}
+
+// TODO(jpoichet) How to check it worked?
+TEST(GraphicsAppTests, GraphicsSurfaceSetZOrder) {
+  DvrSurfaceParameter surface_params[] = {DVR_SURFACE_PARAMETER_LIST_END};
+  DvrGraphicsContext* context = nullptr;
+  int result = dvrGraphicsContextCreate(surface_params, &context);
+  ASSERT_GE(result, 0);
+  ASSERT_NE(context, nullptr);
+  dvrGraphicsSurfaceSetZOrder(context, -1);
+  dvrGraphicsSurfaceSetZOrder(context, 0);
+  dvrGraphicsSurfaceSetZOrder(context, 1);
+  dvrGraphicsSurfaceSetZOrder(context, 2);
+}
+
+TEST(GraphicsAppTests, GraphicsContext) {
+  DvrGraphicsContext* context = 0;
+  int display_width = 0, display_height = 0;
+  int surface_width = 0, surface_height = 0;
+  float inter_lens_meters = 0.0f;
+  float left_fov[4] = {0.0f};
+  float right_fov[4] = {0.0f};
+  uint64_t vsync_period = 0;
+  int disable_warp = 0;
+  DvrSurfaceParameter surface_params[] = {
+      DVR_SURFACE_PARAMETER_IN(DISABLE_DISTORTION, disable_warp),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_WIDTH, &display_width),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_HEIGHT, &display_height),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_WIDTH, &surface_width),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_HEIGHT, &surface_height),
+      DVR_SURFACE_PARAMETER_OUT(INTER_LENS_METERS, &inter_lens_meters),
+      DVR_SURFACE_PARAMETER_OUT(LEFT_FOV_LRBT, left_fov),
+      DVR_SURFACE_PARAMETER_OUT(RIGHT_FOV_LRBT, right_fov),
+      DVR_SURFACE_PARAMETER_OUT(VSYNC_PERIOD, &vsync_period),
+      DVR_SURFACE_PARAMETER_LIST_END,
+  };
+  dvrGraphicsContextCreate(surface_params, &context);
+  EXPECT_NE(nullptr, context);
+
+  DvrFrameSchedule schedule;
+  int wait_result = dvrGraphicsWaitNextFrame(context, 0, &schedule);
+  EXPECT_EQ(wait_result, 0);
+  EXPECT_GE(schedule.vsync_count, 0u);
+
+  dvrBeginRenderFrame(context);
+
+  // Check range of vsync period from 70fps to 100fps.
+  // TODO(jbates) Once we have stable hardware, clamp this range down further.
+  EXPECT_LT(vsync_period, 1000000000ul / 70ul);
+  EXPECT_GT(vsync_period, 1000000000ul / 100ul);
+
+  dvrPresent(context);
+  dvrGraphicsContextDestroy(context);
+}
+
+TEST(GraphicsAppTests, CustomSurfaceSize) {
+  DvrGraphicsContext* context = 0;
+  int display_width = 0, display_height = 0;
+  int surface_width = 0, surface_height = 0;
+  float inter_lens_meters = 0.0f;
+  float left_fov[4] = {0.0f};
+  float right_fov[4] = {0.0f};
+  int disable_warp = 0;
+  int req_width = 256, req_height = 128;
+  DvrSurfaceParameter surface_params[] = {
+      DVR_SURFACE_PARAMETER_IN(WIDTH, req_width),
+      DVR_SURFACE_PARAMETER_IN(HEIGHT, req_height),
+      DVR_SURFACE_PARAMETER_IN(DISABLE_DISTORTION, disable_warp),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_WIDTH, &display_width),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_HEIGHT, &display_height),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_WIDTH, &surface_width),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_HEIGHT, &surface_height),
+      DVR_SURFACE_PARAMETER_OUT(INTER_LENS_METERS, &inter_lens_meters),
+      DVR_SURFACE_PARAMETER_OUT(LEFT_FOV_LRBT, left_fov),
+      DVR_SURFACE_PARAMETER_OUT(RIGHT_FOV_LRBT, right_fov),
+      DVR_SURFACE_PARAMETER_LIST_END,
+  };
+  dvrGraphicsContextCreate(surface_params, &context);
+  EXPECT_NE(nullptr, context);
+
+  EXPECT_EQ(req_width, surface_width);
+  EXPECT_EQ(req_height, surface_height);
+  dvrGraphicsContextDestroy(context);
+}
+
+TEST(GraphicsAppTests, CreateVideoMeshSurface) {
+  DvrSurfaceParameter surface_params[] = {DVR_SURFACE_PARAMETER_LIST_END};
+  DvrGraphicsContext* context = nullptr;
+  int result = dvrGraphicsContextCreate(surface_params, &context);
+  EXPECT_NE(nullptr, context);
+  EXPECT_EQ(result, 0);
+
+  DvrVideoMeshSurface* surface = dvrGraphicsVideoMeshSurfaceCreate(context);
+  EXPECT_NE(nullptr, surface);
+
+  dvrGraphicsVideoMeshSurfaceDestroy(surface);
+}
diff --git a/libs/vr/libdisplay/video_mesh_surface_client.cpp b/libs/vr/libdisplay/video_mesh_surface_client.cpp
new file mode 100644
index 0000000..04cc194
--- /dev/null
+++ b/libs/vr/libdisplay/video_mesh_surface_client.cpp
@@ -0,0 +1,61 @@
+#include "include/private/dvr/video_mesh_surface_client.h"
+
+using android::pdx::LocalChannelHandle;
+
+namespace android {
+namespace dvr {
+
+/* static */
+std::unique_ptr<VideoMeshSurfaceClient> VideoMeshSurfaceClient::Import(
+    LocalChannelHandle handle) {
+  return VideoMeshSurfaceClient::Create(std::move(handle));
+}
+
+VideoMeshSurfaceClient::VideoMeshSurfaceClient(LocalChannelHandle handle)
+    : BASE(std::move(handle), SurfaceTypeEnum::VideoMesh),
+      mapped_metadata_buffer_(nullptr) {
+  // TODO(jwcai) import more data if needed.
+}
+
+std::shared_ptr<ProducerQueue> VideoMeshSurfaceClient::GetProducerQueue() {
+  if (producer_queue_ == nullptr) {
+    // Create producer queue through DisplayRPC
+    auto status =
+        InvokeRemoteMethod<DisplayRPC::VideoMeshSurfaceCreateProducerQueue>();
+    if (!status) {
+      ALOGE(
+          "VideoMeshSurfaceClient::GetProducerQueue: failed to create producer "
+          "queue: %s",
+          status.GetErrorMessage().c_str());
+      return nullptr;
+    }
+
+    producer_queue_ =
+        ProducerQueue::Import<VideoMeshSurfaceBufferMetadata>(status.take());
+  }
+  return producer_queue_;
+}
+
+volatile VideoMeshSurfaceMetadata*
+VideoMeshSurfaceClient::GetMetadataBufferPtr() {
+  if (!mapped_metadata_buffer_) {
+    if (auto buffer_producer = GetMetadataBuffer()) {
+      void* addr = nullptr;
+      const int ret = buffer_producer->GetBlobReadWritePointer(
+          sizeof(VideoMeshSurfaceMetadata), &addr);
+      if (ret < 0) {
+        ALOGE(
+            "VideoMeshSurfaceClient::GetMetadataBufferPtr: Failed to map "
+            "surface metadata: %s",
+            strerror(-ret));
+        return nullptr;
+      }
+      mapped_metadata_buffer_ = static_cast<VideoMeshSurfaceMetadata*>(addr);
+    }
+  }
+
+  return mapped_metadata_buffer_;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/vsync_client.cpp b/libs/vr/libdisplay/vsync_client.cpp
new file mode 100644
index 0000000..c4cad50
--- /dev/null
+++ b/libs/vr/libdisplay/vsync_client.cpp
@@ -0,0 +1,72 @@
+#include "include/private/dvr/vsync_client.h"
+
+#include <cutils/log.h>
+
+#include <pdx/default_transport/client_channel_factory.h>
+#include <private/dvr/display_rpc.h>
+
+using android::pdx::Transaction;
+
+namespace android {
+namespace dvr {
+
+VSyncClient::VSyncClient(long timeout_ms)
+    : BASE(pdx::default_transport::ClientChannelFactory::Create(
+               DisplayVSyncRPC::kClientPath),
+           timeout_ms) {}
+
+VSyncClient::VSyncClient()
+    : BASE(pdx::default_transport::ClientChannelFactory::Create(
+          DisplayVSyncRPC::kClientPath)) {}
+
+int VSyncClient::Wait(int64_t* timestamp_ns) {
+  auto status = InvokeRemoteMethod<DisplayVSyncRPC::Wait>();
+  if (!status) {
+    ALOGE("VSyncClient::Wait: Failed to wait for vsync: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+  *timestamp_ns = status.get();
+  return 0;
+}
+
+int VSyncClient::GetFd() { return event_fd(); }
+
+int VSyncClient::GetLastTimestamp(int64_t* timestamp_ns) {
+  auto status = InvokeRemoteMethod<DisplayVSyncRPC::GetLastTimestamp>();
+  if (!status) {
+    ALOGE("VSyncClient::GetLastTimestamp: Failed to get vsync timestamp: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+  *timestamp_ns = status.get();
+  return 0;
+}
+
+int VSyncClient::GetSchedInfo(int64_t* vsync_period_ns, int64_t* timestamp_ns,
+                              uint32_t* next_vsync_count) {
+  if (!vsync_period_ns || !timestamp_ns || !next_vsync_count)
+    return -EINVAL;
+
+  auto status = InvokeRemoteMethod<DisplayVSyncRPC::GetSchedInfo>();
+  if (!status) {
+    ALOGE("VSyncClient::GetSchedInfo:: Failed to get warp timestamp: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+
+  *vsync_period_ns = status.get().vsync_period_ns;
+  *timestamp_ns = status.get().timestamp_ns;
+  *next_vsync_count = status.get().next_vsync_count;
+  return 0;
+}
+
+int VSyncClient::Acknowledge() {
+  auto status = InvokeRemoteMethod<DisplayVSyncRPC::Acknowledge>();
+  ALOGE_IF(!status, "VSuncClient::Acknowledge: Failed to ack vsync because: %s",
+           status.GetErrorMessage().c_str());
+  return ReturnStatusOrError(status);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdisplay/vsync_client_api.cpp b/libs/vr/libdisplay/vsync_client_api.cpp
new file mode 100644
index 0000000..56103ed
--- /dev/null
+++ b/libs/vr/libdisplay/vsync_client_api.cpp
@@ -0,0 +1,34 @@
+#include "include/private/dvr/vsync_client_api.h"
+
+#include <private/dvr/vsync_client.h>
+
+extern "C" {
+
+dvr_vsync_client* dvr_vsync_client_create() {
+  auto client = android::dvr::VSyncClient::Create();
+  return static_cast<dvr_vsync_client*>(client.release());
+}
+
+void dvr_vsync_client_destroy(dvr_vsync_client* client) {
+  delete static_cast<android::dvr::VSyncClient*>(client);
+}
+
+int dvr_vsync_client_wait(dvr_vsync_client* client, int64_t* timestamp_ns) {
+  return static_cast<android::dvr::VSyncClient*>(client)->Wait(timestamp_ns);
+}
+
+int dvr_vsync_client_get_fd(dvr_vsync_client* client) {
+  return static_cast<android::dvr::VSyncClient*>(client)->GetFd();
+}
+
+int dvr_vsync_client_acknowledge(dvr_vsync_client* client) {
+  return static_cast<android::dvr::VSyncClient*>(client)->Acknowledge();
+}
+
+int dvr_vsync_client_get_last_timestamp(dvr_vsync_client* client,
+                                        int64_t* timestamp_ns) {
+  return static_cast<android::dvr::VSyncClient*>(client)->GetLastTimestamp(
+      timestamp_ns);
+}
+
+}  // extern "C"
diff --git a/libs/vr/libdvrcommon/Android.mk b/libs/vr/libdvrcommon/Android.mk
new file mode 100644
index 0000000..72afab2
--- /dev/null
+++ b/libs/vr/libdvrcommon/Android.mk
@@ -0,0 +1,81 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	frame_time_history.cpp \
+	revision.cpp \
+	revision_path.cpp \
+	sync_util.cpp \
+
+includeFiles := \
+  $(LOCAL_PATH)/include \
+  external/eigen \
+
+sharedLibraries := \
+	libbase \
+	libcutils \
+	liblog \
+	libutils \
+	libEGL \
+	libGLESv2 \
+	libui \
+	libgui \
+	libhardware
+
+staticLibraries := \
+	libchrome \
+	libpdx_default_transport \
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := \
+  $(includeFiles) \
+
+LOCAL_CFLAGS += -DLOG_TAG=\"libdvrcommon\"
+LOCAL_CFLAGS += -DTRACE=0
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+  $(includeFiles) \
+
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_MODULE := libdvrcommon
+include $(BUILD_STATIC_LIBRARY)
+
+testFiles := \
+  tests/numeric_test.cpp \
+  tests/pose_test.cpp \
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libdvrcommon_test
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+  $(testFiles) \
+
+LOCAL_C_INCLUDES := \
+  $(includeFiles) \
+
+LOCAL_SHARED_LIBRARIES := \
+  $(sharedLibraries) \
+
+LOCAL_STATIC_LIBRARIES := \
+  libgmock_main \
+  libgmock \
+  libgtest \
+  $(staticLibraries) \
+
+include $(BUILD_NATIVE_TEST)
+
diff --git a/libs/vr/libdvrcommon/frame_time_history.cpp b/libs/vr/libdvrcommon/frame_time_history.cpp
new file mode 100644
index 0000000..796498c
--- /dev/null
+++ b/libs/vr/libdvrcommon/frame_time_history.cpp
@@ -0,0 +1,37 @@
+#include <private/dvr/frame_time_history.h>
+
+#include <cutils/log.h>
+
+namespace android {
+namespace dvr {
+
+void FrameTimeHistory::AddSample(int64_t frame_time) {
+  if (size_ == frame_times_.size()) {
+    int64_t expired_frame_time = frame_times_[start_];
+    frame_times_[start_] = frame_time;
+    start_ = (start_ + 1) % frame_times_.size();
+    total_frame_time_ -= expired_frame_time;
+  } else {
+    frame_times_[(start_ + size_) % frame_times_.size()] = frame_time;
+    size_++;
+  }
+  total_frame_time_ += frame_time;
+}
+
+int FrameTimeHistory::GetSampleCount() const { return size_; }
+
+int64_t FrameTimeHistory::GetAverage() const {
+  LOG_ALWAYS_FATAL_IF(size_ == 0);
+  return total_frame_time_ / size_;
+}
+
+void FrameTimeHistory::ResetWithSeed(int64_t frame_time_seed) {
+  start_ = 0;
+  size_ = frame_times_.size();
+  for (size_t i = 0; i < size_; ++i)
+    frame_times_[i] = frame_time_seed;
+  total_frame_time_ = frame_time_seed * size_;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdvrcommon/include/private/dvr/benchmark.h b/libs/vr/libdvrcommon/include/private/dvr/benchmark.h
new file mode 100644
index 0000000..2dbb5f2
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/benchmark.h
@@ -0,0 +1,86 @@
+#ifndef ANDROID_DVR_BENCHMARK_H_
+#define ANDROID_DVR_BENCHMARK_H_
+
+#include <stdio.h>
+#include <time.h>
+
+#include <cutils/trace.h>
+
+#include <private/dvr/clock_ns.h>
+
+// Set benchmark traces, using Android systrace.
+//
+// The simplest one-parameter version of btrace automatically sets the
+// timestamp with the system clock. The other versions can optionally set the
+// timestamp manually, or pass additional data to be written to the log line.
+//
+// Example:
+// Btrace("Start execution");
+// ... code to benchmark ...
+// Btrace("End execution");
+//
+// Use compute_benchmarks.py (currently in dreamos/system/core/applications),
+// with the trace path "Start execution,End execution",
+// to report the elapsed time between the two calls.
+//
+// Btrace will either output to standard atrace, or to a file if specified.
+// The versions BtraceData also allow an int64_t to be included in the trace.
+
+// Btrace without data payload.
+static inline void Btrace(const char* name, int64_t nanoseconds_monotonic);
+static inline void Btrace(const char* name);
+static inline void Btrace(FILE* file, const char* name,
+                          int64_t nanoseconds_monotonic);
+static inline void Btrace(FILE* file, const char* name);
+
+// Btrace with data payload.
+static inline void BtraceData(const char* name, int64_t nanoseconds_monotonic,
+                              int64_t data);
+static inline void BtraceData(const char* name, int64_t data);
+static inline void BtraceData(FILE* file, const char* name,
+                              int64_t nanoseconds_monotonic, int64_t data);
+static inline void BtraceData(FILE* file, const char* name, int64_t data);
+
+static inline void Btrace(const char* name, int64_t nanoseconds_monotonic) {
+  const int kLogMessageLength = 256;
+  char log_message[kLogMessageLength];
+  snprintf(log_message, kLogMessageLength, "#btrace#%s", name);
+  atrace_int64(ATRACE_TAG_WEBVIEW, log_message, nanoseconds_monotonic);
+}
+
+static inline void Btrace(const char* name) {
+  Btrace(name, android::dvr::GetSystemClockNs());
+}
+
+static inline void Btrace(FILE* file, const char* name,
+                          int64_t nanoseconds_monotonic) {
+  fprintf(file, "#btrace#%s|%" PRId64 "\n", name, nanoseconds_monotonic);
+}
+
+static inline void Btrace(FILE* file, const char* name) {
+  Btrace(file, name, android::dvr::GetSystemClockNs());
+}
+
+static inline void BtraceData(const char* name, int64_t nanoseconds_monotonic,
+                              int64_t data) {
+  const int kLogMessageLength = 256;
+  char log_message[kLogMessageLength];
+  snprintf(log_message, kLogMessageLength, "#btrace#%s|%" PRId64, name, data);
+  atrace_int64(ATRACE_TAG_WEBVIEW, log_message, nanoseconds_monotonic);
+}
+
+static inline void BtraceData(const char* name, int64_t data) {
+  BtraceData(name, android::dvr::GetSystemClockNs(), data);
+}
+
+static inline void BtraceData(FILE* file, const char* name,
+                              int64_t nanoseconds_monotonic, int64_t data) {
+  fprintf(file, "#btrace#%s|%" PRId64 "|%" PRId64 "\n", name, data,
+          nanoseconds_monotonic);
+}
+
+static inline void BtraceData(FILE* file, const char* name, int64_t data) {
+  BtraceData(file, name, android::dvr::GetSystemClockNs(), data);
+}
+
+#endif  // ANDROID_DVR_BENCHMARK_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/clock_ns.h b/libs/vr/libdvrcommon/include/private/dvr/clock_ns.h
new file mode 100644
index 0000000..8e777ed
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/clock_ns.h
@@ -0,0 +1,84 @@
+#ifndef ANDROID_DVR_CLOCK_NS_H_
+#define ANDROID_DVR_CLOCK_NS_H_
+
+#include <stdint.h>
+#include <time.h>
+
+namespace android {
+namespace dvr {
+
+constexpr int64_t kNanosPerSecond = 1000000000ll;
+
+// Returns the standard Dream OS monotonic system time that corresponds with all
+// timestamps found in Dream OS APIs.
+static inline timespec GetSystemClock() {
+  timespec t;
+  clock_gettime(CLOCK_MONOTONIC, &t);
+  return t;
+}
+
+static inline timespec GetSystemClockRaw() {
+  timespec t;
+  clock_gettime(CLOCK_MONOTONIC_RAW, &t);
+  return t;
+}
+
+static inline int64_t GetSystemClockNs() {
+  timespec t = GetSystemClock();
+  int64_t ns = kNanosPerSecond * (int64_t)t.tv_sec + (int64_t)t.tv_nsec;
+  return ns;
+}
+
+static inline int64_t GetSystemClockRawNs() {
+  timespec t = GetSystemClockRaw();
+  int64_t ns = kNanosPerSecond * (int64_t)t.tv_sec + (int64_t)t.tv_nsec;
+  return ns;
+}
+
+static inline double NsToSec(int64_t nanoseconds) {
+  return nanoseconds / static_cast<double>(kNanosPerSecond);
+}
+
+static inline double GetSystemClockSec() { return NsToSec(GetSystemClockNs()); }
+
+static inline double GetSystemClockMs() { return GetSystemClockSec() * 1000.0; }
+
+// Converts a nanosecond timestamp to a timespec. Based on the kernel function
+// of the same name.
+static inline timespec NsToTimespec(int64_t ns) {
+  timespec t;
+  int32_t remainder;
+
+  t.tv_sec = ns / kNanosPerSecond;
+  remainder = ns % kNanosPerSecond;
+  if (remainder < 0) {
+    t.tv_nsec--;
+    remainder += kNanosPerSecond;
+  }
+  t.tv_nsec = remainder;
+
+  return t;
+}
+
+// Timestamp comparison functions that handle wrapping values correctly.
+static inline bool TimestampLT(int64_t a, int64_t b) {
+  return static_cast<int64_t>(static_cast<uint64_t>(a) -
+                              static_cast<uint64_t>(b)) < 0;
+}
+static inline bool TimestampLE(int64_t a, int64_t b) {
+  return static_cast<int64_t>(static_cast<uint64_t>(a) -
+                              static_cast<uint64_t>(b)) <= 0;
+}
+static inline bool TimestampGT(int64_t a, int64_t b) {
+  return static_cast<int64_t>(static_cast<uint64_t>(a) -
+                              static_cast<uint64_t>(b)) > 0;
+}
+static inline bool TimestampGE(int64_t a, int64_t b) {
+  return static_cast<int64_t>(static_cast<uint64_t>(a) -
+                              static_cast<uint64_t>(b)) >= 0;
+}
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_CLOCK_NS_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/debug.h b/libs/vr/libdvrcommon/include/private/dvr/debug.h
new file mode 100644
index 0000000..7db681a
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/debug.h
@@ -0,0 +1,37 @@
+#ifndef ANDROID_DVR_DEBUG_H_
+#define ANDROID_DVR_DEBUG_H_
+
+#include <GLES3/gl3.h>
+#include <math.h>
+
+#include <base/logging.h>
+
+#ifndef NDEBUG
+#define CHECK_GL()                          \
+  do {                                      \
+    const GLenum err = glGetError();        \
+    if (err != GL_NO_ERROR) {               \
+      LOG(ERROR) << "OpenGL error " << err; \
+    }                                       \
+  } while (0)
+
+#define CHECK_GL_FBO()                                        \
+  do {                                                        \
+    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); \
+    switch (status) {                                         \
+      case GL_FRAMEBUFFER_COMPLETE:                           \
+        break;                                                \
+      case GL_FRAMEBUFFER_UNSUPPORTED:                        \
+        LOG(ERROR) << "GL_FRAMEBUFFER_UNSUPPORTED";           \
+        break;                                                \
+      default:                                                \
+        LOG(ERROR) << "FBO user error: " << status;           \
+        break;                                                \
+    }                                                         \
+  } while (0)
+#else
+#define CHECK_GL()
+#define CHECK_GL_FBO()
+#endif
+
+#endif  // ANDROID_DVR_DEBUG_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/eigen.h b/libs/vr/libdvrcommon/include/private/dvr/eigen.h
new file mode 100644
index 0000000..defaf58
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/eigen.h
@@ -0,0 +1,57 @@
+#ifndef ANDROID_DVR_EIGEN_H_
+#define ANDROID_DVR_EIGEN_H_
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+namespace Eigen {
+
+// Eigen doesn't take advantage of C++ template typedefs, but we can
+template <class T, int N>
+using Vector = Matrix<T, N, 1>;
+
+template <class T>
+using Vector2 = Vector<T, 2>;
+
+template <class T>
+using Vector3 = Vector<T, 3>;
+
+template <class T>
+using Vector4 = Vector<T, 4>;
+
+template <class T, int N>
+using RowVector = Matrix<T, 1, N>;
+
+template <class T>
+using RowVector2 = RowVector<T, 2>;
+
+template <class T>
+using RowVector3 = RowVector<T, 3>;
+
+template <class T>
+using RowVector4 = RowVector<T, 4>;
+
+// In Eigen, the type you should be using for transformation matrices is the
+// `Transform` class, instead of a raw `Matrix`.
+// The `Projective` option means this will not make any assumptions about the
+// last row of the object, making this suitable for use as general OpenGL
+// projection matrices (which is the most common use-case). The one caveat
+// is that in order to apply this transformation to non-homogeneous vectors
+// (e.g., vec3), you must use the `.linear()` method to get the affine part of
+// the matrix.
+//
+// Example:
+//   mat4 transform;
+//   vec3 position;
+//   vec3 transformed = transform.linear() * position;
+//
+// Note, the use of N-1 is because the parameter passed to Eigen is the ambient
+// dimension of the transformation, not the size of the matrix iself.
+// However graphics programmers sometimes get upset when they see a 3 next
+// to a matrix when they expect a 4, so I'm hoping this will avoid that.
+template <class T, int N>
+using AffineMatrix = Transform<T, N-1, Projective>;
+
+}  // namespace Eigen
+
+#endif  // ANDROID_DVR_EIGEN_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/epoll_file_descriptor.h b/libs/vr/libdvrcommon/include/private/dvr/epoll_file_descriptor.h
new file mode 100644
index 0000000..8df741c
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/epoll_file_descriptor.h
@@ -0,0 +1,62 @@
+#ifndef LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_EPOLL_FILE_DESCRIPTOR_H_
+#define LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_EPOLL_FILE_DESCRIPTOR_H_
+
+#include <android-base/unique_fd.h>
+#include <base/logging.h>
+#include <sys/epoll.h>
+
+namespace android {
+namespace dvr {
+
+class EpollFileDescriptor {
+ public:
+  static const int CTL_ADD = EPOLL_CTL_ADD;
+  static const int CTL_MOD = EPOLL_CTL_MOD;
+  static const int CTL_DEL = EPOLL_CTL_DEL;
+
+  EpollFileDescriptor() : fd_(-1) {}
+
+  // Constructs an EpollFileDescriptor from an integer file descriptor and
+  // takes ownership.
+  explicit EpollFileDescriptor(int fd) : fd_(fd) {}
+
+  bool IsValid() const { return fd_.get() >= 0; }
+
+  int Create() {
+    if (IsValid()) {
+      LOG(WARNING) << "epoll fd has already been created.";
+      return -EALREADY;
+    }
+
+    fd_.reset(epoll_create(64));
+
+    if (fd_.get() < 0)
+      return -errno;
+    else
+      return 0;
+  }
+
+  int Control(int op, int target_fd, epoll_event* ev) {
+    if (epoll_ctl(fd_.get(), op, target_fd, ev) < 0)
+      return -errno;
+    else
+      return 0;
+  }
+
+  int Wait(epoll_event* events, int maxevents, int timeout) {
+    int ret = epoll_wait(fd_.get(), events, maxevents, timeout);
+
+    if (ret < 0)
+      return -errno;
+    else
+      return ret;
+  }
+
+ private:
+  base::unique_fd fd_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_EPOLL_FILE_DESCRIPTOR_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/field_of_view.h b/libs/vr/libdvrcommon/include/private/dvr/field_of_view.h
new file mode 100644
index 0000000..d0ee69c
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/field_of_view.h
@@ -0,0 +1,95 @@
+#ifndef ANDROID_DVR_FIELD_OF_VIEW_H_
+#define ANDROID_DVR_FIELD_OF_VIEW_H_
+
+#include <cmath>
+
+#include <private/dvr/eigen.h>
+
+namespace android {
+namespace dvr {
+
+// Encapsulates a generalized, asymmetric field of view with four half angles.
+// Each half angle denotes the angle between the corresponding frustum plane.
+// Together with a near and far plane, a FieldOfView forms the frustum of an
+// off-axis perspective projection.
+class FieldOfView {
+ public:
+  // The default constructor sets an angle of 0 (in any unit) for all four
+  // half-angles.
+  FieldOfView() : left_(0.0f), right_(0.0f), bottom_(0.0f), top_(0.0f) {}
+
+  // Constructs a FieldOfView from four angles.
+  FieldOfView(float left, float right, float bottom, float top)
+      : left_(left), right_(right), bottom_(bottom), top_(top) {}
+
+  explicit FieldOfView(const float* fov)
+      : FieldOfView(fov[0], fov[1], fov[2], fov[3]) {}
+
+  // Accessors for all four half-angles.
+  float GetLeft() const { return left_; }
+  float GetRight() const { return right_; }
+  float GetBottom() const { return bottom_; }
+  float GetTop() const { return top_; }
+
+  // Setters for all four half-angles.
+  void SetLeft(float left) { left_ = left; }
+  void SetRight(float right) { right_ = right; }
+  void SetBottom(float bottom) { bottom_ = bottom; }
+  void SetTop(float top) { top_ = top; }
+
+  Eigen::AffineMatrix<float, 4> GetProjectionMatrix(float z_near,
+                                                    float z_far) const {
+    float x_left = -std::tan(left_) * z_near;
+    float x_right = std::tan(right_) * z_near;
+    float y_bottom = -std::tan(bottom_) * z_near;
+    float y_top = std::tan(top_) * z_near;
+
+    float zero = 0.0f;
+    if (x_left == x_right || y_bottom == y_top || z_near == z_far ||
+        z_near <= zero || z_far <= zero) {
+      return Eigen::AffineMatrix<float, 4>::Identity();
+    }
+
+    float x = (2 * z_near) / (x_right - x_left);
+    float y = (2 * z_near) / (y_top - y_bottom);
+    float a = (x_right + x_left) / (x_right - x_left);
+    float b = (y_top + y_bottom) / (y_top - y_bottom);
+    float c = (z_near + z_far) / (z_near - z_far);
+    float d = (2 * z_near * z_far) / (z_near - z_far);
+
+    // Note: Eigen matrix initialization syntax is always 'column-major'
+    // even if the storage is row-major. Or in other words, just write the
+    // matrix like you'd see in a math textbook.
+    Eigen::AffineMatrix<float, 4> result;
+    result.matrix() << x,  0,  a,  0,
+                       0,  y,  b,  0,
+                       0,  0,  c,  d,
+                       0,  0, -1,  0;
+    return result;
+  }
+
+  static FieldOfView FromProjectionMatrix(
+      const Eigen::AffineMatrix<float, 4>& m) {
+    // Compute tangents.
+    float tan_vert_fov = 1.0f / m(1, 1);
+    float tan_horz_fov = 1.0f / m(0, 0);
+    float t = (m(1, 2) + 1.0f) * tan_vert_fov;
+    float b = (m(1, 2) - 1.0f) * tan_vert_fov;
+    float l = (m(0, 2) - 1.0f) * tan_horz_fov;
+    float r = (m(0, 2) + 1.0f) * tan_horz_fov;
+
+    return FieldOfView(std::atan(-l), std::atan(r), std::atan(-b),
+                       std::atan(t));
+  }
+
+ private:
+  float left_;
+  float right_;
+  float bottom_;
+  float top_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_FIELD_OF_VIEW_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/frame_time_history.h b/libs/vr/libdvrcommon/include/private/dvr/frame_time_history.h
new file mode 100644
index 0000000..008c636
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/frame_time_history.h
@@ -0,0 +1,33 @@
+#ifndef ANDROID_DVR_FRAME_TIME_HISTORY_H_
+#define ANDROID_DVR_FRAME_TIME_HISTORY_H_
+
+#include <stdint.h>
+
+#include <array>
+
+namespace android {
+namespace dvr {
+
+// Maintains frame time history and provides averaging utility methods.
+class FrameTimeHistory {
+ public:
+  void AddSample(int64_t frame_time);
+  int GetSampleCount() const;
+  int64_t GetAverage() const;
+  float GetAverageFps() const {
+    return 1000000000.0f / static_cast<float>(GetAverage());
+  }
+  void ResetWithSeed(int64_t frame_time_seed);
+
+ private:
+  static constexpr int kFrameTimeHistoryNumSamples = 30;
+  std::array<int64_t, kFrameTimeHistoryNumSamples> frame_times_;
+  int start_ = 0;
+  size_t size_ = 0;
+  int64_t total_frame_time_ = 0;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_FRAME_TIME_HISTORY_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/log_helpers.h b/libs/vr/libdvrcommon/include/private/dvr/log_helpers.h
new file mode 100644
index 0000000..c9f7f8e
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/log_helpers.h
@@ -0,0 +1,63 @@
+#ifndef ANDROID_DVR_LOG_HELPERS_H_
+#define ANDROID_DVR_LOG_HELPERS_H_
+
+#include <iomanip>
+
+#include <base/logging.h>
+#include <private/dvr/eigen.h>
+#include <private/dvr/field_of_view.h>
+
+namespace android {
+namespace dvr {
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& out,
+                                const Eigen::Vector<T, 2>& vec) {
+  return out << "vec2(" << vec.x() << ',' << vec.y() << ')';
+}
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& out,
+                                const Eigen::Vector<T, 3>& vec) {
+  return out << "vec3(" << vec.x() << ',' << vec.y() << ',' << vec.z() << ')';
+}
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& out,
+                                const Eigen::Vector<T, 4>& vec) {
+  return out << "vec4(" << vec.x() << ',' << vec.y() << ',' << vec.z() << ','
+             << vec.w() << ')';
+}
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& out,
+                                const Eigen::AffineMatrix<T, 4>& mat) {
+  out << std::setfill(' ') << std::setprecision(4) << std::fixed << std::showpos;
+  out << "\nmat4[";
+  out << std::setw(10) << mat(0, 0) << " " << std::setw(10) << mat(0, 1) << " "
+      << std::setw(10) << mat(0, 2) << " " << std::setw(10) << mat(0, 3);
+  out << "]\n    [";
+  out << std::setw(10) << mat(1, 0) << " " << std::setw(10) << mat(1, 1) << " "
+      << std::setw(10) << mat(1, 2) << " " << std::setw(10) << mat(1, 3);
+  out << "]\n    [";
+  out << std::setw(10) << mat(2, 0) << " " << std::setw(10) << mat(2, 1) << " "
+      << std::setw(10) << mat(2, 2) << " " << std::setw(10) << mat(2, 3);
+  out << "]\n    [";
+  out << std::setw(10) << mat(3, 0) << " " << std::setw(10) << mat(3, 1) << " "
+      << std::setw(10) << mat(3, 2) << " " << std::setw(10) << mat(3, 3);
+  out << "]\n";
+
+  return out;
+}
+
+inline std::ostream& operator<<(std::ostream& out, const FieldOfView& fov) {
+  return out << "fov(" << (fov.GetLeft() * 180.0f / M_PI) << ','
+             << (fov.GetRight() * 180.0f / M_PI) << ','
+             << (fov.GetBottom() * 180.0f / M_PI) << ','
+             << (fov.GetTop() * 180.0f / M_PI) << ')';
+}
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_LOG_HELPERS_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/numeric.h b/libs/vr/libdvrcommon/include/private/dvr/numeric.h
new file mode 100644
index 0000000..584236a
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/numeric.h
@@ -0,0 +1,178 @@
+#ifndef ANDROID_DVR_NUMERIC_H_
+#define ANDROID_DVR_NUMERIC_H_
+
+#include <cmath>
+#include <limits>
+#include <random>
+#include <type_traits>
+
+#include <private/dvr/eigen.h>
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+template <typename FT>
+static inline FT ToDeg(FT f) {
+  return f * static_cast<FT>(180.0 / M_PI);
+}
+
+template <typename FT>
+static inline FT ToRad(FT f) {
+  return f * static_cast<FT>(M_PI / 180.0);
+}
+
+// Adjusts `x` to the periodic range `[lo, hi]` (to normalize angle values
+// for example).
+template <typename T>
+T NormalizePeriodicRange(T x, T lo, T hi) {
+  T range_size = hi - lo;
+
+  while (x < lo) {
+    x += range_size;
+  }
+
+  while (x > hi) {
+    x -= range_size;
+  }
+
+  return x;
+}
+
+// Normalizes a measurement in radians.
+// @param x the angle to be normalized
+// @param centre the point around which to normalize the range
+// @return the value of x, normalized to the range [centre - 180, centre + 180]
+template <typename T>
+T NormalizeDegrees(T x, T centre = static_cast<T>(180.0)) {
+  return NormalizePeriodicRange(x, centre - static_cast<T>(180.0),
+                                centre + static_cast<T>(180.0));
+}
+
+// Normalizes a measurement in radians.
+// @param x the angle to be normalized
+// @param centre the point around which to normalize the range
+// @return the value of x, normalized to the range
+//         [centre - M_PI, centre + M_PI]
+// @remark the centre parameter is to make it possible to specify a different
+//         periodic range. This is useful if you are planning on comparing two
+//         angles close to 0 or M_PI, so that one might not accidentally end
+//         up on the other side of the range
+template <typename T>
+T NormalizeRadians(T x, T centre = static_cast<T>(M_PI)) {
+  return NormalizePeriodicRange(x, centre - static_cast<T>(M_PI),
+                                centre + static_cast<T>(M_PI));
+}
+
+static inline vec2i Round(const vec2& v) {
+  return vec2i(roundf(v.x()), roundf(v.y()));
+}
+
+static inline vec2i Scale(const vec2i& v, float scale) {
+  return vec2i(roundf(static_cast<float>(v.x()) * scale),
+               roundf(static_cast<float>(v.y()) * scale));
+}
+
+// Re-maps `x` from `[lba,uba]` to `[lbb,ubb]`.
+template <typename T>
+T ConvertRange(T x, T lba, T uba, T lbb, T ubb) {
+  return (((x - lba) * (ubb - lbb)) / (uba - lba)) + lbb;
+}
+
+template <typename R1, typename R2>
+static inline vec2 MapPoint(const vec2& pt, const R1& from, const R2& to) {
+  vec2 normalized((pt - vec2(from.p1)).array() / vec2(from.GetSize()).array());
+  return (normalized * vec2(to.GetSize())) + vec2(to.p1);
+}
+
+template <typename T>
+inline bool IsZero(const T& v,
+                   const T& tol = std::numeric_limits<T>::epsilon()) {
+  return std::abs(v) <= tol;
+}
+
+template <typename T>
+inline bool IsEqual(const T& a, const T& b,
+                    const T& tol = std::numeric_limits<T>::epsilon()) {
+  return std::abs(b - a) <= tol;
+}
+
+template <typename T>
+T Square(const T& x) {
+  return x * x;
+}
+
+template <typename T>
+T RandomInRange(T lo, T hi,
+                typename
+                std::enable_if<std::is_floating_point<T>::value>::type* = 0) {
+  std::random_device rd;
+  std::mt19937 gen(rd());
+  std::uniform_real_distribution<T> distro(lo, hi);
+  return distro(gen);
+}
+
+template <typename T>
+T RandomInRange(T lo, T hi,
+                typename
+                std::enable_if<std::is_integral<T>::value>::type* = 0) {
+  std::random_device rd;
+  std::mt19937 gen(rd());
+  std::uniform_int_distribution<T> distro(lo, hi);
+  return distro(gen);
+}
+
+template <typename Derived1, typename Derived2>
+Derived1 RandomInRange(
+    const Eigen::MatrixBase<Derived1>& lo,
+    const Eigen::MatrixBase<Derived2>& hi) {
+  using Matrix1_t = Eigen::MatrixBase<Derived1>;
+  using Matrix2_t = Eigen::MatrixBase<Derived2>;
+
+  EIGEN_STATIC_ASSERT_SAME_MATRIX_SIZE(Matrix1_t, Matrix2_t);
+
+  Derived1 result = Matrix1_t::Zero();
+
+  for (int row = 0; row < result.rows(); ++row) {
+    for (int col = 0; col < result.cols(); ++col) {
+      result(row, col) = RandomInRange(lo(row, col), hi(row, col));
+    }
+  }
+
+  return result;
+}
+
+template <typename T>
+T RandomRange(T x) {
+  return RandomInRange(-x, x);
+}
+
+template <typename T>
+T Clamp(T x, T lo, T hi) {
+  return std::min(std::max(x, lo), hi);
+}
+
+inline mat3 ScaleMatrix(const vec2& scale_xy) {
+  return mat3(Eigen::Scaling(scale_xy[0], scale_xy[1], 1.0f));
+}
+
+inline mat3 TranslationMatrix(const vec2& translation) {
+  return mat3(Eigen::Translation2f(translation));
+}
+
+inline mat4 TranslationMatrix(const vec3& translation) {
+  return mat4(Eigen::Translation3f(translation));
+}
+
+inline vec2 TransformPoint(const mat3& m, const vec2& p) {
+  return m.linear() * p + m.translation();
+}
+
+inline vec2 TransformVector(const mat3& m, const vec2& p) {
+  return m.linear() * p;
+}
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_NUMERIC_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/ortho.h b/libs/vr/libdvrcommon/include/private/dvr/ortho.h
new file mode 100644
index 0000000..fc0bce3
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/ortho.h
@@ -0,0 +1,31 @@
+#ifndef ANDROID_DVR_ORTHO_H_
+#define ANDROID_DVR_ORTHO_H_
+
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+template <class T>
+Eigen::AffineMatrix<T, 4> OrthoMatrix(T left, T right, T bottom, T top,
+                                      T znear, T zfar) {
+  Eigen::AffineMatrix<T, 4> result;
+  const T t2 = static_cast<T>(2);
+  const T a = t2 / (right - left);
+  const T b = t2 / (top - bottom);
+  const T c = t2 / (zfar - znear);
+  const T xoff = -(right + left) / (right - left);
+  const T yoff = -(top + bottom) / (top - bottom);
+  const T zoff = -(zfar + znear) / (zfar - znear);
+  const T t1 = static_cast<T>(1);
+  result.matrix() << a, 0, 0, xoff,
+            0, b, 0, yoff,
+            0, 0, c, zoff,
+            0, 0, 0, t1;
+  return result;
+}
+
+}  // namespace android
+}  // namespace dvr
+
+#endif  // ANDROID_DVR_ORTHO_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/platform_defines.h b/libs/vr/libdvrcommon/include/private/dvr/platform_defines.h
new file mode 100644
index 0000000..71d4c8c
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/platform_defines.h
@@ -0,0 +1,16 @@
+#ifndef ANDROID_DVR_PLATFORM_DEFINES_H_
+#define ANDROID_DVR_PLATFORM_DEFINES_H_
+
+// Platform-specific macros and defines.
+
+// QCOM's GRALLOC_USAGE_PRIVATE_ALLOC_UBWC usage bit.
+#define GRALLOC_USAGE_QCOM_FRAMEBUFFER_COMPRESSION GRALLOC_USAGE_PRIVATE_1
+
+// QCOM bit to use the ADSP heap. This carveout heap is accessible to Linux,
+// Hexagon DSPs, and the GPU.
+#define GRALLOC_USAGE_PRIVATE_ADSP_HEAP 0x01000000
+
+// Force a gralloc buffer to get the uncached ION option set.
+#define GRALLOC_USAGE_PRIVATE_UNCACHED 0x02000000
+
+#endif  // ANDROID_DVR_PLATFORM_DEFINES_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/pose.h b/libs/vr/libdvrcommon/include/private/dvr/pose.h
new file mode 100644
index 0000000..97944e8
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/pose.h
@@ -0,0 +1,118 @@
+#ifndef ANDROID_DVR_POSE_H_
+#define ANDROID_DVR_POSE_H_
+
+#include <private/dvr/eigen.h>
+
+namespace android {
+namespace dvr {
+
+// Encapsulates a 3D pose (rotation and position).
+//
+// @tparam T Data type for storing the position coordinate and rotation
+//     quaternion.
+template <typename T>
+class Pose {
+ public:
+  // Creates identity pose.
+  Pose()
+      : rotation_(Eigen::Quaternion<T>::Identity()),
+        position_(Eigen::Vector3<T>::Zero()) {}
+
+  // Initializes a pose with given rotation and position.
+  //
+  // rotation Initial rotation.
+  // position Initial position.
+  Pose(Eigen::Quaternion<T> rotation, Eigen::Vector3<T> position)
+      : rotation_(rotation), position_(position) {}
+
+  void Invert() {
+    rotation_ = rotation_.inverse();
+    position_ = rotation_ * -position_;
+  }
+
+  Pose Inverse() const {
+    Pose result(*this);
+    result.Invert();
+    return result;
+  }
+
+  // Compute the composition of this pose with another, storing the result
+  // in the current object
+  void ComposeInPlace(const Pose& other) {
+    position_ = position_ + rotation_ * other.position_;
+    rotation_ = rotation_ * other.rotation_;
+  }
+
+  // Computes the composition of this pose with another, and returns the result
+  Pose Compose(const Pose& other) const {
+    Pose result(*this);
+    result.ComposeInPlace(other);
+    return result;
+  }
+
+  Eigen::Vector3<T> TransformPoint(const Eigen::Vector3<T>& v) const {
+    return rotation_ * v + position_;
+  }
+
+  Eigen::Vector3<T> Transform(const Eigen::Vector3<T>& v) const {
+    return rotation_ * v;
+  }
+
+  Pose& operator*=(const Pose& other) {
+    ComposeInPlace(other);
+    return *this;
+  }
+
+  Pose operator*(const Pose& other) const { return Compose(other); }
+
+  // Gets the rotation of the 3D pose.
+  Eigen::Quaternion<T> GetRotation() const { return rotation_; }
+
+  // Gets the position of the 3D pose.
+  Eigen::Vector3<T> GetPosition() const { return position_; }
+
+  // Sets the rotation of the 3D pose.
+  void SetRotation(Eigen::Quaternion<T> rotation) { rotation_ = rotation; }
+
+  // Sets the position of the 3D pose.
+  void SetPosition(Eigen::Vector3<T> position) { position_ = position; }
+
+  // Gets a 4x4 matrix representing a transform from the reference space (that
+  // the rotation and position of the pose are relative to) to the object space.
+  Eigen::AffineMatrix<T, 4> GetObjectFromReferenceMatrix() const;
+
+  // Gets a 4x4 matrix representing a transform from the object space to the
+  // reference space (that the rotation and position of the pose are relative
+  // to).
+  Eigen::AffineMatrix<T, 4> GetReferenceFromObjectMatrix() const;
+
+ private:
+  Eigen::Quaternion<T> rotation_;
+  Eigen::Vector3<T> position_;
+};
+
+template <typename T>
+Eigen::AffineMatrix<T, 4> Pose<T>::GetObjectFromReferenceMatrix() const {
+  // The transfrom from the reference is the inverse of the pose.
+  Eigen::AffineMatrix<T, 4> matrix(rotation_.inverse().toRotationMatrix());
+  return matrix.translate(-position_);
+}
+
+template <typename T>
+Eigen::AffineMatrix<T, 4> Pose<T>::GetReferenceFromObjectMatrix() const {
+  // The transfrom to the reference.
+  Eigen::AffineMatrix<T, 4> matrix(rotation_.toRotationMatrix());
+  return matrix.pretranslate(position_);
+}
+
+//------------------------------------------------------------------------------
+// Type-specific typedefs.
+//------------------------------------------------------------------------------
+
+using Posef = Pose<float>;
+using Posed = Pose<double>;
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_POSE_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/range.h b/libs/vr/libdvrcommon/include/private/dvr/range.h
new file mode 100644
index 0000000..1d06c96
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/range.h
@@ -0,0 +1,43 @@
+#ifndef ANDROID_DVR_RANGE_H_
+#define ANDROID_DVR_RANGE_H_
+
+#include <private/dvr/eigen.h>
+
+namespace android {
+namespace dvr {
+
+// TODO(skiazyk): Replace all instances of this with Eigen::AlignedBox
+
+// Container of two points that define a 2D range.
+template <class T, int d>
+struct Range {
+  // Construct an uninitialized Range.
+  Range() {}
+  Range(Eigen::Vector<T, d> p1, Eigen::Vector<T, d> p2) : p1(p1), p2(p2) {}
+
+  static Range<T, d> FromSize(Eigen::Vector<T, d> p1,
+                              Eigen::Vector<T, d> size) {
+    return Range<T, d>(p1, p1 + size);
+  }
+
+  bool operator==(const Range<T, d>& rhs) const {
+    return p1 == rhs.p1 && p2 == rhs.p2;
+  }
+
+  Eigen::Vector<T, d> GetMinPoint() const { return p1; }
+
+  Eigen::Vector<T, d> GetMaxPoint() const { return p2; }
+
+  Eigen::Vector<T, d> GetSize() const { return p2 - p1; }
+
+  Eigen::Vector<T, d> p1;
+  Eigen::Vector<T, d> p2;
+};
+
+typedef Range<int, 2> Range2i;
+typedef Range<float, 2> Range2f;
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_RANGE_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/revision.h b/libs/vr/libdvrcommon/include/private/dvr/revision.h
new file mode 100644
index 0000000..dda0fce
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/revision.h
@@ -0,0 +1,47 @@
+#ifndef LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_REVISION_H_
+#define LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_REVISION_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// List of DreamOS products
+typedef enum DvrProduct {
+  DVR_PRODUCT_UNKNOWN,
+  DVR_PRODUCT_A00,
+  DVR_PRODUCT_A65R,
+  DVR_PRODUCT_TWILIGHT = DVR_PRODUCT_A65R
+} DvrProduct;
+
+// List of possible revisions.
+typedef enum DvrRevision {
+  DVR_REVISION_UNKNOWN,
+  DVR_REVISION_P1,
+  DVR_REVISION_P2,
+  DVR_REVISION_P3,
+} DvrRevision;
+
+// Query the device's product.
+//
+// @return DvrProduct value, or DvrProductUnknown on error.
+DvrProduct dvr_get_product();
+
+// Query the device's revision.
+//
+// @return DvrRevision value, or DvrRevisionUnknown on error.
+DvrRevision dvr_get_revision();
+
+// Returns the device's board revision string.
+//
+// @return NULL-terminated string such as 'a00-p1'.
+const char* dvr_get_product_revision_str();
+
+// Returns the device's serial number.
+//
+// @return Returns NULL on error, or a NULL-terminated string.
+const char* dvr_get_serial_number();
+
+#ifdef __cplusplus
+}
+#endif  // extern "C"
+#endif  // LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_REVISION_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/ring_buffer.h b/libs/vr/libdvrcommon/include/private/dvr/ring_buffer.h
new file mode 100644
index 0000000..44485a7
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/ring_buffer.h
@@ -0,0 +1,99 @@
+#ifndef ANDROID_DVR_RING_BUFFER_H_
+#define ANDROID_DVR_RING_BUFFER_H_
+
+#include <utility>
+#include <vector>
+
+namespace android {
+namespace dvr {
+
+// A simple ring buffer implementation.
+//
+// A vector works but you either have to keep track of start_ and size_ yourself
+// or erase() from the front which is inefficient.
+//
+// A deque works but the common usage pattern of Append() PopFront() Append()
+// PopFront() looks like it allocates each time size goes from 0 --> 1, which we
+// don't want. This class allocates only once.
+template <typename T>
+class RingBuffer {
+ public:
+  RingBuffer() { Reset(0); }
+
+  explicit RingBuffer(size_t capacity) { Reset(capacity); }
+
+  RingBuffer(const RingBuffer& other) = default;
+  RingBuffer(RingBuffer&& other) = default;
+  RingBuffer& operator=(const RingBuffer& other) = default;
+  RingBuffer& operator=(RingBuffer&& other) = default;
+
+  void Append(const T& val) {
+    if (IsFull())
+      PopFront();
+    Get(size_) = val;
+    size_++;
+  }
+
+  void Append(T&& val) {
+    if (IsFull())
+      PopFront();
+    Get(size_) = std::move(val);
+    size_++;
+  }
+
+  bool IsEmpty() const { return size_ == 0; }
+
+  bool IsFull() const { return size_ == buffer_.size(); }
+
+  size_t GetSize() const { return size_; }
+
+  size_t GetCapacity() const { return buffer_.size(); }
+
+  T& Get(size_t i) { return buffer_[(start_ + i) % buffer_.size()]; }
+
+  const T& Get(size_t i) const {
+    return buffer_[(start_ + i) % buffer_.size()];
+  }
+
+  const T& Back() const { return Get(size_ - 1); }
+
+  T& Back() { return Get(size_ - 1); }
+
+  const T& Front() const { return Get(0); }
+
+  T& Front() { return Get(0); }
+
+  void PopBack() {
+    if (size_ != 0) {
+      Get(size_ - 1) = T();
+      size_--;
+    }
+  }
+
+  void PopFront() {
+    if (size_ != 0) {
+      Get(0) = T();
+      start_ = (start_ + 1) % buffer_.size();
+      size_--;
+    }
+  }
+
+  void Clear() { Reset(GetCapacity()); }
+
+  void Reset(size_t capacity) {
+    start_ = size_ = 0;
+    buffer_.clear();
+    buffer_.resize(capacity);
+  }
+
+ private:
+  // Ideally we'd allocate our own memory and use placement new to instantiate
+  // instances of T instead of using a vector, but the vector is simpler.
+  std::vector<T> buffer_;
+  size_t start_, size_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_RING_BUFFER_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/sync_util.h b/libs/vr/libdvrcommon/include/private/dvr/sync_util.h
new file mode 100644
index 0000000..c6911bc
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/sync_util.h
@@ -0,0 +1,31 @@
+#ifndef ANDROID_DVR_SYNC_UTIL_H_
+#define ANDROID_DVR_SYNC_UTIL_H_
+
+#include <cstdint>
+#include <type_traits>
+
+namespace android {
+namespace dvr {
+
+constexpr size_t kFenceInfoBufferSize = 4096;
+
+// This buffer is eventually mapped to a sync_fence_info_data struct (from
+// sync.h), whose largest member is a uint32_t. We align to 8 bytes to be extra
+// cautious.
+using FenceInfoBuffer = std::aligned_storage<kFenceInfoBufferSize, 8>::type;
+
+// Get fence info. Internally this works just like sync_fence_info(), except the
+// caller supplies a memory buffer instead of allocating memory.
+// On success, returns 0. On error, -1 is returned, and errno is set.
+int GetSyncFenceInfo(int fence_fd, FenceInfoBuffer* buffer);
+
+// Returns the timestamp when the fence was first signaled. buffer is used as
+// described in GetSyncFenceInfo().
+// On success, returns 0. On error, -1 is returned, and errno is set.
+int GetFenceSignaledTimestamp(int fence_fd, FenceInfoBuffer* buffer,
+                              int64_t* timestamp);
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_SYNC_UTIL_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/test/test_macros.h b/libs/vr/libdvrcommon/include/private/dvr/test/test_macros.h
new file mode 100644
index 0000000..6048652
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/test/test_macros.h
@@ -0,0 +1,124 @@
+#ifndef LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_TEST_TEST_MACROS_H_
+#define LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_TEST_TEST_MACROS_H_
+
+#include <gtest/gtest.h>
+
+#include <cmath>
+
+#include <private/dvr/numeric.h>
+
+namespace android {
+namespace dvr {
+
+template <int N, typename A, typename B, typename T>
+::testing::AssertionResult CmpArrayLikeFloatEq(
+    const char* expectedStr, const char* actualStr, const char* toleranceStr,
+    const A& expected, const B& actual, const T& tolerance) {
+  for (int i = 0; i < N; ++i) {
+    if (!IsEqual(expected[i], actual[i], tolerance)) {
+      return ::testing::AssertionFailure()
+             << "\"" << expectedStr << "\" and \"" << actualStr
+             << "\" differ at element " << i << " by at least " << tolerance
+             << " : "
+             << " Expected \"" << expected[i] << "\", was \"" << actual[i]
+             << "\".";
+    }
+  }
+
+  return ::testing::AssertionSuccess();
+}
+
+template <int N, typename A, typename B, typename T>
+::testing::AssertionResult CmpMatrixLikeFloatEq(
+    const char* expectedStr, const char* actualStr, const char* toleranceStr,
+    const A& expected, const B& actual, const T& tolerance) {
+  for (int r = 0; r < N; ++r) {
+    for (int c = 0; c < N; ++c) {
+      if (!IsEqual(expected(r, c), actual(r, c), tolerance)) {
+        return ::testing::AssertionFailure()
+               << "\"" << expectedStr << "\" and \"" << actualStr
+               << "\" differ at (" << r << "," << c << ")"
+               << " by at least " << tolerance << " : "
+               << " Expected \"" << expected(r, c) << "\", was \""
+               << actual(r, c) << "\".";
+      }
+    }
+  }
+
+  return ::testing::AssertionSuccess();
+}
+
+template <int N, typename A, typename B, typename T>
+::testing::AssertionResult CmpArrayLikeFloatNe(
+    const char* expectedStr, const char* actualStr, const char* toleranceStr,
+    const A& expected, const B& actual, const T& tolerance) {
+  for (int i = 0; i < N; ++i) {
+    if (!IsEqual(expected[i], actual[i], tolerance)) {
+      return ::testing::AssertionSuccess();
+    }
+  }
+
+  ::testing::Message message;
+  message << "Expected \"" << expectedStr
+          << "\" to differ from provided value \"" << actualStr
+          << "\" by at least " << tolerance << ".";
+
+  return ::testing::AssertionFailure(message);
+}
+
+template <int N, typename A, typename B, typename T>
+::testing::AssertionResult CmpMatrixLikeFloatNe(
+    const char* expectedStr, const char* actualStr, const char* toleranceStr,
+    const A& expected, const B& actual, const T& tolerance) {
+  for (int r = 0; r < N; ++r) {
+    for (int c = 0; c < N; ++c) {
+      if (!IsEqual(expected(r, c), actual(r, c), tolerance)) {
+        return ::testing::AssertionSuccess();
+      }
+    }
+  }
+
+  ::testing::Message message;
+  message << "Expected \"" << expectedStr
+          << "\" to differ from provided value \"" << actualStr
+          << "\" by at least " << tolerance << ".";
+
+  return ::testing::AssertionFailure(message);
+}
+
+}  // namespace dvr
+}  // namespace android
+
+#define EXPECT_VEC3_NEAR(expected, actual, tol)                               \
+  EXPECT_PRED_FORMAT3(android::dvr::CmpArrayLikeFloatEq<3>, expected, actual, \
+                      tol)
+
+#define EXPECT_VEC3_NOT_NEAR(expected, actual, tol)                           \
+  EXPECT_PRED_FORMAT3(android::dvr::CmpArrayLikeFloatNe<3>, expected, actual, \
+                      tol)
+
+#define EXPECT_QUAT_NEAR(expected, actual, tol)                                \
+  EXPECT_PRED_FORMAT3(android::dvr::CmpArrayLikeFloatEq<3>, expected.coeffs(), \
+                      actual.coeffs(), tol)
+
+#define EXPECT_QUAT_NOT_NEAR(expected, actual, tol)                            \
+  EXPECT_PRED_FORMAT3(android::dvr::CmpArrayLikeFloatNe<3>, expected.coeffs(), \
+                      actual.coeffs(), tol)
+
+#define EXPECT_MAT4_NEAR(expected, actual, tol)                                \
+  EXPECT_PRED_FORMAT3(android::dvr::CmpMatrixLikeFloatEq<4>, expected, actual, \
+                      tol)
+
+#define EXPECT_MAT4_NOT_NEAR(expected, actual, tol)                            \
+  EXPECT_PRED_FORMAT3(android::dvr::CmpMatrixLikeFloatNe<4>, expected, actual, \
+                      tol)
+
+#define EXPECT_MAT3_NEAR(expected, actual, tol) \
+  EXPECT_PRED_FORMAT3(android::dvr              \
+                      : CmpMatrixLikeFloatEq<3>, expected, actual, tol)
+
+#define EXPECT_MAT3_NOT_NEAR(expected, actual, tol)                            \
+  EXPECT_PRED_FORMAT3(android::dvr::CmpMatrixLikeFloatNe<3>, expected, actual, \
+                      tol)
+
+#endif  // LIBS_VR_LIBDVRCOMMON_INCLUDE_PRIVATE_DVR_TEST_TEST_MACROS_H_
diff --git a/libs/vr/libdvrcommon/include/private/dvr/types.h b/libs/vr/libdvrcommon/include/private/dvr/types.h
new file mode 100644
index 0000000..1fa54af
--- /dev/null
+++ b/libs/vr/libdvrcommon/include/private/dvr/types.h
@@ -0,0 +1,51 @@
+#ifndef ANDROID_DVR_TYPES_H_
+#define ANDROID_DVR_TYPES_H_
+
+// All basic types used by VR code.
+
+#include <private/dvr/eigen.h>
+#include <private/dvr/field_of_view.h>
+#include <private/dvr/pose.h>
+#include <private/dvr/range.h>
+
+namespace android {
+namespace dvr {
+
+enum RgbColorChannel { kRed, kGreen, kBlue };
+
+// EyeType: 0 for left, 1 for right.
+enum EyeType { kLeftEye = 0, kRightEye = 1 };
+
+// In the context of VR, vector types are used as much as base types.
+
+using vec2f = Eigen::Vector2f;
+using vec2d = Eigen::Vector2d;
+using vec2i = Eigen::Vector2i;
+using vec2 = vec2f;
+
+using vec3f = Eigen::Vector3f;
+using vec3d = Eigen::Vector3d;
+using vec3i = Eigen::Vector3i;
+using vec3 = vec3f;
+
+using vec4f = Eigen::Vector4f;
+using vec4d = Eigen::Vector4d;
+using vec4i = Eigen::Vector4i;
+using vec4 = vec4f;
+
+using mat3f = Eigen::AffineMatrix<float, 3>;
+using mat3d = Eigen::AffineMatrix<double, 3>;
+using mat3 = mat3f;
+
+using mat4f = Eigen::AffineMatrix<float, 4>;
+using mat4d = Eigen::AffineMatrix<double, 4>;
+using mat4 = mat4f;
+
+using quatf = Eigen::Quaternionf;
+using quatd = Eigen::Quaterniond;
+using quat = quatf;
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_TYPES_H_
diff --git a/libs/vr/libdvrcommon/revision.cpp b/libs/vr/libdvrcommon/revision.cpp
new file mode 100644
index 0000000..ae8603f
--- /dev/null
+++ b/libs/vr/libdvrcommon/revision.cpp
@@ -0,0 +1,175 @@
+#include "private/dvr/revision.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+#include "revision_path.h"
+
+namespace {
+
+// Allows quicker access to the product revision. If non-zero, then
+// the product revision file has already been processed.
+static bool global_product_revision_processed = false;
+
+static bool global_serial_number_processed = false;
+
+// The product.
+static DvrProduct global_product = DVR_PRODUCT_UNKNOWN;
+
+// The revision.
+static DvrRevision global_revision = DVR_REVISION_UNKNOWN;
+
+// Maximum size of the product revision string.
+constexpr int kProductRevisionStringSize = 32;
+
+// Maximum size of the serial number.
+constexpr int kSerialNumberStringSize = 32;
+
+// The product revision string.
+static char global_product_revision_str[kProductRevisionStringSize + 1] = "";
+
+// The serial number string
+static char global_serial_number[kSerialNumberStringSize + 1] = "";
+
+// Product and revision combinations.
+struct DvrProductRevision {
+  const char* str;
+  DvrProduct product;
+  DvrRevision revision;
+};
+
+// Null-terminated list of all product and revision combinations.
+static constexpr DvrProductRevision kProductRevisions[] = {
+    {"a00-p1", DVR_PRODUCT_A00, DVR_REVISION_P1},
+    {"a00-p2", DVR_PRODUCT_A00, DVR_REVISION_P2},
+    {"a00-p3", DVR_PRODUCT_A00, DVR_REVISION_P3},
+    {"twilight-p1", DVR_PRODUCT_A65R, DVR_REVISION_P1},
+    {"twilight-p2", DVR_PRODUCT_A65R, DVR_REVISION_P2},
+    {NULL, DVR_PRODUCT_UNKNOWN, DVR_REVISION_UNKNOWN}};
+
+// Read the product revision string, and store the global data.
+static void process_product_revision() {
+  int fd;
+  ssize_t read_rc;
+  const DvrProductRevision* product_revision = kProductRevisions;
+
+  // Of course in a multi-threaded environment, for a few microseconds
+  // during process startup, it is possible that this function will be
+  // called and execute fully multiple times. That is why the product
+  // revision string is statically allocated.
+
+  if (global_product_revision_processed)
+    return;
+
+  // Whether there was a failure or not, we don't want to do this again.
+  // Upon failure it's most likely to fail again anyway.
+
+  fd = open(dvr_product_revision_file_path(), O_RDONLY);
+  if (fd < 0) {
+    PLOG(ERROR) << "Could not open '" << dvr_product_revision_file_path()
+                << "' to get product revision";
+    global_product_revision_processed = true;
+    return;
+  }
+
+  read_rc = read(fd, global_product_revision_str, kProductRevisionStringSize);
+  if (read_rc <= 0) {
+    PLOG(ERROR) << "Could not read from '" << dvr_product_revision_file_path()
+                << "'";
+    global_product_revision_processed = true;
+    return;
+  }
+
+  close(fd);
+
+  global_product_revision_str[read_rc] = '\0';
+
+  while (product_revision->str) {
+    if (!strcmp(product_revision->str, global_product_revision_str))
+      break;
+    product_revision++;
+  }
+
+  if (product_revision->str) {
+    global_product = product_revision->product;
+    global_revision = product_revision->revision;
+  } else {
+    LOG(ERROR) << "Unable to match '" << global_product_revision_str
+               << "' to a product/revision.";
+  }
+
+  global_product_revision_processed = true;
+}
+
+}  // anonymous namespace
+
+extern "C" DvrProduct dvr_get_product() {
+  process_product_revision();
+  return global_product;
+}
+
+extern "C" DvrRevision dvr_get_revision() {
+  process_product_revision();
+  return global_revision;
+}
+
+extern "C" const char* dvr_get_product_revision_str() {
+  process_product_revision();
+  return global_product_revision_str;
+}
+
+extern "C" const char* dvr_get_serial_number() {
+  process_product_revision();
+  if (global_product == DVR_PRODUCT_A00) {
+    if (!global_serial_number_processed) {
+#ifdef DVR_HOST
+      global_serial_number_processed = true;
+#else
+      int width = 4;
+      uintptr_t addr = 0x00074138;
+      uintptr_t endaddr = addr + width - 1;
+
+      int fd = open("/dev/mem", O_RDWR | O_SYNC);
+      if (fd < 0) {
+        if (errno == EPERM)
+          global_serial_number_processed = true;
+        fprintf(stderr, "cannot open /dev/mem\n");
+        return global_serial_number;
+      }
+
+      off64_t mmap_start = addr & ~(PAGE_SIZE - 1);
+      size_t mmap_size = endaddr - mmap_start + 1;
+      mmap_size = (mmap_size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
+
+      void* page = mmap64(0, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
+                          mmap_start);
+
+      if (page == MAP_FAILED) {
+        global_serial_number_processed = true;
+        fprintf(stderr, "cannot mmap region\n");
+        close(fd);
+        return global_serial_number;
+      }
+
+      uint32_t* x =
+          reinterpret_cast<uint32_t*>((((uintptr_t)page) + (addr & 4095)));
+      snprintf(global_serial_number, kSerialNumberStringSize, "%08x", *x);
+      global_serial_number_processed = true;
+
+      munmap(page, mmap_size);
+      close(fd);
+#endif
+    }
+    return global_serial_number;
+  } else {
+    return nullptr;
+  }
+}
diff --git a/libs/vr/libdvrcommon/revision_path.cpp b/libs/vr/libdvrcommon/revision_path.cpp
new file mode 100644
index 0000000..c49f9aa
--- /dev/null
+++ b/libs/vr/libdvrcommon/revision_path.cpp
@@ -0,0 +1,15 @@
+#include "revision_path.h"
+
+namespace {
+
+// The path to the product revision file.
+static const char* kProductRevisionFilePath =
+    "/sys/firmware/devicetree/base/goog,board-revision";
+
+}  // anonymous namespace
+
+// This exists in a separate file so that it can be replaced for
+// testing.
+const char* dvr_product_revision_file_path() {
+  return kProductRevisionFilePath;
+}
diff --git a/libs/vr/libdvrcommon/revision_path.h b/libs/vr/libdvrcommon/revision_path.h
new file mode 100644
index 0000000..afcea46
--- /dev/null
+++ b/libs/vr/libdvrcommon/revision_path.h
@@ -0,0 +1,9 @@
+#ifndef ANDROID_DVR_LIBDVRCOMMON_REVISION_PATH_H_
+#define ANDROID_DVR_LIBDVRCOMMON_REVISION_PATH_H_
+
+// Returns the revision file path.
+// This exists in a separate file so that it can be replaced for
+// testing.
+const char* dvr_product_revision_file_path();
+
+#endif  // ANDROID_DVR_LIBDVRCOMMON_REVISION_PATH_H_
diff --git a/libs/vr/libdvrcommon/sync_util.cpp b/libs/vr/libdvrcommon/sync_util.cpp
new file mode 100644
index 0000000..3637936
--- /dev/null
+++ b/libs/vr/libdvrcommon/sync_util.cpp
@@ -0,0 +1,87 @@
+#include "include/private/dvr/sync_util.h"
+
+#include <errno.h>
+#include <sys/ioctl.h>
+
+// TODO: http://b/33239638 Move GetSyncFenceInfo() into upstream libsync instead
+//   of duplicating functionality and structure definitions from it.
+struct sync_fence_info_data {
+ uint32_t len;
+ char name[32];
+ int32_t status;
+ uint8_t pt_info[0];
+};
+
+struct sync_pt_info {
+ uint32_t len;
+ char obj_name[32];
+ char driver_name[32];
+ int32_t status;
+ uint64_t timestamp_ns;
+ uint8_t driver_data[0];
+};
+
+#define SYNC_IOC_MAGIC '>'
+#define SYNC_IOC_WAIT _IOW(SYNC_IOC_MAGIC, 0, __s32)
+#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 1, struct sync_merge_data)
+#define SYNC_IOC_FENCE_INFO _IOWR(SYNC_IOC_MAGIC, 2, struct sync_fence_info_data)
+
+namespace android {
+namespace dvr {
+
+namespace {
+
+// This is copied from sync_pt_info() in libsync/sync.c. It's been cleaned up to
+// remove lint warnings.
+sync_pt_info* GetSyncPtInfo(sync_fence_info_data* info, sync_pt_info* itr) {
+  if (itr == nullptr)
+    itr = reinterpret_cast<sync_pt_info*>(info->pt_info);
+  else
+    itr = reinterpret_cast<sync_pt_info*>(reinterpret_cast<uint8_t*>(itr) +
+                                          itr->len);
+
+  if (reinterpret_cast<uint8_t*>(itr) - reinterpret_cast<uint8_t*>(info) >=
+      static_cast<int>(info->len))
+    return nullptr;
+
+  return itr;
+}
+
+}  // namespace
+
+int GetSyncFenceInfo(int fence_fd, FenceInfoBuffer* buffer) {
+  // If the implementation of sync_fence_info() in libsync/sync.c changes, this
+  // function should be changed to match.
+  if (buffer == nullptr) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  sync_fence_info_data* fence_info =
+      reinterpret_cast<sync_fence_info_data*>(buffer);
+  fence_info->len = kFenceInfoBufferSize;
+  return ioctl(fence_fd, SYNC_IOC_FENCE_INFO, fence_info);
+}
+
+int GetFenceSignaledTimestamp(int fence_fd, FenceInfoBuffer* buffer,
+                              int64_t* timestamp) {
+  int result = GetSyncFenceInfo(fence_fd, buffer);
+  if (result < 0)
+    return result;
+
+  sync_fence_info_data* fence_info =
+      reinterpret_cast<sync_fence_info_data*>(buffer);
+  struct sync_pt_info* pt_info = nullptr;
+  while ((pt_info = GetSyncPtInfo(fence_info, pt_info)) != nullptr) {
+    if (pt_info->status == 1) {
+      *timestamp = pt_info->timestamp_ns;
+      return 0;
+    }
+  }
+
+  errno = EAGAIN;
+  return -1;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdvrcommon/tests/numeric_test.cpp b/libs/vr/libdvrcommon/tests/numeric_test.cpp
new file mode 100644
index 0000000..1ee1447
--- /dev/null
+++ b/libs/vr/libdvrcommon/tests/numeric_test.cpp
@@ -0,0 +1,67 @@
+#include <gtest/gtest.h>
+
+#include <private/dvr/numeric.h>
+
+using TestTypes = ::testing::Types<float, double, int>;
+
+using android::dvr::RandomInRange;
+
+template <typename T>
+class NumericTest : public ::testing::TestWithParam<T> {
+ public:
+  using FT = T;
+};
+
+TYPED_TEST_CASE(NumericTest, TestTypes);
+
+TYPED_TEST(NumericTest, RandomInRange) {
+  using FT = typename TestFixture::FT;
+
+  const int kNumTrials = 50;
+  const FT kLowRange = static_cast<FT>(-100);
+  const FT kHighRange = static_cast<FT>(100);
+
+  for (int i = 0; i < kNumTrials; ++i) {
+    FT value = RandomInRange(kLowRange, kHighRange);
+
+    EXPECT_LE(kLowRange, value);
+    EXPECT_GE(kHighRange, value);
+  }
+}
+
+TEST(RandomInRange, TestIntVersion) {
+  // This checks specifically that the function does not always give the lo
+  // value (this was previously a bug)
+
+  const int kNumTrials = 50;
+  const int kLowRange = -100;
+  const int kHighRange = 100;
+
+  for (int i = 0; i < kNumTrials; ++i) {
+    int value = RandomInRange(kLowRange, kHighRange);
+
+    if (value != kLowRange) {
+      SUCCEED();
+      return;
+    }
+  }
+
+  FAIL() << "Did not produce a value other than the range minimum for "
+         << "integers.";
+}
+
+TEST(RandomInRange, TestVectorVersion) {
+  Eigen::Vector3d lo(-3.0, -4.0, -5.0);
+  Eigen::Vector3d hi(5.0, 4.0, 3.0);
+
+  const int kNumTrials = 50;
+
+  for (int i = 0; i < kNumTrials; ++i) {
+    Eigen::Vector3d result = RandomInRange(lo, hi);
+
+    for (int j = 0; j < 3; ++j) {
+      EXPECT_LE(lo[j], result[j]);
+      EXPECT_GE(hi[j], result[j]);
+    }
+  }
+}
diff --git a/libs/vr/libdvrcommon/tests/pose_test.cpp b/libs/vr/libdvrcommon/tests/pose_test.cpp
new file mode 100644
index 0000000..aa1896d
--- /dev/null
+++ b/libs/vr/libdvrcommon/tests/pose_test.cpp
@@ -0,0 +1,154 @@
+#include <gtest/gtest.h>
+
+#include <private/dvr/eigen.h>
+#include <private/dvr/pose.h>
+#include <private/dvr/test/test_macros.h>
+
+using PoseTypes = ::testing::Types<float, double>;
+
+template <class T>
+class PoseTest : public ::testing::TestWithParam<T> {
+ public:
+  using FT = T;
+  using Pose_t = android::dvr::Pose<FT>;
+  using quat_t = Eigen::Quaternion<FT>;
+  using vec3_t = Eigen::Vector3<FT>;
+  using mat4_t = Eigen::AffineMatrix<FT, 4>;
+};
+
+TYPED_TEST_CASE(PoseTest, PoseTypes);
+
+// Check that the two matrix methods are inverses of each other
+TYPED_TEST(PoseTest, SelfInverse) {
+  using quat_t = typename TestFixture::quat_t;
+  using vec3_t = typename TestFixture::vec3_t;
+  using Pose_t = typename TestFixture::Pose_t;
+  using mat4_t = typename TestFixture::mat4_t;
+  using FT = typename TestFixture::FT;
+
+  const auto tolerance = FT(0.0001);
+
+  const quat_t initial_rotation(Eigen::AngleAxis<FT>(
+      FT(M_PI / 3.0), vec3_t(FT(3.0), FT(4.0), FT(5.0)).normalized()));
+  const vec3_t initial_position = vec3_t(FT(2.0), FT(10.0), FT(-4.0));
+  const Pose_t initial_pose(initial_rotation, initial_position);
+
+  auto result_pose = initial_pose.GetReferenceFromObjectMatrix() *
+                     initial_pose.GetObjectFromReferenceMatrix();
+
+  EXPECT_MAT4_NEAR(result_pose, mat4_t::Identity(), tolerance);
+}
+
+TYPED_TEST(PoseTest, TransformPoint) {
+  using quat_t = typename TestFixture::quat_t;
+  using vec3_t = typename TestFixture::vec3_t;
+  using Pose_t = typename TestFixture::Pose_t;
+  using FT = typename TestFixture::FT;
+
+  const auto tolerance = FT(0.0001);
+
+  const quat_t pose_rotation(
+      Eigen::AngleAxis<FT>(FT(M_PI / 2.0), vec3_t(FT(0.0), FT(0.0), FT(1.0))));
+  const auto pose_position = vec3_t(FT(1.0), FT(1.0), FT(2.0));
+
+  const Pose_t test_pose(pose_rotation, pose_position);
+
+  for (int axis = 0; axis < 3; ++axis) {
+    vec3_t start_position = vec3_t::Zero();
+    start_position[axis] = FT(1.0);
+    const vec3_t expected_transformed =
+        (pose_rotation * start_position) + pose_position;
+    const vec3_t actual_transformed = test_pose.TransformPoint(start_position);
+    EXPECT_VEC3_NEAR(expected_transformed, actual_transformed, tolerance);
+  }
+}
+
+TYPED_TEST(PoseTest, TransformVector) {
+  using quat_t = typename TestFixture::quat_t;
+  using vec3_t = typename TestFixture::vec3_t;
+  using Pose_t = typename TestFixture::Pose_t;
+  using FT = typename TestFixture::FT;
+
+  const auto tolerance = FT(0.0001);
+
+  const quat_t pose_rotation(Eigen::AngleAxis<FT>(
+      FT(M_PI / 6.0), vec3_t(FT(3.0), FT(4.0), FT(5.0)).normalized()));
+
+  const auto pose_position = vec3_t(FT(500.0), FT(-500.0), FT(300.0));
+
+  const Pose_t test_pose(pose_rotation, pose_position);
+
+  for (int axis = 0; axis < 3; ++axis) {
+    vec3_t start_position = vec3_t::Zero();
+    start_position[axis] = FT(1.0);
+    const vec3_t expected_rotated = pose_rotation * start_position;
+    const vec3_t actual_rotated = test_pose.Transform(start_position);
+    EXPECT_VEC3_NEAR(expected_rotated, actual_rotated, tolerance);
+  }
+}
+
+TYPED_TEST(PoseTest, Composition) {
+  using quat_t = typename TestFixture::quat_t;
+  using Pose_t = typename TestFixture::Pose_t;
+  using vec3_t = typename TestFixture::vec3_t;
+  using FT = typename TestFixture::FT;
+
+  const auto tolerance = FT(0.0001);
+
+  const quat_t first_rotation(
+      Eigen::AngleAxis<FT>(FT(M_PI / 2.0), vec3_t(FT(0.0), FT(0.0), FT(1.0))));
+  const auto first_offset = vec3_t(FT(-3.0), FT(2.0), FT(-1.0));
+  const quat_t second_rotation(Eigen::AngleAxis<FT>(
+      FT(M_PI / 3.0), vec3_t(FT(1.0), FT(-1.0), FT(0.0)).normalized()));
+  const auto second_offset = vec3_t(FT(6.0), FT(-7.0), FT(-8.0));
+
+  const Pose_t first_pose(first_rotation, first_offset);
+  const Pose_t second_pose(second_rotation, second_offset);
+
+  const auto combined_pose(second_pose.Compose(first_pose));
+
+  for (int axis = 0; axis < 3; ++axis) {
+    vec3_t start_position = vec3_t::Zero();
+    start_position[axis] = FT(1.0);
+    const vec3_t expected_transformed =
+        second_pose.TransformPoint(first_pose.TransformPoint(start_position));
+    const vec3_t actual_transformed =
+        combined_pose.TransformPoint(start_position);
+    EXPECT_VEC3_NEAR(expected_transformed, actual_transformed, tolerance);
+  }
+}
+
+TYPED_TEST(PoseTest, Inverse) {
+  using quat_t = typename TestFixture::quat_t;
+  using vec3_t = typename TestFixture::vec3_t;
+  using Pose_t = typename TestFixture::Pose_t;
+  using FT = typename TestFixture::FT;
+
+  const auto tolerance = FT(0.0001);
+
+  const quat_t pose_rotation(Eigen::AngleAxis<FT>(
+      FT(M_PI / 2.0), vec3_t(FT(4.0), FT(-2.0), FT(-1.0)).normalized()));
+  const auto pose_position = vec3_t(FT(-1.0), FT(2.0), FT(-4.0));
+
+  Pose_t pose(pose_rotation, pose_position);
+  const Pose_t pose_inverse = pose.Inverse();
+
+  for (int axis = 0; axis < 3; ++axis) {
+    vec3_t start_position = vec3_t::Zero();
+    start_position[axis] = FT(1.0);
+    const vec3_t transformed = pose.Transform(start_position);
+    const vec3_t inverted = pose_inverse.Transform(transformed);
+    EXPECT_VEC3_NEAR(start_position, inverted, tolerance);
+  }
+
+  Pose_t nullified_pose[2] = {
+      pose.Compose(pose_inverse), pose_inverse.Compose(pose),
+  };
+
+  for (int i = 0; i < 2; ++i) {
+    EXPECT_QUAT_NEAR(quat_t::Identity(), nullified_pose[i].GetRotation(),
+                     tolerance);
+    EXPECT_VEC3_NEAR(vec3_t::Zero(), nullified_pose[i].GetPosition(),
+                     tolerance);
+  }
+}
diff --git a/libs/vr/libdvrcommon/tests/revision_app_tests.cpp b/libs/vr/libdvrcommon/tests/revision_app_tests.cpp
new file mode 100644
index 0000000..772481b
--- /dev/null
+++ b/libs/vr/libdvrcommon/tests/revision_app_tests.cpp
@@ -0,0 +1,34 @@
+#include <dvr/test/app_test.h>
+#include <gtest/gtest.h>
+#include <private/dvr/revision.h>
+
+// Making sure this information is not available
+// inside the sandbox
+
+namespace {
+
+TEST(RevisionTests, GetProduct) {
+  ASSERT_EQ(DVR_PRODUCT_UNKNOWN, dvr_get_product());
+}
+
+TEST(RevisionTests, GetRevision) {
+  ASSERT_EQ(DVR_REVISION_UNKNOWN, dvr_get_revision());
+}
+
+TEST(RevisionTests, GetRevisionStr) {
+  ASSERT_STREQ("", dvr_get_product_revision_str());
+}
+
+TEST(RevisionTests, GetSerialNo) {
+  ASSERT_EQ(nullptr, dvr_get_serial_number());
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  dreamos::test::AppTestBegin();
+  ::testing::InitGoogleTest(&argc, argv);
+  int result = RUN_ALL_TESTS();
+  dreamos::test::AppTestEnd(result);
+  return result;
+}
diff --git a/libs/vr/libdvrcommon/tests/revision_tests.cpp b/libs/vr/libdvrcommon/tests/revision_tests.cpp
new file mode 100644
index 0000000..9abf480
--- /dev/null
+++ b/libs/vr/libdvrcommon/tests/revision_tests.cpp
@@ -0,0 +1,27 @@
+#include <gtest/gtest.h>
+#include <private/dvr/revision.h>
+
+namespace {
+
+TEST(RevisionTests, GetProduct) {
+  ASSERT_NE(DVR_PRODUCT_UNKNOWN, dvr_get_product());
+}
+
+TEST(RevisionTests, GetRevision) {
+  ASSERT_NE(DVR_REVISION_UNKNOWN, dvr_get_revision());
+}
+
+TEST(RevisionTests, GetRevisionStr) {
+  ASSERT_NE(nullptr, dvr_get_product_revision_str());
+}
+
+TEST(RevisionTests, GetSerialNo) {
+  ASSERT_NE(nullptr, dvr_get_serial_number());
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/libs/vr/libdvrgraphics/Android.mk b/libs/vr/libdvrgraphics/Android.mk
new file mode 100644
index 0000000..3d84319
--- /dev/null
+++ b/libs/vr/libdvrgraphics/Android.mk
@@ -0,0 +1,35 @@
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	blur.cpp \
+	debug_text.cpp \
+	egl_image.cpp \
+	gpu_profiler.cpp \
+	shader_program.cpp \
+	timer_query.cpp \
+	vr_gl_extensions.cpp \
+
+includeFiles := \
+	$(LOCAL_PATH)/include
+
+staticLibraries := \
+	libchrome \
+	libbufferhub \
+	libdvrcommon \
+	libpdx_default_transport \
+
+sharedLibraries := \
+	libcutils \
+	libbase \
+	libpng
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_MODULE := libdvrgraphics
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/libs/vr/libdvrgraphics/assets/controller_proto2.png b/libs/vr/libdvrgraphics/assets/controller_proto2.png
new file mode 100644
index 0000000..ffcb646
--- /dev/null
+++ b/libs/vr/libdvrgraphics/assets/controller_proto2.png
Binary files differ
diff --git a/libs/vr/libdvrgraphics/assets/controller_proto2_body.obj b/libs/vr/libdvrgraphics/assets/controller_proto2_body.obj
new file mode 100644
index 0000000..4e54900
--- /dev/null
+++ b/libs/vr/libdvrgraphics/assets/controller_proto2_body.obj
@@ -0,0 +1,5018 @@
+# This file uses centimeters as units for non-parametric coordinates.
+
+v 0.001777 0.012900 0.101619
+v -0.005750 0.012900 0.096150
+v -0.004652 0.012900 0.092770
+v 0.005750 0.012900 0.096150
+v 0.004652 0.012900 0.099530
+v -0.001777 0.012900 0.090681
+v 0.004652 0.012900 0.092770
+v 0.001777 0.012900 0.090681
+v -0.001777 0.012900 0.101619
+v -0.004652 0.012900 0.099530
+v -0.001777 0.012900 0.073181
+v -0.004652 0.012900 0.075270
+v 0.001777 0.012900 0.073181
+v 0.004652 0.012900 0.075270
+v 0.005750 0.012900 0.078650
+v 0.004652 0.012900 0.082030
+v -0.005750 0.012900 0.078650
+v -0.004652 0.012900 0.082030
+v -0.001777 0.012900 0.084119
+v 0.001777 0.012900 0.084119
+v -0.016000 0.012900 0.050841
+v 0.016000 0.012900 0.050835
+v 0.013856 0.012900 0.058900
+v 0.008000 0.012900 0.064756
+v -0.000000 0.012900 0.066900
+v -0.008000 0.012900 0.064756
+v -0.013856 0.012900 0.058900
+v 0.008000 0.012900 0.037044
+v -0.008000 0.012900 0.037044
+v -0.013856 0.012900 0.042900
+v 0.000000 0.012900 0.034900
+v 0.013856 0.012900 0.042900
+v -0.020000 0.006760 0.075600
+v -0.020000 0.006760 -0.012398
+v -0.018877 -0.001982 0.044797
+v -0.018480 -0.003505 0.048732
+v -0.018777 -0.001945 0.075600
+v -0.017726 -0.003833 0.163093
+v -0.013161 -0.013053 0.058159
+v -0.016670 -0.008191 0.054819
+v -0.016119 -0.008731 0.075600
+v -0.009698 -0.015662 0.059338
+v -0.012025 -0.013632 0.075600
+v -0.006504 -0.016678 0.075600
+v -0.000000 -0.017750 0.075600
+v -0.000000 -0.015479 0.162587
+v 0.006873 -0.016611 0.058552
+v 0.005238 -0.017469 0.059981
+v -0.000000 -0.018152 0.060190
+v -0.006874 -0.016610 0.058551
+v -0.005577 -0.017375 0.059951
+v -0.000001 -0.017790 0.058889
+v -0.017783 -0.005639 0.052082
+v -0.016159 -0.008440 0.053561
+v -0.018202 -0.002766 0.042130
+v -0.019040 -0.001282 0.040457
+v -0.019004 -0.001521 0.036065
+v -0.019278 -0.000710 -0.012015
+v -0.018775 -0.002635 0.031956
+v -0.018323 -0.004451 0.028346
+v -0.017636 -0.005173 0.029543
+v -0.015438 -0.011195 0.021684
+v -0.017576 -0.006793 0.025305
+v -0.017651 -0.007495 -0.011666
+v -0.016133 -0.009192 0.024525
+v -0.009254 -0.016963 0.019047
+v -0.012694 -0.014052 0.021271
+v -0.014657 -0.012757 -0.011402
+v -0.012775 -0.014437 0.019994
+v -0.004877 -0.018641 0.018555
+v 0.000000 -0.019243 0.018406
+v -0.004053 -0.017659 -0.013120
+v 0.000000 -0.020012 -0.011045
+v -0.007741 -0.016386 -0.013187
+v -0.007793 -0.018450 -0.011122
+v -0.011145 -0.016607 -0.011212
+v -0.013377 -0.011450 -0.013446
+v -0.017416 0.000890 -0.014092
+v -0.010471 -0.009590 0.164744
+v -0.020000 0.006760 0.163598
+v -0.017045 0.000607 0.165278
+v -0.019121 0.000433 0.163292
+v -0.012455 -0.011131 0.162773
+v -0.015399 -0.007954 0.162910
+v -0.011105 -0.009754 0.164735
+v -0.008928 -0.013453 0.162674
+v -0.004759 -0.014945 0.162610
+v 0.000000 -0.013582 0.164534
+v -0.016850 0.012900 -0.012204
+v -0.016850 0.011003 -0.014204
+v -0.017833 0.012745 0.163406
+v -0.016850 0.011003 0.165404
+v 0.016850 0.011003 0.165404
+v 0.016850 0.012900 0.163404
+v -0.016850 0.012900 0.163404
+v -0.018496 0.012405 0.163413
+v -0.019171 0.011802 0.163424
+v -0.017585 0.010679 0.165421
+v -0.019183 0.011788 -0.012224
+v -0.019646 0.010959 0.163436
+v -0.017717 0.010496 0.165430
+v -0.019850 0.009900 -0.012263
+v -0.017851 0.009891 0.165462
+v -0.019850 0.009900 0.163463
+v -0.017851 0.009891 -0.014262
+v -0.019650 0.010946 -0.012236
+v -0.018511 0.012395 -0.012213
+v -0.017342 0.010872 -0.014211
+v -0.017849 0.012739 -0.012207
+v 0.019850 0.009900 -0.012263
+v 0.019850 0.009900 0.163463
+v 0.019652 0.010946 0.163437
+v 0.019180 0.011789 0.163424
+v 0.019649 0.010953 -0.012236
+v 0.017351 0.010866 0.165411
+v 0.018506 0.012398 0.163413
+v 0.017845 0.012741 0.163406
+v 0.018501 0.012401 -0.012213
+v 0.016850 0.012900 -0.012204
+v 0.016850 0.011003 -0.014204
+v 0.017836 0.012745 -0.012207
+v 0.017359 0.010861 -0.014211
+v 0.019174 0.011796 -0.012224
+v 0.020000 0.006760 -0.012398
+v 0.020000 0.006760 0.075600
+v 0.020000 0.006760 0.163598
+v 0.017851 0.009891 0.165462
+v 0.004914 -0.018632 0.018558
+v 0.012694 -0.014513 0.019962
+v 0.009842 -0.017439 -0.011172
+v 0.009194 -0.016995 0.019036
+v 0.012696 -0.014051 0.021272
+v 0.016134 -0.009190 0.024527
+v 0.018283 -0.005474 -0.011769
+v 0.015491 -0.011112 0.021736
+v 0.017878 -0.005923 0.026303
+v 0.016851 -0.008573 0.023600
+v 0.018521 -0.003703 0.029628
+v 0.019042 -0.001309 0.037868
+v 0.018891 -0.002096 0.033543
+v 0.018999 -0.001456 0.042247
+v 0.018278 -0.004181 0.049954
+v 0.018757 -0.002471 0.046344
+v 0.017676 -0.004685 0.048340
+v 0.018202 -0.002767 0.042132
+v 0.017483 -0.006410 0.053015
+v 0.016158 -0.008441 0.053562
+v 0.016251 -0.008973 0.055490
+v 0.013246 -0.012968 0.058115
+v 0.012687 -0.013111 0.057055
+v 0.009848 -0.015576 0.059304
+v 0.006504 -0.016678 0.075600
+v 0.012025 -0.013632 0.075600
+v 0.012453 -0.011132 0.162773
+v 0.015398 -0.007956 0.162909
+v 0.018777 -0.001945 0.075600
+v 0.016119 -0.008731 0.075600
+v 0.017726 -0.003834 0.163093
+v 0.016090 -0.005051 -0.013781
+v 0.019494 0.000840 -0.012095
+v 0.014597 -0.009662 -0.013539
+v 0.015987 -0.010626 -0.011508
+v 0.012048 -0.013130 -0.013358
+v 0.012738 -0.015322 -0.011275
+v 0.004233 -0.017617 -0.013122
+v 0.004123 -0.019588 -0.011066
+v 0.008927 -0.013454 0.162674
+v 0.004758 -0.014945 0.162610
+v 0.007966 -0.011801 0.164627
+v 0.011095 -0.009763 0.164734
+v -0.016170 0.000544 0.165275
+v 0.019122 0.000435 0.163292
+v -0.000000 -0.018116 -0.013096
+v 0.008618 -0.015654 -0.013225
+v -0.018000 0.006760 -0.014400
+v -0.016263 -0.005366 -0.013764
+v -0.010678 -0.014560 -0.013283
+v 0.018001 0.006760 -0.014400
+v 0.017116 0.010978 -0.014205
+v -0.017101 0.010981 -0.014205
+v -0.017576 0.010688 -0.014220
+v -0.017712 0.010505 -0.014230
+v 0.017851 0.009891 -0.014262
+v 0.017713 0.010505 -0.014230
+v -0.000000 -0.016565 0.057943
+v 0.005098 -0.015909 0.057716
+v -0.012314 -0.011868 0.055681
+v 0.012233 -0.011949 0.055734
+v -0.009091 -0.014324 0.057057
+v 0.009220 -0.014251 0.057022
+v -0.014780 -0.008209 0.052349
+v 0.014512 -0.008747 0.052978
+v 0.015923 -0.005957 0.049562
+v 0.016463 -0.004315 0.046176
+v -0.016130 -0.005374 0.048538
+v 0.016787 -0.003149 0.038024
+v -0.016587 -0.003874 0.044839
+v 0.016736 -0.003311 0.042260
+v 0.016623 -0.003908 0.033843
+v -0.016780 -0.003141 0.040668
+v 0.016216 -0.005454 0.030112
+v 0.018185 -0.002935 0.035641
+v 0.015501 -0.007542 0.027010
+v 0.014368 -0.009927 0.024580
+v -0.015201 -0.008261 0.026184
+v 0.017637 -0.005172 0.029546
+v 0.011788 -0.013271 0.022276
+v 0.008677 -0.015490 0.021243
+v 0.006874 -0.017616 0.019963
+v 0.004690 -0.016976 0.020729
+v -0.008734 -0.015460 0.021254
+v -0.000000 -0.017541 0.020568
+v 0.000001 -0.018813 0.019687
+v -0.006872 -0.017617 0.019962
+v -0.004662 -0.016983 0.020727
+v -0.011831 -0.013230 0.022299
+v -0.014150 -0.010300 0.024269
+v -0.016017 -0.006100 0.029007
+v -0.018185 -0.002936 0.035639
+v -0.016751 -0.003331 0.036408
+v -0.016508 -0.004384 0.032442
+v -0.017676 -0.004684 0.048338
+v -0.012688 -0.013110 0.057054
+v -0.005290 -0.015857 0.057697
+v -0.017359 0.010861 0.165411
+v -0.017114 0.010978 0.165405
+v 0.017105 0.010980 0.165405
+v 0.017998 0.006760 0.165600
+v 0.013678 -0.007019 0.164878
+v 0.017042 0.000595 0.165277
+v -0.017998 0.006760 0.165600
+v 0.004260 -0.013110 0.164559
+v -0.004262 -0.013110 0.164559
+v -0.007973 -0.011798 0.164627
+v -0.013690 -0.007003 0.164879
+v 0.014199 0.012900 0.042702
+v 0.000000 0.012900 0.034505
+v -0.014199 0.012900 0.042702
+v -0.008198 0.012900 0.036701
+v 0.008198 0.012900 0.036701
+v -0.014199 0.012900 0.059098
+v -0.008198 0.012900 0.065099
+v -0.000000 0.012900 0.067295
+v 0.008198 0.012900 0.065099
+v 0.014199 0.012900 0.059098
+v -0.001880 0.012900 0.072863
+v -0.004923 0.012900 0.075074
+v 0.001880 0.012900 0.072863
+v 0.004923 0.012900 0.075074
+v 0.006085 0.012900 0.078650
+v 0.004923 0.012900 0.082227
+v -0.006085 0.012900 0.078650
+v -0.004923 0.012900 0.082227
+v -0.001880 0.012900 0.084437
+v 0.001880 0.012900 0.084437
+v -0.005003 0.012900 0.099785
+v -0.001911 0.012900 0.102031
+v 0.001911 0.012900 0.090269
+v 0.005003 0.012900 0.092515
+v -0.001911 0.012900 0.090269
+v 0.005003 0.012900 0.099785
+v 0.006184 0.012900 0.096150
+v -0.005003 0.012900 0.092515
+v -0.006184 0.012900 0.096150
+v 0.001911 0.012900 0.102031
+v 0.017551 0.010715 -0.014201
+v 0.017144 0.009891 -0.014262
+v 0.017010 0.010505 -0.014230
+v 0.017287 0.006760 -0.014400
+v -0.017159 0.009891 -0.014262
+v -0.017025 0.010505 -0.014230
+v -0.017302 0.006760 -0.014400
+v -0.003838 -0.017246 -0.013142
+v -0.007274 -0.016060 -0.013204
+v -0.012667 -0.011115 -0.013463
+v -0.016493 0.001142 -0.014106
+v 0.015107 -0.004951 -0.013786
+v 0.013539 -0.009795 -0.013532
+v 0.011678 -0.012609 -0.013385
+v 0.003882 -0.017207 -0.013144
+v -0.000062 -0.017671 -0.013120
+v 0.008313 -0.015223 -0.013248
+v -0.015318 -0.005229 -0.013772
+v -0.010464 -0.013804 -0.013322
+v -0.003962 -0.019557 -0.011198
+v 0.016850 0.012900 0.075600
+v 0.019850 0.009900 0.075600
+v 0.019177 0.011793 0.075600
+v 0.018504 0.012399 0.075600
+v 0.019650 0.010950 0.075600
+v -0.016850 0.012900 0.075600
+v -0.019177 0.011795 0.075600
+v -0.017841 0.012742 0.075600
+v -0.019850 0.009900 0.075600
+v -0.019648 0.010952 0.075600
+v -0.018504 0.012400 0.075600
+v 0.017708 0.010514 0.165429
+v 0.017545 0.010700 0.165401
+v 0.017078 0.010513 0.165429
+v 0.017357 0.006760 0.165600
+v 0.017216 0.009891 0.165462
+v -0.017229 0.009891 0.165462
+v -0.017370 0.006760 0.165600
+v -0.017099 0.010496 0.165430
+v -0.000000 -0.013199 0.164554
+v 0.007511 -0.011520 0.164642
+v 0.010461 -0.009598 0.164743
+v 0.012897 -0.007011 0.164878
+v 0.016165 0.000533 0.165274
+v 0.004017 -0.012754 0.164578
+v -0.004019 -0.012754 0.164578
+v -0.007518 -0.011517 0.164642
+v -0.012909 -0.006996 0.164879
+v 0.000000 0.012900 0.163404
+v 0.000000 0.011003 0.165404
+v -0.000006 0.009891 0.165462
+v -0.000006 0.006760 0.165600
+v -0.000010 0.010505 0.165429
+v -0.000003 0.000539 0.165274
+v -0.000006 -0.007003 0.164879
+v -0.000005 -0.009594 0.164743
+v -0.000003 -0.011518 0.164642
+v -0.016850 0.012900 0.031793
+v -0.020000 0.006760 0.031793
+v 0.020000 0.006760 0.031793
+v 0.016850 0.012900 0.031793
+v -0.019181 0.011791 0.031793
+v -0.017846 0.012740 0.031793
+v 0.018502 0.012400 0.031793
+v 0.019650 0.010952 0.031793
+v 0.017838 0.012744 0.031793
+v 0.019850 0.009900 0.031793
+v 0.019175 0.011795 0.031793
+v -0.019850 0.009900 0.031793
+v -0.019650 0.010948 0.031793
+v -0.018509 0.012397 0.031793
+v -0.019180 0.011791 0.037044
+v -0.020000 0.006760 0.037044
+v -0.017846 0.012741 0.037044
+v -0.016850 0.012900 0.037044
+v -0.019850 0.009900 0.037044
+v -0.019649 0.010948 0.037044
+v -0.018508 0.012397 0.037044
+v -0.014664 -0.003820 0.165046
+v 0.014655 -0.003835 0.165045
+v 0.015513 -0.003806 0.165046
+v -0.015521 -0.003790 0.165047
+v -0.000004 -0.003827 0.165045
+v 0.000251 -0.015641 -0.013226
+v -0.019850 0.009900 0.043222
+v -0.019649 0.010949 0.043222
+v -0.018507 0.012398 0.043222
+v -0.019180 0.011792 0.043222
+v -0.020000 0.006760 0.043222
+v -0.017845 0.012741 0.043222
+v -0.019850 0.009900 0.050841
+v -0.019649 0.010950 0.050841
+v -0.018506 0.012398 0.050841
+v -0.019179 0.011793 0.050841
+v -0.020000 0.006760 0.050841
+v -0.017844 0.012741 0.050841
+v -0.016850 0.012900 0.050841
+v -0.019850 0.009900 0.059353
+v -0.019649 0.010951 0.059352
+v -0.018505 0.012399 0.059353
+v -0.019178 0.011794 0.059352
+v -0.020000 0.006760 0.059352
+v -0.017843 0.012741 0.059352
+v 0.020000 0.006760 0.025653
+v -0.016850 0.012900 0.025680
+v -0.020000 0.006760 0.025653
+v 0.016850 0.012900 0.025680
+v -0.019181 0.011790 0.025677
+v -0.017847 0.012740 0.025679
+v 0.018502 0.012401 0.025678
+v 0.019650 0.010952 0.025675
+v 0.017837 0.012744 0.025679
+v 0.000251 -0.013207 -0.013354
+v 0.000000 0.012900 -0.012204
+v 0.000000 0.012900 0.025680
+v 0.000251 -0.010455 -0.013498
+v -0.000007 0.010505 -0.014230
+v -0.000008 0.009891 -0.014262
+v 0.019850 0.009900 0.025671
+v 0.019175 0.011795 0.025677
+v -0.019850 0.009900 0.025671
+v -0.019650 0.010948 0.025675
+v -0.018509 0.012396 0.025678
+v 0.017448 -0.005338 0.075600
+v 0.019388 0.002407 0.075600
+v 0.017236 0.000838 -0.014090
+v 0.016380 0.000959 -0.014096
+v 0.020000 0.006760 0.037056
+v 0.019850 0.009900 0.037056
+v 0.019175 0.011795 0.037056
+v 0.018502 0.012400 0.037056
+v 0.019650 0.010952 0.037056
+v 0.020000 0.006760 0.043243
+v 0.019850 0.009900 0.043243
+v 0.019176 0.011794 0.043243
+v 0.018502 0.012400 0.043243
+v 0.019650 0.010951 0.043243
+v 0.016395 0.012900 0.050834
+v 0.020000 0.006760 0.050835
+v 0.019850 0.009900 0.050835
+v 0.019176 0.011794 0.050835
+v 0.018503 0.012400 0.050835
+v 0.019650 0.010951 0.050836
+v 0.017838 0.012744 0.043243
+v 0.016850 0.012900 0.037044
+v 0.017838 0.012744 0.037044
+v 0.000251 0.006760 -0.014400
+v 0.000251 -0.005090 -0.013779
+v 0.016850 0.012900 0.050835
+v 0.017838 0.012744 0.050835
+v 0.016850 0.012900 0.043243
+v 0.017838 0.012744 0.075600
+v 0.000251 0.001050 -0.014101
+v -0.017448 -0.005338 0.075600
+v -0.019388 0.002407 0.075600
+v 0.020000 0.006760 0.059351
+v 0.019850 0.009900 0.059351
+v 0.019176 0.011793 0.059351
+v 0.018503 0.012400 0.059351
+v 0.019650 0.010951 0.059351
+v 0.017838 0.012744 0.059351
+v 0.016850 0.012900 0.059351
+v -0.016850 0.012900 0.043222
+v 0.000000 0.011003 -0.014204
+v 0.018424 -0.002660 0.026136
+v -0.016395 0.012900 0.050840
+v -0.016850 0.012900 0.059352
+v -0.009165 0.012900 0.107061
+v 0.018510 -0.002803 0.055317
+v 0.009165 0.012900 0.107061
+v 0.000000 0.012900 0.107061
+v -0.016000 0.009159 0.050841
+v 0.016000 0.009159 0.050835
+v 0.013856 0.009159 0.058900
+v 0.008000 0.009159 0.064756
+v -0.000000 0.009159 0.066900
+v -0.008000 0.009159 0.064756
+v -0.013856 0.009159 0.058900
+v 0.008000 0.009159 0.037044
+v -0.008000 0.009159 0.037044
+v -0.013856 0.009159 0.042900
+v 0.000000 0.009159 0.034900
+v 0.013856 0.009159 0.042900
+v 0.001777 0.009159 0.101619
+v -0.005750 0.009159 0.096150
+v -0.004652 0.009159 0.092770
+v 0.005750 0.009159 0.096150
+v 0.004652 0.009159 0.099530
+v -0.001777 0.009159 0.090681
+v 0.004652 0.009159 0.092770
+v 0.001777 0.009159 0.090681
+v -0.001777 0.009159 0.101619
+v -0.004652 0.009159 0.099530
+v -0.001777 0.009159 0.073181
+v -0.004652 0.009159 0.075270
+v 0.001777 0.009159 0.073181
+v 0.004652 0.009159 0.075270
+v 0.005750 0.009159 0.078650
+v 0.004652 0.009159 0.082030
+v -0.005750 0.009159 0.078650
+v -0.004652 0.009159 0.082030
+v -0.001777 0.009159 0.084119
+v 0.001777 0.009159 0.084119
+v 0.019986 0.007045 -0.012386
+v -0.017987 0.007044 -0.014388
+v 0.017344 0.007044 0.165587
+v 0.017987 0.007044 -0.014388
+v -0.019986 0.007045 -0.012386
+v 0.017985 0.007044 0.165587
+v 0.019986 0.007044 0.163586
+v -0.017985 0.007044 0.165587
+v -0.019986 0.007044 0.163586
+v 0.017274 0.007044 -0.014388
+v -0.017289 0.007044 -0.014388
+v 0.019986 0.007045 0.075600
+v -0.019986 0.007045 0.075600
+v -0.017357 0.007044 0.165587
+v -0.000006 0.007044 0.165587
+v -0.019986 0.007045 0.031793
+v 0.019986 0.007045 0.031793
+v -0.019986 0.007045 0.037044
+v 0.000227 0.007044 -0.014388
+v -0.019986 0.007045 0.043222
+v -0.019986 0.007045 0.050841
+v -0.019986 0.007045 0.059352
+v 0.019986 0.007045 0.025654
+v -0.019986 0.007045 0.025654
+v 0.019986 0.007045 0.037056
+v 0.019986 0.007045 0.043243
+v 0.019986 0.007045 0.050835
+v 0.019986 0.007045 0.059351
+v -0.019559 0.006736 0.075337
+v -0.019559 0.006736 -0.011771
+v -0.019559 0.006736 0.163028
+v 0.019559 0.006736 -0.011771
+v 0.019559 0.006736 0.075337
+v 0.019559 0.006736 0.163028
+v -0.017603 0.006736 -0.013672
+v 0.017604 0.006736 -0.013672
+v 0.017935 0.006736 0.165023
+v -0.017935 0.006736 0.165023
+v 0.016906 0.006736 -0.013672
+v -0.016920 0.006736 -0.013672
+v 0.017297 0.006736 0.165023
+v -0.017309 0.006736 0.165023
+v -0.000006 0.006736 0.165023
+v -0.019559 0.006736 0.031683
+v 0.019559 0.006736 0.031683
+v -0.019559 0.006736 0.036915
+v -0.019559 0.006736 0.043072
+v -0.019559 0.006736 0.050664
+v -0.019559 0.006736 0.059146
+v 0.019559 0.006736 0.025563
+v -0.019559 0.006736 0.025563
+v 0.019559 0.006736 0.036927
+v 0.019559 0.006736 0.043092
+v 0.019559 0.006736 0.050658
+v 0.000245 0.006736 -0.013672
+v 0.019559 0.006736 0.059144
+v 0.019545 0.007020 -0.011759
+v -0.017590 0.007019 -0.013660
+v 0.017284 0.007019 0.165011
+v 0.017590 0.007019 -0.013660
+v -0.019545 0.007020 -0.011759
+v 0.017922 0.007019 0.165011
+v 0.019545 0.007020 0.163016
+v -0.017922 0.007019 0.165011
+v -0.019545 0.007020 0.163016
+v 0.016893 0.007019 -0.013660
+v -0.016908 0.007019 -0.013660
+v 0.019545 0.007020 0.075337
+v -0.019545 0.007020 0.075337
+v -0.017297 0.007019 0.165011
+v -0.000006 0.007019 0.165011
+v -0.019545 0.007020 0.031683
+v 0.019545 0.007020 0.031683
+v -0.019545 0.007020 0.036915
+v 0.000222 0.007019 -0.013660
+v -0.019545 0.007020 0.043072
+v -0.019545 0.007020 0.050664
+v -0.019545 0.007020 0.059146
+v 0.019545 0.007020 0.025565
+v -0.019545 0.007020 0.025565
+v 0.019545 0.007020 0.036927
+v 0.019545 0.007020 0.043092
+v 0.019545 0.007020 0.050658
+v 0.019545 0.007020 0.059144
+v -0.017984 0.007113 -0.014384
+v 0.017341 0.007113 0.165584
+v -0.019983 0.007114 -0.012383
+v -0.019983 0.007114 0.163583
+v 0.019983 0.007114 0.075600
+v -0.000006 0.007113 0.165584
+v -0.019983 0.007114 0.031793
+v -0.019983 0.007114 0.037044
+v -0.019983 0.007114 0.043222
+v -0.019983 0.007114 0.050841
+v -0.019983 0.007114 0.059352
+v -0.019983 0.007114 0.025655
+v 0.019983 0.007114 -0.012383
+v 0.017984 0.007113 -0.014384
+v 0.017981 0.007113 0.165584
+v 0.019983 0.007114 0.163583
+v -0.017981 0.007113 0.165584
+v 0.017271 0.007113 -0.014384
+v -0.017286 0.007113 -0.014384
+v -0.019983 0.007114 0.075600
+v -0.017354 0.007113 0.165584
+v 0.019983 0.007114 0.031793
+v 0.000221 0.007113 -0.014384
+v 0.019983 0.007114 0.025655
+v 0.019983 0.007114 0.037056
+v 0.019983 0.007114 0.043243
+v 0.019983 0.007114 0.050835
+v 0.019983 0.007114 0.059351
+v -0.017987 0.006687 0.165596
+v -0.019990 0.006685 0.163594
+v 0.017987 0.006687 0.165596
+v -0.019991 0.006672 -0.012393
+v 0.017992 0.006690 -0.014396
+v -0.017994 0.006691 -0.014396
+v 0.019990 0.006685 0.163594
+v -0.019993 0.006709 0.075600
+v 0.019994 0.006690 -0.012394
+v -0.017293 0.006694 -0.014397
+v 0.017343 0.006687 0.165596
+v -0.017356 0.006687 0.165596
+v -0.000006 0.006687 0.165596
+v -0.019987 0.006657 0.050770
+v 0.000251 0.006693 -0.014396
+v -0.019989 0.006665 0.043189
+v -0.019988 0.006662 0.037032
+v -0.019986 0.006649 0.031795
+v -0.019980 0.006628 0.025684
+v 0.019993 0.006709 0.075600
+v 0.017276 0.006692 -0.014396
+v 0.019981 0.006649 0.025658
+v 0.019985 0.006651 0.050782
+v 0.019989 0.006665 0.037065
+v 0.019987 0.006656 0.031814
+v 0.019988 0.006663 0.043231
+v -0.019982 0.006639 0.059227
+v 0.019982 0.006647 0.059303
+vt 0.554694 0.657391
+vt 0.433825 0.577068
+vt 0.537413 0.732767
+vt 0.171840 0.756473
+vt 0.004212 0.517422
+vt 0.508978 0.054638
+vt 0.392232 0.366129
+vt 0.535955 0.277647
+vt 0.676569 0.426611
+vt 0.570944 0.608854
+vt 0.657163 0.340708
+vt 0.197012 0.750978
+vt 0.634797 0.224341
+vt 0.536619 0.527298
+vt 0.536314 0.531147
+vt 0.536640 0.005947
+vt 0.644694 0.154642
+vt 0.655354 0.714621
+vt 0.454133 0.386270
+vt 0.251537 0.915962
+vt 0.562843 0.123107
+vt 0.676441 0.480812
+vt 0.655355 0.642524
+vt 0.637665 0.486200
+vt 0.432241 0.529792
+vt 0.433661 0.200774
+vt 0.633750 0.219086
+vt 0.454217 0.366000
+vt 0.347872 0.351543
+vt 0.532084 0.715142
+vt 0.083498 0.916082
+vt 0.432769 0.139167
+vt 0.029777 0.173023
+vt 0.971059 0.270165
+vt 0.196511 0.924853
+vt 0.069097 0.789362
+vt 0.432118 0.226219
+vt 0.960817 0.525768
+vt 0.062888 0.898170
+vt 0.751919 0.532438
+vt 0.645243 0.032928
+vt 0.536503 0.029725
+vt 0.107693 0.763735
+vt 0.746048 0.263094
+vt 0.039255 0.237927
+vt 0.745523 0.377500
+vt 0.794566 0.698936
+vt 0.748277 0.502909
+vt 0.981678 0.688262
+vt 0.655395 0.610961
+vt 0.014520 0.474610
+vt 0.967778 0.039348
+vt 0.955233 0.260973
+vt 0.015769 0.092595
+vt 0.634942 0.252270
+vt 0.709424 0.528492
+vt 0.674041 0.251333
+vt 0.637096 0.502534
+vt 0.535204 0.530002
+vt 0.273950 0.423995
+vt 0.635823 0.535625
+vt 0.359047 0.385937
+vt 0.553218 0.164920
+vt 0.192800 0.757975
+vt 0.634735 0.222563
+vt 0.535521 0.522712
+vt 0.329998 0.345864
+vt 0.029987 0.504457
+vt 0.004768 0.505706
+vt 0.299688 0.366041
+vt 0.595338 0.596712
+vt 0.023828 0.663170
+vt 0.537897 0.678838
+vt 0.961468 0.192683
+vt 0.971275 0.266931
+vt 0.304474 0.848948
+vt 0.031633 0.458764
+vt 0.747975 0.497125
+vt 0.283964 0.890144
+vt 0.960814 0.228346
+vt 0.361245 0.386645
+vt 0.349320 0.402955
+vt 0.330282 0.408849
+vt 0.039492 0.241423
+vt 0.536760 0.713602
+vt 0.442348 0.349999
+vt 0.102746 0.756393
+vt 0.016753 0.149163
+vt 0.214218 0.745926
+vt 0.750133 0.062884
+vt 0.609524 0.161420
+vt 0.014526 0.472387
+vt 0.977669 0.236466
+vt 0.030308 0.150596
+vt 0.297338 0.894235
+vt 0.019649 0.278496
+vt 0.546671 0.126797
+vt 0.015396 0.168157
+vt 0.212882 0.760696
+vt 0.787316 0.081761
+vt 0.012582 0.624053
+vt 0.774112 0.718076
+vt 0.727050 0.629840
+vt 0.099210 0.924604
+vt 0.284711 0.787354
+vt 0.984125 0.711960
+vt 0.983477 0.045252
+vt 0.709486 0.537653
+vt 0.706809 0.620714
+vt 0.775145 0.106442
+vt 0.316737 0.380021
+vt 0.539605 0.004563
+vt 0.696671 0.159777
+vt 0.434073 0.229407
+vt 0.392184 0.386776
+vt 0.748928 0.228219
+vt 0.330347 0.406689
+vt 0.774356 0.710607
+vt 0.539622 0.459478
+vt 0.070462 0.768085
+vt 0.433350 0.596419
+vt 0.017375 0.271882
+vt 0.988091 0.736164
+vt 0.038080 0.286727
+vt 0.956653 0.498984
+vt 0.643348 0.004427
+vt 0.567158 0.121473
+vt 0.004703 0.376145
+vt 0.536588 0.737505
+vt 0.490932 0.375868
+vt 0.675853 0.533596
+vt 0.636743 0.530371
+vt 0.590770 0.475309
+vt 0.538671 0.024090
+vt 0.538521 0.033513
+vt 0.043277 0.857867
+vt 0.749450 0.032688
+vt 0.645173 0.029445
+vt 0.754393 0.023111
+vt 0.589149 0.533687
+vt 0.013383 0.242884
+vt 0.779461 0.710223
+vt 0.531271 0.710635
+vt 0.587157 0.229693
+vt 0.438687 0.371517
+vt 0.674437 0.267925
+vt 0.655378 0.678955
+vt 0.732820 0.609916
+vt 0.709914 0.619362
+vt 0.756039 0.657320
+vt 0.736972 0.091197
+vt 0.261922 0.777755
+vt 0.428189 0.680875
+vt 0.774985 0.650207
+vt 0.566885 0.642223
+vt 0.265301 0.924613
+vt 0.534033 0.735766
+vt 0.403920 0.403362
+vt 0.534025 0.222534
+vt 0.344296 0.372255
+vt 0.423430 0.342895
+vt 0.751600 0.028401
+vt 0.674169 0.257466
+vt 0.655361 0.630077
+vt 0.040907 0.832261
+vt 0.588054 0.224282
+vt 0.954687 0.488290
+vt 0.536350 0.710608
+vt 0.635114 0.258340
+vt 0.434245 0.524422
+vt 0.455933 0.365409
+vt 0.098472 0.751315
+vt 0.034060 0.471008
+vt 0.971354 0.487374
+vt 0.550671 0.093325
+vt 0.277201 0.909329
+vt 0.025321 0.643014
+vt 0.709536 0.503914
+vt 0.984312 0.378254
+vt 0.979750 0.666962
+vt 0.978251 0.601641
+vt 0.709493 0.536905
+vt 0.751446 0.535548
+vt 0.751236 0.536631
+vt 0.746915 0.245359
+vt 0.970965 0.268624
+vt 0.708384 0.222919
+vt 0.549232 0.313231
+vt 0.055990 0.784833
+vt 0.691240 0.378056
+vt 0.009608 0.248066
+vt 0.587869 0.259631
+vt 0.329955 0.343701
+vt 0.432345 0.236884
+vt 0.012306 0.642800
+vt 0.013446 0.468400
+vt 0.962921 0.644716
+vt 0.761952 0.657429
+vt 0.956562 0.255253
+vt 0.682882 0.614869
+vt 0.655394 0.612791
+vt 0.038668 0.275454
+vt 0.154653 0.930696
+vt 0.603935 0.620730
+vt 0.971265 0.271920
+vt 0.980807 0.074201
+vt 0.008589 0.510309
+vt 0.423159 0.409961
+vt 0.979928 0.526074
+vt 0.746895 0.027032
+vt 0.639296 0.291549
+vt 0.707748 0.267567
+vt 0.655352 0.710416
+vt 0.302023 0.385460
+vt 0.978558 0.100001
+vt 0.707715 0.257157
+vt 0.709559 0.481150
+vt 0.678586 0.427749
+vt 0.538563 0.295323
+vt 0.589068 0.302785
+vt 0.321827 0.363518
+vt 0.749767 0.224348
+vt 0.004374 0.516796
+vt 0.537692 0.481776
+vt 0.707680 0.218088
+vt 0.540126 0.738139
+vt 0.095330 0.906145
+vt 0.051304 0.864865
+vt 0.748400 0.008421
+vt 0.749619 0.003632
+vt 0.748082 0.003099
+vt 0.538111 0.005071
+vt 0.541146 0.004472
+vt 0.677240 0.327978
+vt 0.531948 0.713658
+vt 0.655397 0.737485
+vt 0.673187 0.226015
+vt 0.521964 0.411128
+vt 0.979908 0.525604
+vt 0.423475 0.344711
+vt 0.749228 0.023240
+vt 0.274018 0.328282
+vt 0.802967 0.001424
+vt 0.290863 0.785753
+vt 0.311067 0.849306
+vt 0.772869 0.678776
+vt 0.774077 0.718536
+vt 0.526667 0.101677
+vt 0.249993 0.755988
+vt 0.971146 0.485923
+vt 0.779256 0.718195
+vt 0.531589 0.718580
+vt 0.954594 0.266047
+vt 0.433746 0.277285
+vt 0.409325 0.371161
+vt 0.009620 0.247452
+vt 0.330149 0.391516
+vt 0.339081 0.364414
+vt 0.956471 0.519081
+vt 0.025901 0.623647
+vt 0.116296 0.915735
+vt 0.309020 0.825194
+vt 0.008185 0.682350
+vt 0.036461 0.262598
+vt 0.955339 0.493327
+vt 0.676143 0.497530
+vt 0.009030 0.235849
+vt 0.709533 0.487456
+vt 0.535066 0.255380
+vt 0.348072 0.401160
+vt 0.531348 0.710226
+vt 0.636835 0.509223
+vt 0.034995 0.511718
+vt 0.301854 0.366717
+vt 0.536597 0.028305
+vt 0.029675 0.195353
+vt 0.643361 0.009756
+vt 0.536889 0.498065
+vt 0.655354 0.713178
+vt 0.405161 0.350797
+vt 0.590859 0.472850
+vt 0.433423 0.363566
+vt 0.299959 0.386171
+vt 0.972895 0.491371
+vt 0.008259 0.560156
+vt 0.250249 0.930884
+vt 0.297009 0.848558
+vt 0.978844 0.242936
+vt 0.956744 0.521765
+vt 0.432278 0.615510
+vt 0.028368 0.091511
+vt 0.428819 0.074182
+vt 0.300447 0.816374
+vt 0.590645 0.478125
+vt 0.675014 0.537098
+vt 0.588282 0.276082
+vt 0.779220 0.718583
+vt 0.016948 0.111647
+vt 0.525790 0.653731
+vt 0.278146 0.791181
+vt 0.746247 0.257677
+vt 0.533658 0.024110
+vt 0.011107 0.662066
+vt 0.434475 0.469057
+vt 0.033360 0.231697
+vt 0.584750 0.152850
+vt 0.544124 0.678517
+vt 0.503025 0.726987
+vt 0.134691 0.743314
+vt 0.971919 0.480712
+vt 0.361166 0.366144
+vt 0.718219 0.130605
+vt 0.536565 0.504705
+vt 0.633687 0.219826
+vt 0.588135 0.269890
+vt 0.641617 0.463663
+vt 0.640483 0.461614
+vt 0.629973 0.443284
+vt 0.423192 0.408130
+vt 0.645562 0.061918
+vt 0.540987 0.029754
+vt 0.047483 0.832403
+vt 0.645257 0.034371
+vt 0.056776 0.856726
+vt 0.749529 0.032224
+vt 0.754691 0.032153
+vt 0.538302 0.738043
+vt 0.744670 0.062316
+vt 0.533634 0.024500
+vt 0.012978 0.193447
+vt 0.174444 0.741716
+vt 0.544872 0.094017
+vt 0.644305 0.152511
+vt 0.676009 0.503630
+vt 0.248607 0.770773
+vt 0.684540 0.613164
+vt 0.538354 0.735708
+vt 0.803117 0.754596
+vt 0.216163 0.923612
+vt 0.509042 0.698525
+vt 0.534821 0.009833
+vt 0.589833 0.530107
+vt 0.655353 0.732167
+vt 0.977857 0.519571
+vt 0.579456 0.137911
+vt 0.231654 0.764947
+vt 0.034543 0.248596
+vt 0.533591 0.223650
+vt 0.432271 0.234785
+vt 0.434126 0.231462
+vt 0.688923 0.378114
+vt 0.708399 0.221031
+vt 0.034526 0.247939
+vt 0.292615 0.871304
+vt 0.971832 0.273596
+vt 0.799650 0.030402
+vt 0.425214 0.041700
+vt 0.670522 0.152200
+vt 0.695412 0.140669
+vt 0.707712 0.251038
+vt 0.707717 0.244226
+vt 0.676339 0.487134
+vt 0.432252 0.527666
+vt 0.650571 0.588307
+vt 0.311618 0.402432
+vt 0.422919 0.392337
+vt 0.979884 0.228177
+vt 0.535566 0.521602
+vt 0.588120 0.226046
+vt 0.674953 0.536432
+vt 0.628248 0.312708
+vt 0.745517 0.158086
+vt 0.637511 0.490851
+vt 0.954561 0.483546
+vt 0.977404 0.126169
+vt 0.766630 0.678460
+vt 0.020850 0.685237
+vt 0.954700 0.274866
+vt 0.116373 0.930626
+vt 0.778725 0.715144
+vt 0.434018 0.475875
+vt 0.969029 0.717057
+vt 0.504650 0.422273
+vt 0.414114 0.388961
+vt 0.536884 0.715094
+vt 0.658207 0.416637
+vt 0.749371 0.218152
+vt 0.961663 0.611119
+vt 0.707784 0.273832
+vt 0.591159 0.451477
+vt 0.589762 0.500619
+vt 0.033419 0.483409
+vt 0.302237 0.803799
+vt 0.349132 0.349732
+vt 0.502727 0.423468
+vt 0.404913 0.402032
+vt 0.393972 0.366687
+vt 0.359063 0.366870
+vt 0.580721 0.134282
+vt 0.432424 0.518965
+vt 0.029970 0.505122
+vt 0.666956 0.165275
+vt 0.130987 0.758053
+vt 0.033282 0.230988
+vt 0.165394 0.748950
+vt 0.959856 0.239506
+vt 0.673878 0.244528
+vt 0.978602 0.155059
+vt 0.194514 0.939639
+vt 0.432109 0.224126
+vt 0.248619 0.924770
+vt 0.214068 0.938346
+vt 0.743041 0.091680
+vt 0.707623 0.226473
+vt 0.672964 0.217825
+vt 0.408922 0.380917
+vt 0.013225 0.240276
+vt 0.637100 0.165993
+vt 0.019745 0.376348
+vt 0.537181 0.492060
+vt 0.501237 0.328565
+vt 0.971142 0.484002
+vt 0.025254 0.580845
+vt 0.046237 0.804550
+vt 0.799776 0.726015
+vt 0.019320 0.282406
+vt 0.747751 0.491741
+vt 0.747616 0.487298
+vt 0.751529 0.622943
+vt 0.965817 0.694833
+vt 0.775565 0.736773
+vt 0.977723 0.517740
+vt 0.535534 0.266906
+vt 0.536475 0.710112
+vt 0.708447 0.598419
+vt 0.511184 0.375053
+vt 0.588948 0.525537
+vt 0.134639 0.935257
+vt 0.232458 0.750383
+vt 0.977806 0.234635
+vt 0.587127 0.228828
+vt 0.774231 0.710110
+vt 0.033491 0.465502
+vt 0.564276 0.141784
+vt 0.012884 0.072516
+vt 0.961373 0.155913
+vt 0.433206 0.265388
+vt 0.587707 0.253630
+vt 0.667836 0.379009
+vt 0.673044 0.218479
+vt 0.393842 0.386229
+vt 0.673938 0.221318
+vt 0.978894 0.511274
+vt 0.134723 0.920201
+vt 0.533909 0.231992
+vt 0.587527 0.246988
+vt 0.424401 0.360512
+vt 0.779539 0.710633
+vt 0.773947 0.713601
+vt 0.431307 0.106060
+vt 0.019614 0.280387
+vt 0.548780 0.657506
+vt 0.288979 0.807196
+vt 0.031882 0.490231
+vt 0.962067 0.124384
+vt 0.234728 0.920862
+vt 0.627895 0.614871
+vt 0.443320 0.348593
+vt 0.537398 0.293340
+vt 0.432080 0.389619
+vt 0.424600 0.713567
+vt 0.534800 0.248802
+vt 0.723508 0.150293
+vt 0.535050 0.228106
+vt 0.707636 0.225824
+vt 0.772427 0.738055
+vt 0.312856 0.400772
+vt 0.675128 0.528262
+vt 0.029839 0.111471
+vt 0.028708 0.522273
+vt 0.433635 0.553350
+vt 0.014258 0.476311
+vt 0.954473 0.270805
+vt 0.283724 0.911294
+vt 0.273718 0.376163
+vt 0.668375 0.150233
+vt 0.715959 0.128100
+vt 0.680454 0.591088
+vt 0.055062 0.799398
+vt 0.050889 0.880505
+vt 0.754359 0.022717
+vt 0.754641 0.032550
+vt 0.533543 0.033869
+vt 0.538652 0.024553
+vt 0.541071 0.028294
+vt 0.330471 0.361139
+vt 0.747009 0.028476
+vt 0.432501 0.516834
+vt 0.116076 0.745878
+vt 0.588015 0.265250
+vt 0.586855 0.221581
+vt 0.778862 0.713659
+vt 0.956688 0.232390
+vt 0.488712 0.375927
+vt 0.503327 0.028036
+vt 0.321776 0.388098
+vt 0.636669 0.532149
+vt 0.773290 0.732766
+vt 0.748710 0.509329
+vt 0.709556 0.510690
+vt 0.034108 0.477084
+vt 0.634733 0.245542
+vt 0.025427 0.069662
+vt 0.978682 0.641721
+vt 0.731380 0.629037
+vt 0.749174 0.642084
+vt 0.709440 0.529141
+vt 0.674539 0.274234
+vt 0.621388 0.590325
+vt 0.019365 0.276651
+vt 0.776699 0.735852
+vt 0.637867 0.479935
+vt 0.520882 0.339321
+vt 0.750975 0.527204
+vt 0.645827 0.091805
+vt 0.646015 0.117769
+vt 0.971376 0.482292
+vt 0.434189 0.283992
+vt 0.590404 0.484365
+vt 0.959832 0.514154
+vt 0.675237 0.329210
+vt 0.979708 0.191026
+vt 0.437733 0.381368
+vt 0.675843 0.510408
+vt 0.503178 0.329675
+vt 0.025798 0.603493
+vt 0.794708 0.056991
+vt 0.012105 0.604851
+vt 0.017221 0.130184
+vt 0.747482 0.481345
+vt 0.955680 0.473929
+vt 0.745821 0.273495
+vt 0.536629 0.718536
+vt 0.232684 0.935844
+vt 0.185956 0.932429
+vt 0.134021 0.750309
+vt 0.014205 0.470309
+vt 0.770665 0.738140
+vt 0.589917 0.528339
+vt 0.710195 0.532052
+vt 0.434300 0.522334
+vt 0.532968 0.732810
+vt 0.217665 0.930945
+vt 0.579339 0.629079
+vt 0.037980 0.269265
+vt 0.588604 0.532806
+vt 0.030407 0.130646
+vt 0.967376 0.378295
+vt 0.635602 0.534880
+vt 0.433742 0.481481
+vt 0.432791 0.251722
+vt 0.551499 0.623682
+vt 0.675115 0.528897
+vt 0.415208 0.363401
+vt 0.655354 0.708970
+vt 0.673197 0.226652
+vt 0.534984 0.226345
+vt 0.533870 0.230887
+vt 0.774148 0.737529
+vt 0.537461 0.486426
+vt 0.655372 0.620846
+vt 0.710214 0.533946
+vt 0.075712 0.892473
+vt 0.455878 0.386811
+vt 0.635263 0.264024
+vt 0.709525 0.492136
+vt 0.535309 0.261328
+vt 0.540765 0.457546
+vt 0.638200 0.293545
+vt 0.004754 0.505085
+vt 0.536594 0.718076
+vt 0.066970 0.894065
+vt 0.539219 0.009858
+vt 0.064045 0.876558
+vt 0.535776 0.006830
+vt 0.540046 0.006873
+vt 0.752926 0.008342
+vt 0.751905 0.005321
+vt 0.747505 0.005422
+vt 0.643330 0.006812
+vt 0.750869 0.004295
+vt 0.978763 0.243402
+vt 0.748979 0.227388
+vt 0.551316 0.438743
+vt 0.655353 0.717380
+vt 0.516716 0.079815
+vt 0.637316 0.496500
+vt 0.515915 0.675884
+vt 0.750919 0.526369
+vt 0.746520 0.251844
+vt 0.154248 0.923133
+vt 0.194581 0.743421
+vt 0.962073 0.571203
+vt 0.038629 0.281388
+vt 0.295445 0.826673
+vt 0.534980 0.736642
+vt 0.434221 0.177364
+vt 0.537481 0.063633
+vt 0.979562 0.563043
+vt 0.959789 0.240026
+vt 0.074590 0.772179
+vt 0.018629 0.284195
+vt 0.962374 0.092665
+vt 0.433210 0.494645
+vt 0.258438 0.767007
+vt 0.727350 0.114562
+vt 0.697277 0.142739
+vt 0.597429 0.146563
+vt 0.433479 0.271736
+vt 0.635940 0.526832
+vt 0.344015 0.381758
+vt 0.590218 0.488996
+vt 0.589506 0.507278
+vt 0.442222 0.402540
+vt 0.538004 0.475527
+vt 0.588362 0.278896
+vt 0.316426 0.370673
+vt 0.746434 0.003017
+vt 0.275577 0.770394
+vt 0.646095 0.131027
+vt 0.676255 0.491827
+vt 0.773822 0.715094
+vt 0.296673 0.882054
+vt 0.777827 0.732811
+vt 0.786819 0.673874
+vt 0.433003 0.501783
+vt 0.763117 0.122406
+vt 0.123327 0.925350
+vt 0.743824 0.642148
+vt 0.600829 0.619378
+vt 0.655353 0.718823
+vt 0.588988 0.524669
+vt 0.009192 0.236470
+vt 0.154071 0.741716
+vt 0.772291 0.735789
+vt 0.587238 0.220708
+vt 0.978813 0.510808
+vt 0.561531 0.642164
+vt 0.093101 0.914476
+vt 0.271514 0.904463
+vt 0.175243 0.924813
+vt 0.988091 0.019862
+vt 0.745931 0.267570
+vt 0.600260 0.144983
+vt 0.619960 0.152558
+vt 0.621256 0.150570
+vt 0.749384 0.219039
+vt 0.311290 0.349875
+vt 0.536696 0.525523
+vt 0.589997 0.494618
+vt 0.635522 0.274981
+vt 0.533504 0.033479
+vt 0.538597 0.033956
+vt 0.543337 0.062375
+vt 0.749193 0.022772
+vt 0.733832 0.113928
+vt 0.751479 0.026980
+vt 0.644841 0.023120
+vt 0.645011 0.024559
+vt 0.645013 0.028037
+vt 0.535718 0.271520
+vt 0.956413 0.235079
+vt 0.433777 0.158150
+vt 0.054858 0.832802
+vt 0.150736 0.756501
+vt 0.655325 0.735104
+vt 0.745790 0.137738
+vt 0.633948 0.227137
+vt 0.008436 0.512945
+vt 0.633970 0.227876
+vt 0.312513 0.351535
+vt 0.955625 0.280389
+vt 0.751786 0.530237
+vt 0.960831 0.526445
+vt 0.588393 0.281351
+vt 0.034756 0.515254
+vt 0.979851 0.228650
+vt 0.174079 0.939853
+vt 0.583671 0.629880
+vt 0.954771 0.479510
+vt 0.010724 0.585680
+vt 0.430716 0.648801
+vt 0.971613 0.739967
+vt 0.306608 0.873583
+vt 0.012346 0.481019
+vt 0.626238 0.613166
+vt 0.709525 0.497824
+vt 0.404167 0.349459
+vt 0.960824 0.227823
+vt 0.673972 0.223152
+vt 0.655367 0.657822
+vt 0.432992 0.258751
+vt 0.971547 0.015732
+vt 0.228014 0.756387
+vt 0.083275 0.774579
+vt 0.060100 0.807273
+vt 0.025131 0.558293
+vt 0.963614 0.073193
+vt 0.154068 0.937899
+vt 0.972835 0.262921
+vt 0.674278 0.263197
+vt 0.707728 0.262864
+vt 0.749885 0.222152
+vt 0.635912 0.527572
+vt 0.707545 0.217380
+vt 0.646161 0.142743
+vt 0.338718 0.389165
+vt 0.675893 0.531763
+vt 0.959900 0.514674
+vt 0.531552 0.718192
+vt 0.635378 0.268715
+vt 0.443290 0.404005
+vt 0.036256 0.293301
+vt 0.028786 0.521557
+vt 0.963982 0.673742
+vt 0.433455 0.487915
+vn -0.521090 0.772906 0.362053
+vn -0.285351 0.888921 0.358321
+vn -0.567682 0.823248 0.000000
+vn -0.311600 0.950213 0.000000
+vn 0.372764 -0.088140 -0.923731
+vn 0.321855 0.605804 -0.727606
+vn 0.913199 -0.133217 -0.385123
+vn 0.747977 0.586436 -0.310843
+vn -0.992997 0.118139 0.000000
+vn -0.992990 0.118199 0.000000
+vn -0.939966 0.341268 0.000000
+vn -0.939891 0.341476 0.000000
+vn -0.782058 0.623205 0.000000
+vn -0.569305 0.822126 0.000000
+vn -0.782234 0.622985 0.000000
+vn -0.569521 0.821977 0.000000
+vn -0.870160 0.331315 0.364764
+vn -0.720412 0.591619 0.361930
+vn -0.939332 0.343010 0.000000
+vn -0.780839 0.624732 0.000000
+vn -0.984672 -0.174417 0.000336
+vn -0.952300 -0.305104 0.005982
+vn -0.902001 -0.203565 0.380730
+vn -0.857846 -0.346612 0.379420
+vn -0.250746 -0.957540 -0.142281
+vn -0.297408 -0.954362 0.027223
+vn -0.513463 -0.830318 -0.216629
+vn -0.581935 -0.812903 0.023255
+vn -0.778583 -0.505372 0.372032
+vn -0.857846 -0.346612 0.379420
+vn -0.886617 -0.462368 0.011231
+vn -0.952300 -0.305104 0.005982
+vn -0.660766 -0.650298 0.374834
+vn -0.778583 -0.505372 0.372032
+vn -0.788745 -0.614540 0.014893
+vn -0.886617 -0.462368 0.011231
+vn 0.000000 -0.925801 0.378011
+vn -0.000855 -0.999608 0.027986
+vn 0.238233 -0.894792 0.377614
+vn 0.296405 -0.954678 0.027101
+vn 0.296405 -0.954678 0.027101
+vn -0.000855 -0.999608 0.027986
+vn 0.231212 -0.957992 -0.169686
+vn 0.004517 -0.992742 -0.120182
+vn -0.238259 -0.894775 0.377637
+vn -0.471889 -0.796857 0.377279
+vn -0.297408 -0.954362 0.027223
+vn -0.581935 -0.812903 0.023255
+vn 0.581567 -0.813174 0.022981
+vn 0.296405 -0.954678 0.027101
+vn 0.511229 -0.844469 -0.159738
+vn 0.231212 -0.957992 -0.169686
+vn 0.238233 -0.894792 0.377614
+vn 0.296405 -0.954678 0.027101
+vn 0.471859 -0.796889 0.377249
+vn 0.581567 -0.813174 0.022981
+vn 0.007019 -0.844861 -0.534939
+vn 0.004517 -0.992742 -0.120182
+vn -0.250746 -0.957540 -0.142281
+vn 0.241984 -0.835699 -0.493002
+vn 0.231212 -0.957992 -0.169686
+vn 0.004517 -0.992742 -0.120182
+vn 0.007019 -0.844861 -0.534939
+vn 0.241984 -0.835699 -0.493002
+vn 0.004517 -0.992742 -0.120182
+vn -0.243419 -0.829015 -0.503470
+vn -0.250746 -0.957540 -0.142281
+vn -0.513463 -0.830318 -0.216629
+vn -0.243419 -0.829015 -0.503470
+vn 0.007019 -0.844861 -0.534939
+vn -0.250746 -0.957540 -0.142281
+vn -0.243419 -0.829015 -0.503470
+vn -0.513463 -0.830318 -0.216629
+vn -0.710895 -0.695605 -0.103736
+vn -0.460262 -0.773604 -0.435541
+vn -0.710895 -0.695605 -0.103736
+vn -0.849339 -0.521933 -0.078800
+vn -0.243419 -0.829015 -0.503470
+vn -0.710895 -0.695605 -0.103736
+vn -0.460262 -0.773604 -0.435541
+vn -0.604308 -0.711613 -0.358355
+vn -0.849339 -0.521933 -0.078800
+vn -0.913458 -0.399399 -0.077945
+vn -0.460262 -0.773604 -0.435541
+vn -0.849339 -0.521933 -0.078800
+vn -0.604308 -0.711613 -0.358355
+vn -0.639161 -0.723760 -0.260084
+vn -0.913458 -0.399399 -0.077945
+vn -0.939019 -0.338459 -0.060733
+vn -0.604308 -0.711613 -0.358355
+vn -0.913458 -0.399399 -0.077945
+vn -0.639161 -0.723760 -0.260084
+vn -0.639161 -0.723760 -0.260084
+vn -0.939019 -0.338459 -0.060733
+vn -0.949451 -0.312272 -0.032076
+vn -0.648624 -0.756632 -0.082432
+vn -0.949451 -0.312272 -0.032076
+vn -0.954279 -0.298907 0.002472
+vn -0.639161 -0.723760 -0.260084
+vn -0.949451 -0.312272 -0.032076
+vn -0.648624 -0.756632 -0.082432
+vn 0.028932 0.428152 -0.903243
+vn 0.000000 0.418594 -0.908174
+vn 0.068943 0.929284 -0.362875
+vn 0.000000 0.928819 -0.370533
+vn -0.648624 -0.756632 -0.082432
+vn -0.954279 -0.298907 0.002472
+vn -0.951202 -0.308125 0.016572
+vn -0.647672 -0.752810 0.117467
+vn -0.951202 -0.308125 0.016572
+vn -0.949874 -0.309738 0.042452
+vn -0.859060 -0.359823 -0.364066
+vn -0.899920 -0.425957 0.093297
+vn -0.909937 -0.184063 -0.371666
+vn -0.939317 -0.333574 0.080082
+vn -0.648624 -0.756632 -0.082432
+vn -0.951202 -0.308125 0.016572
+vn -0.647672 -0.752810 0.117467
+vn -0.638581 -0.710667 0.295241
+vn -0.949874 -0.309738 0.042452
+vn -0.939317 -0.333574 0.080082
+vn -0.647672 -0.752810 0.117467
+vn -0.949874 -0.309738 0.042452
+vn -0.638581 -0.710667 0.295241
+vn -0.758811 -0.548713 -0.350885
+vn -0.782121 -0.606665 0.142282
+vn -0.859060 -0.359823 -0.364066
+vn -0.899920 -0.425957 0.093297
+vn -0.638581 -0.710667 0.295241
+vn -0.939317 -0.333574 0.080082
+vn -0.899920 -0.425957 0.093297
+vn -0.609223 -0.680913 0.406454
+vn -0.899920 -0.425957 0.093297
+vn -0.782121 -0.606665 0.142282
+vn -0.638581 -0.710667 0.295241
+vn -0.899920 -0.425957 0.093297
+vn -0.609223 -0.680913 0.406454
+vn -0.467923 -0.734358 0.491698
+vn -0.782121 -0.606665 0.142282
+vn -0.633088 -0.762062 0.135871
+vn -0.609223 -0.680913 0.406454
+vn -0.782121 -0.606665 0.142282
+vn -0.467923 -0.734358 0.491698
+vn -0.584690 -0.736127 -0.340962
+vn -0.633088 -0.762062 0.135871
+vn -0.758811 -0.548713 -0.350885
+vn -0.782121 -0.606665 0.142282
+vn -0.467923 -0.734358 0.491698
+vn -0.633088 -0.762062 0.135871
+vn -0.429001 -0.879885 0.204353
+vn -0.429001 -0.879885 0.204353
+vn -0.382251 -0.857281 -0.344896
+vn -0.200814 -0.956675 0.210824
+vn -0.191601 -0.913507 -0.358878
+vn -0.237198 -0.806417 0.541691
+vn -0.429001 -0.879885 0.204353
+vn -0.200814 -0.956675 0.210824
+vn -0.467923 -0.734358 0.491698
+vn -0.429001 -0.879885 0.204353
+vn -0.237198 -0.806417 0.541691
+vn 0.000702 -0.938897 -0.344197
+vn -0.001221 -0.985450 0.169959
+vn -0.191601 -0.913507 -0.358878
+vn -0.200814 -0.956675 0.210824
+vn -0.001221 -0.985450 0.169959
+vn 0.000275 -0.811299 0.584631
+vn -0.200814 -0.956675 0.210824
+vn 0.000702 -0.938897 -0.344197
+vn -0.191601 -0.913507 -0.358878
+vn 0.002747 -0.441857 -0.897081
+vn -0.087345 -0.430315 -0.898443
+vn 0.212324 -0.913234 -0.347739
+vn 0.439571 -0.822101 -0.361838
+vn 0.209422 -0.955035 0.209880
+vn 0.453122 -0.859397 0.236892
+vn 0.000275 -0.811299 0.584631
+vn -0.001221 -0.985450 0.169959
+vn 0.209422 -0.955035 0.209880
+vn -0.277025 -0.360404 -0.890711
+vn -0.171580 -0.405969 -0.897636
+vn -0.584690 -0.736127 -0.340962
+vn -0.382251 -0.857281 -0.344896
+vn 0.106113 -0.434098 -0.894594
+vn 0.212324 -0.913234 -0.347739
+vn 0.002747 -0.441857 -0.897081
+vn 0.000702 -0.938897 -0.344197
+vn -0.342519 -0.277268 -0.897666
+vn -0.277025 -0.360404 -0.890711
+vn -0.758811 -0.548713 -0.350885
+vn -0.584690 -0.736127 -0.340962
+vn -0.386469 -0.166759 -0.907102
+vn -0.342519 -0.277268 -0.897666
+vn -0.859060 -0.359823 -0.364066
+vn -0.758811 -0.548713 -0.350885
+vn -0.991137 -0.132636 -0.007416
+vn -0.734553 0.678551 -0.000092
+vn -0.991018 -0.133704 -0.002472
+vn -0.734685 0.678408 0.000000
+vn 0.319082 -0.282245 0.904723
+vn 0.000000 -0.052339 0.998629
+vn 0.253949 -0.348223 0.902358
+vn 0.000000 -0.052402 0.998626
+vn 0.311907 -0.598482 -0.737925
+vn 0.739459 -0.596169 -0.312703
+vn 0.233778 -0.773910 -0.588566
+vn 0.590148 -0.771523 -0.237652
+vn -0.087286 -0.425991 0.900507
+vn 0.000000 -0.434743 0.900555
+vn 0.000000 -0.052096 0.998642
+vn 0.000000 -0.052157 0.998639
+vn 0.377487 -0.139135 0.915503
+vn 0.000000 -0.052309 0.998631
+vn 0.352987 -0.211408 0.911431
+vn 0.000000 -0.052309 0.998631
+vn -0.254165 -0.348043 0.902367
+vn -0.174140 -0.397386 0.900977
+vn 0.000031 -0.052432 0.998624
+vn -0.000031 -0.052432 0.998624
+vn -0.319323 -0.281907 0.904744
+vn -0.353195 -0.211129 0.911415
+vn -0.778583 -0.505372 0.372032
+vn -0.857846 -0.346612 0.379420
+vn 0.253949 -0.348223 0.902358
+vn 0.660743 -0.650336 0.374808
+vn 0.319082 -0.282245 0.904723
+vn 0.778579 -0.505401 0.372000
+vn -0.353195 -0.211129 0.911415
+vn -0.377613 -0.138923 0.915483
+vn -0.857846 -0.346612 0.379420
+vn -0.902001 -0.203565 0.380730
+vn 0.000000 -0.434743 0.900555
+vn -0.087286 -0.425991 0.900507
+vn 0.000000 -0.925801 0.378011
+vn -0.238259 -0.894775 0.377637
+vn -0.174140 -0.397386 0.900977
+vn -0.254165 -0.348043 0.902367
+vn -0.471889 -0.796857 0.377279
+vn -0.660766 -0.650298 0.374834
+vn 0.238233 -0.894792 0.377614
+vn 0.087254 -0.426017 0.900498
+vn 0.000000 -0.925801 0.378011
+vn 0.000000 -0.434743 0.900555
+vn -0.254165 -0.348043 0.902367
+vn -0.319323 -0.281907 0.904744
+vn -0.660766 -0.650298 0.374834
+vn -0.778583 -0.505372 0.372032
+vn -0.919299 0.124793 0.373250
+vn -0.395796 0.091006 0.913818
+vn -0.870160 0.331315 0.364764
+vn -0.392471 0.192390 0.899418
+vn 0.396049 0.205456 0.894948
+vn 0.396174 0.091223 0.913633
+vn 0.867846 0.335834 0.366140
+vn 0.919256 0.124211 0.373550
+vn -0.312362 0.298720 0.901774
+vn -0.720412 0.591619 0.361930
+vn -0.392471 0.192390 0.899418
+vn -0.870160 0.331315 0.364764
+vn -0.992942 0.118598 0.000000
+vn -0.919299 0.124793 0.373250
+vn -0.939332 0.343010 0.000000
+vn -0.870160 0.331315 0.364764
+vn -0.285351 0.888921 0.358321
+vn -0.122289 0.433856 0.892645
+vn -0.069248 0.929275 0.362841
+vn -0.029177 0.427853 0.903377
+vn -0.782587 0.622541 0.000000
+vn -0.569973 0.821663 0.000000
+vn -0.722183 0.589362 -0.362083
+vn -0.523403 0.771371 -0.361988
+vn -0.313581 0.949562 0.000000
+vn -0.079471 0.996837 0.000000
+vn -0.287517 0.888248 -0.358259
+vn -0.070103 0.929281 -0.362660
+vn -0.122289 0.433856 0.892645
+vn -0.285351 0.888921 0.358321
+vn -0.224988 0.373677 0.899859
+vn -0.521090 0.772906 0.362053
+vn -0.569973 0.821663 0.000000
+vn -0.313581 0.949562 0.000000
+vn -0.523403 0.771371 -0.361988
+vn -0.287517 0.888248 -0.358259
+vn -0.939966 0.341268 0.000000
+vn -0.782587 0.622541 0.000000
+vn -0.870643 0.329573 -0.365189
+vn -0.722183 0.589362 -0.362083
+vn -0.285351 0.888921 0.358321
+vn -0.069248 0.929275 0.362841
+vn -0.311600 0.950213 0.000000
+vn -0.078738 0.996895 0.000000
+vn -0.224988 0.373677 0.899859
+vn -0.521090 0.772906 0.362053
+vn -0.312362 0.298720 0.901774
+vn -0.720412 0.591619 0.361930
+vn -0.312152 0.299273 -0.901663
+vn -0.393298 0.193094 -0.898906
+vn -0.722183 0.589362 -0.362083
+vn -0.870643 0.329573 -0.365189
+vn -0.720412 0.591619 0.361930
+vn -0.521090 0.772906 0.362053
+vn -0.780839 0.624732 0.000000
+vn -0.567682 0.823248 0.000000
+vn -0.224897 0.374747 -0.899436
+vn -0.312152 0.299273 -0.901663
+vn -0.523403 0.771371 -0.361988
+vn -0.722183 0.589362 -0.362083
+vn -0.121224 0.433042 -0.893185
+vn -0.224897 0.374747 -0.899436
+vn -0.287517 0.888248 -0.358259
+vn -0.523403 0.771371 -0.361988
+vn -0.029329 0.427604 -0.903490
+vn -0.121224 0.433042 -0.893185
+vn -0.070103 0.929281 -0.362660
+vn -0.287517 0.888248 -0.358259
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.992997 0.118139 0.000000
+vn -0.992990 0.118199 0.000000
+vn 0.000000 0.052309 -0.998631
+vn 0.000000 0.048190 -0.998838
+vn -0.393298 0.193094 -0.898906
+vn -0.396046 0.091008 -0.913710
+vn -0.734964 0.678106 0.000061
+vn -0.670866 0.741578 0.000000
+vn -0.730909 0.682475 0.000000
+vn -0.670866 0.741578 0.000000
+vn 0.029085 0.427176 0.903701
+vn 0.121893 0.433583 0.892832
+vn 0.069399 0.929325 0.362684
+vn 0.286943 0.888480 0.358145
+vn 0.720131 0.591401 0.362843
+vn 0.527037 0.768230 0.363393
+vn 0.320333 0.310109 0.895108
+vn 0.235030 0.375908 0.896356
+vn 0.121893 0.433583 0.892832
+vn 0.235030 0.375908 0.896356
+vn 0.286943 0.888480 0.358145
+vn 0.527037 0.768230 0.363393
+vn 0.939143 0.343526 0.000000
+vn 0.993040 0.117774 0.000000
+vn 0.868458 0.334950 -0.365499
+vn 0.919351 0.124242 -0.373307
+vn 0.310045 -0.595674 0.740976
+vn 0.000000 -0.675817 0.737069
+vn 0.263930 -0.785352 0.559967
+vn 0.000000 -0.675849 0.737040
+vn 0.568602 0.822613 0.000000
+vn 0.780351 0.625342 0.000000
+vn 0.524598 0.770401 -0.362326
+vn 0.719155 0.592775 -0.362538
+vn 0.780351 0.625342 0.000000
+vn 0.939143 0.343526 0.000000
+vn 0.719155 0.592775 -0.362538
+vn 0.868458 0.334950 -0.365499
+vn 0.867846 0.335834 0.366140
+vn 0.720131 0.591401 0.362843
+vn 0.396049 0.205456 0.894948
+vn 0.320333 0.310109 0.895108
+vn 0.311536 0.950234 0.000000
+vn 0.568602 0.822613 0.000000
+vn 0.286295 0.888581 -0.358411
+vn 0.524598 0.770401 -0.362326
+vn 0.078190 0.996939 0.000000
+vn 0.311536 0.950234 0.000000
+vn 0.068943 0.929284 -0.362875
+vn 0.286295 0.888581 -0.358411
+vn 0.325795 0.316060 -0.891046
+vn 0.719155 0.592775 -0.362538
+vn 0.392297 0.200055 -0.897820
+vn 0.868458 0.334950 -0.365499
+vn -0.381763 0.058475 0.922409
+vn 0.000000 0.044008 0.999031
+vn -0.395796 0.091006 0.913818
+vn 0.000000 0.048434 0.998826
+vn 0.939246 0.343245 0.000000
+vn 0.993048 0.117711 0.000000
+vn 0.939213 0.343335 0.000000
+vn 0.993044 0.117741 0.000000
+vn -0.353195 -0.211129 0.911415
+vn -0.319323 -0.281907 0.904744
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052279 0.998633
+vn 0.239788 -0.804483 0.543423
+vn 0.209422 -0.955035 0.209880
+vn 0.453122 -0.859397 0.236892
+vn 0.239788 -0.804483 0.543423
+vn 0.000275 -0.811299 0.584631
+vn 0.209422 -0.955035 0.209880
+vn 0.640223 -0.679501 -0.358321
+vn 0.802405 -0.482688 -0.350938
+vn 0.640007 -0.753384 0.151006
+vn 0.797224 -0.586336 0.143685
+vn 0.239788 -0.804483 0.543423
+vn 0.453122 -0.859397 0.236892
+vn 0.640007 -0.753384 0.151006
+vn 0.466575 -0.738743 0.486381
+vn 0.640007 -0.753384 0.151006
+vn 0.797224 -0.586336 0.143685
+vn 0.466575 -0.738743 0.486381
+vn 0.239788 -0.804483 0.543423
+vn 0.640007 -0.753384 0.151006
+vn 0.596342 -0.683993 0.420155
+vn 0.797224 -0.586336 0.143685
+vn 0.897571 -0.428521 0.103613
+vn 0.596342 -0.683993 0.420155
+vn 0.466575 -0.738743 0.486381
+vn 0.797224 -0.586336 0.143685
+vn 0.366104 -0.154792 -0.917610
+vn 0.877705 -0.298509 -0.374869
+vn 0.374497 -0.244701 -0.894356
+vn 0.802405 -0.482688 -0.350938
+vn 0.596342 -0.683993 0.420155
+vn 0.897571 -0.428521 0.103613
+vn 0.934625 -0.355612 0.004151
+vn 0.897571 -0.428521 0.103613
+vn 0.797224 -0.586336 0.143685
+vn 0.877705 -0.298509 -0.374869
+vn 0.802405 -0.482688 -0.350938
+vn 0.638345 -0.713270 0.289416
+vn 0.934625 -0.355612 0.004151
+vn 0.949811 -0.310561 0.037569
+vn 0.638345 -0.713270 0.289416
+vn 0.596342 -0.683993 0.420155
+vn 0.934625 -0.355612 0.004151
+vn 0.638345 -0.713270 0.289416
+vn 0.949811 -0.310561 0.037569
+vn 0.950440 -0.308980 0.034579
+vn 0.985937 -0.163336 -0.035341
+vn 0.897571 -0.428521 0.103613
+vn 0.904830 -0.170541 -0.390126
+vn 0.877705 -0.298509 -0.374869
+vn 0.650787 -0.750218 0.116827
+vn 0.950440 -0.308980 0.034579
+vn 0.957065 -0.289866 0.002228
+vn 0.650787 -0.750218 0.116827
+vn 0.638345 -0.713270 0.289416
+vn 0.950440 -0.308980 0.034579
+vn 0.650787 -0.750218 0.116827
+vn 0.957065 -0.289866 0.002228
+vn 0.949985 -0.312053 -0.012299
+vn 0.649294 -0.756172 -0.081364
+vn 0.949985 -0.312053 -0.012299
+vn 0.946362 -0.321059 -0.036318
+vn 0.649294 -0.756172 -0.081364
+vn 0.650787 -0.750218 0.116827
+vn 0.949985 -0.312053 -0.012299
+vn 0.912059 -0.149299 0.381914
+vn 0.989641 -0.143562 -0.000641
+vn 0.750260 0.586248 0.305652
+vn 0.738513 0.674239 0.000000
+vn 0.637546 -0.726174 -0.257307
+vn 0.946362 -0.321059 -0.036318
+vn 0.922100 -0.382311 -0.059756
+vn 0.637546 -0.726174 -0.257307
+vn 0.649294 -0.756172 -0.081364
+vn 0.946362 -0.321059 -0.036318
+vn 0.946362 -0.321059 -0.036318
+vn 0.981702 -0.190316 0.006439
+vn 0.922100 -0.382311 -0.059756
+vn 0.637546 -0.726174 -0.257307
+vn 0.922100 -0.382311 -0.059756
+vn 0.898822 -0.432030 -0.073948
+vn 0.898822 -0.432030 -0.073948
+vn 0.951306 -0.308211 0.004853
+vn 0.834906 -0.542718 -0.091587
+vn 0.884960 -0.465507 0.012208
+vn 0.602210 -0.707044 -0.370720
+vn 0.898822 -0.432030 -0.073948
+vn 0.834906 -0.542718 -0.091587
+vn 0.602210 -0.707044 -0.370720
+vn 0.637546 -0.726174 -0.257307
+vn 0.898822 -0.432030 -0.073948
+vn 0.834906 -0.542718 -0.091587
+vn 0.884960 -0.465507 0.012208
+vn 0.726236 -0.680854 -0.094976
+vn 0.789045 -0.614139 0.015504
+vn 0.602210 -0.707044 -0.370720
+vn 0.834906 -0.542718 -0.091587
+vn 0.726236 -0.680854 -0.094976
+vn 0.789045 -0.614139 0.015504
+vn 0.581567 -0.813174 0.022981
+vn 0.726236 -0.680854 -0.094976
+vn 0.511229 -0.844469 -0.159738
+vn 0.461423 -0.761703 -0.454861
+vn 0.726236 -0.680854 -0.094976
+vn 0.511229 -0.844469 -0.159738
+vn 0.461423 -0.761703 -0.454861
+vn 0.602210 -0.707044 -0.370720
+vn 0.726236 -0.680854 -0.094976
+vn -0.513463 -0.830318 -0.216629
+vn -0.581935 -0.812903 0.023255
+vn -0.710895 -0.695605 -0.103736
+vn -0.788745 -0.614540 0.014893
+vn 0.241984 -0.835699 -0.493002
+vn 0.511229 -0.844469 -0.159738
+vn 0.231212 -0.957992 -0.169686
+vn 0.241984 -0.835699 -0.493002
+vn 0.461423 -0.761703 -0.454861
+vn 0.511229 -0.844469 -0.159738
+vn 0.471859 -0.796889 0.377249
+vn 0.581567 -0.813174 0.022981
+vn 0.660743 -0.650336 0.374808
+vn 0.789045 -0.614139 0.015504
+vn -0.471889 -0.796857 0.377279
+vn -0.660766 -0.650298 0.374834
+vn -0.581935 -0.812903 0.023255
+vn -0.788745 -0.614540 0.014893
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 0.699991 -0.714152
+vn 0.000000 0.699991 -0.714152
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn -0.171580 -0.405969 -0.897636
+vn -0.087345 -0.430315 -0.898443
+vn -0.382251 -0.857281 -0.344896
+vn -0.191601 -0.913507 -0.358878
+vn 0.087254 -0.426017 0.900498
+vn 0.238233 -0.894792 0.377614
+vn 0.173990 -0.397360 0.901018
+vn 0.471859 -0.796889 0.377249
+vn 0.173990 -0.397360 0.901018
+vn 0.471859 -0.796889 0.377249
+vn 0.253949 -0.348223 0.902358
+vn 0.660743 -0.650336 0.374808
+vn 0.319082 -0.282245 0.904723
+vn 0.778579 -0.505401 0.372000
+vn 0.352987 -0.211408 0.911431
+vn 0.857781 -0.346641 0.379541
+vn 0.901970 -0.203534 0.380821
+vn 0.377487 -0.139135 0.915503
+vn 0.857781 -0.346641 0.379541
+vn 0.352987 -0.211408 0.911431
+vn 0.375234 -0.102697 0.921223
+vn 0.912059 -0.149299 0.381914
+vn 0.329149 0.602020 0.727484
+vn 0.750260 0.586248 0.305652
+vn 0.122871 0.434167 -0.892414
+vn 0.286295 0.888581 -0.358411
+vn 0.228679 0.372332 -0.899486
+vn 0.524598 0.770401 -0.362326
+vn 0.028932 0.428152 -0.903243
+vn 0.068943 0.929284 -0.362875
+vn 0.122871 0.434167 -0.892414
+vn 0.286295 0.888581 -0.358411
+vn -0.048831 -0.536314 -0.842605
+vn 0.048037 -0.537777 -0.841717
+vn -0.000397 -0.487642 -0.873044
+vn 0.241984 -0.835699 -0.493002
+vn 0.048037 -0.537777 -0.841717
+vn 0.120276 -0.592529 -0.796519
+vn 0.007019 -0.844861 -0.534939
+vn 0.048037 -0.537777 -0.841717
+vn 0.241984 -0.835699 -0.493002
+vn -0.127296 -0.607792 0.783827
+vn -0.114081 -0.538145 0.835096
+vn 0.133795 -0.607355 0.783083
+vn 0.113316 -0.537375 0.835696
+vn 0.461423 -0.761703 -0.454861
+vn 0.120276 -0.592529 -0.796519
+vn 0.138436 -0.667855 -0.731304
+vn 0.241984 -0.835699 -0.493002
+vn 0.120276 -0.592529 -0.796519
+vn 0.461423 -0.761703 -0.454861
+vn -0.171456 -0.865459 -0.470726
+vn 0.147194 -0.854141 -0.498775
+vn -0.176492 -0.781012 -0.599058
+vn 0.183848 -0.767314 -0.614352
+vn 0.602210 -0.707044 -0.370720
+vn 0.138436 -0.667855 -0.731304
+vn 0.183848 -0.767314 -0.614352
+vn 0.461423 -0.761703 -0.454861
+vn 0.138436 -0.667855 -0.731304
+vn 0.602210 -0.707044 -0.370720
+vn -0.154304 -0.850013 0.503654
+vn 0.165595 -0.881167 0.442857
+vn -0.123083 -0.939557 0.319503
+vn 0.105839 -0.942204 0.317883
+vn 0.602210 -0.707044 -0.370720
+vn 0.183848 -0.767314 -0.614352
+vn 0.147194 -0.854141 -0.498775
+vn -0.123083 -0.939557 0.319503
+vn 0.105839 -0.942204 0.317883
+vn -0.165385 -0.979921 0.111366
+vn 0.128944 -0.988856 0.074406
+vn 0.637546 -0.726174 -0.257307
+vn 0.147194 -0.854141 -0.498775
+vn 0.095371 -0.953529 -0.285809
+vn 0.602210 -0.707044 -0.370720
+vn 0.147194 -0.854141 -0.498775
+vn 0.637546 -0.726174 -0.257307
+vn -0.121832 -0.784555 0.607972
+vn 0.125158 -0.786652 0.604577
+vn -0.154304 -0.850013 0.503654
+vn 0.165595 -0.881167 0.442857
+vn 0.637546 -0.726174 -0.257307
+vn 0.095371 -0.953529 -0.285809
+vn 0.166633 -0.977095 -0.132361
+vn -0.127296 -0.607792 0.783827
+vn 0.133795 -0.607355 0.783083
+vn -0.157268 -0.676224 0.719714
+vn 0.172404 -0.693613 0.699412
+vn 0.649294 -0.756172 -0.081364
+vn 0.166633 -0.977095 -0.132361
+vn 0.128944 -0.988856 0.074406
+vn 0.637546 -0.726174 -0.257307
+vn 0.166633 -0.977095 -0.132361
+vn 0.649294 -0.756172 -0.081364
+vn -0.140114 -0.669504 -0.729474
+vn 0.138436 -0.667855 -0.731304
+vn -0.119057 -0.591407 -0.797536
+vn 0.120276 -0.592529 -0.796519
+vn 0.650787 -0.750218 0.116827
+vn 0.128944 -0.988856 0.074406
+vn 0.105839 -0.942204 0.317883
+vn 0.649294 -0.756172 -0.081364
+vn 0.128944 -0.988856 0.074406
+vn 0.650787 -0.750218 0.116827
+vn -0.165385 -0.979921 0.111366
+vn 0.128944 -0.988856 0.074406
+vn -0.145208 -0.987646 -0.058901
+vn 0.166633 -0.977095 -0.132361
+vn 0.650787 -0.750218 0.116827
+vn 0.105839 -0.942204 0.317883
+vn 0.165595 -0.881167 0.442857
+vn -0.123999 -0.951033 -0.283125
+vn 0.095371 -0.953529 -0.285809
+vn -0.171456 -0.865459 -0.470726
+vn 0.147194 -0.854141 -0.498775
+vn 0.638345 -0.713270 0.289416
+vn 0.165595 -0.881167 0.442857
+vn 0.125158 -0.786652 0.604577
+vn 0.650787 -0.750218 0.116827
+vn 0.165595 -0.881167 0.442857
+vn 0.638345 -0.713270 0.289416
+vn 0.596342 -0.683993 0.420155
+vn 0.125158 -0.786652 0.604577
+vn 0.172404 -0.693613 0.699412
+vn 0.638345 -0.713270 0.289416
+vn 0.125158 -0.786652 0.604577
+vn 0.596342 -0.683993 0.420155
+vn -0.145208 -0.987646 -0.058901
+vn 0.166633 -0.977095 -0.132361
+vn -0.123999 -0.951033 -0.283125
+vn 0.095371 -0.953529 -0.285809
+vn 0.596342 -0.683993 0.420155
+vn 0.172404 -0.693613 0.699412
+vn 0.133795 -0.607355 0.783083
+vn -0.114081 -0.538145 0.835096
+vn -0.046175 -0.491786 0.869491
+vn 0.113316 -0.537375 0.835696
+vn 0.046267 -0.491606 0.869588
+vn 0.466575 -0.738743 0.486381
+vn 0.133795 -0.607355 0.783083
+vn 0.113316 -0.537375 0.835696
+vn 0.596342 -0.683993 0.420155
+vn 0.133795 -0.607355 0.783083
+vn 0.466575 -0.738743 0.486381
+vn 0.239788 -0.804483 0.543423
+vn 0.113316 -0.537375 0.835696
+vn 0.046267 -0.491606 0.869588
+vn 0.466575 -0.738743 0.486381
+vn 0.113316 -0.537375 0.835696
+vn 0.239788 -0.804483 0.543423
+vn -0.046175 -0.491786 0.869491
+vn 0.000031 -0.438709 0.898629
+vn 0.046267 -0.491606 0.869588
+vn 0.239788 -0.804483 0.543423
+vn 0.046267 -0.491606 0.869588
+vn 0.000275 -0.811299 0.584631
+vn -0.237198 -0.806417 0.541691
+vn -0.046175 -0.491786 0.869491
+vn -0.114081 -0.538145 0.835096
+vn 0.000275 -0.811299 0.584631
+vn -0.237198 -0.806417 0.541691
+vn -0.200814 -0.956675 0.210824
+vn -0.237198 -0.806417 0.541691
+vn 0.000275 -0.811299 0.584631
+vn -0.046175 -0.491786 0.869491
+vn -0.467923 -0.734358 0.491698
+vn -0.114081 -0.538145 0.835096
+vn -0.127296 -0.607792 0.783827
+vn -0.467923 -0.734358 0.491698
+vn -0.237198 -0.806417 0.541691
+vn -0.114081 -0.538145 0.835096
+vn -0.467923 -0.734358 0.491698
+vn -0.127296 -0.607792 0.783827
+vn -0.157268 -0.676224 0.719714
+vn -0.609223 -0.680913 0.406454
+vn -0.157268 -0.676224 0.719714
+vn -0.121832 -0.784555 0.607972
+vn -0.609223 -0.680913 0.406454
+vn -0.467923 -0.734358 0.491698
+vn -0.157268 -0.676224 0.719714
+vn -0.609223 -0.680913 0.406454
+vn -0.121832 -0.784555 0.607972
+vn -0.154304 -0.850013 0.503654
+vn -0.638581 -0.710667 0.295241
+vn -0.154304 -0.850013 0.503654
+vn -0.123083 -0.939557 0.319503
+vn -0.638581 -0.710667 0.295241
+vn -0.609223 -0.680913 0.406454
+vn -0.154304 -0.850013 0.503654
+vn -0.647672 -0.752810 0.117467
+vn -0.123083 -0.939557 0.319503
+vn -0.165385 -0.979921 0.111366
+vn -0.647672 -0.752810 0.117467
+vn -0.638581 -0.710667 0.295241
+vn -0.123083 -0.939557 0.319503
+vn -0.647672 -0.752810 0.117467
+vn -0.165385 -0.979921 0.111366
+vn -0.145208 -0.987646 -0.058901
+vn -0.648624 -0.756632 -0.082432
+vn -0.145208 -0.987646 -0.058901
+vn -0.123999 -0.951033 -0.283125
+vn -0.648624 -0.756632 -0.082432
+vn -0.647672 -0.752810 0.117467
+vn -0.145208 -0.987646 -0.058901
+vn -0.639161 -0.723760 -0.260084
+vn -0.123999 -0.951033 -0.283125
+vn -0.171456 -0.865459 -0.470726
+vn -0.639161 -0.723760 -0.260084
+vn -0.648624 -0.756632 -0.082432
+vn -0.123999 -0.951033 -0.283125
+vn -0.639161 -0.723760 -0.260084
+vn -0.171456 -0.865459 -0.470726
+vn -0.176492 -0.781012 -0.599058
+vn -0.604308 -0.711613 -0.358355
+vn -0.639161 -0.723760 -0.260084
+vn -0.176492 -0.781012 -0.599058
+vn -0.460262 -0.773604 -0.435541
+vn -0.140114 -0.669504 -0.729474
+vn -0.119057 -0.591407 -0.797536
+vn -0.460262 -0.773604 -0.435541
+vn -0.604308 -0.711613 -0.358355
+vn -0.140114 -0.669504 -0.729474
+vn -0.243419 -0.829015 -0.503470
+vn -0.119057 -0.591407 -0.797536
+vn -0.048831 -0.536314 -0.842605
+vn -0.243419 -0.829015 -0.503470
+vn -0.460262 -0.773604 -0.435541
+vn -0.119057 -0.591407 -0.797536
+vn -0.243419 -0.829015 -0.503470
+vn -0.048831 -0.536314 -0.842605
+vn 0.007019 -0.844861 -0.534939
+vn -0.745103 0.591285 -0.308552
+vn -0.734685 0.678408 0.000000
+vn -0.500057 0.836990 -0.222241
+vn -0.670866 0.741578 0.000000
+vn 0.253949 -0.348223 0.902358
+vn 0.000000 -0.052402 0.998626
+vn 0.173990 -0.397360 0.901018
+vn 0.000031 -0.052339 0.998629
+vn 0.381762 0.058535 0.922405
+vn 0.396174 0.091223 0.913633
+vn 0.000000 0.044038 0.999030
+vn 0.000000 0.048464 0.998825
+vn 0.000000 0.052126 0.998641
+vn -0.392471 0.192390 0.899418
+vn 0.000000 0.048434 0.998826
+vn -0.395796 0.091006 0.913818
+vn -0.381975 0.058596 -0.922313
+vn -0.396046 0.091008 -0.913710
+vn 0.000000 0.043978 -0.999033
+vn 0.000000 0.048190 -0.998838
+vn -0.316758 0.606690 -0.729103
+vn -0.214888 0.828363 -0.517338
+vn 0.000000 0.699991 -0.714152
+vn 0.000000 0.732851 -0.680389
+vn -0.393298 0.193094 -0.898906
+vn -0.396046 0.091008 -0.913710
+vn -0.870643 0.329573 -0.365189
+vn -0.919325 0.124518 -0.373278
+vn 0.173990 -0.397360 0.901018
+vn 0.000031 -0.052339 0.998629
+vn 0.087254 -0.426017 0.900498
+vn 0.000000 -0.052126 0.998641
+vn -0.912094 -0.149238 0.381855
+vn -0.902001 -0.203565 0.380730
+vn -0.375319 -0.102543 0.921206
+vn -0.377613 -0.138923 0.915483
+vn 0.000000 0.044008 0.999031
+vn 0.000000 0.044069 0.999029
+vn 0.000000 0.048434 0.998826
+vn 0.000000 0.048434 0.998826
+vn 0.000000 0.048160 -0.998840
+vn 0.395989 0.090856 -0.913750
+vn 0.000000 0.043978 -0.999033
+vn 0.382011 0.058628 -0.922296
+vn -0.319323 -0.281907 0.904744
+vn -0.254165 -0.348043 0.902367
+vn 0.000000 -0.052279 0.998633
+vn 0.000031 -0.052432 0.998624
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078433 0.996919 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.719155 0.592775 -0.362538
+vn 0.325795 0.316060 -0.891046
+vn 0.524598 0.770401 -0.362326
+vn 0.228679 0.372332 -0.899486
+vn 0.919351 0.124242 -0.373307
+vn 0.395989 0.090856 -0.913750
+vn 0.868458 0.334950 -0.365499
+vn 0.392297 0.200055 -0.897820
+vn 0.998866 0.047610 0.000000
+vn 0.993055 0.117650 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993066 0.117560 0.000000
+vn 0.395989 0.090856 -0.913750
+vn 0.000000 0.048160 -0.998840
+vn 0.392297 0.200055 -0.897820
+vn 0.002930 0.055209 -0.998470
+vn -0.316758 0.606690 -0.729103
+vn -0.745103 0.591285 -0.308552
+vn -0.214888 0.828363 -0.517338
+vn -0.500057 0.836990 -0.222241
+vn 0.000000 0.052279 -0.998633
+vn 0.000000 0.048190 -0.998838
+vn 0.000000 0.052309 -0.998631
+vn 0.000000 0.048190 -0.998838
+vn 0.392297 0.200055 -0.897820
+vn 0.002930 0.055209 -0.998470
+vn 0.325795 0.316060 -0.891046
+vn 0.228679 0.372332 -0.899486
+vn 0.228679 0.372332 -0.899486
+vn 0.002930 0.055209 -0.998470
+vn 0.122871 0.434167 -0.892414
+vn 0.028932 0.428152 -0.903243
+vn -0.393298 0.193094 -0.898906
+vn -0.312152 0.299273 -0.901663
+vn 0.000000 0.052309 -0.998631
+vn -0.224897 0.374747 -0.899436
+vn -0.029329 0.427604 -0.903490
+vn 0.000000 0.052309 -0.998631
+vn -0.121224 0.433042 -0.893185
+vn -0.224897 0.374747 -0.899436
+vn 0.000000 0.418594 -0.908174
+vn 0.028932 0.428152 -0.903243
+vn 0.000000 0.052279 -0.998633
+vn 0.002930 0.055209 -0.998470
+vn 0.184245 -0.380941 -0.906056
+vn 0.106113 -0.434098 -0.894594
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn -0.171580 -0.405969 -0.897636
+vn -0.277025 -0.360404 -0.890711
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.366104 -0.154792 -0.917610
+vn 0.374497 -0.244701 -0.894356
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn -0.386469 -0.166759 -0.907102
+vn 0.000000 -0.052309 -0.998631
+vn -0.342519 -0.277268 -0.897666
+vn 0.000000 -0.052309 -0.998631
+vn 0.106113 -0.434098 -0.894594
+vn 0.002747 -0.441857 -0.897081
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052339 -0.998629
+vn -0.277025 -0.360404 -0.890711
+vn -0.342519 -0.277268 -0.897666
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn -0.386469 -0.166759 -0.907102
+vn -0.385668 -0.101262 -0.917064
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn -0.171580 -0.405969 -0.897636
+vn 0.000000 -0.052309 -0.998631
+vn -0.087345 -0.430315 -0.898443
+vn 0.000000 -0.052339 -0.998629
+vn 0.184245 -0.380941 -0.906056
+vn 0.000000 -0.052309 -0.998631
+vn 0.304368 -0.320665 -0.896958
+vn 0.000000 -0.052339 -0.998629
+vn -0.087345 -0.430315 -0.898443
+vn 0.000000 -0.052339 -0.998629
+vn 0.002747 -0.441857 -0.897081
+vn 0.000000 -0.052339 -0.998629
+vn 0.374497 -0.244701 -0.894356
+vn 0.304368 -0.320665 -0.896958
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 0.699991 -0.714152
+vn 0.372764 -0.088140 -0.923731
+vn 0.321855 0.605804 -0.727606
+vn -0.316758 0.606690 -0.729103
+vn 0.000000 0.699991 -0.714152
+vn -0.379990 -0.085209 -0.921058
+vn 0.000000 -0.052309 -0.998631
+vn 0.209422 -0.955035 0.209880
+vn -0.001221 -0.985450 0.169959
+vn 0.212324 -0.913234 -0.347739
+vn 0.000702 -0.938897 -0.344197
+vn 0.184245 -0.380941 -0.906056
+vn 0.439571 -0.822101 -0.361838
+vn 0.106113 -0.434098 -0.894594
+vn 0.212324 -0.913234 -0.347739
+vn 0.720131 0.591401 0.362843
+vn 0.867846 0.335834 0.366140
+vn 0.781137 0.624360 0.000000
+vn 0.939468 0.342637 0.000000
+vn 0.527037 0.768230 0.363393
+vn 0.720131 0.591401 0.362843
+vn 0.569396 0.822064 0.000000
+vn 0.781137 0.624360 0.000000
+vn 0.867846 0.335834 0.366140
+vn 0.919256 0.124211 0.373550
+vn 0.939468 0.342637 0.000000
+vn 0.993066 0.117560 0.000000
+vn 0.993066 0.117560 0.000000
+vn 0.919256 0.124211 0.373550
+vn 0.998866 0.047610 0.000000
+vn 0.922245 0.060458 0.381850
+vn -0.992964 0.118414 0.000000
+vn -0.992942 0.118598 0.000000
+vn -0.939600 0.342275 0.000000
+vn -0.939332 0.343010 0.000000
+vn 0.780580 0.625056 0.000000
+vn 0.939246 0.343245 0.000000
+vn 0.780499 0.625156 0.000000
+vn 0.939213 0.343335 0.000000
+vn 0.000000 0.928845 0.370470
+vn 0.000000 0.418172 0.908368
+vn 0.069399 0.929325 0.362684
+vn 0.029085 0.427176 0.903701
+vn 0.396174 0.091223 0.913633
+vn 0.396049 0.205456 0.894948
+vn 0.000000 0.048464 0.998825
+vn 0.003998 0.056093 0.998418
+vn 0.000000 0.052005 0.998647
+vn 0.000000 0.052126 0.998641
+vn 0.000000 0.048434 0.998826
+vn 0.000000 0.048434 0.998826
+vn -0.392471 0.192390 0.899418
+vn 0.000000 0.052126 0.998641
+vn -0.312362 0.298720 0.901774
+vn -0.224988 0.373677 0.899859
+vn -0.029177 0.427853 0.903377
+vn -0.122289 0.433856 0.892645
+vn 0.000000 0.052126 0.998641
+vn -0.224988 0.373677 0.899859
+vn 0.396049 0.205456 0.894948
+vn 0.320333 0.310109 0.895108
+vn 0.003998 0.056093 0.998418
+vn 0.235030 0.375908 0.896356
+vn 0.235030 0.375908 0.896356
+vn 0.121893 0.433583 0.892832
+vn 0.003998 0.056093 0.998418
+vn 0.029085 0.427176 0.903701
+vn 0.000000 0.418172 0.908368
+vn 0.000000 0.052005 0.998647
+vn 0.029085 0.427176 0.903701
+vn 0.003998 0.056093 0.998418
+vn -0.174140 -0.397386 0.900977
+vn -0.087286 -0.425991 0.900507
+vn -0.000031 -0.052432 0.998624
+vn 0.000000 -0.052096 0.998642
+vn 0.087254 -0.426017 0.900498
+vn 0.000000 -0.052126 0.998641
+vn 0.000000 -0.434743 0.900555
+vn 0.000000 -0.052157 0.998639
+vn 0.000000 -0.052309 0.998631
+vn 0.377487 -0.139135 0.915503
+vn 0.000000 -0.052370 0.998628
+vn 0.375234 -0.102697 0.921223
+vn 0.000000 -0.052279 0.998633
+vn 0.000000 0.703021 0.711169
+vn -0.375319 -0.102543 0.921206
+vn -0.329091 0.602117 0.727430
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052339 0.998629
+vn 0.000000 -0.052370 0.998628
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052339 0.998629
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052463 0.998623
+vn 0.000000 -0.052402 0.998626
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052339 0.998629
+vn 0.000000 -0.052248 0.998634
+vn 0.000031 -0.052339 0.998629
+vn 0.000000 -0.052463 0.998623
+vn 0.000000 -0.052402 0.998626
+vn 0.000031 -0.052432 0.998624
+vn -0.000031 -0.052432 0.998624
+vn 0.000000 -0.052463 0.998623
+vn 0.000000 -0.052248 0.998634
+vn 0.000000 -0.052279 0.998633
+vn 0.000031 -0.052432 0.998624
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052463 0.998623
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052279 0.998633
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052279 0.998633
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052339 0.998629
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 0.052126 0.998641
+vn 0.000000 0.052005 0.998647
+vn -0.029177 0.427853 0.903377
+vn 0.000000 0.418172 0.908368
+vn 0.000000 0.048464 0.998825
+vn 0.003998 0.056093 0.998418
+vn 0.000000 0.048434 0.998826
+vn 0.000000 0.052005 0.998647
+vn -0.069248 0.929275 0.362841
+vn -0.029177 0.427853 0.903377
+vn 0.000000 0.928845 0.370470
+vn 0.000000 0.418172 0.908368
+vn 0.000031 -0.052339 0.998629
+vn 0.000000 -0.052248 0.998634
+vn 0.000000 -0.052126 0.998641
+vn 0.000000 -0.052157 0.998639
+vn -0.000031 -0.052432 0.998624
+vn 0.000000 -0.052096 0.998642
+vn 0.000000 -0.052248 0.998634
+vn 0.000000 -0.052157 0.998639
+vn 0.660743 -0.650336 0.374808
+vn 0.789045 -0.614139 0.015504
+vn 0.778579 -0.505401 0.372000
+vn 0.884960 -0.465507 0.012208
+vn 0.310045 -0.595674 0.740976
+vn 0.263930 -0.785352 0.559967
+vn 0.741037 -0.593385 0.314257
+vn 0.621805 -0.753893 0.212141
+vn -0.157268 -0.676224 0.719714
+vn 0.172404 -0.693613 0.699412
+vn -0.121832 -0.784555 0.607972
+vn 0.125158 -0.786652 0.604577
+vn -0.048831 -0.536314 -0.842605
+vn -0.119057 -0.591407 -0.797536
+vn 0.048037 -0.537777 -0.841717
+vn 0.120276 -0.592529 -0.796519
+vn 0.007019 -0.844861 -0.534939
+vn -0.000397 -0.487642 -0.873044
+vn 0.048037 -0.537777 -0.841717
+vn 0.007019 -0.844861 -0.534939
+vn -0.048831 -0.536314 -0.842605
+vn -0.000397 -0.487642 -0.873044
+vn 0.000275 -0.811299 0.584631
+vn 0.046267 -0.491606 0.869588
+vn 0.000031 -0.438709 0.898629
+vn 0.000275 -0.811299 0.584631
+vn 0.000031 -0.438709 0.898629
+vn -0.046175 -0.491786 0.869491
+vn 0.439571 -0.822101 -0.361838
+vn 0.640223 -0.679501 -0.358321
+vn 0.453122 -0.859397 0.236892
+vn 0.640007 -0.753384 0.151006
+vn 0.304368 -0.320665 -0.896958
+vn 0.640223 -0.679501 -0.358321
+vn 0.184245 -0.380941 -0.906056
+vn 0.439571 -0.822101 -0.361838
+vn 0.374497 -0.244701 -0.894356
+vn 0.802405 -0.482688 -0.350938
+vn 0.304368 -0.320665 -0.896958
+vn 0.640223 -0.679501 -0.358321
+vn -0.382251 -0.857281 -0.344896
+vn -0.429001 -0.879885 0.204353
+vn -0.584690 -0.736127 -0.340962
+vn -0.633088 -0.762062 0.135871
+vn -0.176492 -0.781012 -0.599058
+vn 0.183848 -0.767314 -0.614352
+vn -0.140114 -0.669504 -0.729474
+vn 0.138436 -0.667855 -0.731304
+vn -0.604308 -0.711613 -0.358355
+vn -0.176492 -0.781012 -0.599058
+vn -0.140114 -0.669504 -0.729474
+vn 0.004517 -0.992742 -0.120182
+vn -0.000855 -0.999608 0.027986
+vn -0.250746 -0.957540 -0.142281
+vn -0.297408 -0.954362 0.027223
+vn 0.568818 0.822464 0.000000
+vn 0.780580 0.625056 0.000000
+vn 0.568762 0.822502 0.000000
+vn 0.780499 0.625156 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.992964 0.118414 0.000000
+vn -0.992942 0.118598 0.000000
+vn 0.780499 0.625156 0.000000
+vn 0.939213 0.343335 0.000000
+vn 0.780446 0.625223 0.000000
+vn 0.939190 0.343399 0.000000
+vn 0.568762 0.822502 0.000000
+vn 0.780499 0.625156 0.000000
+vn 0.568692 0.822550 0.000000
+vn 0.780446 0.625223 0.000000
+vn 0.939213 0.343335 0.000000
+vn 0.993044 0.117741 0.000000
+vn 0.939190 0.343399 0.000000
+vn 0.993044 0.117741 0.000000
+vn -0.939844 0.341603 0.000000
+vn -0.782234 0.622985 0.000000
+vn -0.939891 0.341476 0.000000
+vn -0.782382 0.622799 0.000000
+vn -0.569521 0.821977 0.000000
+vn -0.313186 0.949692 0.000000
+vn -0.569702 0.821851 0.000000
+vn -0.313370 0.949631 0.000000
+vn -0.313186 0.949692 0.000000
+vn -0.079319 0.996849 0.000000
+vn -0.313370 0.949631 0.000000
+vn -0.079380 0.996844 0.000000
+vn -0.782234 0.622985 0.000000
+vn -0.569521 0.821977 0.000000
+vn -0.782382 0.622799 0.000000
+vn -0.569702 0.821851 0.000000
+vn -0.569305 0.822126 0.000000
+vn -0.313003 0.949752 0.000000
+vn -0.569521 0.821977 0.000000
+vn -0.313186 0.949692 0.000000
+vn -0.939778 0.341785 0.000000
+vn -0.782058 0.623205 0.000000
+vn -0.939844 0.341603 0.000000
+vn -0.782234 0.622985 0.000000
+vn -0.992990 0.118199 0.000000
+vn -0.992986 0.118229 0.000000
+vn -0.939891 0.341476 0.000000
+vn -0.939844 0.341603 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993040 0.117774 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993044 0.117741 0.000000
+vn -0.912094 -0.149238 0.381855
+vn -0.750227 0.586307 0.305621
+vn -0.990001 -0.141058 -0.000305
+vn -0.737682 0.675148 0.000000
+vn -0.991137 -0.132636 -0.007416
+vn -0.992570 -0.121649 -0.002442
+vn -0.734553 0.678551 -0.000092
+vn -0.730939 0.682443 -0.000031
+vn -0.992527 -0.122013 0.001740
+vn -0.992570 -0.121649 -0.002442
+vn -0.954279 -0.298907 0.002472
+vn -0.951202 -0.308125 0.016572
+vn -0.087286 -0.425991 0.900507
+vn -0.174140 -0.397386 0.900977
+vn -0.238259 -0.894775 0.377637
+vn -0.471889 -0.796857 0.377279
+vn 0.000000 -0.925801 0.378011
+vn -0.238259 -0.894775 0.377637
+vn -0.000855 -0.999608 0.027986
+vn -0.297408 -0.954362 0.027223
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn -0.377613 -0.138923 0.915483
+vn -0.353195 -0.211129 0.911415
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052309 0.998631
+vn 0.352987 -0.211408 0.911431
+vn 0.000000 -0.052309 0.998631
+vn 0.319082 -0.282245 0.904723
+vn 0.000000 -0.052339 0.998629
+vn -0.849339 -0.521933 -0.078800
+vn -0.710895 -0.695605 -0.103736
+vn -0.886617 -0.462368 0.011231
+vn -0.788745 -0.614540 0.014893
+vn 0.000000 0.048160 -0.998840
+vn 0.000000 0.048190 -0.998838
+vn 0.002930 0.055209 -0.998470
+vn 0.000000 0.052279 -0.998633
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn -0.916742 -0.125557 -0.379234
+vn -0.909937 -0.184063 -0.371666
+vn -0.991018 -0.133704 -0.002472
+vn -0.939317 -0.333574 0.080082
+vn -0.939703 0.341993 0.000000
+vn -0.781842 0.623477 0.000000
+vn -0.939778 0.341785 0.000000
+vn -0.782058 0.623205 0.000000
+vn -0.569034 0.822314 0.000000
+vn -0.312756 0.949834 0.000000
+vn -0.569305 0.822126 0.000000
+vn -0.313003 0.949752 0.000000
+vn -0.992986 0.118229 0.000000
+vn -0.992979 0.118293 0.000000
+vn -0.939844 0.341603 0.000000
+vn -0.939778 0.341785 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.992990 0.118199 0.000000
+vn -0.992986 0.118229 0.000000
+vn -0.781842 0.623477 0.000000
+vn -0.569034 0.822314 0.000000
+vn -0.782058 0.623205 0.000000
+vn -0.569305 0.822126 0.000000
+vn -0.781571 0.623816 0.000000
+vn -0.568658 0.822574 0.000000
+vn -0.781842 0.623477 0.000000
+vn -0.569034 0.822314 0.000000
+vn -0.079471 0.996837 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.070103 0.929281 -0.362660
+vn 0.000000 0.928819 -0.370533
+vn -0.568658 0.822574 0.000000
+vn -0.312453 0.949933 0.000000
+vn -0.569034 0.822314 0.000000
+vn -0.312756 0.949834 0.000000
+vn -0.939600 0.342275 0.000000
+vn -0.781571 0.623816 0.000000
+vn -0.939703 0.341993 0.000000
+vn -0.781842 0.623477 0.000000
+vn -0.992979 0.118293 0.000000
+vn -0.992975 0.118323 0.000000
+vn -0.939778 0.341785 0.000000
+vn -0.939703 0.341993 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.992986 0.118229 0.000000
+vn -0.992979 0.118293 0.000000
+vn -0.939332 0.343010 0.000000
+vn -0.780839 0.624732 0.000000
+vn -0.939600 0.342275 0.000000
+vn -0.781571 0.623816 0.000000
+vn -0.567682 0.823248 0.000000
+vn -0.311600 0.950213 0.000000
+vn -0.568658 0.822574 0.000000
+vn -0.312453 0.949933 0.000000
+vn -0.780839 0.624732 0.000000
+vn -0.567682 0.823248 0.000000
+vn -0.781571 0.623816 0.000000
+vn -0.568658 0.822574 0.000000
+vn -0.992975 0.118323 0.000000
+vn -0.992964 0.118414 0.000000
+vn -0.939703 0.341993 0.000000
+vn -0.939600 0.342275 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.992979 0.118293 0.000000
+vn -0.992975 0.118323 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.992975 0.118323 0.000000
+vn -0.992964 0.118414 0.000000
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn -0.079471 0.996837 0.000000
+vn -0.079380 0.996844 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 0.699991 -0.714152
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 0.699991 -0.714152
+vn 0.078190 0.996939 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.311664 0.950192 0.000000
+vn 0.078190 0.996939 0.000000
+vn 0.311536 0.950234 0.000000
+vn 0.311664 0.950192 0.000000
+vn 0.568692 0.822550 0.000000
+vn 0.311536 0.950234 0.000000
+vn 0.568602 0.822613 0.000000
+vn 0.780446 0.625223 0.000000
+vn 0.939190 0.343399 0.000000
+vn 0.780351 0.625342 0.000000
+vn 0.939143 0.343526 0.000000
+vn 0.568692 0.822550 0.000000
+vn 0.780446 0.625223 0.000000
+vn 0.568602 0.822613 0.000000
+vn 0.780351 0.625342 0.000000
+vn 0.939190 0.343399 0.000000
+vn 0.993044 0.117741 0.000000
+vn 0.939143 0.343526 0.000000
+vn 0.993040 0.117774 0.000000
+vn -0.939891 0.341476 0.000000
+vn -0.782382 0.622799 0.000000
+vn -0.939966 0.341268 0.000000
+vn -0.782587 0.622541 0.000000
+vn -0.569702 0.821851 0.000000
+vn -0.313370 0.949631 0.000000
+vn -0.569973 0.821663 0.000000
+vn -0.313581 0.949562 0.000000
+vn -0.313370 0.949631 0.000000
+vn -0.079380 0.996844 0.000000
+vn -0.313581 0.949562 0.000000
+vn -0.079471 0.996837 0.000000
+vn -0.782382 0.622799 0.000000
+vn -0.569702 0.821851 0.000000
+vn -0.782587 0.622541 0.000000
+vn -0.569973 0.821663 0.000000
+vn -0.919325 0.124518 -0.373278
+vn -0.992997 0.118139 0.000000
+vn -0.870643 0.329573 -0.365189
+vn -0.939966 0.341268 0.000000
+vn 0.922348 0.060580 -0.381580
+vn 0.919351 0.124242 -0.373307
+vn 0.998866 0.047610 0.000000
+vn 0.993040 0.117774 0.000000
+vn -0.734964 0.678106 0.000061
+vn -0.730909 0.682475 0.000000
+vn -0.990831 -0.134986 0.005646
+vn -0.992527 -0.122013 0.001740
+vn -0.379990 -0.085209 -0.921058
+vn -0.916742 -0.125557 -0.379234
+vn -0.316758 0.606690 -0.729103
+vn -0.745103 0.591285 -0.308552
+vn 0.990046 -0.140663 -0.004853
+vn 0.913199 -0.133217 -0.385123
+vn 0.737157 0.675722 -0.000092
+vn 0.747977 0.586436 -0.310843
+vn 0.778579 -0.505401 0.372000
+vn 0.884960 -0.465507 0.012208
+vn 0.857781 -0.346641 0.379541
+vn 0.951306 -0.308211 0.004853
+vn 0.984351 -0.176215 -0.001099
+vn 0.901970 -0.203534 0.380821
+vn 0.951306 -0.308211 0.004853
+vn 0.857781 -0.346641 0.379541
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.366104 -0.154792 -0.917610
+vn 0.362879 -0.103340 -0.926089
+vn 0.904830 -0.170541 -0.390126
+vn 0.877705 -0.298509 -0.374869
+vn 0.362879 -0.103340 -0.926089
+vn 0.366104 -0.154792 -0.917610
+vn -0.385668 -0.101262 -0.917064
+vn -0.386469 -0.166759 -0.907102
+vn -0.909937 -0.184063 -0.371666
+vn -0.859060 -0.359823 -0.364066
+vn 0.897571 -0.428521 0.103613
+vn 0.985937 -0.163336 -0.035341
+vn 0.934625 -0.355612 0.004151
+vn -0.922339 0.060549 -0.381607
+vn -0.998866 0.047610 0.000000
+vn -0.919325 0.124518 -0.373278
+vn -0.992997 0.118139 0.000000
+vn 0.568888 0.822415 0.000000
+vn 0.780675 0.624937 0.000000
+vn 0.568818 0.822464 0.000000
+vn 0.780580 0.625056 0.000000
+vn 0.780675 0.624937 0.000000
+vn 0.939279 0.343154 0.000000
+vn 0.780580 0.625056 0.000000
+vn 0.939246 0.343245 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993044 0.117741 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993044 0.117741 0.000000
+vn 0.939279 0.343154 0.000000
+vn 0.993052 0.117681 0.000000
+vn 0.939246 0.343245 0.000000
+vn 0.993048 0.117711 0.000000
+vn 0.939335 0.343000 0.000000
+vn 0.993055 0.117650 0.000000
+vn 0.939279 0.343154 0.000000
+vn 0.993052 0.117681 0.000000
+vn 0.780797 0.624784 0.000000
+vn 0.939335 0.343000 0.000000
+vn 0.780675 0.624937 0.000000
+vn 0.939279 0.343154 0.000000
+vn 0.568999 0.822338 0.000000
+vn 0.780797 0.624784 0.000000
+vn 0.568888 0.822415 0.000000
+vn 0.780675 0.624937 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993044 0.117741 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993048 0.117711 0.000000
+vn -0.311879 -0.598488 -0.737932
+vn 0.000000 -0.679106 -0.734040
+vn -0.233778 -0.773910 -0.588566
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 1.000000 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.000000 0.418594 -0.908174
+vn 0.000000 0.052279 -0.998633
+vn -0.029329 0.427604 -0.903490
+vn 0.000000 0.052309 -0.998631
+vn 0.311664 0.950192 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.311692 0.950183 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.311692 0.950183 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.311692 0.950183 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.069399 0.929325 0.362684
+vn 0.286943 0.888480 0.358145
+vn 0.078433 0.996919 0.000000
+vn 0.312178 0.950024 0.000000
+vn 0.286943 0.888480 0.358145
+vn 0.527037 0.768230 0.363393
+vn 0.312178 0.950024 0.000000
+vn 0.569396 0.822064 0.000000
+vn 0.992243 -0.124275 0.003082
+vn 0.992707 -0.120552 -0.000946
+vn 0.731704 0.681622 0.000031
+vn 0.730525 0.682886 0.000000
+vn 0.568762 0.822502 0.000000
+vn 0.568692 0.822550 0.000000
+vn 0.311692 0.950183 0.000000
+vn 0.311664 0.950192 0.000000
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.992707 -0.120552 -0.000946
+vn 0.990875 -0.134469 -0.009247
+vn 0.730525 0.682886 0.000000
+vn 0.735447 0.677583 -0.000153
+vn 0.311756 0.950162 0.000000
+vn 0.568999 0.822338 0.000000
+vn 0.311720 0.950174 0.000000
+vn 0.568888 0.822415 0.000000
+vn 0.311756 0.950162 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.312178 0.950024 0.000000
+vn 0.078433 0.996919 0.000000
+vn 0.311692 0.950183 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.311720 0.950174 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.568818 0.822464 0.000000
+vn 0.568762 0.822502 0.000000
+vn 0.311692 0.950183 0.000000
+vn 0.311692 0.950183 0.000000
+vn 0.311720 0.950174 0.000000
+vn 0.568888 0.822415 0.000000
+vn 0.311692 0.950183 0.000000
+vn 0.568818 0.822464 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078433 0.996919 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.079319 0.996849 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.079380 0.996844 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.886617 -0.462368 0.011231
+vn -0.952300 -0.305104 0.005982
+vn -0.849339 -0.521933 -0.078800
+vn -0.913458 -0.399399 -0.077945
+vn -0.952300 -0.305104 0.005982
+vn -0.984672 -0.174417 0.000336
+vn -0.913458 -0.399399 -0.077945
+vn -0.939019 -0.338459 -0.060733
+vn -0.990831 -0.134986 0.005646
+vn -0.989839 -0.142190 0.001251
+vn -0.734964 0.678106 0.000061
+vn -0.738163 0.674622 0.000000
+vn -0.990001 -0.141058 -0.000305
+vn -0.989839 -0.142190 0.001251
+vn -0.984672 -0.174417 0.000336
+vn -0.939019 -0.338459 -0.060733
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.000000 0.418594 -0.908174
+vn -0.029329 0.427604 -0.903490
+vn 0.000000 0.928819 -0.370533
+vn -0.070103 0.929281 -0.362660
+vn 0.078220 0.996936 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.311720 0.950174 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.311756 0.950162 0.000000
+vn 0.078220 0.996936 0.000000
+vn 0.312178 0.950024 0.000000
+vn 0.569396 0.822064 0.000000
+vn 0.311756 0.950162 0.000000
+vn 0.568999 0.822338 0.000000
+vn 0.569396 0.822064 0.000000
+vn 0.781137 0.624360 0.000000
+vn 0.568999 0.822338 0.000000
+vn 0.780797 0.624784 0.000000
+vn 0.781137 0.624360 0.000000
+vn 0.939468 0.342637 0.000000
+vn 0.780797 0.624784 0.000000
+vn 0.939335 0.343000 0.000000
+vn 0.939468 0.342637 0.000000
+vn 0.993066 0.117560 0.000000
+vn 0.939335 0.343000 0.000000
+vn 0.993055 0.117650 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993048 0.117711 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993052 0.117681 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.078738 0.996895 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 1.000000 0.000000
+vn 0.078190 0.996939 0.000000
+vn 0.000000 0.928819 -0.370533
+vn 0.068943 0.929284 -0.362875
+vn -0.079165 0.996861 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.079259 0.996854 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.079044 0.996871 0.000000
+vn -0.079165 0.996861 0.000000
+vn -0.313003 0.949752 0.000000
+vn -0.079259 0.996854 0.000000
+vn -0.313186 0.949692 0.000000
+vn -0.079319 0.996849 0.000000
+vn -0.312756 0.949834 0.000000
+vn -0.079165 0.996861 0.000000
+vn -0.313003 0.949752 0.000000
+vn -0.079259 0.996854 0.000000
+vn -0.312453 0.949933 0.000000
+vn -0.079044 0.996871 0.000000
+vn -0.312756 0.949834 0.000000
+vn -0.079165 0.996861 0.000000
+vn -0.311600 0.950213 0.000000
+vn -0.078738 0.996895 0.000000
+vn -0.312453 0.949933 0.000000
+vn -0.079044 0.996871 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.079319 0.996849 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.079259 0.996854 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.078738 0.996895 0.000000
+vn -0.079044 0.996871 0.000000
+vn 0.949811 -0.310561 0.037569
+vn 0.934625 -0.355612 0.004151
+vn 0.985937 -0.163336 -0.035341
+vn 0.950440 -0.308980 0.034579
+vn 0.949811 -0.310561 0.037569
+vn 0.985937 -0.163336 -0.035341
+vn 0.990046 -0.140663 -0.004853
+vn 0.990875 -0.134469 -0.009247
+vn 0.985937 -0.163336 -0.035341
+vn 0.950440 -0.308980 0.034579
+vn 0.992243 -0.124275 0.003082
+vn 0.731704 0.681622 0.000031
+vn 0.990438 -0.137855 0.005310
+vn 0.736588 0.676342 0.000092
+vn 0.990438 -0.137855 0.005310
+vn 0.736588 0.676342 0.000092
+vn 0.989089 -0.147315 0.001312
+vn 0.739720 0.672914 0.000000
+vn 0.989641 -0.143562 -0.000641
+vn 0.984351 -0.176215 -0.001099
+vn 0.989089 -0.147315 0.001312
+vn 0.981702 -0.190316 0.006439
+vn 0.898822 -0.432030 -0.073948
+vn 0.981702 -0.190316 0.006439
+vn 0.951306 -0.308211 0.004853
+vn 0.984351 -0.176215 -0.001099
+vn 0.898822 -0.432030 -0.073948
+vn 0.922100 -0.382311 -0.059756
+vn 0.981702 -0.190316 0.006439
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 -0.052339 -0.998629
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.078738 0.996895 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.078433 0.996919 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.069399 0.929325 0.362684
+vn 0.078433 0.996919 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.078738 0.996895 0.000000
+vn -0.069248 0.929275 0.362841
+vn 0.000000 1.000000 0.000000
+vn -0.069248 0.929275 0.362841
+vn 0.000000 0.928845 0.370470
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.069399 0.929325 0.362684
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.928845 0.370470
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.552338 0.770490 -0.318225
+vn 0.000000 1.000000 0.000000
+vn 0.318864 0.770272 -0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.318864 0.770272 -0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.770266 -0.637723
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.770266 -0.637723
+vn 0.000000 1.000000 0.000000
+vn -0.318864 0.770272 -0.552274
+vn 0.000000 1.000000 0.000000
+vn -0.318864 0.770272 -0.552274
+vn 0.000000 1.000000 0.000000
+vn -0.552336 0.770518 -0.318162
+vn 0.000000 1.000000 0.000000
+vn -0.552336 0.770518 -0.318162
+vn 0.000000 1.000000 0.000000
+vn -0.637722 0.770266 0.001282
+vn 0.000000 1.000000 0.000000
+vn -0.637722 0.770266 0.001282
+vn 0.000000 1.000000 0.000000
+vn -0.552246 0.770001 0.319567
+vn 0.000000 1.000000 0.000000
+vn -0.552246 0.770001 0.319567
+vn 0.000000 1.000000 0.000000
+vn -0.318864 0.770272 0.552274
+vn 0.000000 1.000000 0.000000
+vn -0.318864 0.770272 0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.770266 0.637723
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.770266 0.637723
+vn 0.000000 1.000000 0.000000
+vn 0.318864 0.770272 0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.318864 0.770272 0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.552244 0.770028 0.319505
+vn 0.000000 1.000000 0.000000
+vn 0.552244 0.770028 0.319505
+vn 0.000000 1.000000 0.000000
+vn 0.637722 0.770266 0.001160
+vn 0.000000 1.000000 0.000000
+vn 0.637722 0.770266 0.001160
+vn 0.000000 1.000000 0.000000
+vn 0.552338 0.770490 -0.318225
+vn 0.000000 1.000000 0.000000
+vn 0.318864 0.770272 0.552274
+vn 0.552244 0.770028 0.319505
+vn 0.499989 -0.000000 0.866032
+vn 0.865566 -0.000000 0.500796
+vn -0.552246 0.770001 0.319567
+vn -0.318864 0.770272 0.552274
+vn -0.865518 -0.000000 0.500877
+vn -0.499989 -0.000000 0.866032
+vn 0.000000 0.770266 0.637723
+vn 0.318864 0.770272 0.552274
+vn 0.000000 -0.000000 1.000000
+vn 0.499989 -0.000000 0.866032
+vn -0.318864 0.770272 -0.552274
+vn -0.552336 0.770518 -0.318162
+vn -0.499989 0.000000 -0.866032
+vn -0.866523 0.000000 -0.499137
+vn 0.318864 0.770272 -0.552274
+vn 0.000000 0.770266 -0.637723
+vn 0.499989 0.000000 -0.866032
+vn 0.000000 0.000000 -1.000000
+vn 0.637722 0.770266 0.001160
+vn 0.552338 0.770490 -0.318225
+vn 0.999998 -0.000000 0.001831
+vn 0.866482 0.000000 -0.499209
+vn -0.552336 0.770518 -0.318162
+vn -0.637722 0.770266 0.001282
+vn -0.866523 0.000000 -0.499137
+vn -0.999998 -0.000000 0.002014
+vn -0.637722 0.770266 0.001282
+vn -0.552246 0.770001 0.319567
+vn -0.999998 -0.000000 0.002014
+vn -0.865518 -0.000000 0.500877
+vn -0.318864 0.770272 0.552274
+vn 0.000000 0.770266 0.637723
+vn -0.499989 -0.000000 0.866032
+vn 0.000000 -0.000000 1.000000
+vn 0.552338 0.770490 -0.318225
+vn 0.318864 0.770272 -0.552274
+vn 0.866482 0.000000 -0.499209
+vn 0.499989 0.000000 -0.866032
+vn 0.552244 0.770028 0.319505
+vn 0.637722 0.770266 0.001160
+vn 0.865566 -0.000000 0.500796
+vn 0.999998 -0.000000 0.001831
+vn 0.000000 0.770266 -0.637723
+vn -0.318864 0.770272 -0.552274
+vn 0.000000 0.000000 -1.000000
+vn -0.499989 0.000000 -0.866032
+vn 0.502506 0.783714 -0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.191936 0.783706 -0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.191936 0.783706 -0.590733
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 -0.590733
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 -0.590733
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 -0.365076
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 -0.365076
+vn 0.000000 1.000000 0.000000
+vn -0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.502506 0.783714 -0.365076
+vn 0.000000 1.000000 0.000000
+vn -0.191964 0.783728 -0.590695
+vn 0.000000 1.000000 0.000000
+vn -0.502529 0.783702 -0.365070
+vn 0.000000 1.000000 0.000000
+vn -0.502529 0.783702 -0.365070
+vn 0.000000 1.000000 0.000000
+vn -0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.502529 0.783702 -0.365070
+vn 0.000000 1.000000 0.000000
+vn 0.502529 0.783702 -0.365070
+vn 0.000000 1.000000 0.000000
+vn 0.191964 0.783728 -0.590695
+vn 0.000000 1.000000 0.000000
+vn 0.191964 0.783728 -0.590695
+vn 0.000000 1.000000 0.000000
+vn -0.191964 0.783728 -0.590695
+vn 0.000000 1.000000 0.000000
+vn -0.621130 0.783707 0.000000
+vn -0.502506 0.783714 0.365076
+vn -1.000000 0.000000 0.000000
+vn -0.809030 -0.000000 0.587767
+vn -0.502506 0.783714 0.365076
+vn -0.191936 0.783706 0.590733
+vn -0.809030 -0.000000 0.587767
+vn -0.309004 -0.000000 0.951061
+vn 0.502529 0.783702 -0.365070
+vn 0.191964 0.783728 -0.590695
+vn 0.809045 0.000000 -0.587747
+vn 0.309068 0.000000 -0.951040
+vn -0.502529 0.783702 -0.365070
+vn -0.621130 0.783707 0.000000
+vn -0.809045 0.000000 -0.587747
+vn -1.000000 0.000000 0.000000
+vn 0.191964 0.783728 -0.590695
+vn -0.191964 0.783728 -0.590695
+vn 0.309068 0.000000 -0.951040
+vn -0.309068 0.000000 -0.951040
+vn -0.191936 0.783706 0.590733
+vn 0.191936 0.783706 0.590733
+vn -0.309004 -0.000000 0.951061
+vn 0.309004 -0.000000 0.951061
+vn 0.502506 0.783714 0.365076
+vn 0.621130 0.783707 0.000000
+vn 0.809030 -0.000000 0.587767
+vn 1.000000 0.000000 0.000000
+vn 0.191936 0.783706 0.590733
+vn 0.502506 0.783714 0.365076
+vn 0.309004 -0.000000 0.951061
+vn 0.809030 -0.000000 0.587767
+vn 0.621130 0.783707 0.000000
+vn 0.502529 0.783702 -0.365070
+vn 1.000000 0.000000 0.000000
+vn 0.809045 0.000000 -0.587747
+vn -0.191964 0.783728 -0.590695
+vn -0.502529 0.783702 -0.365070
+vn -0.309068 0.000000 -0.951040
+vn -0.809045 0.000000 -0.587747
+vn -0.502506 0.783714 0.365076
+vn -0.191936 0.783706 0.590733
+vn -0.809030 -0.000000 0.587767
+vn -0.309004 -0.000000 0.951061
+vn -0.191936 0.783706 0.590733
+vn 0.191936 0.783706 0.590733
+vn -0.309004 -0.000000 0.951061
+vn 0.309004 -0.000000 0.951061
+vn -0.621130 0.783707 0.000000
+vn -0.502506 0.783714 0.365076
+vn -1.000000 0.000000 0.000000
+vn -0.809030 -0.000000 0.587767
+vn 0.621130 0.783707 0.000000
+vn 0.502506 0.783714 -0.365076
+vn 1.000000 0.000000 0.000000
+vn 0.809030 0.000000 -0.587767
+vn 0.191936 0.783706 0.590733
+vn 0.502506 0.783714 0.365076
+vn 0.309004 -0.000000 0.951061
+vn 0.809030 -0.000000 0.587767
+vn 0.502506 0.783714 0.365076
+vn 0.621130 0.783707 0.000000
+vn 0.809030 -0.000000 0.587767
+vn 1.000000 0.000000 0.000000
+vn -0.191936 0.783706 -0.590733
+vn -0.502506 0.783714 -0.365076
+vn -0.309004 0.000000 -0.951061
+vn -0.809030 0.000000 -0.587767
+vn 0.191936 0.783706 -0.590733
+vn -0.191936 0.783706 -0.590733
+vn 0.309004 0.000000 -0.951061
+vn -0.309004 0.000000 -0.951061
+vn 0.502506 0.783714 -0.365076
+vn 0.191936 0.783706 -0.590733
+vn 0.809030 0.000000 -0.587767
+vn 0.309004 0.000000 -0.951061
+vn -0.502506 0.783714 -0.365076
+vn -0.621130 0.783707 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -1.000000 0.000000 0.000000
+vn -0.730909 0.682475 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.730939 0.682443 -0.000031
+vn -0.670866 0.741578 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993052 0.117681 0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.993055 0.117650 0.000000
+vn -0.737682 0.675148 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.738163 0.674622 0.000000
+vn -0.670866 0.741578 0.000000
+vn 0.735447 0.677583 -0.000153
+vn 0.737157 0.675722 -0.000092
+vn 0.670866 0.741578 0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.000000 0.699991 -0.714152
+vn 0.000000 0.732851 -0.680389
+vn 0.000000 0.699991 -0.714152
+vn 0.000000 0.732866 -0.680373
+vn 0.000000 -0.675849 0.737040
+vn 0.000000 -0.675849 0.737040
+vn 0.000000 -0.675817 0.737069
+vn 0.000000 -0.675866 0.737025
+vn 0.321855 0.605804 -0.727606
+vn 0.000000 0.699991 -0.714152
+vn 0.214916 0.828380 -0.517299
+vn 0.000000 0.732866 -0.680373
+vn -0.329091 0.602117 0.727430
+vn 0.000000 0.703021 0.711169
+vn -0.230361 0.846581 0.479828
+vn 0.000000 0.736456 0.676486
+vn 0.329149 0.602020 0.727484
+vn 0.230361 0.846581 0.479828
+vn 0.000000 0.702975 0.711215
+vn 0.000000 0.736456 0.676486
+vn -0.311879 -0.598488 -0.737932
+vn -0.233778 -0.773910 -0.588566
+vn -0.739452 -0.596163 -0.312731
+vn -0.590148 -0.771523 -0.237652
+vn 0.311907 -0.598482 -0.737925
+vn 0.233778 -0.773910 -0.588566
+vn 0.000000 -0.679106 -0.734040
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 0.699991 -0.714152
+vn 0.000000 0.699991 -0.714152
+vn 0.000000 0.732866 -0.680373
+vn 0.000000 0.732866 -0.680373
+vn 0.000000 0.043978 -0.999033
+vn 0.000000 0.043978 -0.999033
+vn 0.000000 -0.679106 -0.734040
+vn 0.000000 -0.679106 -0.734040
+vn 0.738513 0.674239 0.000000
+vn 0.739720 0.672914 0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.670866 0.741578 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.922245 0.060458 0.381850
+vn -0.741037 -0.593385 0.314257
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn -0.730939 0.682443 -0.000031
+vn -0.670866 0.741578 0.000000
+vn -0.734553 0.678551 -0.000092
+vn -0.670866 0.741578 0.000000
+vn 0.741037 -0.593385 0.314257
+vn 0.621805 -0.753893 0.212141
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn -0.922245 0.060458 0.381850
+vn -0.741037 -0.593385 0.314257
+vn -0.381763 0.058475 0.922409
+vn -0.310013 -0.595732 0.740942
+vn 0.750260 0.586248 0.305652
+vn 0.738513 0.674239 0.000000
+vn 0.531041 0.824090 0.197157
+vn 0.670866 0.741578 0.000000
+vn -0.310013 -0.595732 0.740942
+vn -0.741037 -0.593385 0.314257
+vn -0.263930 -0.785352 0.559967
+vn -0.621790 -0.753906 0.212136
+vn -0.381975 0.058596 -0.922313
+vn -0.311879 -0.598488 -0.737932
+vn -0.922339 0.060549 -0.381607
+vn -0.739452 -0.596163 -0.312731
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn 0.922245 0.060458 0.381850
+vn 0.381762 0.058535 0.922405
+vn 0.741037 -0.593385 0.314257
+vn 0.310045 -0.595674 0.740976
+vn 0.922348 0.060580 -0.381580
+vn 0.739459 -0.596169 -0.312703
+vn 0.382011 0.058628 -0.922296
+vn 0.311907 -0.598482 -0.737925
+vn 0.736588 0.676342 0.000092
+vn 0.731704 0.681622 0.000031
+vn 0.670866 0.741578 0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.000000 0.043978 -0.999033
+vn 0.000000 0.043978 -0.999033
+vn 0.000000 -0.679106 -0.734040
+vn 0.000000 -0.679106 -0.734040
+vn -0.741037 -0.593385 0.314257
+vn -0.742650 -0.669679 -0.000000
+vn -0.621790 -0.753906 0.212136
+vn -0.742346 -0.670016 -0.000000
+vn 0.000000 -0.675866 0.737025
+vn 0.000000 0.736456 0.676486
+vn 0.000000 -0.675849 0.737040
+vn 0.000000 0.736456 0.676486
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 0.732866 -0.680373
+vn 0.000000 0.732866 -0.680373
+vn 0.500093 0.836968 -0.222243
+vn 0.214916 0.828380 -0.517299
+vn 0.590148 -0.771523 -0.237652
+vn 0.233778 -0.773910 -0.588566
+vn 0.621805 -0.753893 0.212141
+vn 0.263930 -0.785352 0.559967
+vn 0.531041 0.824090 0.197157
+vn 0.230361 0.846581 0.479828
+vn -0.233778 -0.773910 -0.588566
+vn -0.214888 0.828363 -0.517338
+vn -0.590148 -0.771523 -0.237652
+vn -0.500057 0.836990 -0.222241
+vn -0.621790 -0.753906 0.212136
+vn -0.531041 0.824090 0.197157
+vn -0.263930 -0.785352 0.559967
+vn -0.230361 0.846581 0.479828
+vn -0.742346 -0.670016 -0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.621790 -0.753906 0.212136
+vn -0.531041 0.824090 0.197157
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 0.732851 -0.680389
+vn 0.000000 0.732866 -0.680373
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn -0.500057 0.836990 -0.222241
+vn -0.670866 0.741578 0.000000
+vn -0.590148 -0.771523 -0.237652
+vn -0.742346 -0.670016 -0.000000
+vn 0.500093 0.836968 -0.222243
+vn 0.590148 -0.771523 -0.237652
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.531041 0.824090 0.197157
+vn 0.621805 -0.753893 0.212141
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.000000 -0.679867 -0.733336
+vn 0.233778 -0.773910 -0.588566
+vn 0.000000 0.732866 -0.680373
+vn 0.214916 0.828380 -0.517299
+vn 0.000000 0.736456 0.676486
+vn 0.000000 0.736456 0.676486
+vn 0.000000 -0.675849 0.737040
+vn 0.000000 -0.675866 0.737025
+vn -0.214888 0.828363 -0.517338
+vn -0.233778 -0.773910 -0.588566
+vn 0.000000 0.732851 -0.680389
+vn 0.000000 -0.679867 -0.733336
+vn 0.230361 0.846581 0.479828
+vn 0.263930 -0.785352 0.559967
+vn 0.000000 0.736456 0.676486
+vn 0.000000 -0.675849 0.737040
+vn -0.230361 0.846581 0.479828
+vn 0.000000 0.736456 0.676486
+vn -0.263930 -0.785352 0.559967
+vn 0.000000 -0.675849 0.737040
+vn -0.670866 0.741578 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn 0.730525 0.682886 0.000000
+vn 0.735447 0.677583 -0.000153
+vn 0.670866 0.741578 0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.329149 0.602020 0.727484
+vn 0.750260 0.586248 0.305652
+vn 0.230361 0.846581 0.479828
+vn 0.531041 0.824090 0.197157
+vn 0.000000 -0.679106 -0.734040
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 -0.679106 -0.734040
+vn 0.000000 -0.679867 -0.733336
+vn -0.329091 0.602117 0.727430
+vn -0.230361 0.846581 0.479828
+vn -0.750227 0.586307 0.305621
+vn -0.531041 0.824090 0.197157
+vn -0.310013 -0.595732 0.740942
+vn -0.263930 -0.785352 0.559967
+vn 0.000000 -0.675849 0.737040
+vn 0.000000 -0.675849 0.737040
+vn 0.000000 -0.675817 0.737069
+vn 0.000000 -0.675817 0.737069
+vn 0.000000 -0.675849 0.737040
+vn 0.000000 -0.675866 0.737025
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn 0.000000 -0.679106 -0.734040
+vn 0.000000 -0.679106 -0.734040
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 -0.679867 -0.733336
+vn 0.000000 0.703021 0.711169
+vn 0.000000 0.702990 0.711200
+vn 0.000000 0.736456 0.676486
+vn 0.000000 0.736456 0.676486
+vn 0.747977 0.586436 -0.310843
+vn 0.500093 0.836968 -0.222243
+vn 0.737157 0.675722 -0.000092
+vn 0.670866 0.741578 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.000000 0.702975 0.711215
+vn 0.000000 0.736456 0.676486
+vn 0.000000 0.702990 0.711200
+vn 0.000000 0.736456 0.676486
+vn -0.734553 0.678551 -0.000092
+vn -0.670866 0.741578 0.000000
+vn -0.734685 0.678408 0.000000
+vn -0.670866 0.741578 0.000000
+vn 0.321855 0.605804 -0.727606
+vn 0.214916 0.828380 -0.517299
+vn 0.747977 0.586436 -0.310843
+vn 0.500093 0.836968 -0.222243
+vn 0.731704 0.681622 0.000031
+vn 0.730525 0.682886 0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.670866 0.741578 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn -0.739452 -0.596163 -0.312731
+vn -0.590148 -0.771523 -0.237652
+vn -0.742650 -0.669679 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn 0.739459 -0.596169 -0.312703
+vn 0.742650 -0.669679 -0.000000
+vn 0.590148 -0.771523 -0.237652
+vn 0.742346 -0.670016 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.738163 0.674622 0.000000
+vn -0.670866 0.741578 0.000000
+vn -0.734964 0.678106 0.000061
+vn -0.670866 0.741578 0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn 0.739720 0.672914 0.000000
+vn 0.736588 0.676342 0.000092
+vn 0.670866 0.741578 0.000000
+vn 0.670866 0.741578 0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.742346 -0.670016 -0.000000
+vn -0.750227 0.586307 0.305621
+vn -0.531041 0.824090 0.197157
+vn -0.737682 0.675148 0.000000
+vn -0.670866 0.741578 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.742346 -0.670016 -0.000000
+vn 0.000000 0.044038 0.999030
+vn 0.000000 0.044069 0.999029
+vn 0.000000 -0.675817 0.737069
+vn 0.000000 -0.675817 0.737069
+vn 0.000000 0.048434 0.998826
+vn 0.000000 0.044069 0.999029
+vn 0.000000 0.048464 0.998825
+vn 0.000000 0.044038 0.999030
+vn 0.000000 0.048190 -0.998838
+vn 0.000000 0.048160 -0.998840
+vn 0.000000 0.043978 -0.999033
+vn 0.000000 0.043978 -0.999033
+vn 0.922348 0.060580 -0.381580
+vn 0.382011 0.058628 -0.922296
+vn 0.919351 0.124242 -0.373307
+vn 0.395989 0.090856 -0.913750
+vn 0.396174 0.091223 0.913633
+vn 0.381762 0.058535 0.922405
+vn 0.919256 0.124211 0.373550
+vn 0.922245 0.060458 0.381850
+vn -0.396046 0.091008 -0.913710
+vn -0.381975 0.058596 -0.922313
+vn -0.919325 0.124518 -0.373278
+vn -0.922339 0.060549 -0.381607
+vn -0.922245 0.060458 0.381850
+vn -0.381763 0.058475 0.922409
+vn -0.919299 0.124793 0.373250
+vn -0.395796 0.091006 0.913818
+vn -0.992942 0.118598 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.919299 0.124793 0.373250
+vn -0.922245 0.060458 0.381850
+vn 0.000000 0.048190 -0.998838
+vn 0.000000 0.048190 -0.998838
+vn 0.000000 0.043978 -0.999033
+vn 0.000000 0.043978 -0.999033
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn -0.739452 -0.596163 -0.312731
+vn -0.742650 -0.669679 -0.000000
+vn -0.922339 0.060549 -0.381607
+vn -0.998866 0.047610 0.000000
+vn 0.739459 -0.596169 -0.312703
+vn 0.922348 0.060580 -0.381580
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.741037 -0.593385 0.314257
+vn 0.922245 0.060458 0.381850
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.742650 -0.669679 -0.000000
+vn 0.998866 0.047610 0.000000
+vn 0.000000 0.043978 -0.999033
+vn 0.382011 0.058628 -0.922296
+vn 0.000000 -0.679106 -0.734040
+vn 0.311907 -0.598482 -0.737925
+vn 0.000000 -0.675849 0.737040
+vn 0.000000 -0.675817 0.737069
+vn 0.000000 0.044008 0.999031
+vn 0.000000 0.044069 0.999029
+vn -0.311879 -0.598488 -0.737932
+vn -0.381975 0.058596 -0.922313
+vn 0.000000 -0.679106 -0.734040
+vn 0.000000 0.043978 -0.999033
+vn 0.310045 -0.595674 0.740976
+vn 0.381762 0.058535 0.922405
+vn 0.000000 -0.675817 0.737069
+vn 0.000000 0.044038 0.999030
+vn -0.310013 -0.595732 0.740942
+vn 0.000000 -0.675849 0.737040
+vn -0.381763 0.058475 0.922409
+vn 0.000000 0.044008 0.999031
+vn -0.742650 -0.669679 -0.000000
+vn -0.742650 -0.669679 -0.000000
+vn -0.998866 0.047610 0.000000
+vn -0.998866 0.047610 0.000000
+vn 0.738513 0.674239 0.000000
+vn 0.989641 -0.143562 -0.000641
+vn 0.739720 0.672914 0.000000
+vn 0.989089 -0.147315 0.001312
+vn 0.946362 -0.321059 -0.036318
+vn 0.990438 -0.137855 0.005310
+vn 0.981702 -0.190316 0.006439
+vn 0.989089 -0.147315 0.001312
+vn 0.949985 -0.312053 -0.012299
+vn 0.992243 -0.124275 0.003082
+vn 0.946362 -0.321059 -0.036318
+vn 0.990438 -0.137855 0.005310
+vn 0.737157 0.675722 -0.000092
+vn 0.735447 0.677583 -0.000153
+vn 0.990046 -0.140663 -0.004853
+vn 0.990875 -0.134469 -0.009247
+vn -0.737682 0.675148 0.000000
+vn -0.738163 0.674622 0.000000
+vn -0.990001 -0.141058 -0.000305
+vn -0.989839 -0.142190 0.001251
+vn -0.949451 -0.312272 -0.032076
+vn -0.939019 -0.338459 -0.060733
+vn -0.990831 -0.134986 0.005646
+vn -0.989839 -0.142190 0.001251
+vn 0.957065 -0.289866 0.002228
+vn 0.950440 -0.308980 0.034579
+vn 0.992707 -0.120552 -0.000946
+vn 0.990875 -0.134469 -0.009247
+vn 0.949985 -0.312053 -0.012299
+vn 0.957065 -0.289866 0.002228
+vn 0.992243 -0.124275 0.003082
+vn 0.992707 -0.120552 -0.000946
+vn 0.985937 -0.163336 -0.035341
+vn 0.904830 -0.170541 -0.390126
+vn 0.990046 -0.140663 -0.004853
+vn 0.913199 -0.133217 -0.385123
+vn -0.909937 -0.184063 -0.371666
+vn -0.916742 -0.125557 -0.379234
+vn -0.385668 -0.101262 -0.917064
+vn -0.379990 -0.085209 -0.921058
+vn -0.990831 -0.134986 0.005646
+vn -0.992527 -0.122013 0.001740
+vn -0.949451 -0.312272 -0.032076
+vn -0.954279 -0.298907 0.002472
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn -0.745103 0.591285 -0.308552
+vn -0.916742 -0.125557 -0.379234
+vn -0.734685 0.678408 0.000000
+vn -0.991018 -0.133704 -0.002472
+vn -0.730909 0.682475 0.000000
+vn -0.730939 0.682443 -0.000031
+vn -0.992527 -0.122013 0.001740
+vn -0.992570 -0.121649 -0.002442
+vn -0.949874 -0.309738 0.042452
+vn -0.951202 -0.308125 0.016572
+vn -0.991137 -0.132636 -0.007416
+vn -0.992570 -0.121649 -0.002442
+vn -0.912094 -0.149238 0.381855
+vn -0.990001 -0.141058 -0.000305
+vn -0.902001 -0.203565 0.380730
+vn -0.984672 -0.174417 0.000336
+vn 0.000000 0.703021 0.711169
+vn 0.000000 -0.052279 0.998633
+vn 0.000000 0.702990 0.711200
+vn 0.000000 -0.052339 0.998629
+vn 0.000000 -0.052339 0.998629
+vn 0.000000 -0.052370 0.998628
+vn 0.000000 0.702990 0.711200
+vn 0.000000 0.702975 0.711215
+vn 0.000000 -0.052309 0.998631
+vn 0.000000 -0.052279 0.998633
+vn -0.377613 -0.138923 0.915483
+vn -0.375319 -0.102543 0.921206
+vn 0.000000 -0.052370 0.998628
+vn 0.375234 -0.102697 0.921223
+vn 0.000000 0.702975 0.711215
+vn 0.329149 0.602020 0.727484
+vn -0.385668 -0.101262 -0.917064
+vn -0.379990 -0.085209 -0.921058
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.362879 -0.103340 -0.926089
+vn 0.372764 -0.088140 -0.923731
+vn -0.750227 0.586307 0.305621
+vn -0.912094 -0.149238 0.381855
+vn -0.329091 0.602117 0.727430
+vn -0.375319 -0.102543 0.921206
+vn 0.377487 -0.139135 0.915503
+vn 0.901970 -0.203534 0.380821
+vn 0.375234 -0.102697 0.921223
+vn 0.912059 -0.149299 0.381914
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.000000 -0.052309 -0.998631
+vn 0.984351 -0.176215 -0.001099
+vn 0.989641 -0.143562 -0.000641
+vn 0.901970 -0.203534 0.380821
+vn 0.912059 -0.149299 0.381914
+vn -0.991137 -0.132636 -0.007416
+vn -0.991018 -0.133704 -0.002472
+vn -0.949874 -0.309738 0.042452
+vn -0.939317 -0.333574 0.080082
+vn 0.362879 -0.103340 -0.926089
+vn 0.372764 -0.088140 -0.923731
+vn 0.904830 -0.170541 -0.390126
+vn 0.913199 -0.133217 -0.385123
+f 96/173/1 91/443/2 296/560/3
+f 296/560/3 91/443/2 293/381/4
+f 585/367/5 178/687/6 589/699/7
+f 589/699/7 178/687/6 124/80/8
+f 386/509/9 334/510/10 387/48/11
+f 387/48/11 334/510/10 335/178/12
+f 353/597/13 352/373/14 337/266/15
+f 337/266/15 352/373/14 343/631/16
+f 100/392/17 97/511/18 295/614/19
+f 295/614/19 97/511/18 292/726/20
+f 420/481/21 37/2/22 82/707/23
+f 82/707/23 37/2/22 38/423/24
+f 51/340/25 44/153/26 42/598/27
+f 42/598/27 44/153/26 43/692/28
+f 84/536/29 38/423/30 419/121/31
+f 419/121/31 38/423/30 37/2/32
+f 83/260/33 84/536/34 41/290/35
+f 41/290/35 84/536/34 419/121/36
+f 46/513/37 45/357/38 168/291/39
+f 168/291/39 45/357/38 152/292/40
+f 152/292/41 45/357/42 48/6/43
+f 48/6/43 45/357/42 49/505/44
+f 87/72/45 86/177/46 44/153/47
+f 44/153/47 86/177/46 43/692/48
+f 153/460/49 152/292/50 151/596/51
+f 151/596/51 152/292/50 48/6/52
+f 168/291/53 152/292/54 167/479/55
+f 167/479/55 152/292/54 153/460/56
+f 52/322/57 49/165/58 51/424/59
+f 47/228/60 48/136/61 49/165/62
+f 52/322/63 47/228/64 49/165/65
+f 50/489/66 51/424/67 42/189/68
+f 50/489/69 52/322/70 51/424/71
+f 50/489/72 42/189/73 39/120/74
+f 223/611/75 39/120/76 40/172/77
+f 50/489/78 39/120/79 223/611/80
+f 54/87/81 40/172/82 53/499/83
+f 223/611/84 40/172/85 54/87/86
+f 222/546/87 53/499/88 36/309/89
+f 54/87/90 53/499/91 222/546/92
+f 222/546/93 36/309/94 35/644/95
+f 55/405/96 35/644/97 56/331/98
+f 222/546/99 35/644/100 55/405/101
+f 120/355/102 429/179/103 119/682/104
+f 119/682/104 429/179/103 379/558/105
+f 55/405/106 56/331/107 57/602/108
+f 219/12/109 57/602/110 59/89/111
+f 64/388/112 63/429/113 58/603/114
+f 58/603/114 63/429/113 60/148/115
+f 55/405/116 57/602/117 219/12/118
+f 61/704/119 59/89/120 60/439/121
+f 219/12/122 59/89/123 61/704/124
+f 68/197/125 62/154/126 64/388/127
+f 64/388/127 62/154/126 63/429/128
+f 61/704/129 60/439/130 63/249/131
+f 65/615/132 63/249/133 62/629/134
+f 61/704/135 63/249/136 65/615/137
+f 67/105/138 62/629/139 69/244/140
+f 65/615/141 62/629/142 67/105/143
+f 76/725/144 69/635/145 68/197/146
+f 68/197/146 69/635/145 62/154/147
+f 67/105/148 69/244/149 66/393/150
+f 66/47/151 75/430/152 70/425/153
+f 70/425/153 75/430/152 285/382/154
+f 214/293/155 66/393/156 70/262/157
+f 67/105/158 66/393/159 214/293/160
+f 73/693/161 71/338/162 285/382/163
+f 285/382/163 71/338/162 70/425/164
+f 71/245/165 213/76/166 70/262/167
+f 73/693/168 285/382/169 173/123/170
+f 173/123/170 285/382/169 72/106/171
+f 166/52/172 130/708/173 128/356/174
+f 128/356/174 130/708/173 131/537/175
+f 213/76/176 71/245/177 128/694/178
+f 177/180/179 74/49/180 76/725/181
+f 76/725/181 74/49/180 75/430/182
+f 165/107/183 166/52/184 173/652/185
+f 173/652/185 166/52/184 73/703/186
+f 77/514/187 177/180/188 68/197/189
+f 68/197/189 177/180/188 76/725/190
+f 176/181/191 77/514/192 64/388/193
+f 64/388/193 77/514/192 68/197/194
+f 598/108/195 324/182/196 599/184/197
+f 599/184/197 324/182/196 371/183/198
+f 229/516/199 308/639/200 170/515/201
+f 170/515/201 308/639/200 307/103/202
+f 472/288/203 469/406/204 528/93/205
+f 528/93/205 469/406/204 525/672/206
+f 233/696/207 88/50/208 311/467/209
+f 311/467/209 88/50/208 305/201/210
+f 230/246/211 309/376/212 346/198/213
+f 346/198/213 309/376/212 345/150/214
+f 85/554/215 234/640/216 79/689/217
+f 79/689/217 234/640/216 312/204/218
+f 235/538/219 347/691/220 84/536/221
+f 84/536/221 347/691/220 38/423/222
+f 170/539/223 154/557/224 229/88/225
+f 229/88/225 154/557/224 155/94/226
+f 347/691/227 81/285/228 38/423/229
+f 38/423/229 81/285/228 82/707/230
+f 88/263/231 233/303/232 46/377/233
+f 46/377/233 233/303/232 87/72/234
+f 234/195/235 85/101/236 86/177/237
+f 86/177/237 85/101/236 83/260/238
+f 168/291/239 232/54/240 46/513/241
+f 46/513/241 232/54/240 88/445/242
+f 85/101/243 235/538/244 83/260/245
+f 83/260/245 235/538/244 84/536/246
+f 104/464/247 103/695/248 100/392/249
+f 100/392/249 103/695/248 101/482/250
+f 297/520/251 127/122/252 112/555/253
+f 112/555/253 127/122/252 111/264/254
+f 98/51/255 97/511/256 101/482/257
+f 101/482/257 97/511/256 100/392/258
+f 294/636/259 104/464/260 295/614/261
+f 295/614/261 104/464/260 100/392/262
+f 91/443/263 226/547/264 95/77/265
+f 95/77/265 226/547/264 92/196/266
+f 373/78/267 388/427/268 99/167/269
+f 99/167/269 388/427/268 107/374/270
+f 374/428/271 370/540/272 109/690/273
+f 109/690/273 370/540/272 89/541/274
+f 226/547/275 91/443/276 225/92/277
+f 225/92/277 91/443/276 96/173/278
+f 388/427/279 374/428/280 107/374/281
+f 107/374/281 374/428/280 109/690/282
+f 387/48/283 373/78/284 106/265/285
+f 106/265/285 373/78/284 99/167/286
+f 91/443/287 95/77/288 293/381/289
+f 293/381/289 95/77/288 291/304/290
+f 225/92/291 96/173/292 98/51/293
+f 98/51/293 96/173/292 97/511/294
+f 181/250/295 182/174/296 99/167/297
+f 99/167/297 182/174/296 106/265/298
+f 97/511/299 96/173/300 292/726/301
+f 292/726/301 96/173/300 296/560/302
+f 108/422/303 181/250/304 107/374/305
+f 107/374/305 181/250/304 99/167/306
+f 180/527/307 108/422/308 109/690/309
+f 109/690/309 108/422/308 107/374/310
+f 90/310/311 180/527/312 89/541/313
+f 89/541/313 180/527/312 109/690/314
+f 564/599/315 559/56/316 386/509/317
+f 386/509/317 559/56/316 334/510/318
+f 271/586/319 270/583/320 182/585/321
+f 182/585/321 270/583/320 105/341/322
+f 360/556/323 516/342/324 354/559/325
+f 354/559/325 516/342/324 515/507/326
+f 93/612/327 227/426/328 94/723/329
+f 94/723/329 227/426/328 117/124/330
+f 113/202/331 116/604/332 298/96/333
+f 298/96/333 116/604/332 115/461/334
+f 227/426/335 115/461/336 117/124/337
+f 117/124/337 115/461/336 116/604/338
+f 376/600/339 384/185/340 114/53/341
+f 114/53/341 384/185/340 110/199/342
+f 474/251/343 471/102/344 530/380/345
+f 530/380/345 471/102/344 527/632/346
+f 375/44/347 385/301/348 118/483/349
+f 118/483/349 385/301/348 123/253/350
+f 385/301/351 376/600/352 123/253/353
+f 123/253/353 376/600/352 114/53/354
+f 112/555/355 113/202/356 297/520/357
+f 297/520/357 113/202/356 298/96/358
+f 377/653/359 375/44/360 121/378/361
+f 121/378/361 375/44/360 118/483/362
+f 372/542/363 377/653/364 119/682/365
+f 119/682/365 377/653/364 121/378/366
+f 266/186/367 123/253/368 184/75/369
+f 184/75/369 123/253/368 114/53/370
+f 569/252/371 573/543/372 103/552/373
+f 103/552/373 573/543/372 302/3/374
+f 402/55/375 399/512/376 397/57/377
+f 397/57/377 399/512/376 394/407/378
+f 347/462/379 235/648/380 344/1/381
+f 344/1/381 235/648/380 313/155/382
+f 209/633/383 128/694/384 131/95/385
+f 209/633/386 213/76/387 128/694/388
+f 164/613/389 162/465/390 129/100/391
+f 129/100/391 162/465/390 135/110/392
+f 209/633/393 131/95/394 129/484/395
+f 132/176/396 129/484/397 135/156/398
+f 132/176/399 209/633/400 129/484/401
+f 133/411/402 135/156/403 137/286/404
+f 133/411/405 132/176/406 135/156/407
+f 159/408/408 134/446/409 161/375/410
+f 161/375/410 134/446/409 162/465/411
+f 133/411/412 137/286/413 136/544/414
+f 137/637/415 135/110/416 134/446/417
+f 134/446/417 135/110/416 162/465/418
+f 206/553/419 136/544/420 138/412/421
+f 206/553/422 133/411/423 136/544/424
+f 206/553/425 138/412/426 140/409/427
+f 430/372/428 137/637/429 160/74/430
+f 160/74/430 137/637/429 134/446/431
+f 202/545/432 140/409/433 139/688/434
+f 202/545/435 206/553/436 140/409/437
+f 202/545/438 139/688/439 141/709/440
+f 145/203/441 141/709/442 143/438/443
+f 145/203/444 202/545/445 141/709/446
+f 587/404/447 600/410/448 126/305/449
+f 126/305/449 600/410/448 125/37/450
+f 144/638/451 143/438/452 142/379/453
+f 144/638/454 145/203/455 143/438/456
+f 143/91/457 434/63/458 142/306/459
+f 144/638/460 142/379/461 146/104/462
+f 146/444/463 156/607/464 148/97/465
+f 148/97/465 156/607/464 389/673/466
+f 147/649/467 146/104/468 148/31/469
+f 147/649/470 144/638/471 146/104/472
+f 148/97/473 389/673/474 149/248/475
+f 149/248/475 389/673/474 157/32/476
+f 147/649/477 148/31/478 149/39/479
+f 157/32/480 153/460/481 149/248/482
+f 149/248/482 153/460/481 151/596/483
+f 150/582/484 149/39/485 151/490/486
+f 150/582/487 147/649/488 149/39/489
+f 42/598/490 43/692/491 39/299/492
+f 39/299/492 43/692/491 41/290/493
+f 47/228/494 151/490/495 48/136/496
+f 47/228/497 150/582/498 151/490/499
+f 167/479/500 153/460/501 154/557/502
+f 154/557/502 153/460/501 157/32/503
+f 86/177/504 83/260/505 43/692/506
+f 43/692/506 83/260/505 41/290/507
+f 277/151/508 278/616/509 413/525/510
+f 413/525/510 278/616/509 381/526/511
+f 595/323/512 590/663/513 412/41/514
+f 412/41/514 590/663/513 272/135/515
+f 413/525/516 283/175/517 418/320/518
+f 418/320/518 283/175/517 276/664/519
+f 74/49/520 72/106/521 75/430/522
+f 75/430/522 72/106/521 285/382/523
+f 232/54/524 168/291/525 169/298/526
+f 169/298/526 168/291/525 167/479/527
+f 169/298/528 167/479/529 170/539/530
+f 170/539/530 167/479/529 154/557/531
+f 229/88/532 155/94/533 346/98/534
+f 346/98/534 155/94/533 158/33/535
+f 172/276/536 230/330/537 158/33/538
+f 158/33/538 230/330/537 346/98/539
+f 583/267/540 587/404/541 228/643/542
+f 228/643/542 587/404/541 126/305/543
+f 179/205/544 121/378/545 122/34/546
+f 122/34/546 121/378/545 118/483/547
+f 120/355/548 119/682/549 179/205/550
+f 179/205/550 119/682/549 121/378/551
+f 224/706/552 186/324/553 185/674/554
+f 47/228/555 186/324/556 190/584/557
+f 52/322/558 186/324/559 47/228/560
+f 216/300/561 211/463/562 207/650/563
+f 207/650/563 211/463/562 208/79/564
+f 150/582/565 190/584/566 188/573/567
+f 47/228/568 190/584/569 150/582/570
+f 195/403/571 193/261/572 191/43/573
+f 191/43/573 193/261/572 192/227/574
+f 147/649/575 188/573/576 192/227/577
+f 150/582/578 188/573/579 147/649/580
+f 218/346/581 201/339/582 221/99/583
+f 221/99/583 201/339/582 199/35/584
+f 147/649/585 192/227/586 193/261/587
+f 221/99/588 199/35/589 220/64/590
+f 220/64/590 199/35/589 196/651/591
+f 144/638/592 193/261/593 194/454/594
+f 147/649/595 193/261/596 144/638/597
+f 205/335/598 203/466/599 218/346/600
+f 218/346/600 203/466/599 201/339/601
+f 144/638/602 194/454/603 198/601/604
+f 216/300/605 207/650/606 217/152/607
+f 217/152/607 207/650/606 204/20/608
+f 145/203/609 198/601/610 196/651/611
+f 144/638/612 198/601/613 145/203/614
+f 187/705/615 188/573/616 189/36/617
+f 189/36/617 188/573/616 190/584/618
+f 202/545/619 196/651/620 199/35/621
+f 145/203/622 196/651/623 202/545/624
+f 220/64/625 196/651/626 200/4/627
+f 200/4/627 196/651/626 198/601/628
+f 202/545/629 199/35/630 201/339/631
+f 197/675/632 194/454/633 195/403/634
+f 195/403/634 194/454/633 193/261/635
+f 206/553/636 201/339/637 203/466/638
+f 202/545/639 201/339/640 206/553/641
+f 133/411/642 203/466/643 204/20/644
+f 206/553/645 203/466/646 133/411/647
+f 200/4/648 198/601/649 197/675/650
+f 197/675/650 198/601/649 194/454/651
+f 133/411/652 204/20/653 207/650/654
+f 211/463/655 215/605/656 208/79/657
+f 208/79/657 215/605/656 210/354/658
+f 132/176/659 207/650/660 208/79/661
+f 133/411/662 207/650/663 132/176/664
+f 209/633/665 208/79/666 210/354/667
+f 132/176/668 208/79/669 209/633/670
+f 215/605/671 212/287/672 210/354/673
+f 209/633/674 210/354/675 213/76/676
+f 214/293/677 215/605/678 211/463/679
+f 213/76/680 214/293/681 70/262/682
+f 214/293/683 213/76/684 215/605/685
+f 67/105/686 211/463/687 216/300/688
+f 67/105/689 214/293/690 211/463/691
+f 67/105/692 216/300/693 217/152/694
+f 65/615/695 217/152/696 205/335/697
+f 65/615/698 67/105/699 217/152/700
+f 65/615/701 205/335/702 218/346/703
+f 61/704/704 218/346/705 221/99/706
+f 61/704/707 65/615/708 218/346/709
+f 219/12/710 221/99/711 220/64/712
+f 219/12/713 61/704/714 221/99/715
+f 219/12/716 220/64/717 200/4/718
+f 55/405/719 200/4/720 197/675/721
+f 55/405/722 219/12/723 200/4/724
+f 222/546/725 197/675/726 195/403/727
+f 222/546/728 55/405/729 197/675/730
+f 222/546/731 195/403/732 191/43/733
+f 54/87/734 222/546/735 191/43/736
+f 223/611/737 187/705/738 189/36/739
+f 223/611/740 54/87/741 187/705/742
+f 50/489/743 189/36/744 224/706/745
+f 50/489/746 223/611/747 189/36/748
+f 50/489/749 224/706/750 52/322/751
+f 34/38/752 371/183/753 498/289/754
+f 498/289/754 371/183/753 519/40/755
+f 170/515/756 307/103/757 169/149/758
+f 169/149/758 307/103/757 306/109/759
+f 567/297/760 127/634/761 554/247/762
+f 554/247/762 127/634/761 301/508/763
+f 304/337/764 101/157/765 302/3/766
+f 302/3/766 101/157/765 103/552/767
+f 553/302/768 105/341/769 571/134/770
+f 571/134/770 105/341/769 270/583/771
+f 175/662/772 503/42/773 272/135/774
+f 272/135/774 503/42/773 508/321/775
+f 182/174/776 105/284/777 106/265/778
+f 106/265/778 105/284/777 102/125/779
+f 169/149/780 306/109/781 232/336/782
+f 232/336/782 306/109/781 310/200/783
+f 582/480/784 82/707/785 581/5/786
+f 581/5/786 82/707/785 81/285/787
+f 573/543/788 558/641/789 302/3/790
+f 302/3/790 558/641/789 316/343/791
+f 267/229/792 183/587/793 570/665/794
+f 570/665/794 183/587/793 566/491/795
+f 235/648/796 85/554/797 313/155/798
+f 313/155/798 85/554/797 79/689/799
+f 254/115/800 260/81/801 255/7/802
+f 255/7/802 260/81/801 258/311/803
+f 255/7/804 258/311/805 251/698/806
+f 251/698/806 258/311/805 259/394/807
+f 263/82/808 260/81/809 253/158/810
+f 253/158/810 260/81/809 254/115/811
+f 264/83/812 263/82/813 252/208/814
+f 252/208/814 263/82/813 253/158/815
+f 251/698/816 259/394/817 250/161/818
+f 250/161/818 259/394/817 262/193/819
+f 248/171/820 243/504/821 246/574/822
+f 244/421/823 243/504/824 249/468/825
+f 249/468/825 243/504/824 248/171/826
+f 242/395/827 247/722/828 243/504/829
+f 243/504/829 247/722/828 246/574/830
+f 244/421/831 286/528/832 245/469/833
+f 245/469/833 286/528/832 427/8/834
+f 265/70/835 257/283/836 436/485/837
+f 123/253/838 266/186/839 118/483/840
+f 118/483/840 266/186/839 122/34/841
+f 110/199/842 183/710/843 114/53/844
+f 114/53/844 183/710/843 184/75/845
+f 580/455/846 422/472/847 557/194/848
+f 557/194/848 422/472/847 287/561/849
+f 183/587/850 267/229/851 184/588/852
+f 184/588/852 267/229/851 268/589/853
+f 175/239/854 34/38/855 503/344/856
+f 503/344/856 34/38/855 498/289/857
+f 382/590/858 383/277/859 271/586/860
+f 271/586/860 383/277/859 270/583/861
+f 184/588/862 268/589/863 266/591/864
+f 122/230/865 266/591/864 268/589/863
+f 122/230/866 268/589/867 179/231/868
+f 179/231/868 268/589/867 120/628/869
+f 182/585/870 181/16/871 271/586/872
+f 181/16/871 108/232/873 271/586/872
+f 90/233/874 271/586/875 180/112/876
+f 180/112/876 271/586/875 108/232/877
+f 429/126/878 120/628/879 382/590/880
+f 382/590/880 120/628/879 268/589/881
+f 174/617/882 165/358/883 282/359/884
+f 282/359/884 165/358/883 280/486/885
+f 74/618/886 177/345/887 274/654/888
+f 274/654/888 177/345/887 284/399/889
+f 159/413/890 161/666/891 277/151/892
+f 277/151/892 161/666/891 278/616/893
+f 176/332/894 283/175/895 77/21/896
+f 77/21/896 283/175/895 275/127/897
+f 165/358/898 173/17/899 280/486/900
+f 280/486/900 173/17/899 281/333/901
+f 177/345/902 77/21/903 284/399/904
+f 284/399/904 77/21/903 275/127/905
+f 176/332/906 78/608/907 283/175/908
+f 283/175/908 78/608/907 276/664/909
+f 74/618/910 274/654/911 72/655/912
+f 72/655/912 274/654/911 273/656/913
+f 174/617/914 282/359/915 163/312/916
+f 163/312/916 282/359/915 279/487/917
+f 72/655/918 273/656/919 173/17/920
+f 173/17/920 273/656/919 281/333/921
+f 161/666/922 163/312/923 278/616/924
+f 278/616/924 163/312/923 279/487/925
+f 601/137/926 269/325/927 585/492/928
+f 585/492/928 269/325/927 178/326/929
+f 175/662/930 272/135/931 586/493/932
+f 586/493/932 272/135/931 590/663/933
+f 128/356/934 71/243/935 166/52/936
+f 166/52/936 71/243/935 73/703/937
+f 174/206/938 130/708/939 165/107/940
+f 165/107/940 130/708/939 166/52/941
+f 113/202/942 112/555/943 288/447/944
+f 288/447/944 112/555/943 290/702/945
+f 116/604/946 113/202/947 289/619/948
+f 289/619/948 113/202/947 288/447/949
+f 112/555/950 111/264/951 290/702/952
+f 290/702/952 111/264/951 287/561/953
+f 287/561/954 111/264/955 557/194/956
+f 557/194/956 111/264/955 568/347/957
+f 363/313/958 294/636/959 364/278/960
+f 364/278/960 294/636/959 295/614/961
+f 400/169/962 402/55/963 395/163/964
+f 395/163/964 402/55/963 397/57/965
+f 314/419/966 315/128/967 94/723/968
+f 94/723/968 315/128/967 93/612/969
+f 127/634/970 297/521/971 301/508/972
+f 301/508/972 297/521/971 299/645/973
+f 318/676/974 304/337/975 316/343/976
+f 316/343/976 304/337/975 302/3/977
+f 101/157/978 304/337/979 98/606/980
+f 98/606/980 304/337/979 225/129/981
+f 92/226/982 226/327/983 304/337/984
+f 304/337/984 226/327/983 225/129/985
+f 297/521/986 298/431/987 299/645/988
+f 298/431/987 115/569/989 299/645/988
+f 115/569/990 227/476/991 299/645/992
+f 299/645/992 227/476/991 93/548/993
+f 315/236/994 318/676/995 93/548/996
+f 93/548/996 318/676/995 299/645/997
+f 234/640/998 233/696/999 312/204/1000
+f 312/204/1000 233/696/999 311/467/1001
+f 232/336/1002 310/200/1003 88/50/1004
+f 88/50/1004 310/200/1003 305/201/1005
+f 309/376/1006 230/246/1007 591/442/1008
+f 591/442/1008 230/246/1007 583/142/1009
+f 592/434/1010 303/168/1011 581/271/1012
+f 581/271/1012 303/168/1011 231/143/1013
+f 319/147/1014 309/376/1015 593/565/1016
+f 593/565/1016 309/376/1015 591/442/1017
+f 320/23/1018 308/639/1019 348/701/1020
+f 348/701/1020 308/639/1019 345/150/1021
+f 321/164/1022 307/103/1023 320/23/1024
+f 320/23/1024 307/103/1023 308/639/1025
+f 322/571/1026 306/109/1027 321/164/1028
+f 321/164/1028 306/109/1027 307/103/1029
+f 79/689/1030 312/204/1031 321/164/1032
+f 321/164/1032 312/204/1031 322/571/1033
+f 313/155/1034 79/689/1035 320/23/1036
+f 320/23/1036 79/689/1035 321/164/1037
+f 344/1/1038 313/155/1039 348/701/1040
+f 348/701/1040 313/155/1039 320/23/1041
+f 592/434/1042 171/307/1043 593/565/1044
+f 593/565/1044 171/307/1043 319/147/1045
+f 304/337/1046 318/676/1047 92/226/1048
+f 92/226/1048 318/676/1047 315/236/1049
+f 301/508/1050 299/645/1051 316/343/1052
+f 316/343/1052 299/645/1051 318/676/1053
+f 95/77/1054 92/196/1055 314/419/1056
+f 314/419/1056 92/196/1055 315/128/1057
+f 306/109/1058 322/571/1059 310/200/1060
+f 310/200/1060 322/571/1059 305/201/1061
+f 312/204/1062 311/467/1063 322/571/1064
+f 322/571/1064 311/467/1063 305/201/1065
+f 154/557/1066 157/32/1067 155/94/1068
+f 155/94/1068 157/32/1067 389/673/1069
+f 474/256/1070 530/141/1071 475/353/1072
+f 475/353/1072 530/141/1071 531/84/1073
+f 217/152/1074 204/20/1075 205/335/1076
+f 205/335/1076 204/20/1075 203/466/1077
+f 224/706/1078 189/36/1079 186/324/1080
+f 186/324/1080 189/36/1079 190/584/1081
+f 52/322/1082 185/674/1083 186/324/1084
+f 52/322/1085 224/706/1086 185/674/1087
+f 213/76/1088 210/354/1089 212/287/1090
+f 213/76/1091 212/287/1092 215/605/1093
+f 130/708/1094 164/613/1095 131/537/1096
+f 131/537/1096 164/613/1095 129/100/1097
+f 163/215/1098 164/613/1099 174/206/1100
+f 174/206/1100 164/613/1099 130/708/1101
+f 161/375/1102 162/465/1103 163/215/1104
+f 163/215/1104 162/465/1103 164/613/1105
+f 75/430/1106 66/47/1107 76/725/1108
+f 76/725/1108 66/47/1107 69/635/1109
+f 191/43/1110 192/227/1111 187/705/1112
+f 187/705/1112 192/227/1111 188/573/1113
+f 54/87/1114 191/43/1115 187/705/1116
+f 49/308/1117 45/471/1118 51/340/1119
+f 51/340/1119 45/471/1118 44/153/1120
+f 401/575/1121 400/169/1122 396/711/1123
+f 396/711/1123 400/169/1122 395/163/1124
+f 563/368/1125 572/498/1126 363/313/1127
+f 363/313/1127 572/498/1126 294/636/1128
+f 395/163/1129 397/57/1130 333/216/1131
+f 333/216/1131 397/57/1130 330/360/1132
+f 396/711/1133 395/163/1134 329/712/1135
+f 329/712/1135 395/163/1134 333/216/1136
+f 397/57/1137 394/407/1138 330/360/1139
+f 330/360/1139 394/407/1138 332/361/1140
+f 342/334/1141 337/266/1142 335/178/1143
+f 335/178/1143 337/266/1142 327/697/1144
+f 343/631/1145 339/362/1146 336/576/1147
+f 336/576/1147 339/362/1146 328/268/1148
+f 339/362/1149 340/22/1150 328/268/1151
+f 328/268/1151 340/22/1150 323/217/1152
+f 337/266/1153 343/631/1154 327/697/1155
+f 327/697/1155 343/631/1154 336/576/1156
+f 352/373/1157 355/24/1158 343/631/1159
+f 343/631/1159 355/24/1158 339/362/1160
+f 351/58/1161 353/597/1162 342/334/1163
+f 342/334/1163 353/597/1162 337/266/1164
+f 334/510/1165 341/534/1166 335/178/1167
+f 335/178/1167 341/534/1166 342/334/1168
+f 576/116/1169 384/185/1170 574/414/1171
+f 574/414/1171 384/185/1170 332/361/1172
+f 582/480/1173 80/724/1174 588/25/1175
+f 588/25/1175 80/724/1174 33/363/1176
+f 598/108/1177 597/295/1178 324/182/1179
+f 324/182/1179 597/295/1178 338/370/1180
+f 596/61/1181 597/295/1182 56/364/1183
+f 56/364/1183 597/295/1182 57/488/1184
+f 233/303/1185 234/195/1186 87/72/1187
+f 87/72/1187 234/195/1186 86/177/1188
+f 46/377/1189 87/72/1190 45/471/1191
+f 45/471/1191 87/72/1190 44/153/1192
+f 171/307/1193 344/1/1194 319/147/1195
+f 319/147/1195 344/1/1194 348/701/1196
+f 348/701/1197 345/150/1198 319/147/1199
+f 319/147/1199 345/150/1198 309/376/1200
+f 81/73/1201 347/462/1202 171/307/1203
+f 171/307/1203 347/462/1202 344/1/1204
+f 346/198/1205 345/150/1206 229/516/1207
+f 229/516/1207 345/150/1206 308/639/1208
+f 40/562/1209 39/299/1210 419/121/1211
+f 419/121/1211 39/299/1210 41/290/1212
+f 267/229/1213 383/277/1214 268/589/1215
+f 268/589/1215 383/277/1214 382/590/1216
+f 284/399/1217 378/630/1218 274/654/1219
+f 274/654/1219 378/630/1218 349/716/1220
+f 584/684/1221 58/603/1222 599/184/1223
+f 599/184/1223 58/603/1222 60/148/1224
+f 357/391/1225 359/660/1226 351/58/1227
+f 351/58/1227 359/660/1226 353/597/1228
+f 358/622/1229 361/529/1230 352/373/1231
+f 352/373/1231 361/529/1230 355/24/1232
+f 341/534/1233 350/272/1234 342/334/1235
+f 342/334/1235 350/272/1234 351/58/1236
+f 559/56/1237 560/478/1238 334/510/1239
+f 334/510/1239 560/478/1238 341/534/1240
+f 359/660/1241 358/622/1242 353/597/1243
+f 353/597/1243 358/622/1242 352/373/1244
+f 366/420/1245 365/570/1246 359/660/1247
+f 359/660/1247 365/570/1246 358/622/1248
+f 370/540/1249 380/46/1250 89/541/1251
+f 89/541/1251 380/46/1250 379/558/1252
+f 365/570/1253 368/224/1254 358/622/1255
+f 358/622/1255 368/224/1254 361/529/1256
+f 364/278/1257 366/420/1258 357/391/1259
+f 357/391/1259 366/420/1258 359/660/1260
+f 350/272/1261 356/623/1262 351/58/1263
+f 351/58/1263 356/623/1262 357/391/1264
+f 560/478/1265 561/620/1266 341/534/1267
+f 341/534/1267 561/620/1266 350/272/1268
+f 295/614/1269 292/726/1270 364/278/1271
+f 364/278/1271 292/726/1270 366/420/1272
+f 296/560/1273 293/381/1274 365/570/1275
+f 365/570/1275 293/381/1274 368/224/1276
+f 292/726/1277 296/560/1278 366/420/1279
+f 366/420/1279 296/560/1278 365/570/1280
+f 356/623/1281 363/313/1282 357/391/1283
+f 357/391/1283 363/313/1282 364/278/1284
+f 561/620/1285 562/642/1286 350/272/1287
+f 350/272/1287 562/642/1286 356/623/1288
+f 562/642/1289 563/368/1290 356/623/1291
+f 356/623/1291 563/368/1290 363/313/1292
+f 378/630/1293 381/526/1294 279/487/1295
+f 279/487/1295 381/526/1294 278/616/1296
+f 392/328/1297 277/151/1298 418/320/1299
+f 418/320/1299 277/151/1298 413/525/1300
+f 370/540/1301 323/217/1302 380/46/1303
+f 380/46/1303 323/217/1302 237/190/1304
+f 595/323/1305 412/41/1306 601/137/1307
+f 601/137/1307 412/41/1306 269/325/1308
+f 372/542/1309 380/46/1310 326/389/1311
+f 326/389/1311 380/46/1310 237/190/1312
+f 326/389/1313 331/212/1314 372/542/1315
+f 372/542/1315 331/212/1314 377/653/1316
+f 331/212/1317 329/712/1318 377/653/1319
+f 377/653/1319 329/712/1318 375/44/1320
+f 333/216/1321 330/360/1322 385/301/1323
+f 385/301/1323 330/360/1322 376/600/1324
+f 329/712/1325 333/216/1326 375/44/1327
+f 375/44/1327 333/216/1326 385/301/1328
+f 330/360/1329 332/361/1330 376/600/1331
+f 376/600/1331 332/361/1330 384/185/1332
+f 335/178/1333 327/697/1334 387/48/1335
+f 387/48/1335 327/697/1334 373/78/1336
+f 336/576/1337 328/268/1338 388/427/1339
+f 388/427/1339 328/268/1338 374/428/1340
+f 328/268/1341 323/217/1342 374/428/1343
+f 374/428/1343 323/217/1342 370/540/1344
+f 327/697/1345 336/576/1346 373/78/1347
+f 373/78/1347 336/576/1346 388/427/1348
+f 102/125/1349 386/509/1350 106/265/1351
+f 106/265/1351 386/509/1350 387/48/1352
+f 565/610/1353 110/199/1354 576/116/1355
+f 576/116/1355 110/199/1354 384/185/1356
+f 360/556/1357 354/559/1358 594/140/1359
+f 594/140/1359 354/559/1358 596/61/1360
+f 586/209/1361 584/684/1362 175/239/1363
+f 175/239/1363 584/684/1362 34/38/1364
+f 602/387/1365 589/699/1366 369/657/1367
+f 369/657/1367 589/699/1366 124/80/1368
+f 155/94/1369 389/673/1370 158/33/1371
+f 158/33/1371 389/673/1370 156/607/1372
+f 390/26/1373 172/276/1374 156/607/1375
+f 156/607/1375 172/276/1374 158/33/1376
+f 277/151/1377 392/328/1378 159/413/1379
+f 159/413/1379 392/328/1378 391/90/1380
+f 160/74/1381 134/446/1382 391/532/1383
+f 391/532/1383 134/446/1382 159/408/1384
+f 78/609/1385 176/181/1386 58/603/1387
+f 58/603/1387 176/181/1386 64/388/1388
+f 137/637/1389 430/372/1390 136/677/1391
+f 555/530/1392 564/599/1393 102/125/1394
+f 102/125/1394 564/599/1393 386/509/1395
+f 407/500/1396 406/192/1397 401/575/1398
+f 401/575/1398 406/192/1397 400/169/1399
+f 406/192/1400 408/448/1401 400/169/1402
+f 400/169/1402 408/448/1401 402/55/1403
+f 574/414/1404 332/361/1405 577/566/1406
+f 577/566/1406 332/361/1405 394/407/1407
+f 408/448/1408 405/456/1409 402/55/1410
+f 402/55/1410 405/456/1409 399/512/1411
+f 425/269/1412 422/472/1413 408/448/1414
+f 408/448/1414 422/472/1413 405/456/1415
+f 423/577/1416 425/269/1417 406/192/1418
+f 406/192/1418 425/269/1417 408/448/1419
+f 424/433/1420 423/577/1421 407/500/1422
+f 407/500/1422 423/577/1421 406/192/1423
+f 577/566/1424 394/407/1425 578/680/1426
+f 578/680/1426 394/407/1425 399/512/1427
+f 470/329/1428 479/494/1429 526/275/1430
+f 526/275/1430 479/494/1429 535/495/1431
+f 245/469/1432 427/8/1433 403/626/1434
+f 403/626/1434 427/8/1433 414/296/1435
+f 429/126/1436 382/590/1437 90/233/1438
+f 90/233/1438 382/590/1437 271/586/1439
+f 331/212/1440 326/389/1441 411/146/1442
+f 411/146/1442 326/389/1441 410/518/1443
+f 411/146/1444 410/518/1445 409/721/1446
+f 409/721/1446 410/518/1445 416/661/1447
+f 94/723/1448 117/124/1449 286/528/1450
+f 286/528/1450 117/124/1449 417/254/1451
+f 117/124/1452 116/604/1453 417/254/1454
+f 417/254/1454 116/604/1453 289/619/1455
+f 606/27/1456 604/415/1457 398/314/1458
+f 398/314/1458 604/415/1457 393/450/1459
+f 396/711/1460 329/712/1461 411/146/1462
+f 411/146/1462 329/712/1461 331/212/1463
+f 283/175/1464 413/525/1465 275/127/1466
+f 275/127/1466 413/525/1465 381/526/1467
+f 604/415/1468 605/715/1469 393/450/1470
+f 393/450/1470 605/715/1469 325/225/1471
+f 426/671/1472 424/433/1473 415/315/1474
+f 415/315/1474 424/433/1473 407/500/1475
+f 426/671/1476 427/8/1477 417/254/1478
+f 417/254/1478 427/8/1477 286/528/1479
+f 409/721/1480 416/661/1481 415/315/1482
+f 415/315/1482 416/661/1481 414/296/1483
+f 401/575/1484 396/711/1485 409/721/1486
+f 409/721/1486 396/711/1485 411/146/1487
+f 415/315/1488 407/500/1489 409/721/1490
+f 409/721/1490 407/500/1489 401/575/1491
+f 244/421/1492 249/468/1493 286/528/1494
+f 286/528/1494 249/468/1493 250/161/1495
+f 340/22/1496 239/218/1497 323/217/1498
+f 323/217/1498 239/218/1497 237/190/1499
+f 416/661/1500 410/518/1501 236/211/1502
+f 236/211/1502 410/518/1501 240/234/1503
+f 419/121/1504 37/2/1505 40/562/1506
+f 40/562/1506 37/2/1505 53/10/1507
+f 37/2/1508 420/481/1509 53/10/1510
+f 53/10/1510 420/481/1509 36/71/1511
+f 594/140/1512 607/15/1513 360/556/1514
+f 360/556/1514 607/15/1513 367/59/1515
+f 588/25/1516 607/15/1517 420/481/1518
+f 420/481/1518 607/15/1517 36/71/1519
+f 237/190/1520 240/234/1521 326/389/1522
+f 326/389/1522 240/234/1521 410/518/1523
+f 429/179/1524 90/310/1525 379/558/1526
+f 379/558/1526 90/310/1525 89/541/1527
+f 414/296/1528 416/661/1529 403/626/1530
+f 403/626/1530 416/661/1529 236/211/1531
+f 415/315/1532 414/296/1533 426/671/1534
+f 426/671/1534 414/296/1533 427/8/1535
+f 417/254/1536 289/619/1537 426/671/1538
+f 426/671/1538 289/619/1537 424/433/1539
+f 289/619/1540 288/447/1541 424/433/1542
+f 424/433/1542 288/447/1541 423/577/1543
+f 288/447/1544 290/702/1545 423/577/1546
+f 423/577/1546 290/702/1545 425/269/1547
+f 290/702/1548 287/561/1549 425/269/1550
+f 425/269/1550 287/561/1549 422/472/1551
+f 578/680/1552 399/512/1553 579/144/1554
+f 579/144/1554 399/512/1553 405/456/1555
+f 252/208/1556 247/722/1557 291/304/1558
+f 291/304/1558 247/722/1557 242/395/1559
+f 378/630/1560 284/399/1561 381/526/1562
+f 381/526/1562 284/399/1561 275/127/1563
+f 349/716/1564 378/630/1565 282/359/1566
+f 282/359/1566 378/630/1565 279/487/1567
+f 380/46/1568 372/542/1569 379/558/1570
+f 379/558/1570 372/542/1569 119/682/1571
+f 362/294/1572 431/133/1573 428/522/1574
+f 428/522/1574 431/133/1573 238/316/1575
+f 241/119/1576 431/133/1577 432/625/1578
+f 432/625/1578 431/133/1577 362/294/1579
+f 355/24/1580 428/522/1581 339/362/1582
+f 339/362/1582 428/522/1581 340/22/1583
+f 361/529/1584 362/294/1585 355/24/1586
+f 355/24/1586 362/294/1585 428/522/1587
+f 368/224/1588 432/625/1589 361/529/1590
+f 361/529/1590 432/625/1589 362/294/1591
+f 293/381/1592 291/304/1593 368/224/1594
+f 368/224/1594 291/304/1593 432/625/1595
+f 239/218/1596 340/22/1597 238/316/1598
+f 238/316/1598 340/22/1597 428/522/1599
+f 242/395/1600 241/119/1601 291/304/1602
+f 291/304/1602 241/119/1601 432/625/1603
+f 138/473/1604 136/677/1605 430/372/1606
+f 140/113/1607 138/473/1608 430/372/1609
+f 602/387/1610 605/715/1611 430/372/1612
+f 430/372/1612 605/715/1611 140/113/1613
+f 606/27/1614 398/314/1615 603/646/1616
+f 603/646/1616 398/314/1615 404/501/1617
+f 603/646/1618 404/501/1619 608/159/1620
+f 608/159/1620 404/501/1619 421/348/1621
+f 600/410/1622 390/26/1623 608/159/1624
+f 608/159/1624 390/26/1623 434/63/1625
+f 146/444/1626 434/63/1627 156/607/1628
+f 156/607/1628 434/63/1627 390/26/1629
+f 146/444/1630 142/306/1631 434/63/1632
+f 274/654/1633 349/716/1634 273/656/1635
+f 273/656/1635 349/716/1634 281/333/1636
+f 282/359/1637 280/486/1638 349/716/1639
+f 349/716/1639 280/486/1638 281/333/1640
+f 433/60/1641 436/485/1642 256/365/1643
+f 256/365/1643 436/485/1642 257/283/1644
+f 262/193/1645 261/658/1646 435/242/1647
+f 264/83/1648 433/60/1649 256/365/1650
+f 252/208/1651 291/304/1652 264/83/1653
+f 264/83/1653 291/304/1652 433/60/1654
+f 435/242/1655 286/528/1656 262/193/1657
+f 262/193/1657 286/528/1656 250/161/1658
+f 94/723/1659 286/528/1660 435/242/1661
+f 291/304/1662 95/77/1663 433/60/1664
+f 95/77/1665 314/419/1666 433/60/1667
+f 433/60/1667 314/419/1666 436/485/1668
+f 94/723/1669 435/242/1670 314/419/1671
+f 314/419/1671 435/242/1670 436/485/1672
+f 265/70/1673 436/485/1674 261/658/1675
+f 261/658/1675 436/485/1674 435/242/1676
+f 27/578/1677 241/119/1678 26/383/1679
+f 26/383/1679 241/119/1678 242/395/1680
+f 26/383/1681 242/395/1682 25/130/1683
+f 25/130/1683 242/395/1682 243/504/1684
+f 25/130/1685 243/504/1686 24/535/1687
+f 24/535/1687 243/504/1686 244/421/1688
+f 24/535/1689 244/421/1690 23/219/1691
+f 23/219/1691 244/421/1690 245/469/1692
+f 23/219/1693 245/469/1694 22/685/1695
+f 22/685/1695 245/469/1694 403/626/1696
+f 22/685/1697 403/626/1698 32/579/1699
+f 32/579/1699 403/626/1698 236/211/1700
+f 32/579/1701 236/211/1702 28/531/1703
+f 28/531/1703 236/211/1702 240/234/1704
+f 28/531/1705 240/234/1706 31/351/1707
+f 31/351/1707 240/234/1706 237/190/1708
+f 31/351/1709 237/190/1710 29/9/1711
+f 29/9/1711 237/190/1710 239/218/1712
+f 29/9/1713 239/218/1714 30/317/1715
+f 30/317/1715 239/218/1714 238/316/1716
+f 30/317/1717 238/316/1718 21/281/1719
+f 21/281/1719 238/316/1718 431/133/1720
+f 21/281/1721 431/133/1722 27/578/1723
+f 27/578/1723 431/133/1722 241/119/1724
+f 29/9/1725 30/317/1726 445/386/1727
+f 445/386/1727 30/317/1726 446/318/1728
+f 32/579/1729 28/531/1730 448/371/1731
+f 448/371/1731 28/531/1730 444/11/1732
+f 31/351/1733 29/9/1734 447/449/1735
+f 447/449/1735 29/9/1734 445/386/1736
+f 24/535/1737 23/219/1738 440/523/1739
+f 440/523/1739 23/219/1738 439/188/1740
+f 26/383/1741 25/130/1742 442/238/1743
+f 442/238/1743 25/130/1742 441/436/1744
+f 21/281/1745 27/578/1746 437/390/1747
+f 437/390/1747 27/578/1746 443/594/1748
+f 23/219/1749 22/685/1750 439/188/1751
+f 439/188/1751 22/685/1750 438/220/1752
+f 22/685/1753 32/579/1754 438/220/1755
+f 438/220/1755 32/579/1754 448/371/1756
+f 28/531/1757 31/351/1758 444/11/1759
+f 444/11/1759 31/351/1758 447/449/1760
+f 27/578/1761 26/383/1762 443/594/1763
+f 443/594/1763 26/383/1762 442/238/1764
+f 30/317/1765 21/281/1766 446/318/1767
+f 446/318/1767 21/281/1766 437/390/1768
+f 25/130/1769 24/535/1770 441/436/1771
+f 441/436/1771 24/535/1770 440/523/1772
+f 18/396/1773 253/158/1774 19/451/1775
+f 19/451/1775 253/158/1774 254/115/1776
+f 19/451/1777 254/115/1778 20/397/1779
+f 20/397/1779 254/115/1778 255/7/1780
+f 20/397/1781 255/7/1782 16/280/1783
+f 16/280/1783 255/7/1782 251/698/1784
+f 16/280/1785 251/698/1786 15/240/1787
+f 15/240/1787 251/698/1786 250/161/1788
+f 15/240/1789 250/161/1790 14/86/1791
+f 14/86/1791 250/161/1790 249/468/1792
+f 14/86/1793 249/468/1794 13/28/1795
+f 13/28/1795 249/468/1794 248/171/1796
+f 13/28/1797 248/171/1798 11/19/1799
+f 11/19/1799 248/171/1798 246/574/1800
+f 11/19/1801 246/574/1802 12/624/1803
+f 12/624/1803 246/574/1802 247/722/1804
+f 12/624/1805 247/722/1806 17/319/1807
+f 17/319/1807 247/722/1806 252/208/1808
+f 17/319/1809 252/208/1810 18/396/1811
+f 18/396/1811 252/208/1810 253/158/1812
+f 1/274/1813 265/70/1814 5/681/1815
+f 5/681/1815 265/70/1814 261/658/1816
+f 5/681/1817 261/658/1818 4/67/1819
+f 4/67/1819 261/658/1818 262/193/1820
+f 4/67/1821 262/193/1822 7/29/1823
+f 7/29/1823 262/193/1822 259/394/1824
+f 7/29/1825 259/394/1826 8/398/1827
+f 8/398/1827 259/394/1826 258/311/1828
+f 8/398/1829 258/311/1830 6/62/1831
+f 6/62/1831 258/311/1830 260/81/1832
+f 6/62/1833 260/81/1834 3/270/1835
+f 3/270/1835 260/81/1834 263/82/1836
+f 3/270/1837 263/82/1838 2/117/1839
+f 2/117/1839 263/82/1838 264/83/1840
+f 2/117/1841 264/83/1842 10/477/1843
+f 10/477/1843 264/83/1842 256/365/1844
+f 10/477/1845 256/365/1846 9/214/1847
+f 9/214/1847 256/365/1846 257/283/1848
+f 9/214/1849 257/283/1850 1/274/1851
+f 1/274/1851 257/283/1850 265/70/1852
+f 4/67/1853 7/29/1854 452/496/1855
+f 452/496/1855 7/29/1854 455/258/1856
+f 7/29/1857 8/398/1858 455/258/1859
+f 455/258/1859 8/398/1858 456/160/1860
+f 10/477/1861 9/214/1862 458/506/1863
+f 458/506/1863 9/214/1862 457/111/1864
+f 5/681/1865 4/67/1866 453/221/1867
+f 453/221/1867 4/67/1866 452/496/1868
+f 9/214/1869 1/274/1870 457/111/1871
+f 457/111/1871 1/274/1870 449/627/1872
+f 8/398/1873 6/62/1874 456/160/1875
+f 456/160/1875 6/62/1874 454/621/1876
+f 3/270/1877 2/117/1878 451/717/1879
+f 451/717/1879 2/117/1878 450/257/1880
+f 6/62/1881 3/270/1882 454/621/1883
+f 454/621/1883 3/270/1882 451/717/1884
+f 2/117/1885 10/477/1886 450/257/1887
+f 450/257/1887 10/477/1886 458/506/1888
+f 1/274/1889 5/681/1890 449/627/1891
+f 449/627/1891 5/681/1890 453/221/1892
+f 14/86/1893 13/28/1894 462/282/1895
+f 462/282/1895 13/28/1894 461/145/1896
+f 13/28/1897 11/19/1898 461/145/1899
+f 461/145/1899 11/19/1898 459/533/1900
+f 15/240/1901 14/86/1902 463/457/1903
+f 463/457/1903 14/86/1902 462/282/1904
+f 17/319/1905 18/396/1906 465/366/1907
+f 465/366/1907 18/396/1906 466/384/1908
+f 11/19/1909 12/624/1910 459/533/1911
+f 459/533/1911 12/624/1910 460/470/1912
+f 12/624/1913 17/319/1914 460/470/1915
+f 460/470/1915 17/319/1914 465/366/1916
+f 20/397/1917 16/280/1918 468/255/1919
+f 468/255/1919 16/280/1918 464/564/1920
+f 19/451/1921 20/397/1922 467/416/1923
+f 467/416/1923 20/397/1922 468/255/1924
+f 18/396/1925 19/451/1926 466/384/1927
+f 466/384/1927 19/451/1926 467/416/1928
+f 16/280/1929 15/240/1930 464/564/1931
+f 464/564/1931 15/240/1930 463/457/1932
+f 354/559/1933 515/507/1934 338/370/1935
+f 338/370/1935 515/507/1934 514/131/1936
+f 579/144/1937 405/456/1938 580/455/1939
+f 580/455/1939 405/456/1938 422/472/1940
+f 33/363/1941 497/170/1942 367/59/1943
+f 367/59/1943 497/170/1942 517/14/1944
+f 325/225/1945 369/657/1946 513/352/1947
+f 513/352/1947 369/657/1946 518/713/1948
+f 272/135/1949 508/321/1950 412/41/1951
+f 412/41/1951 508/321/1950 523/138/1952
+f 482/581/1953 538/385/1954 483/595/1955
+f 483/595/1955 538/385/1954 539/18/1956
+f 178/326/1957 269/325/1958 504/162/1959
+f 504/162/1959 269/325/1958 507/497/1960
+f 231/143/1961 303/168/1962 506/235/1963
+f 506/235/1963 303/168/1962 510/85/1964
+f 228/458/1965 505/502/1966 300/118/1967
+f 300/118/1967 505/502/1966 509/459/1968
+f 470/453/1969 526/432/1970 473/719/1971
+f 473/719/1971 526/432/1970 529/259/1972
+f 472/139/1973 528/667/1974 478/241/1975
+f 478/241/1975 528/667/1974 534/210/1976
+f 269/325/1977 412/41/1978 507/497/1979
+f 507/497/1979 412/41/1978 523/138/1980
+f 571/134/1981 575/668/1982 479/494/1983
+f 479/494/1983 575/668/1982 487/669/1984
+f 125/37/1985 421/348/1986 501/114/1987
+f 501/114/1987 421/348/1986 524/567/1988
+f 572/498/1989 481/400/1990 556/68/1991
+f 556/68/1991 481/400/1990 477/401/1992
+f 480/349/1993 536/350/1994 496/568/1995
+f 496/568/1995 536/350/1994 552/474/1996
+f 338/370/1997 514/131/1998 324/182/1999
+f 324/182/1999 514/131/1998 512/572/2000
+f 475/353/2001 531/84/2002 480/349/2003
+f 480/349/2003 531/84/2002 536/350/2004
+f 556/68/2005 477/401/2006 569/580/2007
+f 569/580/2007 477/401/2006 476/69/2008
+f 126/305/2009 125/37/2010 502/45/2011
+f 502/45/2011 125/37/2010 501/114/2012
+f 476/69/2013 477/401/2014 532/207/2015
+f 532/207/2015 477/401/2014 533/273/2016
+f 553/647/2017 470/453/2018 555/530/2019
+f 555/530/2019 470/453/2018 473/719/2020
+f 481/400/2021 490/66/2022 537/551/2023
+f 537/551/2023 490/66/2022 546/659/2024
+f 568/347/2025 567/191/2026 475/353/2027
+f 475/353/2027 567/191/2026 474/256/2028
+f 565/610/2029 469/406/2030 566/592/2031
+f 566/592/2031 469/406/2030 472/288/2032
+f 404/501/2033 398/314/2034 522/166/2035
+f 522/166/2035 398/314/2034 521/65/2036
+f 575/668/2037 570/665/2038 487/669/2039
+f 487/669/2039 570/665/2038 478/241/2040
+f 477/401/2041 481/400/2042 533/273/2043
+f 533/273/2043 481/400/2042 537/551/2044
+f 539/18/2045 511/279/2046 527/632/2047
+f 527/632/2047 511/279/2046 509/459/2048
+f 543/670/2049 534/210/2050 523/138/2051
+f 523/138/2051 534/210/2050 507/497/2052
+f 500/503/2053 504/440/2054 525/672/2055
+f 525/672/2055 504/440/2054 528/93/2056
+f 531/84/2057 530/141/2058 502/45/2059
+f 502/45/2059 530/141/2058 505/417/2060
+f 526/432/2061 503/344/2062 529/259/2063
+f 529/259/2063 503/344/2062 498/289/2064
+f 533/273/2065 499/686/2066 532/207/2067
+f 532/207/2067 499/686/2066 506/679/2068
+f 537/551/2069 497/170/2070 533/273/2071
+f 533/273/2071 497/170/2070 499/686/2072
+f 535/495/2073 543/670/2074 508/321/2075
+f 508/321/2075 543/670/2074 523/138/2076
+f 522/166/2077 551/369/2078 524/567/2079
+f 524/567/2079 551/369/2078 552/474/2080
+f 521/65/2081 550/13/2082 522/166/2083
+f 522/166/2083 550/13/2082 551/369/2084
+f 520/452/2085 549/700/2086 521/65/2087
+f 521/65/2087 549/700/2086 550/13/2088
+f 513/352/2089 541/187/2090 520/452/2091
+f 520/452/2091 541/187/2090 549/700/2092
+f 498/289/2093 519/40/2094 529/259/2095
+f 529/259/2095 519/40/2094 548/683/2096
+f 500/503/2097 525/672/2098 518/713/2099
+f 518/713/2099 525/672/2098 547/222/2100
+f 516/342/2101 517/14/2102 545/549/2103
+f 545/549/2103 517/14/2102 546/659/2104
+f 515/507/2105 516/342/2106 544/132/2107
+f 544/132/2107 516/342/2106 545/549/2108
+f 514/131/2109 515/507/2110 542/718/2111
+f 542/718/2111 515/507/2110 544/132/2112
+f 512/572/2113 514/131/2114 540/550/2115
+f 540/550/2115 514/131/2114 542/718/2116
+f 518/713/2117 547/222/2118 513/352/2119
+f 513/352/2119 547/222/2118 541/187/2120
+f 517/14/2121 497/170/2122 546/659/2123
+f 546/659/2123 497/170/2122 537/551/2124
+f 501/114/2125 536/350/2126 502/45/2127
+f 502/45/2127 536/350/2126 531/84/2128
+f 524/567/2129 552/474/2130 501/114/2131
+f 501/114/2131 552/474/2130 536/350/2132
+f 534/210/2133 528/667/2134 507/497/2135
+f 507/497/2135 528/667/2134 504/162/2136
+f 510/85/2137 511/279/2138 538/385/2139
+f 538/385/2139 511/279/2138 539/18/2140
+f 503/42/2141 526/275/2142 508/321/2143
+f 508/321/2143 526/275/2142 535/495/2144
+f 505/502/2145 530/380/2146 509/459/2147
+f 509/459/2147 530/380/2146 527/632/2148
+f 506/235/2149 510/85/2150 532/30/2151
+f 532/30/2151 510/85/2150 538/385/2152
+f 519/40/2153 512/572/2154 548/683/2155
+f 548/683/2155 512/572/2154 540/550/2156
+f 393/450/2157 325/225/2158 520/452/2159
+f 520/452/2159 325/225/2158 513/352/2160
+f 228/643/2161 126/305/2162 505/417/2163
+f 505/417/2163 126/305/2162 502/45/2164
+f 478/241/2165 534/210/2166 487/669/2167
+f 487/669/2167 534/210/2166 543/670/2168
+f 231/223/2169 506/679/2170 80/724/2171
+f 80/724/2171 506/679/2170 499/686/2172
+f 476/720/2173 532/30/2174 482/581/2175
+f 482/581/2175 532/30/2174 538/385/2176
+f 471/102/2177 483/595/2178 527/632/2179
+f 527/632/2179 483/595/2178 539/18/2180
+f 496/568/2181 552/474/2182 495/441/2183
+f 495/441/2183 552/474/2182 551/369/2184
+f 484/517/2185 492/524/2186 540/550/2187
+f 540/550/2187 492/524/2186 548/683/2188
+f 479/494/2189 487/669/2190 535/495/2191
+f 535/495/2191 487/669/2190 543/670/2192
+f 303/168/2193 317/213/2194 510/85/2195
+f 510/85/2195 317/213/2194 511/279/2196
+f 124/80/2197 500/503/2198 369/657/2199
+f 369/657/2199 500/503/2198 518/713/2200
+f 495/441/2201 551/369/2202 494/678/2203
+f 494/678/2203 551/369/2202 550/13/2204
+f 494/678/2205 550/13/2206 493/237/2207
+f 493/237/2207 550/13/2206 549/700/2208
+f 300/118/2209 509/459/2210 317/213/2211
+f 317/213/2211 509/459/2210 511/279/2212
+f 324/182/2213 512/572/2214 371/183/2215
+f 371/183/2215 512/572/2214 519/40/2216
+f 178/687/2217 504/440/2218 124/80/2219
+f 124/80/2219 504/440/2218 500/503/2220
+f 398/314/2221 393/450/2222 521/65/2223
+f 521/65/2223 393/450/2222 520/452/2224
+f 493/237/2225 549/700/2226 485/475/2227
+f 485/475/2227 549/700/2226 541/187/2228
+f 473/719/2229 529/259/2230 492/524/2231
+f 492/524/2231 529/259/2230 548/683/2232
+f 469/406/2233 491/593/2234 525/672/2235
+f 525/672/2235 491/593/2234 547/222/2236
+f 490/66/2237 489/437/2238 546/659/2239
+f 546/659/2239 489/437/2238 545/549/2240
+f 367/59/2241 517/14/2242 360/556/2243
+f 360/556/2243 517/14/2242 516/342/2244
+f 489/437/2245 488/714/2246 545/549/2247
+f 545/549/2247 488/714/2246 544/132/2248
+f 421/348/2249 404/501/2250 524/567/2251
+f 524/567/2251 404/501/2250 522/166/2252
+f 488/714/2253 486/563/2254 544/132/2255
+f 544/132/2255 486/563/2254 542/718/2256
+f 486/563/2257 484/517/2258 542/718/2259
+f 542/718/2259 484/517/2258 540/550/2260
+f 80/724/2261 499/686/2262 33/363/2263
+f 33/363/2263 499/686/2262 497/170/2264
+f 485/475/2265 541/187/2266 491/593/2267
+f 491/593/2267 541/187/2266 547/222/2268
+f 554/247/2269 558/641/2270 471/102/2271
+f 471/102/2271 558/641/2270 483/595/2272
+f 316/343/2273 558/641/2274 301/508/2275
+f 301/508/2275 558/641/2274 554/247/2276
+f 383/277/2277 267/229/2278 575/668/2279
+f 575/668/2279 267/229/2278 570/665/2280
+f 565/610/2281 566/592/2282 110/199/2283
+f 110/199/2283 566/592/2282 183/710/2284
+f 127/122/2285 567/191/2286 111/264/2287
+f 111/264/2287 567/191/2286 568/347/2288
+f 105/284/2289 553/647/2290 102/125/2291
+f 102/125/2291 553/647/2290 555/530/2292
+f 556/68/2293 569/580/2294 104/464/2295
+f 104/464/2295 569/580/2294 103/695/2296
+f 294/636/2297 572/498/2298 104/464/2299
+f 104/464/2299 572/498/2298 556/68/2300
+f 270/583/2301 383/277/2302 571/134/2303
+f 571/134/2303 383/277/2302 575/668/2304
+f 495/441/2305 579/144/2306 496/568/2307
+f 496/568/2307 579/144/2306 580/455/2308
+f 494/678/2309 578/680/2310 495/441/2311
+f 495/441/2311 578/680/2310 579/144/2312
+f 493/237/2313 577/566/2314 494/678/2315
+f 494/678/2315 577/566/2314 578/680/2316
+f 485/475/2317 574/414/2318 493/237/2319
+f 493/237/2319 574/414/2318 577/566/2320
+f 473/719/2321 492/524/2322 555/530/2323
+f 555/530/2323 492/524/2322 564/599/2324
+f 469/406/2325 565/610/2326 491/593/2327
+f 491/593/2327 565/610/2326 576/116/2328
+f 489/437/2329 490/66/2330 562/642/2331
+f 562/642/2331 490/66/2330 563/368/2332
+f 488/714/2333 489/437/2334 561/620/2335
+f 561/620/2335 489/437/2334 562/642/2336
+f 486/563/2337 488/714/2338 560/478/2339
+f 560/478/2339 488/714/2338 561/620/2340
+f 484/517/2341 486/563/2342 559/56/2343
+f 559/56/2343 486/563/2342 560/478/2344
+f 491/593/2345 576/116/2346 485/475/2347
+f 485/475/2347 576/116/2346 574/414/2348
+f 490/66/2349 481/400/2350 563/368/2351
+f 563/368/2351 481/400/2350 572/498/2352
+f 480/349/2353 557/194/2354 475/353/2355
+f 475/353/2355 557/194/2354 568/347/2356
+f 496/568/2357 580/455/2358 480/349/2359
+f 480/349/2359 580/455/2358 557/194/2360
+f 570/665/2361 566/491/2362 478/241/2363
+f 478/241/2363 566/491/2362 472/139/2364
+f 482/581/2365 483/595/2366 573/543/2367
+f 573/543/2367 483/595/2366 558/641/2368
+f 470/329/2369 553/302/2370 479/494/2371
+f 479/494/2371 553/302/2370 571/134/2372
+f 474/251/2373 567/297/2374 471/102/2375
+f 471/102/2375 567/297/2374 554/247/2376
+f 476/720/2377 482/581/2378 569/252/2379
+f 569/252/2379 482/581/2378 573/543/2380
+f 492/524/2381 484/517/2382 564/599/2383
+f 564/599/2383 484/517/2382 559/56/2384
+f 125/37/2385 600/410/2386 421/348/2387
+f 421/348/2387 600/410/2386 608/159/2388
+f 143/91/2389 603/646/2390 434/63/2391
+f 434/63/2391 603/646/2390 608/159/2392
+f 141/418/2393 606/27/2394 143/91/2395
+f 143/91/2395 606/27/2394 603/646/2396
+f 369/657/2397 325/225/2398 602/387/2399
+f 602/387/2399 325/225/2398 605/715/2400
+f 33/363/2401 367/59/2402 588/25/2403
+f 588/25/2403 367/59/2402 607/15/2404
+f 35/519/2405 36/71/2406 594/140/2407
+f 594/140/2407 36/71/2406 607/15/2408
+f 139/402/2409 140/113/2410 604/415/2411
+f 604/415/2411 140/113/2410 605/715/2412
+f 141/418/2413 139/402/2414 606/27/2415
+f 606/27/2415 139/402/2414 604/415/2416
+f 430/372/2417 160/74/2418 602/387/2419
+f 602/387/2419 160/74/2418 589/699/2420
+f 58/603/2421 584/684/2422 78/609/2423
+f 78/609/2423 584/684/2422 586/209/2424
+f 594/140/2425 596/61/2426 35/519/2427
+f 35/519/2427 596/61/2426 56/364/2428
+f 418/320/2429 595/323/2430 392/328/2431
+f 392/328/2431 595/323/2430 601/137/2432
+f 34/38/2433 584/684/2434 371/183/2435
+f 371/183/2435 584/684/2434 599/184/2436
+f 354/559/2437 338/370/2438 596/61/2439
+f 596/61/2439 338/370/2438 597/295/2440
+f 59/435/2441 57/488/2442 598/108/2443
+f 598/108/2443 57/488/2442 597/295/2444
+f 582/480/2445 588/25/2446 82/707/2447
+f 82/707/2447 588/25/2446 420/481/2448
+f 303/168/2449 592/434/2450 317/213/2451
+f 317/213/2451 592/434/2450 593/565/2452
+f 593/565/2453 591/442/2454 317/213/2455
+f 317/213/2455 591/442/2454 300/118/2456
+f 171/307/2457 592/434/2458 81/73/2459
+f 81/73/2459 592/434/2458 581/271/2460
+f 591/442/2461 583/142/2462 300/118/2463
+f 300/118/2463 583/142/2462 228/458/2464
+f 78/608/2465 586/493/2466 276/664/2467
+f 276/664/2467 586/493/2466 590/663/2468
+f 392/328/2469 601/137/2470 391/90/2471
+f 391/90/2471 601/137/2470 585/492/2472
+f 80/724/2473 582/480/2474 231/223/2475
+f 231/223/2475 582/480/2474 581/5/2476
+f 230/330/2477 172/276/2478 583/267/2479
+f 583/267/2479 172/276/2478 587/404/2480
+f 418/320/2481 276/664/2482 595/323/2483
+f 595/323/2483 276/664/2482 590/663/2484
+f 390/26/2485 600/410/2486 172/276/2487
+f 172/276/2487 600/410/2486 587/404/2488
+f 598/108/2489 599/184/2490 59/435/2491
+f 59/435/2491 599/184/2490 60/148/2492
+f 391/532/2493 585/367/2494 160/74/2495
+f 160/74/2495 585/367/2494 589/699/2496
diff --git a/libs/vr/libdvrgraphics/assets/controller_proto2_button.obj b/libs/vr/libdvrgraphics/assets/controller_proto2_button.obj
new file mode 100644
index 0000000..2057c27
--- /dev/null
+++ b/libs/vr/libdvrgraphics/assets/controller_proto2_button.obj
@@ -0,0 +1,595 @@
+# This file uses centimeters as units for non-parametric coordinates.
+
+v -0.003544 0.012417 0.078987
+v -0.003544 0.013694 0.078987
+v -0.003544 0.012417 0.078281
+v -0.003544 0.013694 0.078281
+v 0.003544 0.012417 0.078987
+v 0.003544 0.013694 0.078987
+v 0.003544 0.012417 0.078281
+v 0.003544 0.013694 0.078281
+v -0.003685 0.013694 0.078769
+v -0.003685 0.013694 0.078498
+v -0.003685 0.012417 0.078498
+v -0.003685 0.012417 0.078769
+v 0.003685 0.013694 0.078498
+v 0.003685 0.013694 0.078769
+v 0.003685 0.012417 0.078769
+v 0.003685 0.012417 0.078498
+v 0.005601 0.013400 0.078650
+v 0.004531 0.013400 0.081942
+v 0.004531 0.013400 0.075358
+v -0.001731 0.013400 0.073324
+v -0.005601 0.013400 0.078650
+v -0.001731 0.013400 0.083977
+v 0.001731 0.013400 0.083977
+v -0.004531 0.013400 0.081942
+v 0.001731 0.013400 0.073324
+v -0.004531 0.013400 0.075358
+v 0.005463 0.013400 0.078650
+v 0.004420 0.013400 0.081861
+v 0.004420 0.013400 0.075439
+v -0.001688 0.013400 0.073454
+v -0.005463 0.013400 0.078650
+v -0.001688 0.013400 0.083846
+v 0.001688 0.013400 0.083846
+v -0.004420 0.013400 0.081861
+v 0.001688 0.013400 0.073454
+v -0.004420 0.013400 0.075439
+v 0.000000 0.013400 0.078650
+v -0.003544 0.013694 0.078987
+v 0.005601 0.008414 0.078650
+v 0.004531 0.008414 0.081942
+v 0.004531 0.008414 0.075358
+v -0.001731 0.008414 0.073324
+v -0.005601 0.008414 0.078650
+v -0.001731 0.008414 0.083977
+v 0.001731 0.008414 0.083977
+v -0.004531 0.008414 0.081942
+v 0.001731 0.008414 0.073324
+v -0.004531 0.008414 0.075358
+v 0.007115 0.008414 0.078650
+v 0.005756 0.008414 0.082832
+v 0.005756 0.008414 0.074468
+v -0.002199 0.008414 0.071883
+v -0.007115 0.008414 0.078650
+v -0.002199 0.008414 0.085417
+v 0.002199 0.008414 0.085417
+v -0.005756 0.008414 0.082832
+v 0.002199 0.008414 0.071883
+v -0.005756 0.008414 0.074468
+v -0.001731 0.013280 0.083977
+v 0.001731 0.013280 0.083977
+v 0.005601 0.013280 0.078650
+v 0.004531 0.013280 0.075358
+v 0.001731 0.013280 0.073324
+v -0.004531 0.013280 0.081942
+v -0.005601 0.013280 0.078650
+v -0.004531 0.013280 0.075358
+v -0.001731 0.013280 0.073324
+v 0.004531 0.013280 0.081942
+v -0.001731 0.008498 0.083977
+v 0.005601 0.008498 0.078650
+v -0.004531 0.008498 0.081942
+v -0.005601 0.008498 0.078650
+v -0.004531 0.008498 0.075358
+v -0.001731 0.008498 0.073324
+v 0.004531 0.008498 0.081942
+v 0.001731 0.008498 0.083977
+v 0.004531 0.008498 0.075358
+v 0.001731 0.008498 0.073324
+vt 0.842894 0.803524
+vt 0.835648 0.808965
+vt 0.842327 0.831331
+vt 0.887089 0.817309
+vt 0.918325 0.883860
+vt 0.914475 0.893532
+vt 0.851925 0.804110
+vt 0.831948 0.817360
+vt 0.834920 0.826127
+vt 0.835239 0.825914
+vt 0.861947 0.818080
+vt 0.858905 0.826714
+vt 0.796511 0.818262
+vt 0.834940 0.856335
+vt 0.851468 0.832257
+vt 0.879150 0.794052
+vt 0.814609 0.794688
+vt 0.879631 0.793683
+vt 0.859802 0.855418
+vt 0.918325 0.877630
+vt 0.952893 0.877630
+vt 0.917637 0.875247
+vt 0.860005 0.855991
+vt 0.953582 0.876570
+vt 0.954157 0.893532
+vt 0.914475 0.887302
+vt 0.915739 0.887302
+vt 0.831492 0.866397
+vt 0.859621 0.809104
+vt 0.814110 0.794343
+vt 0.955480 0.893532
+vt 0.918325 0.874188
+vt 0.952893 0.874188
+vt 0.953582 0.875247
+vt 0.953582 0.886243
+vt 0.917637 0.886242
+vt 0.953582 0.884920
+vt 0.917637 0.884919
+vt 0.897742 0.817179
+vt 0.842083 0.832031
+vt 0.835347 0.808727
+vt 0.842998 0.803894
+vt 0.842194 0.831691
+vt 0.880142 0.841098
+vt 0.835113 0.855751
+vt 0.952893 0.883860
+vt 0.917637 0.876570
+vt 0.830731 0.769044
+vt 0.852058 0.803750
+vt 0.835058 0.808516
+vt 0.852169 0.803410
+vt 0.887696 0.817293
+vt 0.806054 0.847763
+vt 0.814622 0.841758
+vt 0.888895 0.846984
+vt 0.862304 0.818081
+vt 0.805357 0.788458
+vt 0.815102 0.841390
+vt 0.956743 0.887302
+vt 0.956743 0.893532
+vt 0.851358 0.831917
+vt 0.862760 0.769044
+vt 0.954157 0.887302
+vt 0.915739 0.893532
+vt 0.859312 0.779106
+vt 0.806556 0.818148
+vt 0.834631 0.826337
+vt 0.859194 0.826925
+vt 0.859332 0.809314
+vt 0.859013 0.809527
+vt 0.832305 0.817361
+vt 0.832688 0.817376
+vt 0.847126 0.817721
+vt 0.859139 0.779690
+vt 0.834450 0.780024
+vt 0.834248 0.779451
+vt 0.807163 0.818133
+vt 0.863522 0.866397
+vt 0.952893 0.893532
+vt 0.917062 0.887302
+vt 0.918325 0.887302
+vt 0.918325 0.893532
+vt 0.917062 0.893532
+vt 0.952893 0.887302
+vt 0.955480 0.887302
+vt 0.879643 0.840753
+vt 0.888198 0.787678
+vt 0.861564 0.818065
+vt 0.842784 0.803184
+vt 0.851254 0.831547
+vt 0.858604 0.826476
+vn -0.838651 0.000004 -0.544669
+vn -0.838651 0.000004 -0.544669
+vn -0.838651 0.000004 -0.544669
+vn -0.838651 0.000004 -0.544669
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.838651 0.000004 0.544669
+vn 0.838651 0.000004 0.544669
+vn 0.838651 0.000004 0.544669
+vn 0.838651 0.000004 0.544669
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.838651 -0.000004 -0.544669
+vn 0.838651 -0.000004 -0.544669
+vn 0.838651 -0.000004 -0.544669
+vn 0.838651 -0.000004 -0.544669
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn -0.838651 -0.000004 0.544669
+vn -0.838651 -0.000004 0.544669
+vn -0.838651 -0.000004 0.544669
+vn -0.838651 -0.000004 0.544669
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn 0.619120 0.643718 -0.449797
+vn 0.236462 0.643710 -0.727821
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.619120 0.643718 0.449797
+vn -0.236462 0.643710 0.727821
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.236462 0.643710 -0.727821
+vn -0.236462 0.643710 -0.727821
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.765269 0.643711 0.000000
+vn 0.619120 0.643718 -0.449797
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.236462 0.643710 -0.727821
+vn -0.619120 0.643718 -0.449797
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.236462 0.643710 0.727821
+vn 0.236462 0.643710 0.727821
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.236462 0.643710 0.727821
+vn 0.619120 0.643718 0.449797
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.619120 0.643718 0.449797
+vn 0.765269 0.643711 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.765269 0.643711 0.000000
+vn -0.619120 0.643718 0.449797
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.619120 0.643718 -0.449797
+vn -0.765269 0.643711 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.309004 -0.000000 0.951061
+vn -0.309004 -0.000000 0.951061
+vn 0.191936 0.783706 0.590733
+vn -0.191936 0.783706 0.590733
+vn 0.809030 0.000000 -0.587767
+vn 1.000000 0.000000 0.000000
+vn 0.502506 0.783714 -0.365076
+vn 0.621130 0.783707 0.000000
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 0.000000 -0.587767
+vn 0.191936 0.783706 -0.590733
+vn 0.502506 0.783714 -0.365076
+vn -0.309004 -0.000000 0.951061
+vn -0.809030 -0.000000 0.587767
+vn -0.191936 0.783706 0.590733
+vn -0.502506 0.783714 0.365076
+vn -0.809030 -0.000000 0.587767
+vn -1.000000 0.000000 0.000000
+vn -0.502506 0.783714 0.365076
+vn -0.621130 0.783707 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -0.621130 0.783707 0.000000
+vn -0.502506 0.783714 -0.365076
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn -0.502506 0.783714 -0.365076
+vn -0.191936 0.783706 -0.590733
+vn 1.000000 0.000000 0.000000
+vn 0.809030 -0.000000 0.587767
+vn 0.621130 0.783707 0.000000
+vn 0.502506 0.783714 0.365076
+vn 0.809030 -0.000000 0.587767
+vn 0.309004 -0.000000 0.951061
+vn 0.502506 0.783714 0.365076
+vn 0.191936 0.783706 0.590733
+vn -0.309004 0.000000 -0.951061
+vn 0.309004 0.000000 -0.951061
+vn -0.191936 0.783706 -0.590733
+vn 0.191936 0.783706 -0.590733
+vn 0.621130 0.783707 0.000000
+vn 0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.502506 0.783714 0.365076
+vn 0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 -0.590733
+vn 0.191936 0.783706 -0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.191936 0.783706 0.590733
+vn -0.191936 0.783706 0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.502506 0.783714 -0.365076
+vn 0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.191936 0.783706 -0.590733
+vn 0.502506 0.783714 -0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 0.590733
+vn -0.502506 0.783714 0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 0.365076
+vn -0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.621130 0.783707 0.000000
+vn -0.502506 0.783714 -0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 -0.365076
+vn -0.191936 0.783706 -0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.236462 0.643710 -0.727821
+vn 0.236462 0.643710 -0.727821
+vn -0.309004 0.000000 -0.951061
+vn 0.309004 0.000000 -0.951061
+vn 0.619120 0.643718 0.449797
+vn 0.236462 0.643710 0.727821
+vn 0.809030 -0.000000 0.587767
+vn 0.309004 -0.000000 0.951061
+vn 0.765269 0.643711 0.000000
+vn 0.619120 0.643718 0.449797
+vn 1.000000 0.000000 0.000000
+vn 0.809030 -0.000000 0.587767
+vn -0.619120 0.643718 -0.449797
+vn -0.236462 0.643710 -0.727821
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn -0.765269 0.643711 0.000000
+vn -0.619120 0.643718 -0.449797
+vn -1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -0.619120 0.643718 0.449797
+vn -0.765269 0.643711 0.000000
+vn -0.809030 -0.000000 0.587767
+vn -1.000000 0.000000 0.000000
+vn -0.236462 0.643710 0.727821
+vn -0.619120 0.643718 0.449797
+vn -0.309004 -0.000000 0.951061
+vn -0.809030 -0.000000 0.587767
+vn 0.236462 0.643710 -0.727821
+vn 0.619120 0.643718 -0.449797
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 0.000000 -0.587767
+vn 0.619120 0.643718 -0.449797
+vn 0.765269 0.643711 0.000000
+vn 0.809030 0.000000 -0.587767
+vn 1.000000 0.000000 0.000000
+vn 0.236462 0.643710 0.727821
+vn -0.236462 0.643710 0.727821
+vn 0.309004 -0.000000 0.951061
+vn -0.309004 -0.000000 0.951061
+vn -0.309004 0.000000 -0.951061
+vn 0.309004 0.000000 -0.951061
+vn -0.309004 0.000000 -0.951061
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 -0.000000 0.587767
+vn 0.309004 -0.000000 0.951061
+vn 0.809030 -0.000000 0.587767
+vn 0.309004 -0.000000 0.951061
+vn 1.000000 0.000000 0.000000
+vn 0.809030 -0.000000 0.587767
+vn 1.000000 0.000000 0.000000
+vn 0.809030 -0.000000 0.587767
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn -1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -0.809030 -0.000000 0.587767
+vn -1.000000 0.000000 0.000000
+vn -0.809030 -0.000000 0.587767
+vn -1.000000 0.000000 0.000000
+vn -0.309004 -0.000000 0.951061
+vn -0.809030 -0.000000 0.587767
+vn -0.309004 -0.000000 0.951061
+vn -0.809030 -0.000000 0.587767
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 0.000000 -0.587767
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 0.000000 -0.587767
+vn 0.809030 0.000000 -0.587767
+vn 1.000000 0.000000 0.000000
+vn 0.809030 0.000000 -0.587767
+vn 1.000000 0.000000 0.000000
+vn 0.309004 -0.000000 0.951061
+vn -0.309004 -0.000000 0.951061
+vn 0.309004 -0.000000 0.951061
+vn -0.309004 -0.000000 0.951061
+f 10/80/1 4/81/2 11/83/3
+f 11/83/3 4/81/2 3/82/4
+f 4/81/5 8/84/6 3/82/7
+f 3/82/7 8/84/6 7/79/8
+f 14/85/9 6/59/10 15/31/11
+f 15/31/11 6/59/10 5/60/12
+f 6/46/13 2/5/14 5/21/15
+f 5/21/15 2/5/14 1/20/16
+f 11/22/17 3/32/18 16/34/19
+f 16/34/19 3/32/18 7/33/20
+f 13/35/21 8/84/22 10/36/23
+f 10/36/23 8/84/22 4/81/24
+f 6/46/25 14/37/26 2/5/27
+f 2/5/27 14/37/26 9/38/28
+f 14/37/29 13/35/30 9/38/31
+f 9/38/31 13/35/30 10/36/32
+f 1/20/33 12/47/34 5/21/35
+f 5/21/35 12/47/34 15/24/36
+f 12/47/37 11/22/38 15/24/39
+f 15/24/39 11/22/38 16/34/40
+f 8/84/41 13/63/42 7/79/43
+f 7/79/43 13/63/42 16/25/44
+f 13/63/45 14/85/46 16/25/47
+f 16/25/47 14/85/46 15/31/48
+f 2/26/49 9/27/50 1/6/51
+f 1/6/51 9/27/50 12/64/52
+f 9/27/53 10/80/54 12/64/55
+f 12/64/55 10/80/54 11/83/56
+f 19/12/57 25/61/58 29/91/59
+f 29/91/59 25/61/58 35/90/60
+f 24/41/61 22/1/62 34/2/63
+f 34/2/63 22/1/62 32/42/64
+f 25/61/65 20/43/66 35/90/67
+f 35/90/67 20/43/66 30/3/68
+f 17/11/69 19/12/70 27/88/71
+f 27/88/71 19/12/70 29/91/72
+f 20/43/73 26/9/74 30/3/75
+f 30/3/75 26/9/74 36/10/76
+f 22/1/77 23/49/78 32/42/79
+f 32/42/79 23/49/78 33/7/80
+f 23/49/81 18/69/82 33/7/83
+f 33/7/83 18/69/82 28/70/84
+f 18/69/85 17/11/86 28/70/87
+f 28/70/87 17/11/86 27/88/88
+f 21/71/89 24/41/90 31/72/91
+f 31/72/91 24/41/90 34/2/92
+f 26/9/93 21/71/94 36/10/95
+f 36/10/95 21/71/94 31/72/96
+f 33/7/97 28/70/98 37/73/99
+f 35/90/100 30/3/101 37/73/102
+f 32/42/103 33/7/104 37/73/105
+f 27/88/106 29/91/107 37/73/108
+f 29/91/109 35/90/110 37/73/111
+f 34/2/112 32/42/113 37/73/114
+f 31/72/115 34/2/116 37/73/117
+f 36/10/118 31/72/119 37/73/120
+f 30/3/121 36/10/122 37/73/123
+f 28/70/124 27/88/125 37/73/126
+f 76/74/127 69/75/128 45/65/129
+f 45/65/129 69/75/128 44/76/130
+f 77/86/131 70/4/132 41/44/133
+f 41/44/133 70/4/132 39/52/134
+f 78/19/135 77/86/136 47/23/137
+f 47/23/137 77/86/136 41/44/138
+f 69/75/139 71/17/140 44/76/141
+f 44/76/141 71/17/140 46/30/142
+f 71/17/143 72/77/144 46/30/145
+f 46/30/145 72/77/144 43/66/146
+f 72/77/147 73/58/148 43/66/149
+f 43/66/149 73/58/148 48/54/150
+f 73/58/151 74/45/152 48/54/153
+f 48/54/153 74/45/152 42/14/154
+f 70/4/155 75/16/156 39/52/157
+f 39/52/157 75/16/156 40/18/158
+f 75/16/159 76/74/160 40/18/161
+f 40/18/161 76/74/160 45/65/162
+f 74/45/163 78/19/164 42/14/165
+f 42/14/165 78/19/164 47/23/166
+f 39/52/167 40/18/168 49/39/169
+f 49/39/169 40/18/168 50/87/170
+f 40/18/171 45/65/172 50/87/173
+f 50/87/173 45/65/172 55/62/174
+f 42/14/175 47/23/176 52/28/177
+f 52/28/177 47/23/176 57/78/178
+f 45/65/179 44/76/180 55/62/181
+f 55/62/181 44/76/180 54/48/182
+f 41/44/183 39/52/184 51/55/185
+f 51/55/185 39/52/184 49/39/186
+f 47/23/187 41/44/188 57/78/189
+f 57/78/189 41/44/188 51/55/190
+f 44/76/191 46/30/192 54/48/193
+f 54/48/193 46/30/192 56/57/194
+f 46/30/195 43/66/196 56/57/197
+f 56/57/197 43/66/196 53/13/198
+f 43/66/199 48/54/200 53/13/201
+f 53/13/201 48/54/200 58/53/202
+f 48/54/203 42/14/204 58/53/205
+f 58/53/205 42/14/204 52/28/206
+f 20/43/207 25/61/208 67/40/209
+f 67/40/209 25/61/208 63/15/210
+f 18/69/211 23/49/212 68/29/213
+f 68/29/213 23/49/212 60/51/214
+f 17/11/215 18/69/216 61/56/217
+f 61/56/217 18/69/216 68/29/218
+f 26/9/219 20/43/220 66/67/221
+f 66/67/221 20/43/220 67/40/222
+f 21/71/223 26/9/224 65/8/225
+f 65/8/225 26/9/224 66/67/226
+f 24/41/227 21/71/228 64/50/229
+f 64/50/229 21/71/228 65/8/230
+f 22/1/231 24/41/232 59/89/233
+f 59/89/233 24/41/232 64/50/234
+f 25/61/235 19/12/236 63/15/237
+f 63/15/237 19/12/236 62/68/238
+f 19/12/239 17/11/240 62/68/241
+f 62/68/241 17/11/240 61/56/242
+f 23/49/243 22/1/244 60/51/245
+f 60/51/245 22/1/244 59/89/246
+f 67/40/247 63/15/248 74/45/249
+f 74/45/249 63/15/248 78/19/250
+f 68/29/251 60/51/252 75/16/253
+f 75/16/253 60/51/252 76/74/254
+f 61/56/255 68/29/256 70/4/257
+f 70/4/257 68/29/256 75/16/258
+f 66/67/259 67/40/260 73/58/261
+f 73/58/261 67/40/260 74/45/262
+f 65/8/263 66/67/264 72/77/265
+f 72/77/265 66/67/264 73/58/266
+f 64/50/267 65/8/268 71/17/269
+f 71/17/269 65/8/268 72/77/270
+f 59/89/271 64/50/272 69/75/273
+f 69/75/273 64/50/272 71/17/274
+f 63/15/275 62/68/276 78/19/277
+f 78/19/277 62/68/276 77/86/278
+f 62/68/279 61/56/280 77/86/281
+f 77/86/281 61/56/280 70/4/282
+f 60/51/283 59/89/284 76/74/285
+f 76/74/285 59/89/284 69/75/286
diff --git a/libs/vr/libdvrgraphics/assets/controller_proto2_button2.obj b/libs/vr/libdvrgraphics/assets/controller_proto2_button2.obj
new file mode 100644
index 0000000..2f3bd1e
--- /dev/null
+++ b/libs/vr/libdvrgraphics/assets/controller_proto2_button2.obj
@@ -0,0 +1,794 @@
+# This file uses centimeters as units for non-parametric coordinates.
+
+v 0.001731 0.013400 0.090823
+v -0.005601 0.013400 0.096150
+v 0.001731 0.013400 0.101477
+v -0.004531 0.013400 0.099442
+v 0.004531 0.013400 0.099442
+v 0.005601 0.013400 0.096150
+v -0.001731 0.013400 0.101477
+v -0.001731 0.013400 0.090823
+v 0.004531 0.013400 0.092858
+v -0.004531 0.013400 0.092858
+v 0.001690 0.013400 0.090950
+v -0.005467 0.013400 0.096150
+v 0.001690 0.013400 0.101350
+v -0.004423 0.013400 0.099364
+v 0.004423 0.013400 0.099364
+v 0.005467 0.013400 0.096150
+v -0.001690 0.013400 0.101350
+v -0.001690 0.013400 0.090950
+v 0.004423 0.013400 0.092936
+v -0.004423 0.013400 0.092936
+v 0.000000 0.013400 0.096150
+v 0.001731 0.008414 0.090823
+v -0.005601 0.008414 0.096150
+v 0.001731 0.008414 0.101477
+v -0.004531 0.008414 0.099442
+v 0.004531 0.008414 0.099442
+v 0.005601 0.008414 0.096150
+v -0.001731 0.008414 0.101477
+v -0.001731 0.008414 0.090823
+v 0.004531 0.008414 0.092858
+v -0.004531 0.008414 0.092858
+v 0.002212 0.008414 0.089341
+v -0.007159 0.008414 0.096150
+v 0.002212 0.008414 0.102959
+v -0.005792 0.008414 0.100358
+v 0.005792 0.008414 0.100358
+v 0.007159 0.008414 0.096150
+v -0.002212 0.008414 0.102959
+v -0.002212 0.008414 0.089341
+v 0.005792 0.008414 0.091942
+v -0.005792 0.008414 0.091942
+v -0.001731 0.013209 0.101477
+v 0.001731 0.013209 0.101477
+v -0.004531 0.013209 0.099442
+v -0.005601 0.013209 0.096150
+v -0.004531 0.013209 0.092858
+v 0.004531 0.013209 0.092858
+v 0.005601 0.013209 0.096150
+v 0.004531 0.013209 0.099442
+v -0.001731 0.013209 0.090823
+v 0.001731 0.013209 0.090823
+v 0.001731 0.008535 0.101477
+v -0.005601 0.008535 0.096150
+v 0.004531 0.008535 0.099442
+v 0.001731 0.008535 0.090823
+v -0.001731 0.008535 0.101477
+v -0.004531 0.008535 0.099442
+v -0.004531 0.008535 0.092858
+v 0.004531 0.008535 0.092858
+v 0.005601 0.008535 0.096150
+v -0.001731 0.008535 0.090823
+v 0.001108 0.013694 0.092741
+v -0.003585 0.013694 0.096150
+v 0.001108 0.013694 0.099560
+v -0.002900 0.013694 0.098257
+v 0.002900 0.013694 0.098257
+v 0.003585 0.013694 0.096150
+v -0.001108 0.013694 0.099560
+v -0.001108 0.013694 0.092741
+v 0.002900 0.013694 0.094043
+v -0.002900 0.013694 0.094043
+v 0.000955 0.013694 0.093211
+v -0.003090 0.013694 0.096150
+v 0.000955 0.013694 0.099090
+v -0.002500 0.013694 0.097967
+v 0.002500 0.013694 0.097967
+v 0.003090 0.013694 0.096150
+v -0.000955 0.013694 0.099090
+v -0.000955 0.013694 0.093211
+v 0.002500 0.013694 0.094334
+v -0.002500 0.013694 0.094334
+v 0.001108 0.012417 0.092741
+v -0.003585 0.012417 0.096150
+v 0.001108 0.012417 0.099560
+v -0.002900 0.012417 0.098257
+v 0.002900 0.012417 0.098257
+v 0.003585 0.012417 0.096150
+v -0.001108 0.012417 0.099560
+v -0.001108 0.012417 0.092741
+v 0.002900 0.012417 0.094043
+v -0.002900 0.012417 0.094043
+v 0.000955 0.012417 0.093211
+v -0.003090 0.012417 0.096150
+v 0.000955 0.012417 0.099090
+v -0.002500 0.012417 0.097967
+v 0.002500 0.012417 0.097967
+v 0.003090 0.012417 0.096150
+v -0.000955 0.012417 0.099090
+v -0.000955 0.012417 0.093211
+v 0.002500 0.012417 0.094334
+v -0.002500 0.012417 0.094334
+vt 0.932232 0.937151
+vt 0.945647 0.941314
+vt 0.825593 0.923815
+vt 0.788802 0.909064
+vt 0.935598 0.930484
+vt 0.909311 0.932196
+vt 0.907009 0.929014
+vt 0.922229 0.927744
+vt 0.923371 0.959651
+vt 0.926270 0.980012
+vt 0.923981 0.964843
+vt 0.907571 0.956084
+vt 0.910064 0.946637
+vt 0.932187 0.961332
+vt 0.913791 0.964746
+vt 0.931115 0.938999
+vt 0.906593 0.976987
+vt 0.814667 0.938118
+vt 0.907941 0.945842
+vt 0.817792 0.929265
+vt 0.924220 0.967204
+vt 0.899768 0.959058
+vt 0.834177 0.952426
+vt 0.841570 0.947355
+vt 0.817336 0.900595
+vt 0.797907 0.915598
+vt 0.841862 0.947586
+vt 0.797489 0.962416
+vt 0.863053 0.962016
+vt 0.842840 0.976907
+vt 0.845500 0.938935
+vt 0.900358 0.943670
+vt 0.896353 0.942255
+vt 0.950018 0.956861
+vt 0.914874 0.948129
+vt 0.914648 0.953699
+vt 0.937578 0.926986
+vt 0.925672 0.975465
+vt 0.931991 0.947345
+vt 0.825215 0.952174
+vt 0.842325 0.930150
+vt 0.842016 0.930356
+vt 0.834916 0.924929
+vt 0.844928 0.938934
+vt 0.844556 0.938918
+vt 0.830059 0.938555
+vt 0.790654 0.938738
+vt 0.933703 0.963036
+vt 0.936911 0.954039
+vt 0.813826 0.890060
+vt 0.797202 0.915105
+vt 0.861850 0.915045
+vt 0.817977 0.976306
+vt 0.798179 0.961897
+vt 0.789797 0.938756
+vt 0.842554 0.976087
+vt 0.841940 0.901082
+vt 0.845641 0.890060
+vt 0.950018 0.939483
+vt 0.932035 0.952826
+vt 0.835221 0.924030
+vt 0.909829 0.955422
+vt 0.895358 0.960445
+vt 0.915261 0.939767
+vt 0.923140 0.936945
+vt 0.936417 0.945527
+vt 0.938885 0.954567
+vt 0.909516 0.972375
+vt 0.817813 0.946906
+vt 0.818124 0.946700
+vt 0.917914 0.958206
+vt 0.923549 0.941808
+vt 0.928754 0.943094
+vt 0.846431 0.987663
+vt 0.870407 0.938130
+vt 0.817616 0.901404
+vt 0.842181 0.900257
+vt 0.862534 0.914520
+vt 0.869546 0.938152
+vt 0.834277 0.952786
+vt 0.862338 0.961524
+vt 0.923050 0.934996
+vt 0.915227 0.962597
+vt 0.946468 0.956010
+vt 0.928741 0.957476
+vt 0.922006 0.924371
+vt 0.939379 0.968571
+vt 0.918286 0.943618
+vt 0.942241 0.971610
+vt 0.814114 0.987501
+vt 0.788568 0.968560
+vt 0.779102 0.938526
+vt 0.870794 0.908363
+vt 0.881150 0.937707
+vt 0.834453 0.953332
+vt 0.824904 0.953069
+vt 0.842788 0.929814
+vt 0.842325 0.947924
+vt 0.817349 0.947240
+vt 0.835045 0.924577
+vt 0.825883 0.924738
+vt 0.815235 0.938118
+vt 0.818251 0.929599
+vt 0.818529 0.929812
+vt 0.815603 0.938133
+vt 0.825771 0.924361
+vt 0.825084 0.952524
+vt 0.938637 0.944674
+vt 0.914016 0.937979
+vt 0.872150 0.968124
+vt 0.817725 0.977137
+vn -0.765269 0.643711 0.000000
+vn -0.619128 0.643727 0.449773
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.619128 0.643727 0.449773
+vn -0.236496 0.643720 0.727801
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.236462 0.643710 -0.727821
+vn -0.619120 0.643718 -0.449797
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.619120 0.643718 -0.449797
+vn 0.236462 0.643710 -0.727821
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.236496 0.643720 0.727801
+vn 0.619128 0.643727 0.449773
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.236462 0.643710 -0.727821
+vn -0.236462 0.643710 -0.727821
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.619128 0.643727 0.449773
+vn 0.765269 0.643711 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.236496 0.643720 0.727801
+vn 0.236496 0.643720 0.727801
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.619120 0.643718 -0.449797
+vn -0.765269 0.643711 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.765269 0.643711 0.000000
+vn 0.619120 0.643718 -0.449797
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -0.621130 0.783707 0.000000
+vn -0.502506 0.783714 -0.365076
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 0.000000 -0.587767
+vn 0.191936 0.783706 -0.590733
+vn 0.502506 0.783714 -0.365076
+vn 0.309041 -0.000000 0.951049
+vn -0.309041 -0.000000 0.951049
+vn 0.191935 0.783732 0.590698
+vn -0.191935 0.783732 0.590698
+vn -0.309041 -0.000000 0.951049
+vn -0.809045 -0.000000 0.587747
+vn -0.191935 0.783732 0.590698
+vn -0.502529 0.783702 0.365070
+vn 0.809045 -0.000000 0.587747
+vn 0.309041 -0.000000 0.951049
+vn 0.502529 0.783702 0.365070
+vn 0.191935 0.783732 0.590698
+vn -0.809045 -0.000000 0.587747
+vn -1.000000 0.000000 0.000000
+vn -0.502529 0.783702 0.365070
+vn -0.621130 0.783707 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn -0.502506 0.783714 -0.365076
+vn -0.191936 0.783706 -0.590733
+vn 0.809030 0.000000 -0.587767
+vn 1.000000 0.000000 0.000000
+vn 0.502506 0.783714 -0.365076
+vn 0.621130 0.783707 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.809045 -0.000000 0.587747
+vn 0.621130 0.783707 0.000000
+vn 0.502529 0.783702 0.365070
+vn -0.309004 0.000000 -0.951061
+vn 0.309004 0.000000 -0.951061
+vn -0.191936 0.783706 -0.590733
+vn 0.191936 0.783706 -0.590733
+vn 0.191936 0.783706 -0.590733
+vn 0.502506 0.783714 -0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.191935 0.783732 0.590698
+vn -0.191935 0.783732 0.590698
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.191935 0.783732 0.590698
+vn -0.502529 0.783702 0.365070
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.502506 0.783714 -0.365076
+vn -0.191936 0.783706 -0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.502529 0.783702 0.365070
+vn -0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.502529 0.783702 0.365070
+vn 0.191935 0.783732 0.590698
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.191936 0.783706 -0.590733
+vn 0.191936 0.783706 -0.590733
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.621130 0.783707 0.000000
+vn 0.502529 0.783702 0.365070
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.502506 0.783714 -0.365076
+vn 0.621130 0.783707 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.621130 0.783707 0.000000
+vn -0.502506 0.783714 -0.365076
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.236462 0.643710 -0.727821
+vn 0.236462 0.643710 -0.727821
+vn -0.309004 0.000000 -0.951061
+vn 0.309004 0.000000 -0.951061
+vn 0.765269 0.643711 0.000000
+vn 0.619128 0.643727 0.449773
+vn 1.000000 0.000000 0.000000
+vn 0.809045 -0.000000 0.587747
+vn 0.619120 0.643718 -0.449797
+vn 0.765269 0.643711 0.000000
+vn 0.809030 0.000000 -0.587767
+vn 1.000000 0.000000 0.000000
+vn -0.619120 0.643718 -0.449797
+vn -0.236462 0.643710 -0.727821
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn -0.619128 0.643727 0.449773
+vn -0.765269 0.643711 0.000000
+vn -0.809045 -0.000000 0.587747
+vn -1.000000 0.000000 0.000000
+vn 0.619128 0.643727 0.449773
+vn 0.236496 0.643720 0.727801
+vn 0.809045 -0.000000 0.587747
+vn 0.309041 -0.000000 0.951049
+vn -0.236496 0.643720 0.727801
+vn -0.619128 0.643727 0.449773
+vn -0.309041 -0.000000 0.951049
+vn -0.809045 -0.000000 0.587747
+vn 0.236496 0.643720 0.727801
+vn -0.236496 0.643720 0.727801
+vn 0.309041 -0.000000 0.951049
+vn -0.309041 -0.000000 0.951049
+vn 0.236462 0.643710 -0.727821
+vn 0.619120 0.643718 -0.449797
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 0.000000 -0.587767
+vn -0.765269 0.643711 0.000000
+vn -0.619120 0.643718 -0.449797
+vn -1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn 0.309004 0.000000 -0.951061
+vn -0.309004 0.000000 -0.951061
+vn 0.309004 0.000000 -0.951061
+vn 1.000000 0.000000 0.000000
+vn 0.809045 -0.000000 0.587747
+vn 1.000000 0.000000 0.000000
+vn 0.809045 -0.000000 0.587747
+vn 0.809030 0.000000 -0.587767
+vn 1.000000 0.000000 0.000000
+vn 0.809030 0.000000 -0.587767
+vn 1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn -0.809030 0.000000 -0.587767
+vn -0.309004 0.000000 -0.951061
+vn -0.809045 -0.000000 0.587747
+vn -1.000000 0.000000 0.000000
+vn -0.809045 -0.000000 0.587747
+vn -1.000000 0.000000 0.000000
+vn 0.809045 -0.000000 0.587747
+vn 0.309041 -0.000000 0.951049
+vn 0.809045 -0.000000 0.587747
+vn 0.309041 -0.000000 0.951049
+vn -0.309041 -0.000000 0.951049
+vn -0.809045 -0.000000 0.587747
+vn -0.309041 -0.000000 0.951049
+vn -0.809045 -0.000000 0.587747
+vn 0.309041 -0.000000 0.951049
+vn -0.309041 -0.000000 0.951049
+vn 0.309041 -0.000000 0.951049
+vn -0.309041 -0.000000 0.951049
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 0.000000 -0.587767
+vn 0.309004 0.000000 -0.951061
+vn 0.809030 0.000000 -0.587767
+vn -1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn -1.000000 0.000000 0.000000
+vn -0.809030 0.000000 -0.587767
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.587858 0.000000 -0.808964
+vn 0.587858 0.000000 -0.808964
+vn 0.587858 0.000000 -0.808964
+vn 0.587858 0.000000 -0.808964
+vn 0.951057 -0.000000 0.309014
+vn 0.951057 -0.000000 0.309014
+vn 0.951057 -0.000000 0.309014
+vn 0.951057 -0.000000 0.309014
+vn -0.587858 0.000000 -0.808964
+vn -0.587858 0.000000 -0.808964
+vn -0.587858 0.000000 -0.808964
+vn -0.587858 0.000000 -0.808964
+vn 0.951058 0.000000 -0.309013
+vn 0.951058 0.000000 -0.309013
+vn 0.951058 0.000000 -0.309013
+vn 0.951058 0.000000 -0.309013
+vn -0.587785 0.000000 -0.809017
+vn -0.587785 0.000000 -0.809017
+vn -0.587785 0.000000 -0.809017
+vn -0.587785 0.000000 -0.809017
+vn 0.951057 0.000000 -0.309014
+vn 0.951057 0.000000 -0.309014
+vn 0.951057 0.000000 -0.309014
+vn 0.951057 0.000000 -0.309014
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn -0.951057 0.000000 -0.309014
+vn -0.951057 0.000000 -0.309014
+vn -0.951057 0.000000 -0.309014
+vn -0.951057 0.000000 -0.309014
+vn 0.587785 0.000000 -0.809017
+vn 0.587785 0.000000 -0.809017
+vn 0.587785 0.000000 -0.809017
+vn 0.587785 0.000000 -0.809017
+vn -0.951058 0.000000 -0.309013
+vn -0.951058 0.000000 -0.309013
+vn -0.951058 0.000000 -0.309013
+vn -0.951058 0.000000 -0.309013
+vn -0.951058 -0.000000 0.309012
+vn -0.951058 -0.000000 0.309012
+vn -0.951058 -0.000000 0.309012
+vn -0.951058 -0.000000 0.309012
+vn 0.587785 -0.000000 0.809018
+vn 0.587785 -0.000000 0.809018
+vn 0.587785 -0.000000 0.809018
+vn 0.587785 -0.000000 0.809018
+vn -0.587857 -0.000000 0.808965
+vn -0.587857 -0.000000 0.808965
+vn -0.587857 -0.000000 0.808965
+vn -0.587857 -0.000000 0.808965
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.587857 -0.000000 0.808965
+vn 0.587857 -0.000000 0.808965
+vn 0.587857 -0.000000 0.808965
+vn 0.587857 -0.000000 0.808965
+vn -0.587785 -0.000000 0.809018
+vn -0.587785 -0.000000 0.809018
+vn -0.587785 -0.000000 0.809018
+vn -0.587785 -0.000000 0.809018
+vn 0.951058 -0.000000 0.309012
+vn 0.951058 -0.000000 0.309012
+vn 0.951058 -0.000000 0.309012
+vn 0.951058 -0.000000 0.309012
+vn -0.951057 -0.000000 0.309014
+vn -0.951057 -0.000000 0.309014
+vn -0.951057 -0.000000 0.309014
+vn -0.951057 -0.000000 0.309014
+f 2/102/1 4/103/2 12/105/3
+f 12/105/3 4/103/2 14/104/4
+f 4/103/5 7/106/6 14/104/7
+f 14/104/7 7/106/6 17/101/8
+f 8/107/9 10/69/10 18/40/11
+f 18/40/11 10/69/10 20/70/12
+f 9/27/13 1/80/14 19/24/15
+f 19/24/15 1/80/14 11/23/16
+f 3/100/17 5/41/18 13/43/19
+f 13/43/19 5/41/18 15/42/20
+f 1/80/21 8/107/22 11/23/23
+f 11/23/23 8/107/22 18/40/24
+f 5/41/25 6/44/26 15/42/27
+f 15/42/27 6/44/26 16/45/28
+f 7/106/29 3/100/30 17/101/31
+f 17/101/31 3/100/30 13/43/32
+f 10/69/33 2/102/34 20/70/35
+f 20/70/35 2/102/34 12/105/36
+f 6/44/37 9/27/38 16/45/39
+f 16/45/39 9/27/38 19/24/40
+f 12/105/41 14/104/42 21/46/43
+f 18/40/44 20/70/45 21/46/46
+f 14/104/47 17/101/48 21/46/49
+f 13/43/50 15/42/51 21/46/52
+f 15/42/53 16/45/54 21/46/55
+f 17/101/56 13/43/57 21/46/58
+f 16/45/59 19/24/60 21/46/61
+f 19/24/62 11/23/63 21/46/64
+f 11/23/65 18/40/66 21/46/67
+f 20/70/68 12/105/69 21/46/70
+f 53/47/71 58/54/72 23/55/73
+f 23/55/73 58/54/72 31/28/74
+f 55/56/75 59/81/76 22/30/77
+f 22/30/77 59/81/76 30/29/78
+f 52/57/79 56/76/80 24/77/81
+f 24/77/81 56/76/80 28/25/82
+f 56/76/83 57/26/84 28/25/85
+f 28/25/85 57/26/84 25/51/86
+f 54/52/87 52/57/88 26/78/89
+f 26/78/89 52/57/88 24/77/90
+f 57/26/91 53/47/92 25/51/93
+f 25/51/93 53/47/92 23/55/94
+f 58/54/95 61/53/96 31/28/97
+f 31/28/97 61/53/96 29/111/98
+f 59/81/99 60/79/100 30/29/101
+f 30/29/101 60/79/100 27/75/102
+f 60/79/103 54/52/104 27/75/105
+f 27/75/105 54/52/104 26/78/106
+f 61/53/107 55/56/108 29/111/109
+f 29/111/109 55/56/108 22/30/110
+f 22/30/111 30/29/112 32/74/113
+f 32/74/113 30/29/112 40/110/114
+f 24/77/115 28/25/116 34/58/117
+f 34/58/117 28/25/116 38/50/118
+f 28/25/119 25/51/120 38/50/121
+f 38/50/121 25/51/120 35/4/122
+f 31/28/123 29/111/124 41/91/125
+f 41/91/125 29/111/124 39/90/126
+f 25/51/127 23/55/128 35/4/129
+f 35/4/129 23/55/128 33/92/130
+f 26/78/131 24/77/132 36/93/133
+f 36/93/133 24/77/132 34/58/134
+f 29/111/135 22/30/136 39/90/137
+f 39/90/137 22/30/136 32/74/138
+f 27/75/139 26/78/140 37/94/141
+f 37/94/141 26/78/140 36/93/142
+f 30/29/143 27/75/144 40/110/145
+f 40/110/145 27/75/144 37/94/146
+f 23/55/147 31/28/148 33/92/149
+f 33/92/149 31/28/148 41/91/150
+f 8/107/151 1/80/152 50/96/153
+f 50/96/153 1/80/152 51/95/154
+f 6/44/155 5/41/156 48/31/157
+f 48/31/157 5/41/156 49/97/158
+f 9/27/159 6/44/160 47/98/161
+f 47/98/161 6/44/160 48/31/162
+f 10/69/163 8/107/164 46/99/165
+f 46/99/165 8/107/164 50/96/166
+f 4/103/167 2/102/168 44/20/169
+f 44/20/169 2/102/168 45/18/170
+f 5/41/171 3/100/172 49/97/173
+f 49/97/173 3/100/172 43/61/174
+f 7/106/175 4/103/176 42/3/177
+f 42/3/177 4/103/176 44/20/178
+f 3/100/179 7/106/180 43/61/181
+f 43/61/181 7/106/180 42/3/182
+f 1/80/183 9/27/184 51/95/185
+f 51/95/185 9/27/184 47/98/186
+f 2/102/187 10/69/188 45/18/189
+f 45/18/189 10/69/188 46/99/190
+f 50/96/191 51/95/192 61/53/193
+f 61/53/193 51/95/192 55/56/194
+f 48/31/195 49/97/196 60/79/197
+f 60/79/197 49/97/196 54/52/198
+f 47/98/199 48/31/200 59/81/201
+f 59/81/201 48/31/200 60/79/202
+f 46/99/203 50/96/204 58/54/205
+f 58/54/205 50/96/204 61/53/206
+f 44/20/207 45/18/208 57/26/209
+f 57/26/209 45/18/208 53/47/210
+f 49/97/211 43/61/212 54/52/213
+f 54/52/213 43/61/212 52/57/214
+f 42/3/215 44/20/216 56/76/217
+f 56/76/217 44/20/216 57/26/218
+f 43/61/219 42/3/220 52/57/221
+f 52/57/221 42/3/220 56/76/222
+f 51/95/223 47/98/224 55/56/225
+f 55/56/225 47/98/224 59/81/226
+f 45/18/227 46/99/228 53/47/229
+f 53/47/229 46/99/228 58/54/230
+f 62/15/231 69/12/232 72/83/233
+f 72/83/233 69/12/232 79/62/234
+f 64/108/235 74/66/236 68/1/237
+f 68/1/237 74/66/236 78/16/238
+f 63/109/239 65/82/240 73/64/241
+f 73/64/241 65/82/240 75/65/242
+f 63/109/243 73/64/244 71/19/245
+f 71/19/245 73/64/244 81/13/246
+f 62/15/247 72/83/248 70/21/249
+f 70/21/249 72/83/248 80/11/250
+f 66/67/251 67/48/252 76/49/253
+f 76/49/253 67/48/252 77/14/254
+f 65/82/255 68/1/256 75/65/257
+f 75/65/257 68/1/256 78/16/258
+f 69/12/259 71/19/260 79/62/261
+f 79/62/261 71/19/260 81/13/262
+f 67/48/263 70/21/264 77/14/265
+f 77/14/265 70/21/264 80/11/266
+f 64/108/267 66/67/268 74/66/269
+f 74/66/269 66/67/268 76/49/270
+f 82/68/271 92/17/272 89/22/273
+f 89/22/273 92/17/272 99/63/274
+f 84/2/275 88/5/276 94/59/277
+f 94/59/277 88/5/276 98/37/278
+f 83/6/279 93/7/280 85/8/281
+f 85/8/281 93/7/280 95/86/282
+f 83/6/283 91/32/284 93/7/285
+f 93/7/285 91/32/284 101/33/286
+f 82/68/287 90/38/288 92/17/289
+f 92/17/289 90/38/288 100/10/290
+f 86/84/291 96/34/292 87/87/293
+f 87/87/293 96/34/292 97/89/294
+f 85/8/295 95/86/296 88/5/297
+f 88/5/297 95/86/296 98/37/298
+f 89/22/299 99/63/300 91/32/301
+f 91/32/301 99/63/300 101/33/302
+f 87/87/303 97/89/304 90/38/305
+f 90/38/305 97/89/304 100/10/306
+f 84/2/307 94/59/308 86/84/309
+f 86/84/309 94/59/308 96/34/310
+f 75/65/311 78/16/312 95/72/313
+f 95/72/313 78/16/312 98/73/314
+f 67/48/315 66/67/316 87/87/317
+f 87/87/317 66/67/316 86/84/318
+f 74/66/319 76/49/320 94/39/321
+f 94/39/321 76/49/320 96/60/322
+f 70/21/323 67/48/324 90/38/325
+f 90/38/325 67/48/324 87/87/326
+f 71/19/327 69/12/328 91/32/329
+f 91/32/329 69/12/328 89/22/330
+f 73/64/331 75/65/332 93/88/333
+f 93/88/333 75/65/332 95/72/334
+f 64/108/335 68/1/336 84/2/337
+f 84/2/337 68/1/336 88/5/338
+f 69/12/339 62/15/340 89/22/341
+f 89/22/341 62/15/340 82/68/342
+f 76/49/343 77/14/344 96/60/345
+f 96/60/345 77/14/344 97/85/346
+f 62/15/347 70/21/348 82/68/349
+f 82/68/349 70/21/348 90/38/350
+f 63/109/351 71/19/352 83/6/353
+f 83/6/353 71/19/352 91/32/354
+f 77/14/355 80/11/356 97/85/357
+f 97/85/357 80/11/356 100/9/358
+f 79/62/359 81/13/360 99/36/361
+f 99/36/361 81/13/360 101/35/362
+f 68/1/363 65/82/364 88/5/365
+f 88/5/365 65/82/364 85/8/366
+f 78/16/367 74/66/368 98/73/369
+f 98/73/369 74/66/368 94/39/370
+f 72/83/371 79/62/372 92/71/373
+f 92/71/373 79/62/372 99/36/374
+f 66/67/375 64/108/376 86/84/377
+f 86/84/377 64/108/376 84/2/378
+f 80/11/379 72/83/380 100/9/381
+f 100/9/381 72/83/380 92/71/382
+f 81/13/383 73/64/384 101/35/385
+f 101/35/385 73/64/384 93/88/386
+f 65/82/387 63/109/388 85/8/389
+f 85/8/389 63/109/388 83/6/390
diff --git a/libs/vr/libdvrgraphics/assets/controller_proto2_joystick.obj b/libs/vr/libdvrgraphics/assets/controller_proto2_joystick.obj
new file mode 100644
index 0000000..c4daae3
--- /dev/null
+++ b/libs/vr/libdvrgraphics/assets/controller_proto2_joystick.obj
@@ -0,0 +1,556 @@
+# This file uses centimeters as units for non-parametric coordinates.
+
+v 0.013683 0.008414 0.043000
+v 0.013683 0.013400 0.058800
+v 0.013683 0.008414 0.058800
+v 0.013683 0.013400 0.043000
+v -0.013683 0.013400 0.043000
+v -0.015800 0.013400 0.050900
+v 0.007900 0.013400 0.064583
+v 0.015800 0.013400 0.050900
+v -0.013683 0.013400 0.058800
+v -0.007900 0.013400 0.064583
+v -0.000000 0.013400 0.066700
+v 0.007900 0.013400 0.037217
+v 0.000000 0.013400 0.035100
+v -0.007900 0.013400 0.037217
+v 0.013247 0.013400 0.058548
+v 0.013247 0.013400 0.043252
+v -0.013247 0.013400 0.043252
+v -0.015297 0.013400 0.050900
+v 0.007648 0.013400 0.064148
+v 0.015297 0.013400 0.050900
+v -0.013247 0.013400 0.058548
+v -0.007648 0.013400 0.064148
+v -0.000000 0.013400 0.066197
+v 0.007648 0.013400 0.037652
+v 0.000000 0.013400 0.035603
+v -0.007648 0.013400 0.037652
+v 0.000000 0.013400 0.050900
+v -0.013683 0.008414 0.043000
+v -0.015800 0.008414 0.050900
+v 0.007900 0.008414 0.064583
+v 0.015800 0.008414 0.050900
+v -0.013683 0.008414 0.058800
+v -0.007900 0.008414 0.064583
+v -0.000000 0.008414 0.066700
+v 0.007900 0.008414 0.037217
+v 0.000000 0.008414 0.035100
+v -0.007900 0.008414 0.037217
+v 0.015123 0.008414 0.042169
+v 0.015123 0.008414 0.059631
+v -0.015123 0.008414 0.042169
+v -0.017462 0.008414 0.050900
+v 0.008731 0.008414 0.066023
+v 0.017462 0.008414 0.050900
+v -0.015123 0.008414 0.059631
+v -0.008731 0.008414 0.066023
+v -0.000000 0.008414 0.068362
+v 0.008731 0.008414 0.035777
+v 0.000000 0.008414 0.033438
+v -0.008731 0.008414 0.035777
+v -0.007900 0.013082 0.037217
+v 0.000000 0.013082 0.035100
+v 0.007900 0.013082 0.037217
+v -0.000000 0.013082 0.066700
+v -0.007900 0.013082 0.064583
+v -0.013683 0.013082 0.058800
+v 0.013683 0.013082 0.043000
+v 0.015800 0.013082 0.050900
+v 0.013683 0.013082 0.058800
+v 0.007900 0.013082 0.064583
+v -0.015800 0.013082 0.050900
+v -0.013683 0.013082 0.043000
+v -0.007900 0.008785 0.064583
+v 0.015800 0.008785 0.050900
+v 0.007900 0.008785 0.064583
+v -0.013683 0.008785 0.043000
+v -0.007900 0.008785 0.037217
+v 0.000000 0.008785 0.035100
+v 0.007900 0.008785 0.037217
+v -0.000000 0.008785 0.066700
+v -0.013683 0.008785 0.058800
+v 0.013683 0.008785 0.043000
+v 0.013683 0.008785 0.058800
+v -0.015800 0.008785 0.050900
+vt 0.598611 0.792613
+vt 0.581181 0.842714
+vt 0.572095 0.840361
+vt 0.583337 0.843249
+vt 0.583900 0.891691
+vt 0.740650 0.932332
+vt 0.764677 0.890275
+vt 0.764114 0.841833
+vt 0.623873 0.817102
+vt 0.649036 0.776482
+vt 0.607278 0.849202
+vt 0.609406 0.849762
+vt 0.609695 0.884296
+vt 0.626984 0.914208
+vt 0.624971 0.818153
+vt 0.626666 0.819833
+vt 0.740622 0.848600
+vt 0.740736 0.884322
+vt 0.738608 0.883762
+vt 0.738319 0.849228
+vt 0.742081 0.848175
+vt 0.605933 0.885349
+vt 0.698978 0.957041
+vt 0.724141 0.916422
+vt 0.650543 0.957262
+vt 0.691947 0.798514
+vt 0.742213 0.884683
+vt 0.697472 0.776262
+vt 0.657036 0.931225
+vt 0.691585 0.931208
+vt 0.625435 0.915771
+vt 0.747828 0.791704
+vt 0.766832 0.890810
+vt 0.766241 0.841221
+vt 0.775920 0.838541
+vt 0.775919 0.893164
+vt 0.741246 0.798397
+vt 0.607392 0.884924
+vt 0.739706 0.799996
+vt 0.723631 0.816655
+vt 0.655425 0.798733
+vt 0.650009 0.959409
+vt 0.647491 0.969131
+vt 0.648421 0.774349
+vt 0.700524 0.764393
+vt 0.674007 0.866762
+vt 0.699593 0.959175
+vt 0.742243 0.933868
+vt 0.749403 0.940910
+vt 0.702098 0.968221
+vt 0.581773 0.892303
+vt 0.606768 0.935127
+vt 0.600187 0.941819
+vt 0.572095 0.894983
+vt 0.698005 0.774114
+vt 0.607364 0.801191
+vt 0.692589 0.934791
+vt 0.656067 0.935009
+vt 0.608309 0.933528
+vt 0.605801 0.848841
+vt 0.624384 0.916869
+vt 0.692164 0.933331
+vt 0.656428 0.933533
+vt 0.655850 0.800192
+vt 0.691586 0.799991
+vt 0.690978 0.802299
+vt 0.656429 0.802316
+vt 0.723043 0.915371
+vt 0.721348 0.913691
+vt 0.722579 0.817753
+vt 0.721030 0.819316
+vt 0.645916 0.765302
+vt 0.605771 0.799655
+vn 0.552274 0.770272 0.318864
+vn 0.318864 0.770272 0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.318864 0.770272 0.552274
+vn 0.000000 0.770266 0.637723
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.378558 0.653230 -0.655732
+vn -0.655715 0.653243 -0.378566
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.655715 0.653243 -0.378566
+vn 0.378558 0.653230 -0.655732
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.378558 0.653230 -0.655732
+vn 0.000000 0.653236 -0.757154
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.653236 -0.757154
+vn -0.378558 0.653230 -0.655732
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.655715 0.653243 -0.378566
+vn -0.757167 0.653221 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.378558 0.653230 0.655732
+vn 0.655715 0.653243 0.378566
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.655715 0.653243 0.378566
+vn -0.378558 0.653230 0.655732
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.757167 0.653221 0.000000
+vn 0.655715 0.653243 -0.378566
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.653236 0.757154
+vn 0.378558 0.653230 0.655732
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.378558 0.653230 0.655732
+vn 0.000000 0.653236 0.757154
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.655715 0.653243 0.378566
+vn 0.757167 0.653221 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.757167 0.653221 0.000000
+vn -0.655715 0.653243 0.378566
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.552274 0.770272 -0.318864
+vn 0.637723 0.770266 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.318864 0.770272 -0.552274
+vn 0.000000 0.770266 -0.637723
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.770266 0.637723
+vn -0.318864 0.770272 0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.318864 0.770272 -0.552274
+vn 0.552274 0.770272 -0.318864
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.318864 0.770272 0.552274
+vn -0.552274 0.770272 0.318864
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.552274 0.770272 0.318864
+vn -0.637723 0.770266 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.552274 0.770272 -0.318864
+vn -0.318864 0.770272 -0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.637723 0.770266 0.000000
+vn 0.552274 0.770272 0.318864
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.770266 -0.637723
+vn 0.318864 0.770272 -0.552274
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.637723 0.770266 0.000000
+vn -0.552274 0.770272 -0.318864
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.866032 0.000000 -0.499989
+vn -0.499989 0.000000 -0.866032
+vn -0.552274 0.770272 -0.318864
+vn -0.318864 0.770272 -0.552274
+vn 1.000000 0.000000 0.000000
+vn 0.866032 -0.000000 0.499989
+vn 0.637723 0.770266 0.000000
+vn 0.552274 0.770272 0.318864
+vn 0.499989 -0.000000 0.866032
+vn 0.000000 -0.000000 1.000000
+vn 0.318864 0.770272 0.552274
+vn 0.000000 0.770266 0.637723
+vn -0.499989 0.000000 -0.866032
+vn 0.000000 0.000000 -1.000000
+vn -0.318864 0.770272 -0.552274
+vn 0.000000 0.770266 -0.637723
+vn 0.000000 0.000000 -1.000000
+vn 0.499989 0.000000 -0.866032
+vn 0.000000 0.770266 -0.637723
+vn 0.318864 0.770272 -0.552274
+vn 0.499989 0.000000 -0.866032
+vn 0.866032 0.000000 -0.499989
+vn 0.318864 0.770272 -0.552274
+vn 0.552274 0.770272 -0.318864
+vn -0.499989 -0.000000 0.866032
+vn -0.866032 -0.000000 0.499989
+vn -0.318864 0.770272 0.552274
+vn -0.552274 0.770272 0.318864
+vn 0.000000 -0.000000 1.000000
+vn -0.499989 -0.000000 0.866032
+vn 0.000000 0.770266 0.637723
+vn -0.318864 0.770272 0.552274
+vn -0.866032 -0.000000 0.499989
+vn -1.000000 0.000000 0.000000
+vn -0.552274 0.770272 0.318864
+vn -0.637723 0.770266 0.000000
+vn 0.866032 0.000000 -0.499989
+vn 1.000000 0.000000 0.000000
+vn 0.552274 0.770272 -0.318864
+vn 0.637723 0.770266 0.000000
+vn 0.866032 -0.000000 0.499989
+vn 0.499989 -0.000000 0.866032
+vn 0.552274 0.770272 0.318864
+vn 0.318864 0.770272 0.552274
+vn -1.000000 0.000000 0.000000
+vn -0.866032 0.000000 -0.499989
+vn -0.637723 0.770266 0.000000
+vn -0.552274 0.770272 -0.318864
+vn -0.757167 0.653221 0.000000
+vn -0.655715 0.653243 -0.378566
+vn -1.000000 0.000000 0.000000
+vn -0.866032 0.000000 -0.499989
+vn 0.655715 0.653243 0.378566
+vn 0.378558 0.653230 0.655732
+vn 0.866032 -0.000000 0.499989
+vn 0.499989 -0.000000 0.866032
+vn 0.655715 0.653243 -0.378566
+vn 0.757167 0.653221 0.000000
+vn 0.866032 0.000000 -0.499989
+vn 1.000000 0.000000 0.000000
+vn -0.655715 0.653243 0.378566
+vn -0.757167 0.653221 0.000000
+vn -0.866032 -0.000000 0.499989
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.653236 0.757154
+vn -0.378558 0.653230 0.655732
+vn 0.000000 -0.000000 1.000000
+vn -0.499989 -0.000000 0.866032
+vn -0.378558 0.653230 0.655732
+vn -0.655715 0.653243 0.378566
+vn -0.499989 -0.000000 0.866032
+vn -0.866032 -0.000000 0.499989
+vn 0.378558 0.653230 -0.655732
+vn 0.655715 0.653243 -0.378566
+vn 0.499989 0.000000 -0.866032
+vn 0.866032 0.000000 -0.499989
+vn 0.000000 0.653236 -0.757154
+vn 0.378558 0.653230 -0.655732
+vn 0.000000 0.000000 -1.000000
+vn 0.499989 0.000000 -0.866032
+vn -0.378558 0.653230 -0.655732
+vn 0.000000 0.653236 -0.757154
+vn -0.499989 0.000000 -0.866032
+vn 0.000000 0.000000 -1.000000
+vn 0.378558 0.653230 0.655732
+vn 0.000000 0.653236 0.757154
+vn 0.499989 -0.000000 0.866032
+vn 0.000000 -0.000000 1.000000
+vn 0.757167 0.653221 0.000000
+vn 0.655715 0.653243 0.378566
+vn 1.000000 0.000000 0.000000
+vn 0.866032 -0.000000 0.499989
+vn -0.655715 0.653243 -0.378566
+vn -0.378558 0.653230 -0.655732
+vn -0.866032 0.000000 -0.499989
+vn -0.499989 0.000000 -0.866032
+vn -1.000000 0.000000 0.000000
+vn -0.866032 0.000000 -0.499989
+vn -1.000000 0.000000 0.000000
+vn -0.866032 0.000000 -0.499989
+vn 0.866032 -0.000000 0.499989
+vn 0.499989 -0.000000 0.866032
+vn 0.866032 -0.000000 0.499989
+vn 0.499989 -0.000000 0.866032
+vn 0.866032 0.000000 -0.499989
+vn 1.000000 0.000000 0.000000
+vn 0.866032 0.000000 -0.499989
+vn 1.000000 0.000000 0.000000
+vn -0.866032 -0.000000 0.499989
+vn -1.000000 0.000000 0.000000
+vn -0.866032 -0.000000 0.499989
+vn -1.000000 0.000000 0.000000
+vn 0.000000 -0.000000 1.000000
+vn -0.499989 -0.000000 0.866032
+vn 0.000000 -0.000000 1.000000
+vn -0.499989 -0.000000 0.866032
+vn -0.499989 -0.000000 0.866032
+vn -0.866032 -0.000000 0.499989
+vn -0.499989 -0.000000 0.866032
+vn -0.866032 -0.000000 0.499989
+vn 0.499989 0.000000 -0.866032
+vn 0.866032 0.000000 -0.499989
+vn 0.499989 0.000000 -0.866032
+vn 0.866032 0.000000 -0.499989
+vn 0.000000 0.000000 -1.000000
+vn 0.499989 0.000000 -0.866032
+vn 0.000000 0.000000 -1.000000
+vn 0.499989 0.000000 -0.866032
+vn -0.499989 0.000000 -0.866032
+vn 0.000000 0.000000 -1.000000
+vn -0.499989 0.000000 -0.866032
+vn 0.000000 0.000000 -1.000000
+vn 0.499989 -0.000000 0.866032
+vn 0.000000 -0.000000 1.000000
+vn 0.499989 -0.000000 0.866032
+vn 0.000000 -0.000000 1.000000
+vn 1.000000 0.000000 0.000000
+vn 0.866032 -0.000000 0.499989
+vn 1.000000 0.000000 0.000000
+vn 0.866032 -0.000000 0.499989
+vn -0.866032 0.000000 -0.499989
+vn -0.499989 0.000000 -0.866032
+vn -0.866032 0.000000 -0.499989
+vn -0.499989 0.000000 -0.866032
+f 3/33/1 30/34/2 39/36/3
+f 39/36/3 30/34/2 42/35/4
+f 30/34/5 34/37/6 42/35/7
+f 42/35/7 34/37/6 46/32/8
+f 14/38/9 5/11/10 26/13/11
+f 26/13/11 5/11/10 17/12/12
+f 4/62/13 12/63/14 16/30/15
+f 16/30/15 12/63/14 24/29/16
+f 12/63/17 13/31/18 24/29/19
+f 24/29/19 13/31/18 25/14/20
+f 13/31/21 14/38/22 25/14/23
+f 25/14/23 14/38/22 26/13/24
+f 5/11/25 6/15/26 17/12/27
+f 17/12/27 6/15/26 18/16/28
+f 7/17/29 2/18/30 19/20/31
+f 19/20/31 2/18/30 15/19/32
+f 9/64/33 10/65/34 21/67/35
+f 21/67/35 10/65/34 22/66/36
+f 8/68/37 4/62/38 20/69/39
+f 20/69/39 4/62/38 16/30/40
+f 11/70/41 7/17/42 23/71/43
+f 23/71/43 7/17/42 19/20/44
+f 10/65/45 11/70/46 22/66/47
+f 22/66/47 11/70/46 23/71/48
+f 2/18/49 8/68/50 15/19/51
+f 15/19/51 8/68/50 20/69/52
+f 6/15/53 9/64/54 18/16/55
+f 18/16/55 9/64/54 21/67/56
+f 20/69/57 16/30/58 27/46/59
+f 19/20/60 15/19/61 27/46/62
+f 16/30/63 24/29/64 27/46/65
+f 22/66/66 23/71/67 27/46/68
+f 21/67/69 22/66/70 27/46/71
+f 18/16/72 21/67/73 27/46/74
+f 23/71/75 19/20/76 27/46/77
+f 26/13/78 17/12/79 27/46/80
+f 24/29/81 25/14/82 27/46/83
+f 15/19/84 20/69/85 27/46/86
+f 17/12/87 18/16/88 27/46/89
+f 25/14/90 26/13/91 27/46/92
+f 1/47/93 31/48/94 38/50/95
+f 38/50/95 31/48/94 43/49/96
+f 37/51/97 36/52/98 49/54/99
+f 49/54/99 36/52/98 48/53/100
+f 34/37/101 33/55/102 46/32/103
+f 46/32/103 33/55/102 45/45/104
+f 35/42/105 1/47/106 47/43/107
+f 47/43/107 1/47/106 38/50/108
+f 33/55/109 32/44/110 45/45/111
+f 45/45/111 32/44/110 44/72/112
+f 32/44/113 29/73/114 44/72/115
+f 44/72/115 29/73/114 41/1/116
+f 28/2/117 37/51/118 40/3/119
+f 40/3/119 37/51/118 49/54/120
+f 31/48/121 3/33/122 43/49/123
+f 43/49/123 3/33/122 39/36/124
+f 36/52/125 35/42/126 48/53/127
+f 48/53/127 35/42/126 47/43/128
+f 29/73/129 28/2/130 41/1/131
+f 41/1/131 28/2/130 40/3/132
+f 65/4/133 66/5/134 28/2/135
+f 28/2/135 66/5/134 37/51/136
+f 63/6/137 72/7/138 31/48/139
+f 31/48/139 72/7/138 3/33/140
+f 64/8/141 69/39/142 30/34/143
+f 30/34/143 69/39/142 34/37/144
+f 66/5/145 67/59/146 37/51/147
+f 37/51/147 67/59/146 36/52/148
+f 67/59/149 68/25/150 36/52/151
+f 36/52/151 68/25/150 35/42/152
+f 68/25/153 71/23/154 35/42/155
+f 35/42/155 71/23/154 1/47/156
+f 62/28/157 70/10/158 33/55/159
+f 33/55/159 70/10/158 32/44/160
+f 69/39/161 62/28/162 34/37/163
+f 34/37/163 62/28/162 33/55/164
+f 70/10/165 73/56/166 32/44/167
+f 32/44/167 73/56/166 29/73/168
+f 71/23/169 63/6/170 1/47/171
+f 1/47/171 63/6/170 31/48/172
+f 72/7/173 64/8/174 3/33/175
+f 3/33/175 64/8/174 30/34/176
+f 73/56/177 65/4/178 29/73/179
+f 29/73/179 65/4/178 28/2/180
+f 6/15/181 5/11/182 60/9/183
+f 60/9/183 5/11/182 61/60/184
+f 2/18/185 7/17/186 58/27/187
+f 58/27/187 7/17/186 59/21/188
+f 4/62/189 8/68/190 56/57/191
+f 56/57/191 8/68/190 57/24/192
+f 9/64/193 6/15/194 55/41/195
+f 55/41/195 6/15/194 60/9/196
+f 11/70/197 10/65/198 53/40/199
+f 53/40/199 10/65/198 54/26/200
+f 10/65/201 9/64/202 54/26/203
+f 54/26/203 9/64/202 55/41/204
+f 12/63/205 4/62/206 52/58/207
+f 52/58/207 4/62/206 56/57/208
+f 13/31/209 12/63/210 51/61/211
+f 51/61/211 12/63/210 52/58/212
+f 14/38/213 13/31/214 50/22/215
+f 50/22/215 13/31/214 51/61/216
+f 7/17/217 11/70/218 59/21/219
+f 59/21/219 11/70/218 53/40/220
+f 8/68/221 2/18/222 57/24/223
+f 57/24/223 2/18/222 58/27/224
+f 5/11/225 14/38/226 61/60/227
+f 61/60/227 14/38/226 50/22/228
+f 60/9/229 61/60/230 73/56/231
+f 73/56/231 61/60/230 65/4/232
+f 58/27/233 59/21/234 72/7/235
+f 72/7/235 59/21/234 64/8/236
+f 56/57/237 57/24/238 71/23/239
+f 71/23/239 57/24/238 63/6/240
+f 55/41/241 60/9/242 70/10/243
+f 70/10/243 60/9/242 73/56/244
+f 53/40/245 54/26/246 69/39/247
+f 69/39/247 54/26/246 62/28/248
+f 54/26/249 55/41/250 62/28/251
+f 62/28/251 55/41/250 70/10/252
+f 52/58/253 56/57/254 68/25/255
+f 68/25/255 56/57/254 71/23/256
+f 51/61/257 52/58/258 67/59/259
+f 67/59/259 52/58/258 68/25/260
+f 50/22/261 51/61/262 66/5/263
+f 66/5/263 51/61/262 67/59/264
+f 59/21/265 53/40/266 64/8/267
+f 64/8/267 53/40/266 69/39/268
+f 57/24/269 58/27/270 63/6/271
+f 63/6/271 58/27/270 72/7/272
+f 61/60/273 50/22/274 65/4/275
+f 65/4/275 50/22/274 66/5/276
diff --git a/libs/vr/libdvrgraphics/assets/controller_proto2_trigger.obj b/libs/vr/libdvrgraphics/assets/controller_proto2_trigger.obj
new file mode 100644
index 0000000..7b87239
--- /dev/null
+++ b/libs/vr/libdvrgraphics/assets/controller_proto2_trigger.obj
@@ -0,0 +1,1240 @@
+# This file uses centimeters as units for non-parametric coordinates.
+
+v 0.012469 0.002342 0.063363
+v 0.009351 -0.007408 0.065472
+v 0.010658 -0.006647 0.065307
+v 0.006441 -0.008510 0.065710
+v 0.003284 -0.009190 0.065857
+v 0.012469 -0.001545 0.064204
+v 0.012469 -0.003489 0.064624
+v 0.012469 0.002342 0.037855
+v 0.012469 0.002342 0.035622
+v 0.012469 0.002342 0.040088
+v 0.012469 0.002342 0.042320
+v 0.010019 -0.002861 0.035742
+v 0.010019 -0.009748 0.045459
+v 0.008153 -0.014181 0.046940
+v 0.006671 -0.015226 0.047180
+v 0.010019 -0.012511 0.046558
+v 0.010019 -0.004041 0.040214
+v 0.010019 -0.007400 0.044006
+v 0.010019 -0.005473 0.042235
+v 0.010019 -0.003159 0.038022
+v 0.004895 -0.016332 0.047274
+v 0.002649 -0.002861 0.035742
+v 0.002649 -0.009748 0.045459
+v 0.002649 -0.014181 0.046940
+v 0.002649 -0.015226 0.047180
+v 0.002649 -0.012511 0.046558
+v 0.002649 -0.004041 0.040214
+v 0.002649 -0.007400 0.044006
+v 0.002649 -0.005473 0.042235
+v 0.002649 -0.003159 0.038022
+v 0.002649 -0.016789 0.047312
+v 0.012469 -0.001216 0.035704
+v 0.012469 -0.001420 0.037969
+v 0.012469 -0.002023 0.040174
+v 0.012469 -0.003002 0.042262
+v 0.010658 -0.013756 0.048907
+v 0.003284 -0.017321 0.049383
+v 0.006441 -0.016360 0.049298
+v 0.009351 -0.014801 0.049145
+v 0.012469 -0.009331 0.047481
+v 0.012469 -0.006949 0.046118
+v 0.012469 0.002342 0.046118
+v 0.012469 -0.001298 0.035706
+v 0.011089 -0.002861 0.035742
+v 0.012469 -0.001595 0.037974
+v 0.011089 -0.003159 0.038022
+v 0.012469 -0.002483 0.040183
+v 0.011089 -0.004041 0.040214
+v 0.012469 -0.003949 0.042252
+v 0.011089 -0.005473 0.042235
+v 0.012469 -0.007147 0.045190
+v 0.011089 -0.007400 0.044006
+v 0.012469 -0.009503 0.046649
+v 0.011089 -0.009748 0.045459
+v 0.011089 -0.012511 0.046558
+v 0.012469 -0.010955 0.047226
+v 0.011466 -0.013144 0.048119
+v 0.011466 -0.006105 0.065190
+v 0.012469 -0.004454 0.064833
+v 0.007971 -0.015226 0.047180
+v 0.009351 -0.014935 0.048529
+v 0.010658 -0.013889 0.048289
+v 0.009277 -0.014181 0.046940
+v 0.003284 -0.017462 0.048733
+v 0.002990 -0.017308 0.047351
+v 0.006441 -0.016497 0.048661
+v 0.005551 -0.016526 0.047290
+v 0.012469 -0.010781 0.048032
+v 0.011466 -0.013010 0.048737
+v 0.000000 -0.003159 0.038022
+v 0.000000 -0.009748 0.045459
+v 0.000000 -0.007400 0.044006
+v 0.000000 -0.002861 0.035742
+v 0.000000 -0.009419 0.065907
+v 0.000000 -0.005473 0.042235
+v -0.012469 0.002342 0.063363
+v 0.000000 -0.014181 0.046940
+v 0.000000 -0.015226 0.047180
+v 0.000000 -0.012511 0.046558
+v 0.000000 -0.004041 0.040214
+v 0.000000 -0.016789 0.047312
+v -0.009351 -0.007408 0.065472
+v -0.010658 -0.006647 0.065307
+v -0.006441 -0.008510 0.065710
+v -0.003284 -0.009190 0.065857
+v -0.012469 -0.001545 0.064204
+v -0.012469 -0.003489 0.064624
+v -0.012469 0.002342 0.037855
+v -0.012469 0.002342 0.035622
+v -0.012469 0.002342 0.040088
+v -0.012469 0.002342 0.042320
+v -0.010019 -0.002861 0.035742
+v -0.010019 -0.009748 0.045459
+v -0.008153 -0.014181 0.046940
+v -0.006671 -0.015226 0.047180
+v -0.010019 -0.012511 0.046558
+v -0.010019 -0.004041 0.040214
+v -0.010019 -0.007400 0.044006
+v -0.010019 -0.005473 0.042235
+v -0.010019 -0.003159 0.038022
+v -0.004895 -0.016332 0.047274
+v -0.002649 -0.002861 0.035742
+v -0.002649 -0.009748 0.045459
+v -0.002649 -0.014181 0.046940
+v -0.002649 -0.015226 0.047180
+v -0.002649 -0.012511 0.046558
+v -0.002649 -0.004041 0.040214
+v -0.002649 -0.007400 0.044006
+v -0.002649 -0.005473 0.042235
+v -0.002649 -0.003159 0.038022
+v -0.002649 -0.016789 0.047312
+v -0.012469 -0.001216 0.035704
+v -0.012469 -0.001420 0.037969
+v -0.012469 -0.002023 0.040174
+v -0.012469 -0.003002 0.042262
+v -0.010658 -0.013756 0.048907
+v 0.000000 -0.017646 0.049412
+v -0.003284 -0.017321 0.049383
+v -0.006441 -0.016360 0.049298
+v -0.009351 -0.014801 0.049145
+v -0.012469 -0.009331 0.047481
+v -0.012469 -0.006949 0.046118
+v -0.012469 0.002342 0.046118
+v -0.012469 -0.001298 0.035706
+v -0.011089 -0.002861 0.035742
+v -0.012469 -0.001595 0.037974
+v -0.011089 -0.003159 0.038022
+v -0.012469 -0.002483 0.040183
+v -0.011089 -0.004041 0.040214
+v -0.012469 -0.003949 0.042252
+v -0.011089 -0.005473 0.042235
+v -0.012469 -0.007147 0.045190
+v -0.011089 -0.007400 0.044006
+v -0.012469 -0.009503 0.046649
+v -0.011089 -0.009748 0.045459
+v -0.011089 -0.012511 0.046558
+v -0.012469 -0.010955 0.047226
+v -0.011466 -0.013144 0.048119
+v -0.011466 -0.006105 0.065190
+v -0.012469 -0.004454 0.064833
+v -0.007971 -0.015226 0.047180
+v -0.009351 -0.014935 0.048529
+v -0.010658 -0.013889 0.048289
+v -0.009277 -0.014181 0.046940
+v -0.003284 -0.017462 0.048733
+v -0.002990 -0.017308 0.047351
+v 0.000000 -0.017604 0.047373
+v 0.000000 -0.017787 0.048757
+v -0.006441 -0.016497 0.048661
+v -0.005551 -0.016526 0.047290
+v -0.012469 -0.010781 0.048032
+v -0.011466 -0.013010 0.048737
+v 0.000000 -0.016909 0.052819
+v 0.003284 -0.016586 0.052783
+v 0.006441 -0.015629 0.052675
+v 0.012469 -0.008622 0.050921
+v 0.012469 -0.006181 0.049710
+v 0.012469 0.002342 0.049484
+v 0.010658 -0.013032 0.052252
+v 0.009351 -0.014078 0.052488
+v 0.011466 -0.012286 0.052084
+v 0.012469 -0.010048 0.051420
+v -0.003284 -0.016586 0.052783
+v -0.006441 -0.015629 0.052675
+v -0.012469 -0.008622 0.050921
+v -0.012469 -0.006181 0.049710
+v -0.012469 0.002342 0.049484
+v -0.010658 -0.013032 0.052252
+v -0.009351 -0.014078 0.052488
+v -0.011466 -0.012286 0.052084
+v -0.012469 -0.010048 0.051420
+vt 0.389626 0.855156
+vt 0.390521 0.876103
+vt 0.497178 0.870536
+vt 0.484333 0.855130
+vt 0.412289 0.967976
+vt 0.478403 0.774806
+vt 0.463433 0.778867
+vt 0.412380 0.809901
+vt 0.409798 0.834762
+vt 0.396714 0.803798
+vt 0.445493 0.843045
+vt 0.390505 0.834206
+vt 0.408648 0.830875
+vt 0.389815 0.844760
+vt 0.330514 0.823054
+vt 0.432027 0.965825
+vt 0.391682 0.823360
+vt 0.437068 0.901524
+vt 0.402269 0.864596
+vt 0.493086 0.959935
+vt 0.480141 0.959822
+vt 0.407446 0.893095
+vt 0.404279 0.890129
+vt 0.494272 0.915752
+vt 0.405408 0.893816
+vt 0.445499 0.867230
+vt 0.458613 0.855135
+vt 0.434528 0.906466
+vt 0.478084 0.921347
+vt 0.433534 0.865962
+vt 0.458054 0.868399
+vt 0.392761 0.817328
+vt 0.404255 0.820169
+vt 0.336182 0.937153
+vt 0.405821 0.786943
+vt 0.413215 0.855147
+vt 0.416743 0.904914
+vt 0.413479 0.906889
+vt 0.389822 0.865551
+vt 0.328339 0.855183
+vt 0.406268 0.863663
+vt 0.405868 0.923348
+vt 0.393637 0.897111
+vt 0.478510 0.773673
+vt 0.409635 0.808495
+vt 0.403113 0.836120
+vt 0.402263 0.845706
+vt 0.407910 0.840728
+vt 0.497174 0.839720
+vt 0.478060 0.788901
+vt 0.329051 0.838946
+vt 0.334987 0.787175
+vt 0.401168 0.835782
+vt 0.448778 0.784859
+vt 0.447886 0.797316
+vt 0.462582 0.792299
+vt 0.412210 0.742297
+vt 0.422076 0.845553
+vt 0.416711 0.805374
+vt 0.464373 0.798874
+vt 0.431956 0.744434
+vt 0.450212 0.930782
+vt 0.427474 0.913081
+vt 0.423235 0.915539
+vt 0.399149 0.912516
+vt 0.335447 0.927703
+vt 0.335050 0.923173
+vt 0.412698 0.863505
+vt 0.416158 0.863975
+vt 0.411799 0.881077
+vt 0.409812 0.875534
+vt 0.493410 0.773030
+vt 0.413445 0.803401
+vt 0.402794 0.825572
+vt 0.437042 0.808751
+vt 0.400328 0.864755
+vt 0.408394 0.855149
+vt 0.333617 0.909517
+vt 0.332644 0.902621
+vt 0.403126 0.874181
+vt 0.422082 0.864736
+vt 0.416711 0.855146
+vt 0.402042 0.855151
+vt 0.422934 0.899416
+vt 0.334248 0.913876
+vt 0.450172 0.779477
+vt 0.493052 0.750284
+vt 0.458048 0.841870
+vt 0.433528 0.844320
+vt 0.434498 0.803810
+vt 0.494257 0.794492
+vt 0.410463 0.825689
+vt 0.415040 0.889351
+vt 0.471407 0.855132
+vt 0.446098 0.855138
+vt 0.479212 0.914296
+vt 0.493852 0.922939
+vt 0.410483 0.884606
+vt 0.447916 0.912950
+vt 0.434120 0.855141
+vt 0.470937 0.869361
+vt 0.406318 0.889536
+vt 0.493399 0.772491
+vt 0.332599 0.807735
+vt 0.467185 0.749518
+vt 0.413259 0.817561
+vt 0.393606 0.813194
+vt 0.423197 0.794742
+vt 0.479191 0.795954
+vt 0.406262 0.846637
+vt 0.406068 0.855150
+vt 0.407893 0.847406
+vt 0.406295 0.820760
+vt 0.406924 0.838997
+vt 0.399108 0.797782
+vt 0.422669 0.855144
+vt 0.407898 0.862892
+vt 0.391706 0.886948
+vt 0.401182 0.874520
+vt 0.404771 0.884253
+vt 0.483983 0.870072
+vt 0.400087 0.855152
+vt 0.329066 0.871418
+vt 0.330545 0.887307
+vt 0.497404 0.855128
+vt 0.416152 0.846318
+vt 0.454204 0.747884
+vt 0.404751 0.826046
+vt 0.411782 0.829217
+vt 0.400321 0.845549
+vt 0.463943 0.775990
+vt 0.337941 0.751811
+vt 0.333566 0.800837
+vt 0.412692 0.846790
+vt 0.483978 0.840187
+vt 0.334192 0.796476
+vt 0.493834 0.787302
+vt 0.450179 0.803181
+vt 0.408665 0.879421
+vt 0.406935 0.871302
+vt 0.407920 0.869569
+vt 0.467237 0.960717
+vt 0.454263 0.962359
+vt 0.450205 0.907085
+vt 0.464397 0.911384
+vt 0.462610 0.917958
+vt 0.402814 0.884728
+vt 0.425386 0.895350
+vt 0.336106 0.773189
+vt 0.422907 0.810868
+vt 0.338033 0.958524
+vt 0.412410 0.900390
+vt 0.409666 0.901797
+vt 0.396751 0.906503
+vt 0.463979 0.934259
+vt 0.478433 0.935435
+vt 0.493434 0.937204
+vt 0.493422 0.937742
+vt 0.478541 0.936568
+vt 0.463468 0.931384
+vt 0.448815 0.925403
+vt 0.427439 0.797198
+vt 0.480098 0.750404
+vt 0.425362 0.814933
+vt 0.335380 0.782643
+vt 0.470932 0.840902
+vt 0.405382 0.816481
+vt 0.415019 0.820940
+vt 0.407421 0.817201
+vt 0.392788 0.892978
+vt 0.413283 0.892731
+vn 0.941739 -0.326680 -0.080053
+vn 0.945242 -0.323626 -0.042239
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.933306 -0.319627 -0.163644
+vn 0.941739 -0.326680 -0.080053
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.919246 -0.303577 -0.250656
+vn 0.933306 -0.319627 -0.163644
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.911448 -0.243205 -0.331832
+vn 0.919246 -0.303577 -0.250656
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.911448 -0.243205 -0.331832
+vn 1.000000 0.000000 0.000000
+vn 0.916678 -0.176432 -0.358571
+vn 1.000000 0.000000 0.000000
+vn 0.916678 -0.176432 -0.358571
+vn 1.000000 0.000000 0.000000
+vn 0.910980 -0.276630 -0.305928
+vn 0.979394 -0.197399 0.042666
+vn 0.973672 -0.216165 0.072360
+vn 1.000000 0.000000 0.000000
+vn 0.978343 -0.189186 0.083988
+vn 1.000000 0.000000 0.000000
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.223005 -0.974817
+vn 0.161752 -0.860157 0.483701
+vn 0.000000 -0.869145 0.494557
+vn 0.178204 -0.920500 0.347740
+vn 0.000000 -0.934880 0.354963
+vn 0.325059 -0.831219 0.451012
+vn 0.161752 -0.860157 0.483701
+vn 0.352896 -0.876974 0.326161
+vn 0.178204 -0.920500 0.347740
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.154215 -0.988037
+vn -0.000092 -0.079746 -0.996815
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.079380 -0.996844
+vn 0.596627 -0.720598 0.353234
+vn 0.491395 -0.774189 0.398951
+vn 0.629820 -0.732455 0.258526
+vn 0.520259 -0.802134 0.293106
+vn 0.352896 -0.876974 0.326161
+vn 0.520259 -0.802134 0.293106
+vn 0.325059 -0.831219 0.451012
+vn 0.491395 -0.774189 0.398951
+vn 0.382681 -0.903004 0.195293
+vn 0.195076 -0.958627 0.207314
+vn 0.395898 -0.918084 -0.019654
+vn 0.200818 -0.979187 0.029421
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.291820 -0.619012 -0.729154
+vn 0.221873 -0.646208 -0.730197
+vn 0.000000 -0.154215 -0.988037
+vn -0.000244 -0.081790 -0.996650
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.877817 -0.478995
+vn 0.341206 -0.695262 -0.632605
+vn 0.383839 -0.804668 -0.452965
+vn 0.395898 -0.918084 -0.019654
+vn 0.552792 -0.829295 -0.081791
+vn 0.382681 -0.903004 0.195293
+vn 0.555564 -0.812685 0.175758
+vn 0.772999 -0.620157 -0.133706
+vn 0.819595 -0.559998 0.121101
+vn 0.635399 -0.761197 -0.129796
+vn 0.661102 -0.733340 0.158607
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.991581 -0.129491
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.991581 -0.129491
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.991581 -0.129491
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.991581 -0.129491
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.449678 -0.893191
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.449678 -0.893191
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.154215 -0.988037
+vn -0.000244 -0.081790 -0.996650
+vn 0.000000 -0.154215 -0.988037
+vn -0.000092 -0.079746 -0.996815
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.223005 -0.974817
+vn 0.195076 -0.958627 0.207314
+vn 0.000000 -0.977405 0.211375
+vn 0.200818 -0.979187 0.029421
+vn 0.000000 -0.999153 0.041139
+vn 0.221873 -0.646208 -0.730197
+vn 0.120368 -0.692302 -0.711498
+vn -0.000244 -0.081790 -0.996650
+vn -0.000092 -0.079746 -0.996815
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.750417 -0.660964
+vn 0.333728 -0.575562 -0.746561
+vn 0.341206 -0.695262 -0.632605
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.991581 -0.129491
+vn 0.412992 -0.879180 -0.237657
+vn 0.422817 -0.898585 -0.117347
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.967377 -0.253342
+vn 0.383839 -0.804668 -0.452965
+vn 0.412992 -0.879180 -0.237657
+vn 0.457368 -0.496250 -0.737936
+vn 0.313953 -0.551668 -0.772720
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.449666 -0.893197
+vn 0.457368 -0.496250 -0.737936
+vn 0.350609 -0.424894 -0.834589
+vn 0.635399 -0.761197 -0.129796
+vn 0.661102 -0.733340 0.158607
+vn 0.552792 -0.829295 -0.081791
+vn 0.555564 -0.812685 0.175758
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.223005 -0.974817
+vn 0.291820 -0.619012 -0.729154
+vn 0.313953 -0.551668 -0.772720
+vn 0.120368 -0.692302 -0.711498
+vn 0.000000 -0.707900 -0.706313
+vn -0.000092 -0.079746 -0.996815
+vn 0.000000 -0.079380 -0.996844
+vn 0.788994 -0.557075 0.259142
+vn 0.596627 -0.720598 0.353234
+vn 0.791884 -0.577089 0.199718
+vn 0.629820 -0.732455 0.258526
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.604128 -0.796887
+vn 0.350609 -0.424894 -0.834589
+vn 0.333728 -0.575562 -0.746561
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.457368 -0.496250 -0.737936
+vn 0.910980 -0.276630 -0.305928
+vn 0.772999 -0.620157 -0.133706
+vn 0.313953 -0.551668 -0.772720
+vn 0.457368 -0.496250 -0.737936
+vn 0.635399 -0.761197 -0.129796
+vn 0.772999 -0.620157 -0.133706
+vn 0.945242 -0.323626 -0.042239
+vn 0.941739 -0.326680 -0.080053
+vn 0.422817 -0.898585 -0.117347
+vn 0.412992 -0.879180 -0.237657
+vn 0.941739 -0.326680 -0.080053
+vn 0.933306 -0.319627 -0.163644
+vn 0.412992 -0.879180 -0.237657
+vn 0.383839 -0.804668 -0.452965
+vn 0.933306 -0.319627 -0.163644
+vn 0.919246 -0.303577 -0.250656
+vn 0.383839 -0.804668 -0.452965
+vn 0.341206 -0.695262 -0.632605
+vn 0.919246 -0.303577 -0.250656
+vn 0.911448 -0.243205 -0.331832
+vn 0.341206 -0.695262 -0.632605
+vn 0.333728 -0.575562 -0.746561
+vn 0.916678 -0.176432 -0.358571
+vn 0.350609 -0.424894 -0.834589
+vn 0.911448 -0.243205 -0.331832
+vn 0.333728 -0.575562 -0.746561
+vn 0.916678 -0.176432 -0.358571
+vn 0.910980 -0.276630 -0.305928
+vn 0.350609 -0.424894 -0.834589
+vn 0.457368 -0.496250 -0.737936
+vn 0.819595 -0.559998 0.121101
+vn 0.772999 -0.620157 -0.133706
+vn 0.979394 -0.197399 0.042666
+vn 0.910980 -0.276630 -0.305928
+vn 0.552792 -0.829295 -0.081791
+vn 0.395898 -0.918084 -0.019654
+vn 0.291820 -0.619012 -0.729154
+vn 0.221873 -0.646208 -0.730197
+vn 0.395898 -0.918084 -0.019654
+vn 0.200818 -0.979187 0.029421
+vn 0.221873 -0.646208 -0.730197
+vn 0.120368 -0.692302 -0.711498
+vn 0.200818 -0.979187 0.029421
+vn 0.000000 -0.999153 0.041139
+vn 0.120368 -0.692302 -0.711498
+vn 0.000000 -0.707900 -0.706313
+vn 0.291820 -0.619012 -0.729154
+vn 0.313953 -0.551668 -0.772720
+vn 0.552792 -0.829295 -0.081791
+vn 0.635399 -0.761197 -0.129796
+vn 0.791884 -0.577089 0.199718
+vn 0.819595 -0.559998 0.121101
+vn 0.973672 -0.216165 0.072360
+vn 0.979394 -0.197399 0.042666
+vn -0.941739 -0.326680 -0.080053
+vn -1.000000 0.000000 0.000000
+vn -0.945242 -0.323626 -0.042239
+vn -1.000000 0.000000 0.000000
+vn -0.933306 -0.319627 -0.163644
+vn -1.000000 0.000000 0.000000
+vn -0.941739 -0.326680 -0.080053
+vn -1.000000 0.000000 0.000000
+vn -0.919246 -0.303577 -0.250656
+vn -1.000000 0.000000 0.000000
+vn -0.933306 -0.319627 -0.163644
+vn -1.000000 0.000000 0.000000
+vn -0.911448 -0.243205 -0.331832
+vn -1.000000 0.000000 0.000000
+vn -0.919246 -0.303577 -0.250656
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.916678 -0.176432 -0.358571
+vn -0.911448 -0.243205 -0.331832
+vn -0.979394 -0.197399 0.042666
+vn -1.000000 0.000000 0.000000
+vn -0.910980 -0.276630 -0.305928
+vn -0.916678 -0.176432 -0.358571
+vn -0.973672 -0.216165 0.072360
+vn -0.978343 -0.189186 0.083988
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.934880 0.354963
+vn 0.000000 -0.869145 0.494557
+vn -0.178204 -0.920500 0.347740
+vn -0.161752 -0.860157 0.483701
+vn -0.178204 -0.920500 0.347740
+vn -0.161752 -0.860157 0.483701
+vn -0.352896 -0.876974 0.326161
+vn -0.325059 -0.831219 0.451012
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.154215 -0.988037
+vn 0.000092 -0.079746 -0.996815
+vn 0.000000 -0.079380 -0.996844
+vn -0.520259 -0.802134 0.293106
+vn -0.491395 -0.774189 0.398951
+vn -0.629820 -0.732455 0.258526
+vn -0.596627 -0.720598 0.353234
+vn -0.352896 -0.876974 0.326161
+vn -0.325059 -0.831219 0.451012
+vn -0.520259 -0.802134 0.293106
+vn -0.491395 -0.774189 0.398951
+vn -0.200818 -0.979187 0.029421
+vn -0.195076 -0.958627 0.207314
+vn -0.395898 -0.918084 -0.019654
+vn -0.382681 -0.903004 0.195293
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.291820 -0.619012 -0.729154
+vn 0.000000 -0.154215 -0.988037
+vn -0.221873 -0.646208 -0.730197
+vn 0.000244 -0.081790 -0.996650
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 -0.750417 -0.660964
+vn -0.341206 -0.695262 -0.632605
+vn 0.000000 -0.877817 -0.478995
+vn -0.383839 -0.804668 -0.452965
+vn -0.395898 -0.918084 -0.019654
+vn -0.382681 -0.903004 0.195293
+vn -0.552792 -0.829295 -0.081791
+vn -0.555564 -0.812685 0.175758
+vn -0.772999 -0.620157 -0.133706
+vn -0.635399 -0.761197 -0.129796
+vn -0.819595 -0.559998 0.121101
+vn -0.661102 -0.733340 0.158607
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.991581 -0.129491
+vn 0.000000 -0.991581 -0.129491
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.991581 -0.129491
+vn 0.000000 -0.991581 -0.129491
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.967377 -0.253342
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.877817 -0.478995
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.750417 -0.660964
+vn 0.000000 -0.449678 -0.893191
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.604128 -0.796887
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.449678 -0.893191
+vn 0.000000 -0.449666 -0.893197
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.154215 -0.988037
+vn 0.000244 -0.081790 -0.996650
+vn 0.000092 -0.079746 -0.996815
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.297194 -0.954817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.154215 -0.988037
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.999153 0.041139
+vn 0.000000 -0.977405 0.211375
+vn -0.200818 -0.979187 0.029421
+vn -0.195076 -0.958627 0.207314
+vn -0.221873 -0.646208 -0.730197
+vn 0.000244 -0.081790 -0.996650
+vn -0.120368 -0.692302 -0.711498
+vn 0.000092 -0.079746 -0.996815
+vn 0.000000 -0.604128 -0.796887
+vn -0.333728 -0.575562 -0.746561
+vn 0.000000 -0.750417 -0.660964
+vn -0.341206 -0.695262 -0.632605
+vn 0.000000 -0.967377 -0.253342
+vn -0.412992 -0.879180 -0.237657
+vn 0.000000 -0.991581 -0.129491
+vn -0.422817 -0.898585 -0.117347
+vn 0.000000 -0.877817 -0.478995
+vn -0.383839 -0.804668 -0.452965
+vn 0.000000 -0.967377 -0.253342
+vn -0.412992 -0.879180 -0.237657
+vn -0.457368 -0.496250 -0.737936
+vn 0.000000 -0.297194 -0.954817
+vn -0.313953 -0.551668 -0.772720
+vn 0.000000 -0.223005 -0.974817
+vn 0.000000 -0.297194 -0.954817
+vn -0.457368 -0.496250 -0.737936
+vn 0.000000 -0.449666 -0.893197
+vn -0.350609 -0.424894 -0.834589
+vn -0.635399 -0.761197 -0.129796
+vn -0.552792 -0.829295 -0.081791
+vn -0.661102 -0.733340 0.158607
+vn -0.555564 -0.812685 0.175758
+vn 0.000000 -0.154215 -0.988037
+vn -0.291820 -0.619012 -0.729154
+vn 0.000000 -0.223005 -0.974817
+vn -0.313953 -0.551668 -0.772720
+vn -0.120368 -0.692302 -0.711498
+vn 0.000092 -0.079746 -0.996815
+vn 0.000000 -0.707900 -0.706313
+vn 0.000000 -0.079380 -0.996844
+vn -0.629820 -0.732455 0.258526
+vn -0.596627 -0.720598 0.353234
+vn -0.791884 -0.577089 0.199718
+vn -0.788994 -0.557075 0.259142
+vn 0.000000 -0.449666 -0.893197
+vn -0.350609 -0.424894 -0.834589
+vn 0.000000 -0.604128 -0.796887
+vn -0.333728 -0.575562 -0.746561
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.457368 -0.496250 -0.737936
+vn -0.772999 -0.620157 -0.133706
+vn -0.910980 -0.276630 -0.305928
+vn -0.313953 -0.551668 -0.772720
+vn -0.635399 -0.761197 -0.129796
+vn -0.457368 -0.496250 -0.737936
+vn -0.772999 -0.620157 -0.133706
+vn -0.945242 -0.323626 -0.042239
+vn -0.422817 -0.898585 -0.117347
+vn -0.941739 -0.326680 -0.080053
+vn -0.412992 -0.879180 -0.237657
+vn -0.941739 -0.326680 -0.080053
+vn -0.412992 -0.879180 -0.237657
+vn -0.933306 -0.319627 -0.163644
+vn -0.383839 -0.804668 -0.452965
+vn -0.933306 -0.319627 -0.163644
+vn -0.383839 -0.804668 -0.452965
+vn -0.919246 -0.303577 -0.250656
+vn -0.341206 -0.695262 -0.632605
+vn -0.919246 -0.303577 -0.250656
+vn -0.341206 -0.695262 -0.632605
+vn -0.911448 -0.243205 -0.331832
+vn -0.333728 -0.575562 -0.746561
+vn -0.333728 -0.575562 -0.746561
+vn -0.350609 -0.424894 -0.834589
+vn -0.911448 -0.243205 -0.331832
+vn -0.916678 -0.176432 -0.358571
+vn -0.916678 -0.176432 -0.358571
+vn -0.350609 -0.424894 -0.834589
+vn -0.910980 -0.276630 -0.305928
+vn -0.457368 -0.496250 -0.737936
+vn -0.819595 -0.559998 0.121101
+vn -0.979394 -0.197399 0.042666
+vn -0.772999 -0.620157 -0.133706
+vn -0.910980 -0.276630 -0.305928
+vn -0.552792 -0.829295 -0.081791
+vn -0.291820 -0.619012 -0.729154
+vn -0.395898 -0.918084 -0.019654
+vn -0.221873 -0.646208 -0.730197
+vn -0.395898 -0.918084 -0.019654
+vn -0.221873 -0.646208 -0.730197
+vn -0.200818 -0.979187 0.029421
+vn -0.120368 -0.692302 -0.711498
+vn -0.200818 -0.979187 0.029421
+vn -0.120368 -0.692302 -0.711498
+vn 0.000000 -0.999153 0.041139
+vn 0.000000 -0.707900 -0.706313
+vn -0.291820 -0.619012 -0.729154
+vn -0.552792 -0.829295 -0.081791
+vn -0.313953 -0.551668 -0.772720
+vn -0.635399 -0.761197 -0.129796
+vn -0.791884 -0.577089 0.199718
+vn -0.973672 -0.216165 0.072360
+vn -0.819595 -0.559998 0.121101
+vn -0.979394 -0.197399 0.042666
+vn -0.978343 -0.189186 0.083988
+vn -0.973672 -0.216165 0.072360
+vn -0.788994 -0.557075 0.259142
+vn -0.791884 -0.577089 0.199718
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.819595 -0.559998 0.121101
+vn -0.661102 -0.733340 0.158607
+vn -0.791884 -0.577089 0.199718
+vn -0.629820 -0.732455 0.258526
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.382681 -0.903004 0.195293
+vn -0.352896 -0.876974 0.326161
+vn -0.555564 -0.812685 0.175758
+vn -0.520259 -0.802134 0.293106
+vn -0.661102 -0.733340 0.158607
+vn -0.555564 -0.812685 0.175758
+vn -0.629820 -0.732455 0.258526
+vn -0.520259 -0.802134 0.293106
+vn -0.195076 -0.958627 0.207314
+vn -0.178204 -0.920500 0.347740
+vn -0.382681 -0.903004 0.195293
+vn -0.352896 -0.876974 0.326161
+vn 0.000000 -0.977405 0.211375
+vn 0.000000 -0.934880 0.354963
+vn -0.195076 -0.958627 0.207314
+vn -0.178204 -0.920500 0.347740
+vn -0.973672 -0.216165 0.072360
+vn -1.000000 0.000000 0.000000
+vn -0.979394 -0.197399 0.042666
+vn -1.000000 0.000000 0.000000
+vn 0.791884 -0.577089 0.199718
+vn 0.973672 -0.216165 0.072360
+vn 0.788994 -0.557075 0.259142
+vn 0.978343 -0.189186 0.083988
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.819595 -0.559998 0.121101
+vn 0.791884 -0.577089 0.199718
+vn 0.661102 -0.733340 0.158607
+vn 0.629820 -0.732455 0.258526
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.382681 -0.903004 0.195293
+vn 0.555564 -0.812685 0.175758
+vn 0.352896 -0.876974 0.326161
+vn 0.520259 -0.802134 0.293106
+vn 0.661102 -0.733340 0.158607
+vn 0.629820 -0.732455 0.258526
+vn 0.555564 -0.812685 0.175758
+vn 0.520259 -0.802134 0.293106
+vn 0.352896 -0.876974 0.326161
+vn 0.178204 -0.920500 0.347740
+vn 0.382681 -0.903004 0.195293
+vn 0.195076 -0.958627 0.207314
+vn 0.178204 -0.920500 0.347740
+vn 0.000000 -0.934880 0.354963
+vn 0.195076 -0.958627 0.207314
+vn 0.000000 -0.977405 0.211375
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.979394 -0.197399 0.042666
+vn 0.973672 -0.216165 0.072360
+f 45/156/1 43/157/2 33/159/3
+f 33/159/3 43/157/2 32/158/4
+f 47/160/5 45/156/6 34/155/7
+f 34/155/7 45/156/6 33/159/8
+f 49/161/9 47/160/10 35/62/11
+f 35/62/11 47/160/10 34/155/12
+f 51/63/13 49/161/14 41/64/15
+f 41/64/15 49/161/14 35/62/16
+f 51/63/17 41/64/18 53/37/19
+f 53/37/19 41/64/18 40/38/20
+f 53/37/21 40/38/22 56/152/23
+f 56/152/23 40/38/22 68/153/24
+f 162/154/25 156/65/26 59/67/27
+f 59/67/27 156/65/26 7/66/28
+f 25/68/29 24/69/30 15/71/31
+f 15/71/31 24/69/30 14/70/32
+f 5/123/33 74/40/34 154/39/35
+f 154/39/35 74/40/34 153/1/36
+f 4/124/37 5/123/38 155/2/39
+f 155/2/39 5/123/38 154/39/40
+f 26/81/41 24/69/42 79/116/43
+f 79/116/43 24/69/42 77/82/44
+f 25/68/45 31/117/46 78/36/47
+f 78/36/47 31/117/46 81/77/48
+f 3/78/49 2/79/50 159/170/51
+f 159/170/51 2/79/50 160/118/52
+f 155/2/53 160/118/54 4/124/55
+f 4/124/55 160/118/54 2/79/56
+f 38/119/57 37/76/58 66/80/59
+f 66/80/59 37/76/58 64/19/60
+f 33/159/61 32/158/62 8/21/63
+f 8/21/63 32/158/62 9/20/64
+f 157/42/65 6/34/66 156/65/67
+f 156/65/67 6/34/66 7/66/68
+f 60/139/69 67/140/70 15/71/71
+f 15/71/71 67/140/70 21/141/72
+f 35/62/73 34/155/74 11/143/75
+f 11/143/75 34/155/74 10/142/76
+f 34/155/77 33/159/78 10/142/79
+f 10/142/79 33/159/78 8/21/80
+f 19/144/81 17/145/82 50/99/83
+f 50/99/83 17/145/82 48/146/84
+f 66/80/85 61/120/86 38/119/87
+f 38/119/87 61/120/86 39/147/88
+f 57/22/89 69/25/90 62/102/91
+f 62/102/91 69/25/90 36/23/92
+f 26/81/93 23/30/94 16/93/95
+f 16/93/95 23/30/94 13/148/96
+f 23/30/97 28/26/98 13/148/99
+f 13/148/99 28/26/98 18/18/100
+f 28/26/101 29/31/102 18/18/103
+f 18/18/103 29/31/102 19/144/104
+f 29/31/105 27/101/106 19/144/107
+f 19/144/107 27/101/106 17/145/108
+f 27/101/109 30/121/110 17/145/111
+f 17/145/111 30/121/110 20/96/112
+f 30/121/113 22/3/114 20/96/115
+f 20/96/115 22/3/114 12/24/116
+f 70/4/117 73/125/118 30/121/119
+f 30/121/119 73/125/118 22/3/120
+f 80/94/121 70/4/122 27/101/123
+f 27/101/123 70/4/122 30/121/124
+f 75/27/125 80/94/126 29/31/127
+f 29/31/127 80/94/126 27/101/128
+f 72/95/129 75/27/130 28/26/131
+f 28/26/131 75/27/130 29/31/132
+f 71/100/133 72/95/134 23/30/135
+f 23/30/135 72/95/134 28/26/136
+f 79/116/137 71/100/138 26/81/139
+f 26/81/139 71/100/138 23/30/140
+f 15/71/141 21/141/142 25/68/143
+f 25/68/143 21/141/142 31/117/144
+f 16/93/145 14/70/146 26/81/147
+f 26/81/147 14/70/146 24/69/148
+f 78/36/149 77/82/150 25/68/151
+f 25/68/151 77/82/150 24/69/152
+f 37/76/153 117/122/154 64/19/155
+f 64/19/155 117/122/154 148/83/156
+f 67/140/157 65/41/158 21/141/159
+f 21/141/159 65/41/158 31/117/160
+f 18/18/161 19/144/162 52/28/163
+f 52/28/163 19/144/162 50/99/164
+f 20/96/165 12/24/166 46/29/167
+f 46/29/167 12/24/166 44/97/168
+f 17/145/169 20/96/170 48/146/171
+f 48/146/171 20/96/170 46/29/172
+f 55/171/173 63/98/174 16/93/175
+f 16/93/175 63/98/174 14/70/176
+f 16/93/177 13/148/178 55/171/179
+f 55/171/179 13/148/178 54/84/180
+f 62/102/181 36/23/182 61/120/183
+f 61/120/183 36/23/182 39/147/184
+f 15/71/185 14/70/186 60/139/187
+f 60/139/187 14/70/186 63/98/188
+f 65/41/189 147/111/190 31/117/191
+f 31/117/191 147/111/190 81/77/192
+f 58/85/193 3/78/194 161/43/195
+f 161/43/195 3/78/194 159/170/196
+f 13/148/197 18/18/198 54/84/199
+f 54/84/199 18/18/198 52/28/200
+f 11/143/201 42/16/202 35/62/203
+f 35/62/203 42/16/202 41/64/204
+f 41/64/205 42/16/206 157/42/207
+f 157/42/207 42/16/206 158/5/208
+f 55/171/209 56/152/210 57/22/211
+f 63/98/212 55/171/213 62/102/214
+f 62/102/214 55/171/213 57/22/215
+f 43/157/216 45/156/217 44/97/218
+f 44/97/218 45/156/217 46/29/219
+f 45/156/220 47/160/221 46/29/222
+f 46/29/222 47/160/221 48/146/223
+f 47/160/224 49/161/225 48/146/226
+f 48/146/226 49/161/225 50/99/227
+f 49/161/228 51/63/229 50/99/230
+f 50/99/230 51/63/229 52/28/231
+f 53/37/232 54/84/233 51/63/234
+f 51/63/234 54/84/233 52/28/235
+f 53/37/236 56/152/237 54/84/238
+f 54/84/238 56/152/237 55/171/239
+f 69/25/240 57/22/241 68/153/242
+f 68/153/242 57/22/241 56/152/243
+f 61/120/244 66/80/245 60/139/246
+f 60/139/246 66/80/245 67/140/247
+f 66/80/248 64/19/249 67/140/250
+f 67/140/250 64/19/249 65/41/251
+f 64/19/252 148/83/253 65/41/254
+f 65/41/254 148/83/253 147/111/255
+f 60/139/256 63/98/257 61/120/258
+f 61/120/258 63/98/257 62/102/259
+f 161/43/260 69/25/261 162/154/262
+f 162/154/262 69/25/261 68/153/263
+f 126/6/264 113/44/265 124/72/266
+f 124/72/266 113/44/265 112/103/267
+f 128/7/268 114/131/269 126/6/270
+f 126/6/270 114/131/269 113/44/271
+f 130/54/272 115/86/273 128/7/274
+f 128/7/274 115/86/273 114/131/275
+f 132/162/276 122/108/277 130/54/278
+f 130/54/278 122/108/277 115/86/279
+f 121/73/280 122/108/281 134/59/282
+f 134/59/282 122/108/281 132/162/283
+f 151/45/284 121/73/285 137/8/286
+f 137/8/286 121/73/285 134/59/287
+f 171/10/288 140/52/289 165/115/290
+f 165/115/290 140/52/289 87/165/291
+f 105/134/292 95/9/293 104/126/294
+f 104/126/294 95/9/293 94/129/295
+f 153/1/296 74/40/297 163/14/298
+f 163/14/298 74/40/297 85/51/299
+f 163/14/300 85/51/301 164/12/302
+f 164/12/302 85/51/301 84/15/303
+f 106/58/304 79/116/305 104/126/306
+f 104/126/306 79/116/305 77/82/307
+f 105/134/308 78/36/309 111/112/310
+f 111/112/310 78/36/309 81/77/311
+f 169/17/312 82/104/313 168/32/314
+f 168/32/314 82/104/313 83/133/315
+f 164/12/316 84/15/317 169/17/318
+f 169/17/318 84/15/317 82/104/319
+f 145/47/320 118/130/321 149/46/322
+f 149/46/322 118/130/321 119/53/323
+f 113/44/324 88/163/325 112/103/326
+f 112/103/326 88/163/325 89/87/327
+f 87/165/328 86/149/329 165/115/330
+f 165/115/330 86/149/329 166/35/331
+f 141/13/332 95/9/333 150/114/334
+f 150/114/334 95/9/333 101/48/335
+f 115/86/336 91/127/337 114/131/338
+f 114/131/338 91/127/337 90/105/339
+f 114/131/340 90/105/341 113/44/342
+f 113/44/342 90/105/341 88/163/343
+f 99/138/344 131/55/345 97/60/346
+f 97/60/346 131/55/345 129/56/347
+f 149/46/348 119/53/349 142/128/350
+f 142/128/350 119/53/349 120/74/351
+f 138/169/352 143/113/353 152/167/354
+f 152/167/354 143/113/353 116/33/355
+f 106/58/356 96/168/357 103/89/358
+f 103/89/358 96/168/357 93/164/359
+f 103/89/360 93/164/361 108/11/362
+f 108/11/362 93/164/361 98/75/363
+f 108/11/364 98/75/365 109/88/366
+f 109/88/366 98/75/365 99/138/367
+f 109/88/368 99/138/369 107/166/370
+f 107/166/370 99/138/369 97/60/371
+f 107/166/372 97/60/373 110/135/374
+f 110/135/374 97/60/373 100/109/375
+f 110/135/376 100/109/377 102/49/378
+f 102/49/378 100/109/377 92/91/379
+f 70/4/380 110/135/381 73/125/382
+f 73/125/382 110/135/381 102/49/383
+f 80/94/384 107/166/385 70/4/386
+f 70/4/386 107/166/385 110/135/387
+f 75/27/388 109/88/389 80/94/390
+f 80/94/390 109/88/389 107/166/391
+f 72/95/392 108/11/393 75/27/394
+f 75/27/394 108/11/393 109/88/395
+f 71/100/396 103/89/397 72/95/398
+f 72/95/398 103/89/397 108/11/399
+f 79/116/400 106/58/401 71/100/402
+f 71/100/402 106/58/401 103/89/403
+f 95/9/404 105/134/405 101/48/406
+f 101/48/406 105/134/405 111/112/407
+f 96/168/408 106/58/409 94/129/410
+f 94/129/410 106/58/409 104/126/411
+f 78/36/412 105/134/413 77/82/414
+f 77/82/414 105/134/413 104/126/415
+f 148/83/416 117/122/417 145/47/418
+f 145/47/418 117/122/417 118/130/419
+f 150/114/420 101/48/421 146/110/422
+f 146/110/422 101/48/421 111/112/423
+f 98/75/424 133/90/425 99/138/426
+f 99/138/426 133/90/425 131/55/427
+f 100/109/428 127/50/429 92/91/430
+f 92/91/430 127/50/429 125/137/431
+f 97/60/432 129/56/433 100/109/434
+f 100/109/434 129/56/433 127/50/435
+f 136/106/436 96/168/437 144/92/438
+f 144/92/438 96/168/437 94/129/439
+f 96/168/440 136/106/441 93/164/442
+f 93/164/442 136/106/441 135/150/443
+f 143/113/444 142/128/445 116/33/446
+f 116/33/446 142/128/445 120/74/447
+f 95/9/448 141/13/449 94/129/450
+f 94/129/450 141/13/449 144/92/451
+f 146/110/452 111/112/453 147/111/454
+f 147/111/454 111/112/453 81/77/455
+f 168/32/456 83/133/457 170/107/458
+f 170/107/458 83/133/457 139/136/459
+f 93/164/460 135/150/461 98/75/462
+f 98/75/462 135/150/461 133/90/463
+f 91/127/464 115/86/465 123/61/466
+f 123/61/466 115/86/465 122/108/467
+f 167/57/468 123/61/469 166/35/470
+f 166/35/470 123/61/469 122/108/471
+f 136/106/472 138/169/473 137/8/474
+f 144/92/475 143/113/476 136/106/477
+f 136/106/477 143/113/476 138/169/478
+f 124/72/479 125/137/480 126/6/481
+f 126/6/481 125/137/480 127/50/482
+f 126/6/483 127/50/484 128/7/485
+f 128/7/485 127/50/484 129/56/486
+f 128/7/487 129/56/488 130/54/489
+f 130/54/489 129/56/488 131/55/490
+f 130/54/491 131/55/492 132/162/493
+f 132/162/493 131/55/492 133/90/494
+f 133/90/495 135/150/496 132/162/497
+f 132/162/497 135/150/496 134/59/498
+f 134/59/499 135/150/500 137/8/501
+f 137/8/501 135/150/500 136/106/502
+f 152/167/503 151/45/504 138/169/505
+f 138/169/505 151/45/504 137/8/506
+f 142/128/507 141/13/508 149/46/509
+f 149/46/509 141/13/508 150/114/510
+f 149/46/511 150/114/512 145/47/513
+f 145/47/513 150/114/512 146/110/514
+f 145/47/515 146/110/516 148/83/517
+f 148/83/517 146/110/516 147/111/518
+f 141/13/519 142/128/520 144/92/521
+f 144/92/521 142/128/520 143/113/522
+f 170/107/523 171/10/524 152/167/525
+f 152/167/525 171/10/524 151/45/526
+f 140/52/527 171/10/528 139/136/529
+f 139/136/529 171/10/528 170/107/530
+f 76/132/531 167/57/532 86/149/533
+f 86/149/533 167/57/532 166/35/534
+f 152/167/535 116/33/536 170/107/537
+f 170/107/537 116/33/536 168/32/538
+f 165/115/539 166/35/540 121/73/541
+f 121/73/541 166/35/540 122/108/542
+f 119/53/543 164/12/544 120/74/545
+f 120/74/545 164/12/544 169/17/546
+f 116/33/547 120/74/548 168/32/549
+f 168/32/549 120/74/548 169/17/550
+f 118/130/551 163/14/552 119/53/553
+f 119/53/553 163/14/552 164/12/554
+f 117/122/555 153/1/556 118/130/557
+f 118/130/557 153/1/556 163/14/558
+f 171/10/559 165/115/560 151/45/561
+f 151/45/561 165/115/560 121/73/562
+f 161/43/563 162/154/564 58/85/565
+f 58/85/565 162/154/564 59/67/566
+f 157/42/567 158/5/568 6/34/569
+f 6/34/569 158/5/568 1/151/570
+f 69/25/571 161/43/572 36/23/573
+f 36/23/573 161/43/572 159/170/574
+f 41/64/575 157/42/576 40/38/577
+f 40/38/577 157/42/576 156/65/578
+f 38/119/579 39/147/580 155/2/581
+f 155/2/581 39/147/580 160/118/582
+f 36/23/583 159/170/584 39/147/585
+f 39/147/585 159/170/584 160/118/586
+f 155/2/587 154/39/588 38/119/589
+f 38/119/589 154/39/588 37/76/590
+f 154/39/591 153/1/592 37/76/593
+f 37/76/593 153/1/592 117/122/594
+f 40/38/595 156/65/596 68/153/597
+f 68/153/597 156/65/596 162/154/598
diff --git a/libs/vr/libdvrgraphics/assets/laser.obj b/libs/vr/libdvrgraphics/assets/laser.obj
new file mode 100644
index 0000000..32737e4
--- /dev/null
+++ b/libs/vr/libdvrgraphics/assets/laser.obj
@@ -0,0 +1,28 @@
+# This file uses centimeters as units for non-parametric coordinates.
+
+v -0.010000 -0.000000 -1.000000
+v 0.010000 -0.000000 -1.000000
+v -0.010000 -0.000000 0.000000
+v 0.010000 -0.000000 0.000000
+v 0.000000 0.010000 -1.000000
+v 0.000000 -0.010000 -1.000000
+v 0.000000 0.010000 -0.000000
+v 0.000000 -0.010000 0.000000
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn 0.000000 -1.000000 -0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+f 1/1/1 2/2/2 4/4/3 3/3/4
+f 5/5/5 6/6/6 8/7/7 7/8/8
diff --git a/libs/vr/libdvrgraphics/assets/laser.png b/libs/vr/libdvrgraphics/assets/laser.png
new file mode 100644
index 0000000..a96c68d
--- /dev/null
+++ b/libs/vr/libdvrgraphics/assets/laser.png
Binary files differ
diff --git a/libs/vr/libdvrgraphics/blur.cpp b/libs/vr/libdvrgraphics/blur.cpp
new file mode 100644
index 0000000..7365b0e
--- /dev/null
+++ b/libs/vr/libdvrgraphics/blur.cpp
@@ -0,0 +1,247 @@
+#include "include/private/dvr/graphics/blur.h"
+
+// clang-format off
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+#include <GLES2/gl2.h>
+// clang-format on
+#include <hardware/gralloc.h>
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <private/dvr/debug.h>
+#include <private/dvr/graphics/egl_image.h>
+#include <private/dvr/graphics/shader_program.h>
+#include <private/dvr/types.h>
+
+#define POSITION_ATTR 0
+#define OFFSET_BINDING 0
+#define SAMPLER_BINDING 1
+
+namespace {
+
+std::string screen_space_vert_shader = SHADER0([]() {  // NOLINT
+  layout(location = 0) in vec4 position_uv;
+  out vec2 texCoords;
+
+  void main() {
+    gl_Position = vec4(position_uv.xy, 0.0, 1.0);
+    texCoords = position_uv.zw;
+  }
+});
+
+std::string kawase_blur_frag_shader = SHADER0([]() {  // NOLINT
+  precision mediump float;
+  layout(location = 0) uniform vec2 uSampleOffsets[4];
+  layout(binding = 1) uniform APP_SAMPLER_2D uTexture;
+  in vec2 texCoords;
+  out vec4 color;
+
+  void main() {
+    vec2 tc = texCoords;
+    color = texture(uTexture, tc + uSampleOffsets[0]);
+    color += texture(uTexture, tc + uSampleOffsets[1]);
+    color += texture(uTexture, tc + uSampleOffsets[2]);
+    color += texture(uTexture, tc + uSampleOffsets[3]);
+    color *= (1.0 / 4.0);
+  }
+});
+
+constexpr int g_num_samples = 4;
+
+// Modified kernel patterns originally based on:
+// https://software.intel.com/en-us/blogs/2014/07/15/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms
+// The modification is left and right rotations of the 3rd and 4th patterns.
+const android::dvr::vec2 g_blur_samples[][g_num_samples] = {
+    {{0.5f, 0.5f}, {-0.5f, 0.5f}, {0.5f, -0.5f}, {-0.5f, -0.5f}},
+    {{1.5f, 1.5f}, {-1.5f, 1.5f}, {1.5f, -1.5f}, {-1.5f, -1.5f}},
+    {{2.5f, 1.5f}, {-1.5f, 2.5f}, {1.5f, -2.5f}, {-2.5f, -1.5f}},
+    {{2.5f, 3.5f}, {-3.5f, 2.5f}, {3.5f, -2.5f}, {-2.5f, -3.5f}},
+    // Last pass disabled, because it is more blur than we need.
+    // {{3.5f, 3.5f}, {-3.5f, 3.5f}, {3.5f, -3.5f}, {-3.5f, -3.5f}},
+};
+
+}  // namespace
+
+namespace android {
+namespace dvr {
+
+Blur::Blur(int w, int h, GLuint source_texture, GLint source_texture_target,
+           GLint target_texture_target, bool is_external, EGLDisplay display,
+           int num_blur_outputs)
+    : display_(display),
+      target_texture_target_(target_texture_target),
+      width_(w),
+      height_(h),
+      fbo_q_free_(1 + num_blur_outputs) {
+  CHECK(num_blur_outputs > 0);
+  source_fbo_ =
+      CreateFbo(w, h, source_texture, source_texture_target, is_external);
+  fbo_half_ = CreateFbo(w / 2, h / 2, 0, target_texture_target, is_external);
+  // Create the quarter res fbos.
+  for (size_t i = 0; i < fbo_q_free_.GetCapacity(); ++i)
+    fbo_q_.push_back(
+        CreateFbo(w / 4, h / 4, 0, target_texture_target, is_external));
+  scale_ = 1.0f;
+}
+
+Blur::~Blur() {
+  glFinish();
+  glDeleteFramebuffers(1, &source_fbo_.fbo);
+  glDeleteFramebuffers(1, &fbo_half_.fbo);
+  // Note: source_fbo_.texture is not deleted because it was created externally.
+  glDeleteTextures(1, &fbo_half_.texture);
+  if (fbo_half_.egl_image)
+    eglDestroyImageKHR(display_, fbo_half_.egl_image);
+  for (const auto& fbo : fbo_q_) {
+    glDeleteFramebuffers(1, &fbo.fbo);
+    glDeleteTextures(1, &fbo.texture);
+    if (fbo.egl_image)
+      eglDestroyImageKHR(display_, fbo.egl_image);
+  }
+  CHECK_GL();
+}
+
+void Blur::StartFrame() {
+  fbo_q_free_.Clear();
+  for (const auto& fbo : fbo_q_)
+    fbo_q_free_.Append(fbo);
+}
+
+GLuint Blur::DrawBlur(GLuint source_texture) {
+  CHECK(fbo_q_free_.GetSize() >= 2);
+
+  // Downsample to half w x half h.
+  glBindFramebuffer(GL_READ_FRAMEBUFFER, source_fbo_.fbo);
+  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_half_.fbo);
+  glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                         target_texture_target_, source_texture, 0);
+  glBlitFramebuffer(0, 0, width_, height_, 0, 0, width_ / 2, height_ / 2,
+                    GL_COLOR_BUFFER_BIT, GL_LINEAR);
+  CHECK_GL();
+
+  // Downsample to quarter w x quarter h.
+  glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_half_.fbo);
+  Fbo fbo_out = fbo_q_free_.Front();
+  fbo_q_free_.PopFront();
+  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_out.fbo);
+  glBlitFramebuffer(0, 0, width_ / 2, height_ / 2, 0, 0, width_ / 4,
+                    height_ / 4, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+  glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+  CHECK_GL();
+
+  // Blur shader is initialized statically to share between multiple blur
+  // instances.
+  static ShaderProgram kawase_prog[2];
+  int prog_index = (target_texture_target_ == GL_TEXTURE_EXTERNAL_OES) ? 1 : 0;
+  if (!kawase_prog[prog_index].IsUsable()) {
+    std::string prefix = "#version 310 es\n";
+    if (target_texture_target_ == GL_TEXTURE_EXTERNAL_OES) {
+      prefix += "#extension GL_OES_EGL_image_external_essl3 : require\n";
+      prefix += "#define APP_SAMPLER_2D samplerExternalOES\n";
+    } else {
+      prefix += "#define APP_SAMPLER_2D sampler2D\n";
+    }
+    std::string vert = prefix + screen_space_vert_shader;
+    std::string frag = prefix + kawase_blur_frag_shader;
+    kawase_prog[prog_index].Link(vert, frag);
+    CHECK_GL();
+  }
+
+  int blur_w = width_ / 4;
+  int blur_h = height_ / 4;
+  float pix_w = 1.0f / static_cast<float>(blur_w);
+  float pix_h = 1.0f / static_cast<float>(blur_h);
+  vec2 pixel_size(pix_w, pix_h);
+  constexpr int num_passes = sizeof(g_blur_samples) / sizeof(g_blur_samples[0]);
+  vec2 blur_offsets[num_passes][g_num_samples];
+  for (int i = 0; i < num_passes; ++i) {
+    for (int dir = 0; dir < g_num_samples; ++dir) {
+      blur_offsets[i][dir] = pixel_size.array() *
+          g_blur_samples[i][dir].array() * scale_;
+    }
+  }
+
+  kawase_prog[prog_index].Use();
+
+  vec4 screen_tri_strip[4] = {vec4(-1, 1, 0, 1), vec4(-1, -1, 0, 0),
+                              vec4(1, 1, 1, 1), vec4(1, -1, 1, 0)};
+
+  glViewport(0, 0, blur_w, blur_h);
+  glVertexAttribPointer(POSITION_ATTR, 4, GL_FLOAT, GL_FALSE, sizeof(vec4),
+                        screen_tri_strip);
+  glEnableVertexAttribArray(POSITION_ATTR);
+  CHECK_GL();
+
+  // Ping-pong between fbos from fbo_q_free_ to compute the passes.
+  Fbo fbo_in = fbo_out;
+  for (int i = 0; i < num_passes; ++i) {
+    fbo_out = fbo_q_free_.Front();
+    fbo_q_free_.PopFront();
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo_out.fbo);
+    glActiveTexture(GL_TEXTURE0 + SAMPLER_BINDING);
+    glBindTexture(target_texture_target_, fbo_in.texture);
+    glUniform2fv(OFFSET_BINDING, 4, &blur_offsets[i][0][0]);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+    CHECK_GL();
+    // Put fbo_in back into the free fbo pool.
+    fbo_q_free_.Append(fbo_in);
+    // Next iteration's in buffer is this iteration's out buffer.
+    fbo_in = fbo_out;
+  }
+  glDisableVertexAttribArray(POSITION_ATTR);
+  glBindTexture(target_texture_target_, 0);
+  glUseProgram(0);
+  glActiveTexture(GL_TEXTURE0);
+  CHECK_GL();
+  // fbo_out remains out of the fbo_q_free_ list, since the application will be
+  // using it as a texture.
+  return fbo_out.texture;
+}
+
+Blur::Fbo Blur::CreateFbo(int w, int h, GLuint source_texture, GLint tex_target,
+                          bool is_external) {
+  Fbo fbo;
+  glGenFramebuffers(1, &fbo.fbo);
+  if (source_texture) {
+    fbo.texture = source_texture;
+  } else {
+    glGenTextures(1, &fbo.texture);
+  }
+
+  glBindFramebuffer(GL_FRAMEBUFFER, fbo.fbo);
+  CHECK_GL();
+
+  if (!source_texture) {
+    glBindTexture(tex_target, fbo.texture);
+    glTexParameteri(tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    if (is_external) {
+      fbo.egl_image =
+          CreateEglImage(display_, w, h, HAL_PIXEL_FORMAT_RGBA_8888,
+                         GRALLOC_USAGE_HW_FB | GRALLOC_USAGE_HW_RENDER);
+      glEGLImageTargetTexture2DOES(tex_target, fbo.egl_image);
+    } else {
+      glTexImage2D(tex_target, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                   nullptr);
+    }
+  }
+  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex_target,
+                         fbo.texture, 0);
+  CHECK_GL();
+  CHECK_GL_FBO();
+
+  glBindFramebuffer(GL_FRAMEBUFFER, 0);
+  return fbo;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdvrgraphics/debug_text.cpp b/libs/vr/libdvrgraphics/debug_text.cpp
new file mode 100644
index 0000000..1875b14
--- /dev/null
+++ b/libs/vr/libdvrgraphics/debug_text.cpp
@@ -0,0 +1,186 @@
+#include "include/private/dvr/graphics/debug_text.h"
+
+#include <algorithm>
+
+#include <private/dvr/debug.h>
+
+namespace android {
+namespace dvr {
+namespace {
+
+// 658x11 alpha texture with ascii characters starting with !: "!"#$%&'("...
+// Each character is 7x11 pixels, monospace.
+// clang-format off
+const uint8_t ascii_texture[] = {
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xff,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+  ,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,0x00,0x49,0x92,0x49,0x92,0x00,0x00,0x24,0xdb,0xff,0xff,0xdb,0x00,0x49,0xff,0x49,0x00,0x24,0xb6,0x00,0x00,0x6d,0xff,0xff,0x49,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x00,0x00,0x00,0x00,0x92,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0xff,0x49,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0xb6,0xdb,0xb6,0x00,0x00,0x00,0x24,0xb6,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xb6,0x24,0x00,0x00,0xff,0xff,0xff,0xb6,0x24,0x00,0x00,0x00,0x00,0x00,0x6d,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x6d,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x24,0xdb,0xff,0xdb,0x24,0x00,0x00,0x24,0xdb,0xff,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0xff,0xff,0x6d,0x00,0x00,0x00,0x6d,0xff,0xff,0x92,0x00,0x00,0x00,0x00,0x24,0xff,0x24,0x00,0x00,0x00,0xff,0xff,0xff,0xdb,0x49,0x00,0x00,0x24,0xb6,0xff,0xff,0xb6,0x00,0x00,0xff,0xff,0xdb,0x6d,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x24,0xb6,0xff,0xff,0xdb,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x6d,0x6d,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x49,0x00,0x49,0xff,0x00,0x00,0xff,0x6d,0x00,0x00,0xff,0x00,0x00,0x00,0xb6,0xdb,0xb6,0x00,0x00,0x00,0xff,0xff,0xff,0xdb,0x24,0x00,0x00,0x00,0xb6,0xdb,0xb6,0x00,0x00,0x00,0xff,0xff,0xff,0xdb,0x49,0x00,0x00,0x24,0xb6,0xff,0xff,0xdb,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xb6,0x24,0x00,0x24,0xb6,0x00,0x00,0xdb,0x24,0x00,0x24,0xdb,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xdb,0x6d,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xff,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xff,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+  ,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,0x00,0x92,0x6d,0x92,0x6d,0x00,0x00,0xdb,0x6d,0xff,0x00,0x00,0x00,0xb6,0x24,0xb6,0x00,0xb6,0x24,0x00,0x00,0xff,0x24,0x24,0xb6,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x24,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0x00,0xb6,0xff,0xb6,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0x92,0x6d,0x00,0x6d,0x92,0x00,0x00,0xb6,0x24,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0xdb,0x00,0x00,0x00,0x00,0x00,0x49,0xff,0x00,0x00,0x00,0x00,0x24,0xb6,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x49,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xb6,0x00,0x00,0xff,0x49,0x00,0x49,0xff,0x00,0x00,0xb6,0x6d,0x00,0x49,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x24,0x00,0x00,0x00,0x00,0x92,0x00,0x24,0xff,0x00,0x00,0x24,0xdb,0x24,0x00,0xdb,0x6d,0x00,0x00,0x00,0x6d,0xff,0x6d,0x00,0x00,0x00,0xff,0x00,0x00,0x49,0xff,0x00,0x00,0xdb,0x6d,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x24,0xdb,0x49,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x6d,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x6d,0x6d,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xb6,0x00,0x92,0xff,0x00,0x00,0xff,0xdb,0x00,0x00,0xff,0x00,0x00,0x92,0x6d,0x00,0x6d,0x92,0x00,0x00,0xff,0x00,0x00,0x49,0xff,0x00,0x00,0x92,0x6d,0x00,0x6d,0x92,0x00,0x00,0xff,0x00,0x00,0x49,0xff,0x00,0x00,0xdb,0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xb6,0x49,0x00,0x49,0xb6,0x00,0x00,0xdb,0x24,0x00,0x00,0xdb,0x00,0x00,0x24,0xb6,0x00,0xb6,0x24,0x00,0x00,0x6d,0x92,0x00,0x92,0x6d,0x00,0x00,0x00,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x92,0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x49,0xb6,0x00,0xb6,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+  ,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0xb6,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0x49,0xff,0x00,0x00,0x00,0xb6,0x24,0xb6,0x24,0xb6,0x00,0x00,0x00,0xb6,0x49,0xdb,0x49,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0x92,0x00,0x00,0x00,0x00,0xb6,0xff,0xb6,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x24,0x92,0xb6,0x00,0x00,0x00,0x00,0xdb,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0x00,0x00,0xb6,0x49,0x00,0x49,0xb6,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x24,0xb6,0xb6,0x24,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x24,0xb6,0xb6,0x24,0x00,0x00,0x00,0x00,0x49,0xdb,0x00,0x00,0xb6,0x6d,0x6d,0xff,0x92,0xdb,0x00,0x00,0x00,0xb6,0x92,0xb6,0x00,0x00,0x00,0xff,0x00,0x00,0x49,0xdb,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x49,0xdb,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x6d,0x6d,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xb6,0x49,0xb6,0xff,0x00,0x00,0xff,0xb6,0x49,0x00,0xff,0x00,0x00,0xdb,0x24,0x00,0x24,0xdb,0x00,0x00,0xff,0x00,0x00,0x49,0xdb,0x00,0x00,0xdb,0x24,0x00,0x24,0xdb,0x00,0x00,0xff,0x00,0x00,0x49,0xdb,0x00,0x00,0xdb,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x6d,0x92,0x00,0x92,0x6d,0x00,0x00,0x92,0x24,0x00,0x00,0xdb,0x00,0x00,0x00,0xb6,0x6d,0xb6,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xb6,0x24,0x00,0x24,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0xff,0xff,0xdb,0x49,0x00,0x00,0xff,0x6d,0xff,0xdb,0x24,0x00,0x00,0x00,0x92,0xff,0xff,0xdb,0x00,0x00,0x24,0xdb,0xff,0x6d,0xff,0x00,0x00,0x00,0xb6,0xff,0xdb,0x24,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x49,0xff,0xdb,0xb6,0xff,0x00,0x00,0xff,0x00,0x92,0xb6,0x24,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x6d,0x6d,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0xb6,0x6d,0xdb,0x49,0x00,0x00,0xff,0x00,0x92,0xb6,0x24,0x00,0x00,0x00,0xb6,0xff,0xb6,0x00,0x00,0x00,0xff,0x6d,0xff,0xdb,0x24,0x00,0x00,0x24,0xdb,0xff,0x6d,0xff,0x00,0x00,0x00,0xff,0x49,0xff,0xdb,0x00,0x00,0x49,0xdb,0xff,0xff,0xdb,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xdb,0x24,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xb6,0x49,0x00,0x49,0xb6,0x00,0x00,0xdb,0x24,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+  ,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,0x24,0xdb,0xff,0x49,0x00,0x00,0x49,0xff,0x49,0xb6,0x24,0x00,0x00,0x00,0x6d,0xff,0x6d,0x00,0x24,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xb6,0x49,0xff,0x49,0xb6,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0xff,0x00,0xb6,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xb6,0x24,0x00,0xff,0x00,0x00,0x00,0xff,0xff,0xdb,0x24,0x00,0x00,0xff,0x92,0xff,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0xdb,0xff,0xdb,0x00,0x00,0x00,0xdb,0x6d,0x00,0x92,0xff,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0xff,0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0xff,0x00,0x00,0x00,0x49,0xff,0x24,0x00,0x00,0xdb,0x00,0xdb,0xdb,0x49,0xff,0x00,0x00,0x00,0xff,0x24,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x24,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x6d,0xb6,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x6d,0xff,0x6d,0xff,0x00,0x00,0xff,0x24,0xdb,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xdb,0x24,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0x92,0x00,0x00,0x00,0x24,0xdb,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x24,0xdb,0x00,0xdb,0x24,0x00,0x00,0x6d,0x6d,0xff,0x24,0xb6,0x00,0x00,0x00,0x24,0xff,0x24,0x00,0x00,0x00,0x00,0x92,0xdb,0x92,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xff,0x00,0x00,0xff,0x92,0x00,0x49,0xb6,0x00,0x00,0x92,0x6d,0x00,0x00,0x00,0x00,0x00,0xb6,0x92,0x00,0x6d,0xff,0x00,0x00,0xb6,0x49,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x24,0x24,0xff,0x00,0x00,0x00,0xff,0xb6,0x00,0x49,0xdb,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x49,0xff,0x49,0xdb,0x00,0x00,0xff,0xb6,0x00,0x49,0xdb,0x00,0x00,0xb6,0x92,0x00,0x92,0xb6,0x00,0x00,0xff,0x6d,0x00,0x92,0xb6,0x00,0x00,0xb6,0x92,0x00,0x6d,0xff,0x00,0x00,0x00,0xff,0xb6,0x00,0x00,0x00,0x00,0xff,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x6d,0x6d,0x00,0x6d,0x92,0x00,0x00,0xdb,0x24,0x00,0x24,0xdb,0x00,0x00,0x00,0xdb,0x24,0xdb,0x00,0x00,0x00,0x6d,0x6d,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0x00,0xb6,0x24,0x00,0x00,0x00,0x49,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+  ,0x00,0x00,0x00,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xb6,0x24,0xb6,0x00,0x00,0x00,0x00,0x00,0xff,0xdb,0x49,0x00,0x00,0x00,0x24,0xb6,0x49,0xff,0x49,0x49,0xdb,0x49,0xdb,0x00,0xb6,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0x00,0x00,0x00,0x00,0xff,0x00,0xb6,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0x24,0x92,0xb6,0x00,0x00,0x92,0x6d,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x6d,0xdb,0x00,0x00,0xff,0x92,0x00,0x6d,0xdb,0x00,0x00,0x00,0x00,0x92,0x6d,0x00,0x00,0x00,0xb6,0x6d,0x00,0x6d,0xb6,0x00,0x00,0x24,0xdb,0xff,0x92,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xdb,0xb6,0x24,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x24,0xb6,0xdb,0x49,0x00,0x00,0x00,0xff,0x24,0x00,0x00,0x00,0xff,0x00,0xff,0xdb,0x24,0xff,0x00,0x00,0x24,0xdb,0x00,0xdb,0x24,0x00,0x00,0xff,0x00,0x00,0x92,0xb6,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x49,0xb6,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x6d,0xb6,0x49,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x24,0xff,0x24,0xff,0x00,0x00,0xff,0x00,0xb6,0x24,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0xb6,0x49,0x00,0x00,0x00,0x00,0x00,0x6d,0xdb,0x24,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x24,0xff,0x00,0x00,0x00,0x49,0xb6,0xff,0x6d,0x92,0x00,0x00,0x00,0x24,0xff,0x24,0x00,0x00,0x00,0x00,0x24,0xff,0x24,0x00,0x00,0x00,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0x92,0xdb,0xff,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xdb,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x24,0x24,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x6d,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x49,0xdb,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xdb,0x00,0xdb,0x24,0x00,0x00,0xb6,0x49,0x92,0x49,0xb6,0x00,0x00,0x00,0x49,0xff,0x49,0x00,0x00,0x00,0x00,0xdb,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x92,0x6d,0x00,0x00,0x00,0x00,0xff,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xff,0x00,0x00,0x49,0xb6,0x92,0x24,0x00,0x92,0x00
+  ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,0xb6,0x24,0xb6,0x24,0xb6,0xff,0x00,0x00,0x24,0xb6,0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x49,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0xdb,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x49,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0xff,0x6d,0xb6,0x00,0x00,0x6d,0xff,0xff,0xff,0x6d,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x49,0xdb,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x24,0xdb,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xdb,0x24,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x49,0xb6,0xff,0x00,0x00,0xdb,0x24,0x00,0x24,0xdb,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x24,0xff,0x00,0x00,0xff,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x24,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xb6,0xb6,0xb6,0x00,0x00,0x00,0x00,0xdb,0xdb,0xdb,0x6d,0x00,0x00,0x00,0xb6,0x6d,0xb6,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x92,0x24,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xdb,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x49,0xff,0xff,0x49,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x92,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0xdb,0x6d,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xdb,0x49,0xdb,0x00,0x00,0x00,0x92,0x92,0xff,0xdb,0x92,0x00,0x00,0x00,0x49,0xff,0x49,0x00,0x00,0x00,0x00,0xb6,0x49,0xdb,0x00,0x00,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0x00,0x49,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0x00,0x00,0x92,0x00,0x24,0x92,0xb6,0x49,0x00
+  ,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0xb6,0x49,0x00,0x00,0x00,0x00,0x00,0xff,0x6d,0xdb,0x00,0x00,0x24,0xb6,0x00,0xb6,0x24,0xb6,0xdb,0x6d,0x00,0x24,0xdb,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x92,0x6d,0x00,0x00,0x00,0x00,0x00,0x92,0x6d,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x49,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x6d,0xb6,0x00,0x00,0x6d,0x92,0x00,0x24,0xdb,0x00,0x00,0x00,0x49,0x92,0x00,0x00,0x00,0x00,0xdb,0x6d,0x00,0x6d,0xdb,0x00,0x00,0x00,0x00,0x24,0xdb,0x49,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0xdb,0x49,0xb6,0xdb,0xdb,0x00,0x00,0x00,0xb6,0x49,0x00,0x49,0xb6,0x00,0x00,0xff,0x00,0x00,0x92,0xdb,0x00,0x00,0xdb,0x6d,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x24,0xdb,0x49,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x6d,0x00,0x24,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xdb,0x00,0x00,0x00,0xff,0x00,0x00,0x24,0xdb,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0xdb,0xff,0x00,0x00,0x92,0x6d,0x00,0x6d,0x92,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x6d,0x00,0x6d,0xb6,0x00,0x00,0xff,0x00,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0x6d,0xdb,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xdb,0x6d,0x00,0x6d,0xdb,0x00,0x00,0x00,0x6d,0xff,0x6d,0x00,0x00,0x00,0x00,0xff,0x92,0xff,0x49,0x00,0x00,0x24,0xb6,0x00,0xb6,0x24,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x92,0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x24,0x00,0x92,0xff,0x00,0x00,0xff,0x92,0x00,0x49,0xb6,0x00,0x00,0x92,0x6d,0x00,0x00,0x00,0x00,0x00,0xb6,0x92,0x00,0x6d,0xff,0x00,0x00,0x92,0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xb6,0x49,0x00,0x6d,0xb6,0x00,0x00,0xff,0x6d,0x00,0x92,0xb6,0x00,0x00,0xb6,0x92,0x00,0x6d,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xff,0x00,0x00,0x00,0x00,0xff,0x24,0x00,0x00,0x00,0xff,0x49,0x00,0xb6,0xff,0x00,0x00,0x00,0x6d,0xff,0x6d,0x00,0x00,0x00,0x6d,0xff,0xb6,0xff,0x49,0x00,0x00,0x00,0xdb,0x24,0xdb,0x00,0x00,0x00,0x00,0x6d,0xff,0x6d,0x00,0x00,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+  ,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xb6,0x24,0x00,0x00,0xb6,0x24,0x00,0x49,0xff,0x49,0x24,0xb6,0xff,0xdb,0x49,0x49,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0xdb,0xb6,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0xff,0xdb,0x00,0x00,0x00,0x00,0xdb,0xdb,0xff,0x49,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x24,0xdb,0xff,0xdb,0x24,0x00,0x00,0xff,0xff,0xff,0x6d,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x00,0x00,0x00,0x00,0x49,0xff,0x24,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xdb,0x00,0x00,0xff,0xff,0xff,0xb6,0x00,0x00,0x00,0x24,0xb6,0xff,0xff,0xb6,0x00,0x00,0xff,0xff,0xdb,0x6d,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0xff,0xff,0x92,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0xdb,0xff,0xdb,0x49,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x49,0x92,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x6d,0xff,0x00,0x00,0x00,0xb6,0xdb,0xb6,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0xdb,0xff,0x24,0x00,0x00,0xff,0x00,0x00,0x00,0x6d,0x92,0x00,0xff,0xff,0xff,0xb6,0x24,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x24,0xdb,0xff,0xdb,0x24,0x00,0x00,0x00,0x24,0xff,0x24,0x00,0x00,0x00,0x00,0xdb,0x49,0xff,0x49,0x00,0x00,0xb6,0x24,0x00,0x24,0xb6,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xff,0xdb,0x6d,0xff,0x00,0x00,0xff,0x6d,0xff,0xdb,0x00,0x00,0x00,0x00,0x92,0xff,0xff,0xff,0x00,0x00,0x24,0xdb,0xff,0x6d,0xff,0x00,0x00,0x00,0x92,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x92,0xff,0xff,0xff,0xb6,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xb6,0xdb,0xb6,0x00,0x00,0x00,0xff,0x6d,0xff,0xdb,0x00,0x00,0x00,0x24,0xdb,0xff,0x6d,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xdb,0x49,0x00,0x00,0x00,0x00,0x49,0xff,0xff,0x00,0x00,0x49,0xb6,0x92,0x24,0xff,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0x49,0xff,0x00,0xff,0x24,0x00,0x00,0xb6,0x49,0x00,0x49,0xb6,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+  ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x24,0x00,0x00,0x00,0x00,0x00,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0x49,0x00,0x49,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x24,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x24,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+  ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x00,0x00,0x00,0x00,0x92,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xff,0xff,0xb6,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdb,0xdb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xff,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0xff,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+};
+// clang-format on
+
+constexpr char kTextureFirstChar = '!';
+constexpr char kTextureLastChar = '~';
+constexpr int kTextureTotalChars = kTextureLastChar - kTextureFirstChar + 1;
+constexpr int kCharWidth = 7;
+constexpr int kCharHeight = 11;
+constexpr int kTextureWidth = kTextureTotalChars * kCharWidth;
+constexpr int kTextureHeight = kCharHeight;
+
+std::string vert_shader = SHADER0([]() {  // NOLINT
+  layout(location = 0) in vec2 position;
+  layout(location = 1) in vec2 uv;
+
+  out vec2 texCoords;
+
+  void main() {
+    gl_Position = vec4(position, 0.0, 1.0f);
+    texCoords = vec2(uv.x, 1.0 - uv.y);
+  }
+});
+
+// Uniform locations used in the following shader
+#define UNIFORM_LOCATION_DIGITS 0
+#define UNIFORM_LOCATION_COLOR 1
+
+std::string frag_shader = SHADER0([]() {  // NOLINT
+  precision mediump float;
+
+  out vec4 color;
+  in vec2 texCoords;
+  layout(location = 0) uniform sampler2D digitsTexture;
+  layout(location = 1) uniform vec4 mixColor;
+
+  void main() {
+    float alpha = texture(digitsTexture, texCoords).r;
+    color = vec4(mixColor.rgb, alpha * mixColor.a);
+  }
+});
+
+}  // anonymous namespace
+
+void DebugText::SetViewportSize(int viewport_width, int viewport_height) {
+  pixel_size_screen_space_ = vec2(2.0f / static_cast<float>(viewport_width),
+                                  2.0f / static_cast<float>(viewport_height));
+}
+
+DebugText::DebugText(int max_digits, int viewport_width, int viewport_height) {
+  max_digits_ = max_digits;
+  SetViewportSize(viewport_width, viewport_height);
+
+  shader_.Link(vert_shader, frag_shader);
+  shader_.Use();
+  glUniform1i(UNIFORM_LOCATION_DIGITS, 0);
+
+  // Num quads * 6 vertices per quad.
+  mesh_.SetVertices(max_digits * 6, nullptr, GL_TRIANGLES, GL_DYNAMIC_DRAW);
+
+  glGenTextures(1, &texture_);
+  glBindTexture(GL_TEXTURE_2D, texture_);
+  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, kTextureWidth, kTextureHeight, 0,
+               GL_RED, GL_UNSIGNED_BYTE, ascii_texture);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+  glBindTexture(GL_TEXTURE_2D, 0);
+  CHECK_GL();
+}
+
+DebugText::~DebugText() {}
+
+void DebugText::Draw(float x, float y, float scale, float r, float g, float b,
+                     float a, const char* str, float stereo_offset,
+                     uint8_t axis) {
+  assert(axis < 2);
+  shader_.Use();
+  glUniform4f(UNIFORM_LOCATION_COLOR, r, g, b, a);
+  glActiveTexture(GL_TEXTURE0);
+  glBindTexture(GL_TEXTURE_2D, texture_);
+  CHECK_GL();
+
+  float px = x;
+  float py = y;
+  float x_advance = scale * static_cast<float>(kCharWidth);
+  float y_advance = 0.0f;
+  float x_height = 0.0f;
+  float y_height = scale * static_cast<float>(kCharHeight);
+  if (axis) {
+    std::swap(x_advance, y_advance);
+    std::swap(x_height, y_height);
+  }
+  x_advance *= pixel_size_screen_space_[0];
+  x_height *= pixel_size_screen_space_[0];
+  y_advance *= pixel_size_screen_space_[1];
+  y_height *= pixel_size_screen_space_[1];
+  int max_digits = stereo_offset != 0.0f ? max_digits_ / 2 : max_digits_;
+  int len = std::min(max_digits, static_cast<int>(strlen(str)));
+
+  int num_quads = stereo_offset != 0.0f ? len * 2 : len;
+  std::tuple<vec2, vec2>* vbo =
+      mesh_.Map(GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT, num_quads * 6);
+
+  int v = 0;
+  for (int i = 0; i < len; ++i) {
+    char digit = str[i];
+    if (digit == '\n') {
+      if (axis == 0) {
+        py += y_height;
+        px = x;
+      } else {
+        px -= x_height;
+        py = y;
+      }
+      continue;
+    }
+    if (digit < kTextureFirstChar || digit > kTextureLastChar) {
+      px += x_advance;
+      py += y_advance;
+      continue;
+    }
+
+    int tex_digit = digit - kTextureFirstChar;
+
+    // Add screenspace tri vertices in CCW order starting with bottom left.
+    float tx =
+        static_cast<float>(tex_digit) / static_cast<float>(kTextureTotalChars);
+    float tx2 = tx + 1.0f / static_cast<float>(kTextureTotalChars);
+    vbo[v * 6 + 0] = {vec2(px, py), vec2(tx, 0.0f)};
+    vbo[v * 6 + 1] = {vec2(px + x_advance, py + y_advance), vec2(tx2, 0.0f)};
+    vbo[v * 6 + 2] = {
+        vec2(px + x_advance + x_height, py + y_advance + y_height),
+        vec2(tx2, 1.0f)};
+    vbo[v * 6 + 3] = vbo[v * 6 + 0];
+    vbo[v * 6 + 4] = vbo[v * 6 + 2];
+    vbo[v * 6 + 5] = {vec2(px + x_height, py + y_height), vec2(tx, 1.0f)};
+    px += x_advance;
+    py += y_advance;
+    ++v;
+  }
+
+  if (stereo_offset != 0.0f) {
+    int num_chars = v;
+    for (int i = 0; i < num_chars; ++i) {
+      for (int j = 0; j < 6; ++j) {
+        vbo[v * 6 + j] = vbo[i * 6 + j];
+        // The 0th tuple element is the vertex position vec2.
+        std::get<0>(vbo[v * 6 + j])[axis] += stereo_offset;
+      }
+      ++v;
+    }
+  }
+
+  mesh_.Unmap();
+
+  mesh_.Draw(v * 6);
+  glBindTexture(GL_TEXTURE_2D, texture_);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdvrgraphics/egl_image.cpp b/libs/vr/libdvrgraphics/egl_image.cpp
new file mode 100644
index 0000000..26d68cd
--- /dev/null
+++ b/libs/vr/libdvrgraphics/egl_image.cpp
@@ -0,0 +1,22 @@
+#include "include/private/dvr/graphics/egl_image.h"
+
+#include <hardware/gralloc.h>
+
+#include <memory>
+
+#include <private/dvr/native_buffer.h>
+
+namespace android {
+namespace dvr {
+
+EGLImageKHR CreateEglImage(EGLDisplay dpy, int width, int height, int format,
+                           int usage) {
+  auto image = std::make_shared<IonBuffer>(width, height, format, usage);
+
+  return eglCreateImageKHR(
+      dpy, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
+      static_cast<ANativeWindowBuffer*>(new NativeBuffer(image)), nullptr);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdvrgraphics/gpu_profiler.cpp b/libs/vr/libdvrgraphics/gpu_profiler.cpp
new file mode 100644
index 0000000..d252a34
--- /dev/null
+++ b/libs/vr/libdvrgraphics/gpu_profiler.cpp
@@ -0,0 +1,248 @@
+#include "include/private/dvr/graphics/gpu_profiler.h"
+
+#include <cutils/log.h>
+
+#include <private/dvr/clock_ns.h>
+
+namespace android {
+namespace dvr {
+
+static int64_t AdjustTimerQueryToNs(int64_t gpu_time) { return gpu_time; }
+
+void GpuProfiler::TimerData::reset() {
+  total_elapsed_ns = 0;
+  num_events = 0;
+}
+
+void GpuProfiler::TimerData::print(const char* name) const {
+  ALOGI("GPU_TIME[%s]: %f ms", name,
+        (float)((double)total_elapsed_ns / 1000000.0 / (double)num_events));
+}
+
+// Enter a scope, records the timestamp for later matching with leave.
+void GpuProfiler::TimerData::enter(int64_t timestamp_ns) {
+  enter_timestamp_ns = timestamp_ns;
+}
+
+// Compute the elapsed time for the scope.
+void GpuProfiler::TimerData::leave(int64_t timestamp_ns, const char* name,
+                                   std::weak_ptr<int64_t> duration_ns,
+                                   int print_period) {
+  int64_t elapsed = timestamp_ns - enter_timestamp_ns;
+  if (elapsed > 1000 * 1000 * 1000) {
+    // More than one second, drop it as invalid data.
+    return;
+  }
+  if (auto out_ns = duration_ns.lock()) {
+    *out_ns = elapsed;
+  }
+  total_elapsed_ns += elapsed;
+  if (print_period > 0 && ++num_events >= print_period) {
+    print(name);
+    reset();
+  }
+}
+
+GpuProfiler* GpuProfiler::Get() {
+  static GpuProfiler* profiler = new GpuProfiler();
+  return profiler;
+}
+
+GpuProfiler::GpuProfiler()
+    : enable_gpu_tracing_(true),
+      sync_with_cpu_time_(false),
+      gl_timer_offset_ns_(0) {
+  SyncGlTimebase();
+}
+
+GpuProfiler::~GpuProfiler() {}
+
+bool GpuProfiler::IsGpuProfilingSupported() const {
+  // TODO(jbates) check for GL_EXT_disjoint_timer_query
+  return true;
+}
+
+GLuint GpuProfiler::TryAllocateGlQueryId() {
+  GLuint query_id = 0;
+  if (gl_timer_query_id_pool_.empty()) {
+    glGenQueries(1, &query_id);
+  } else {
+    query_id = gl_timer_query_id_pool_.top();
+    gl_timer_query_id_pool_.pop();
+  }
+  return query_id;
+}
+
+void GpuProfiler::EnterGlScope(const char* scope_name) {
+  GLuint query_id = TryAllocateGlQueryId();
+  if (query_id != 0) {
+    glQueryCounter(query_id, GL_TIMESTAMP_EXT);
+    pending_gpu_queries_.push_back(
+        GpuTimerQuery(GetSystemClockNs(), scope_name, std::weak_ptr<int64_t>(),
+                      -1, query_id, GpuTimerQuery::kQueryBeginScope));
+  }
+}
+
+void GpuProfiler::LeaveGlScope(const char* scope_name,
+                               std::weak_ptr<int64_t> duration_ns,
+                               int print_period) {
+  GLuint query_id = TryAllocateGlQueryId();
+  if (query_id != 0) {
+    glQueryCounter(query_id, GL_TIMESTAMP_EXT);
+    pending_gpu_queries_.push_back(
+        GpuTimerQuery(GetSystemClockNs(), scope_name, duration_ns, print_period,
+                      query_id, GpuTimerQuery::kQueryEndScope));
+  }
+}
+
+void GpuProfiler::SyncGlTimebase() {
+  if (!sync_with_cpu_time_) {
+    return;
+  }
+
+  // Clear disjoint error status.
+  // This error status indicates that we need to ignore the result of the
+  // timer query because of some kind of disjoint GPU event such as heat
+  // throttling.
+  GLint disjoint = 0;
+  glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint);
+
+  // Try to get the current GL timestamp. Since the GPU can supposedly fail to
+  // produce a timestamp occasionally we try a few times before giving up.
+  int attempts_remaining = 3;
+  do {
+    GLint64 gl_timestamp = 0;
+    glGetInteger64v(GL_TIMESTAMP_EXT, &gl_timestamp);
+    gl_timestamp = AdjustTimerQueryToNs(gl_timestamp);
+
+    // Now get the CPU timebase.
+    int64_t cpu_timebase_ns = static_cast<int64_t>(GetSystemClockNs());
+
+    disjoint = 0;
+    glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint);
+    if (!disjoint) {
+      gl_timer_offset_ns_ = cpu_timebase_ns - gl_timestamp;
+      break;
+    }
+    ALOGW("WARNING: Skipping disjoint GPU timestamp");
+  } while (--attempts_remaining > 0);
+
+  if (attempts_remaining == 0) {
+    ALOGE("ERROR: Failed to sync GL timebase due to disjoint results\n");
+    gl_timer_offset_ns_ = 0;
+  }
+}
+
+void GpuProfiler::QueryFrameBegin() {
+  GLuint begin_frame_id = TryAllocateGlQueryId();
+  if (begin_frame_id != 0) {
+    glQueryCounter(begin_frame_id, GL_TIMESTAMP_EXT);
+    pending_gpu_queries_.push_back(
+        GpuTimerQuery(GetSystemClockNs(), 0, std::weak_ptr<int64_t>(), -1,
+                      begin_frame_id, GpuTimerQuery::kQueryBeginFrame));
+  }
+}
+
+void GpuProfiler::PollGlTimerQueries() {
+  if (!enabled()) {
+    return;
+  }
+
+#ifdef ENABLE_DISJOINT_TIMER_IGNORING
+  bool has_checked_disjoint = false;
+  bool was_disjoint = false;
+#endif
+  for (;;) {
+    if (pending_gpu_queries_.empty()) {
+      // No queries pending.
+      return;
+    }
+
+    GpuTimerQuery query = pending_gpu_queries_.front();
+
+    GLint available = 0;
+    glGetQueryObjectiv(query.query_id, GL_QUERY_RESULT_AVAILABLE_EXT,
+                       &available);
+    if (!available) {
+      // No queries available.
+      return;
+    }
+
+    // Found an available query, remove it from pending queue.
+    pending_gpu_queries_.pop_front();
+    gl_timer_query_id_pool_.push(query.query_id);
+
+#ifdef ENABLE_DISJOINT_TIMER_IGNORING
+    if (!has_checked_disjoint) {
+      // Check if we need to ignore the result of the timer query because
+      // of some kind of disjoint GPU event such as heat throttling.
+      // If so, we ignore all events that are available during this loop.
+      has_checked_disjoint = true;
+      GLint disjoint_occurred = 0;
+      glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_occurred);
+      was_disjoint = !!disjoint_occurred;
+      if (was_disjoint) {
+        ALOGW("Skipping disjoint GPU events");
+      }
+    }
+
+    if (was_disjoint) {
+      continue;
+    }
+#endif
+
+    GLint64 timestamp_ns = 0;
+    glGetQueryObjecti64v(query.query_id, GL_QUERY_RESULT_EXT, &timestamp_ns);
+    timestamp_ns = AdjustTimerQueryToNs(timestamp_ns);
+
+    int64_t adjusted_timestamp_ns;
+
+    if (sync_with_cpu_time_) {
+      adjusted_timestamp_ns = timestamp_ns + gl_timer_offset_ns_;
+
+      if (query.type == GpuTimerQuery::kQueryBeginFrame ||
+          query.type == GpuTimerQuery::kQueryBeginScope) {
+        if (adjusted_timestamp_ns < query.timestamp_ns) {
+          // GPU clock is behind, adjust our offset to correct it.
+          gl_timer_offset_ns_ += query.timestamp_ns - adjusted_timestamp_ns;
+          adjusted_timestamp_ns = query.timestamp_ns;
+        }
+      }
+    } else {
+      adjusted_timestamp_ns = timestamp_ns;
+    }
+
+    switch (query.type) {
+      case GpuTimerQuery::kQueryBeginFrame:
+        break;
+      case GpuTimerQuery::kQueryBeginScope:
+        events_[query.scope_name].enter(adjusted_timestamp_ns);
+        break;
+      case GpuTimerQuery::kQueryEndScope:
+        events_[query.scope_name].leave(adjusted_timestamp_ns, query.scope_name,
+                                        query.duration_ns, query.print_period);
+        break;
+    }
+  }
+}
+
+void GpuProfiler::FinishGlTimerQueries() {
+  if (!enabled()) {
+    return;
+  }
+
+  glFlush();
+  PollGlTimerQueries();
+  int max_iterations = 100;
+  while (!pending_gpu_queries_.empty()) {
+    if (--max_iterations <= 0) {
+      ALOGE("Error: GL timer queries failed to finish.");
+      break;
+    }
+    PollGlTimerQueries();
+    usleep(1000);
+  }
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/blur.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/blur.h
new file mode 100644
index 0000000..c1c2b91
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/blur.h
@@ -0,0 +1,86 @@
+#ifndef ANDROID_DVR_GRAPHICS_BLUR_H_
+#define ANDROID_DVR_GRAPHICS_BLUR_H_
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+
+#include <algorithm>
+#include <vector>
+
+#include <private/dvr/ring_buffer.h>
+
+namespace android {
+namespace dvr {
+
+class Blur {
+ public:
+  // Construct a blur kernel for GL that works on source textures of the given
+  // size. The given source_texture is configured for linear filtering.
+  // |source_texture_target| is for |source_texture| while
+  // |target_texture_target| is used for all the intermediate and output
+  // buffers.
+  // |num_blur_outputs| determines how many blurs this instance can be used for
+  // in a single frame.
+  Blur(int w, int h, GLuint source_texture, GLint source_texture_target,
+       GLint target_texture_target, bool is_external, EGLDisplay display,
+       int num_blur_outputs);
+  ~Blur();
+
+  // Place all output textures back into the FBO pool for a new frame.
+  // Call this at the start of each frame before doing one or more blurs.
+  void StartFrame();
+
+  // Draw a multipass blur from the given source_texture. The resulting texture
+  // is returned. The given source_texture is configured for linear filtering.
+  // A segfault will occur if the application calls DrawBlur more times than
+  // |num_blur_outputs| without calling StartFrame.
+  // It is up to the calling code to change the framebuffer after this method.
+  GLuint DrawBlur(GLuint source_texture);
+
+  float width() const { return width_; }
+  float height() const { return height_; }
+  float scale() const { return scale_; }
+
+  // Set the scale of the blur, usually between 0 and 1. This is only useful for
+  // animation.
+  // At the steady state, the scale should be set to 1. To change the steady
+  // state blur appearance, the kernel patterns in DrawBlur should be modified
+  // instead of using scale.
+  void set_scale(float scale) { scale_ = scale; }
+
+  // Animate the blur by |delta|. Clamp the result between |low| and |high|.
+  // Recommended range is between 0 and 1, but other values will also work.
+  void animate(float delta, float low, float high) {
+    scale_ += delta;
+    scale_ = std::min(high, std::max(low, scale_));
+  }
+
+ private:
+  struct Fbo {
+    Fbo() : fbo(0), renderbuffer(0), texture(0), egl_image(0) {}
+    GLuint fbo;
+    GLuint renderbuffer;
+    GLuint texture;
+    EGLImageKHR egl_image;
+  };
+
+  Fbo CreateFbo(int w, int h, GLuint source_texture, GLint tex_target,
+                bool is_external);
+
+  // EGL display for when target texture format is EGL image.
+  EGLDisplay display_;
+  GLint target_texture_target_;
+  int width_;
+  int height_;
+  Fbo source_fbo_;
+  Fbo fbo_half_;
+  std::vector<Fbo> fbo_q_;
+  RingBuffer<Fbo> fbo_q_free_;
+  float scale_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_GRAPHICS_BLUR_H_
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/debug_text.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/debug_text.h
new file mode 100644
index 0000000..bbe891b
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/debug_text.h
@@ -0,0 +1,44 @@
+#ifndef ANDROID_DVR_GRAPHICS_FPS_GRAPH_H
+#define ANDROID_DVR_GRAPHICS_FPS_GRAPH_H
+
+#include <private/dvr/graphics/mesh.h>
+#include <private/dvr/graphics/shader_program.h>
+
+namespace android {
+namespace dvr {
+
+// Debug text class that draws small text with Open GL.
+class DebugText {
+ public:
+  DebugText(int max_digits, int viewport_width, int viewport_height);
+  ~DebugText();
+
+  void SetViewportSize(int viewport_width, int viewport_height);
+
+  // Draw text at given screen-space location, scale and color.
+  // A |scale| of 1.0 means 1:1 pixel mapping with current viewport size.
+  // If |stereo_offset| is not zero, the string will be rendered again
+  // with the given offset for stereo rendering. The stereo axis can be on
+  // screenspace x or y axis, which is given by |axis| as 0 or 1,
+  // respectively. |axis| also determines the direction that text is rendered.
+  void Draw(float x, float y, float scale, float r, float g, float b, float a,
+            const char* str, float stereo_offset, uint8_t axis);
+
+  // Helper that draws green text at render target resolution.
+  void Draw(float x, float y, const char* str, float stereo_offset,
+            uint8_t axis) {
+    Draw(x, y, 1.0f, 0, 1, 0, 1, str, stereo_offset, axis);
+  }
+
+ private:
+  int max_digits_;
+  vec2 pixel_size_screen_space_;
+  ShaderProgram shader_;
+  GLuint texture_;
+  Mesh<vec2, vec2> mesh_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_GRAPHICS_FPS_GRAPH_H
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/egl_image.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/egl_image.h
new file mode 100644
index 0000000..59de61e
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/egl_image.h
@@ -0,0 +1,21 @@
+#ifndef ANDROID_DVR_GRAPHICS_EGL_IMAGE_H_
+#define ANDROID_DVR_GRAPHICS_EGL_IMAGE_H_
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+namespace android {
+namespace dvr {
+
+// Create an EGLImage with texture storage defined by the given format and
+// usage flags.
+// For example, to create an RGBA texture for rendering to, specify:
+//   format = HAL_PIXEL_FORMAT_RGBA_8888;
+//   usage = GRALLOC_USAGE_HW_FB | GRALLOC_USAGE_HW_RENDER;
+EGLImageKHR CreateEglImage(EGLDisplay dpy, int width, int height, int format,
+                           int usage);
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_GRAPHICS_EGL_IMAGE_H_
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/gpu_profiler.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/gpu_profiler.h
new file mode 100644
index 0000000..2905d00
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/gpu_profiler.h
@@ -0,0 +1,215 @@
+#ifndef ANDROID_DVR_GPU_PROFILER_H_
+#define ANDROID_DVR_GPU_PROFILER_H_
+
+// This file contains classes and macros related to run-time performance
+// profiling of GPU processing.
+
+#include <deque>
+#include <map>
+#include <memory>
+#include <stack>
+#include <vector>
+
+#include <private/dvr/graphics/vr_gl_extensions.h>
+
+namespace android {
+namespace dvr {
+
+// While enabled, GL commands will be submitted each frame to query timestamps
+// of GPU workloads that have been traced using the ION_PROFILE_GPU macro
+// defined below.
+//
+// Basic workflow:
+//  - have the app framework call PollGlTimerQueries at the start of each frame.
+//  - place ION_PROFILE_GPU("MyGlWorkload") at the start of code scopes where
+//    GL draw commands are performed that you want to trace.
+class GpuProfiler {
+ public:
+  // Gets the GpuProfiler singleton instance.
+  static GpuProfiler* Get();
+
+  GpuProfiler();
+  ~GpuProfiler();
+
+  bool IsGpuProfilingSupported() const;
+
+  // Enables runtime GPU tracing. While enabled, GL commands will be submitted
+  // each frame to query timestamps of GPU workloads that have been traced using
+  // one of the TRACE_GPU* macros defined below.
+  void SetEnableGpuTracing(bool enabled) { enable_gpu_tracing_ = enabled; }
+
+  bool enabled() const { return enable_gpu_tracing_; }
+
+  // Attempt to keep the GPU times in sync with CPU times.
+  void SetEnableSyncCpuTime(bool enabled) { sync_with_cpu_time_ = enabled; }
+
+  // When sync cpu time is enabled because of mobile GPU timer query issues,
+  // it can sometimes help to put a beginning timer query at the start of the
+  // frame to sync the CPU time when GPU work begins.
+  void QueryFrameBegin();
+
+  // Polls (non-blocking) for completed GL timer query data and adds events into
+  // the trace buffer. Must call once close to the start of each frame.
+  void PollGlTimerQueries();
+
+  // Call glFinish and process all pending timer queries.
+  void FinishGlTimerQueries();
+
+  // Records the beginning of a scoped GL trace event.
+  void EnterGlScope(const char* scope_name);
+
+  // Records the end of a scoped GL trace event.
+  void LeaveGlScope(const char* scope_name, std::weak_ptr<int64_t> duration_ns,
+                    int print_period);
+
+ private:
+  // Data to queue the pending GPU timer queries that need to be polled
+  // for completion.
+  struct GpuTimerQuery {
+    enum QueryType {
+      kQueryBeginFrame,
+      kQueryBeginScope,
+      kQueryEndScope,
+    };
+
+    // scope_id is only required for kQueryBeginScope query types.
+    GpuTimerQuery(int64_t timestamp_ns, const char* scope_name,
+                  std::weak_ptr<int64_t> duration_ns, int print_period,
+                  GLuint query_id, QueryType type)
+        : timestamp_ns(timestamp_ns),
+          scope_name(scope_name),
+          duration_ns(duration_ns),
+          print_period(print_period),
+          query_id(query_id),
+          type(type) {}
+
+    int64_t timestamp_ns;
+    const char* scope_name;
+    std::weak_ptr<int64_t> duration_ns;
+    int print_period;
+    GLuint query_id;
+    QueryType type;
+  };
+
+  // Struct that tracks timing data for a particular trace scope.
+  struct TimerData {
+    void reset();
+
+    // Print the profiling data.
+    void print(const char* name) const;
+
+    // Enter a scope, records the timestamp for later matching with leave.
+    void enter(int64_t timestamp_ns);
+
+    // Compute the elapsed time for the scope.
+    void leave(int64_t timestamp_ns, const char* name,
+               std::weak_ptr<int64_t> duration_ns, int print_period);
+
+    int64_t total_elapsed_ns = 0;
+    int64_t enter_timestamp_ns = 0;
+    int num_events = 0;
+  };
+
+  // Synchronises the GL timebase with the CallTraceManager timebase.
+  void SyncGlTimebase();
+
+  // Returns a GL timer query ID if possible. Otherwise returns 0.
+  GLuint TryAllocateGlQueryId();
+
+  // Setting for enabling GPU tracing.
+  bool enable_gpu_tracing_;
+
+  // Setting for synchronizing GPU timestamps with CPU time.
+  bool sync_with_cpu_time_;
+
+  // Nanosecond offset to the GL timebase to compute the CallTraceManager time.
+  int64_t gl_timer_offset_ns_;
+
+  std::map<const char*, TimerData> events_;
+
+  // For GPU event TraceRecords, this tracks the pending queries that will
+  // be asynchronously polled (in order) and then added to the TraceRecorder
+  // buffer with the GPU timestamps.
+  std::deque<GpuTimerQuery> pending_gpu_queries_;
+
+  // Available ids for use with GLTimerQuery as needed. This will generally
+  // reach a steady state after a few frames. Always push and pop from the back
+  // to avoid shifting the vector.
+  std::stack<GLuint, std::vector<GLuint> > gl_timer_query_id_pool_;
+};
+
+// Traces the GPU start and end times of the GL commands submitted in the
+// same scope. Typically used via the TRACE_GPU macro.
+class ScopedGlTracer {
+ public:
+  ScopedGlTracer(const char* name, std::weak_ptr<int64_t> duration_ns,
+                 int print_period, bool finish)
+      : name_(name),
+        duration_ns_(duration_ns),
+        print_period_(print_period),
+        is_finish_(finish) {
+    GpuProfiler* profiler = GpuProfiler::Get();
+    if (profiler->enabled()) {
+      profiler->EnterGlScope(name);
+    }
+  }
+
+  ~ScopedGlTracer() {
+    GpuProfiler* profiler = GpuProfiler::Get();
+    if (profiler->enabled()) {
+      profiler->LeaveGlScope(name_, duration_ns_, print_period_);
+      if (is_finish_) {
+        GpuProfiler::Get()->FinishGlTimerQueries();
+      }
+    }
+  }
+
+ private:
+  const char* name_;
+  std::weak_ptr<int64_t> duration_ns_;
+  int print_period_;
+  bool is_finish_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#define PROFILING_PASTE1(x, y) x##y
+#define PROFILING_PASTE2(x, y) PROFILING_PASTE1(x, y)
+#define PROFILING_PASTE3(x) PROFILING_PASTE2(x, __LINE__)
+
+// This macro can be used in any GL operation scope to trace the resulting
+// GPU work. The argument must be a literal string. Specify the number of frames
+// to wait before printing an average result in the num_frames_period argument.
+#define TRACE_GPU_PRINT(group_name, num_frames_period)        \
+  (void)group_name " must be a literal string.";              \
+  android::dvr::ScopedGlTracer PROFILING_PASTE3(gpu_tracer_)( \
+      group_name, std::weak_ptr<int64_t>(), num_frames_period, false)
+
+// This macro can be used in any GL operation scope to trace the resulting
+// GPU work. The argument must be a literal string. The duration parameter
+// is a weak_ptr to a int64_t that will receive duration values asynchronously
+// during calls to PollGlTimerQueries.
+#define TRACE_GPU(group_name, duration_ns_weak_ptr)           \
+  (void)group_name " must be a literal string.";              \
+  android::dvr::ScopedGlTracer PROFILING_PASTE3(gpu_tracer_)( \
+      group_name, duration_ns_weak_ptr, -1, false)
+
+// This macro can be used in any GL operation scope to trace the resulting
+// GPU work. The argument must be a literal string. Specify the number of frames
+// to wait before printing an average result in the num_frames_period argument.
+#define TRACE_GPU_PRINT_FINISH(group_name)                    \
+  (void)group_name " must be a literal string.";              \
+  android::dvr::ScopedGlTracer PROFILING_PASTE3(gpu_tracer_)( \
+      group_name, std::weak_ptr<int64_t>(), 1, true)
+
+// This macro can be used in any GL operation scope to trace the resulting
+// GPU work. The argument must be a literal string. The duration parameter
+// is a weak_ptr to a int64_t that will receive duration values asynchronously
+// during calls to PollGlTimerQueries.
+#define TRACE_GPU_FINISH(group_name, duration_ns_weak_ptr)    \
+  (void)group_name " must be a literal string.";              \
+  android::dvr::ScopedGlTracer PROFILING_PASTE3(gpu_tracer_)( \
+      group_name, duration_ns_weak_ptr, -1, true)
+
+#endif  // ANDROID_DVR_GPU_PROFILER_H_
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/indexed_mesh.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/indexed_mesh.h
new file mode 100644
index 0000000..7e74a75
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/indexed_mesh.h
@@ -0,0 +1,154 @@
+#ifndef ANDROID_DVR_GRAPHICS_INDEXED_MESH_H_
+#define ANDROID_DVR_GRAPHICS_INDEXED_MESH_H_
+
+#include <private/dvr/graphics/vertex_attributes.h>
+#include <private/dvr/types.h>
+
+#include <EGL/egl.h>
+#include <GLES3/gl3.h>
+#include <tuple>
+
+namespace android {
+namespace dvr {
+
+namespace Details {
+
+// We can have 16 and 32bit indices.
+template <typename T>
+GLenum GetIndexType();
+template <>
+inline GLenum GetIndexType<uint16_t>() {
+  return GL_UNSIGNED_SHORT;
+}
+template <>
+inline GLenum GetIndexType<uint32_t>() {
+  return GL_UNSIGNED_INT;
+}
+
+}  // namespace Details
+
+template <typename INDEX_TYPE, typename... Attributes>
+class IndexedMesh {
+ public:
+  static const int attribute_size = sizeof(std::tuple<Attributes...>);
+
+  IndexedMesh() {}
+  IndexedMesh(INDEX_TYPE number_of_vertices, const void* vertices,
+              INDEX_TYPE number_of_indices, const void* indices) {
+    SetVertices(number_of_vertices, vertices, number_of_indices, indices);
+  }
+
+  IndexedMesh(INDEX_TYPE number_of_vertices, const void* vertices,
+              INDEX_TYPE number_of_indices, const void* indices,
+              GLenum element_type) {
+    SetVertices(number_of_vertices, vertices, number_of_indices, indices,
+                element_type);
+  }
+
+  IndexedMesh(IndexedMesh&& to_move) { Swap(to_move); }
+
+  ~IndexedMesh() { DeleteGLData(); }
+
+  IndexedMesh& operator=(IndexedMesh&& to_move) {
+    Swap(to_move);
+    return *this;
+  }
+
+  operator bool() const { return mesh_vbo_ != 0; }
+
+  void Swap(IndexedMesh& to_swap) {
+    std::swap(mesh_vbo_, to_swap.mesh_vbo_);
+    std::swap(mesh_vao_, to_swap.mesh_vao_);
+    std::swap(mesh_ibo_, to_swap.mesh_ibo_);
+    std::swap(number_of_indices_, to_swap.number_of_indices_);
+    std::swap(element_type_, to_swap.element_type_);
+  }
+
+  void Draw() {
+    if (!mesh_vbo_)
+      return;
+
+    glBindVertexArray(mesh_vao_);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh_ibo_);
+
+    glDrawElements(element_type_, number_of_indices_,
+                   Details::GetIndexType<INDEX_TYPE>(), nullptr);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+    glBindVertexArray(0);
+  }
+
+  void SetVertices(INDEX_TYPE number_of_vertices, const void* vertices,
+                   INDEX_TYPE number_of_indices, const void* indices,
+                   GLenum element_type) {
+    element_type_ = element_type;
+    SetVertices(number_of_vertices, vertices, number_of_indices, indices);
+  }
+
+  void SetVertices(INDEX_TYPE number_of_vertices, const void* vertices,
+                   INDEX_TYPE number_of_indices, const void* indices) {
+    DeleteGLData();
+    number_of_indices_ = number_of_indices;
+    glGenBuffers(1, &mesh_vbo_);
+    glGenVertexArrays(1, &mesh_vao_);
+    glGenBuffers(1, &mesh_ibo_);
+    glBindVertexArray(mesh_vao_);
+
+    glBindBuffer(GL_ARRAY_BUFFER, mesh_vbo_);
+    glBufferData(GL_ARRAY_BUFFER, attribute_size * number_of_vertices, vertices,
+                 GL_STATIC_DRAW);
+
+    SetupAttributes();
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh_ibo_);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+                 sizeof(INDEX_TYPE) * number_of_indices_, indices,
+                 GL_STATIC_DRAW);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glBindVertexArray(0);
+  }
+
+  size_t GetAttributesSize() const { return attribute_size; }
+
+ private:
+  IndexedMesh(const IndexedMesh&) = delete;
+  IndexedMesh& operator=(const IndexedMesh&) = delete;
+
+  void DeleteGLData() {
+    if (mesh_vbo_) {
+      glDeleteBuffers(1, &mesh_vbo_);
+      glDeleteVertexArrays(1, &mesh_vao_);
+      glDeleteBuffers(1, &mesh_ibo_);
+      mesh_vbo_ = 0;
+      mesh_vao_ = 0;
+      mesh_ibo_ = 0;
+      number_of_indices_ = 0;
+    }
+  }
+
+  void SetupAttributes() {
+    const auto size = std::tuple_size<std::tuple<Attributes...>>::value;
+    Details::VertexAttribHelper<size - 1, Attributes...>{}();
+  }
+
+ private:
+  GLuint mesh_vbo_ = 0;
+  GLuint mesh_vao_ = 0;
+  GLuint mesh_ibo_ = 0;
+  INDEX_TYPE number_of_indices_ = 0;
+
+  GLenum element_type_ = GL_TRIANGLES;
+};
+
+template <typename... Attributes>
+using Indexed16Mesh = IndexedMesh<uint16_t, Attributes...>;
+
+template <typename... Attributes>
+using Indexed32Mesh = IndexedMesh<uint32_t, Attributes...>;
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_GRAPHICS_INDEXED_MESH_H_
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/mesh.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/mesh.h
new file mode 100644
index 0000000..45bc108
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/mesh.h
@@ -0,0 +1,128 @@
+#ifndef ANDROID_DVR_GRAPHICS_MESH_H_
+#define ANDROID_DVR_GRAPHICS_MESH_H_
+
+#include <private/dvr/graphics/vertex_attributes.h>
+#include <private/dvr/types.h>
+
+#include <EGL/egl.h>
+#include <GLES3/gl3.h>
+#include <tuple>
+
+namespace android {
+namespace dvr {
+
+template <typename... Attributes>
+class Mesh {
+ public:
+  static const int attribute_size = sizeof(std::tuple<Attributes...>);
+
+  Mesh() {}
+
+  Mesh(uint32_t number_of_vertices, const void* vertices) {
+    SetVertices(number_of_vertices, vertices);
+  }
+
+  Mesh(uint32_t number_of_vertices, const void* vertices, GLenum element_type) {
+    SetVertices(number_of_vertices, vertices, element_type);
+  }
+
+  Mesh(Mesh&& to_move) { Swap(to_move); }
+
+  ~Mesh() { DeleteGLData(); }
+
+  Mesh& operator=(const Mesh&& to_move) {
+    Swap(to_move);
+    return *this;
+  }
+
+  operator bool() const { return mesh_vbo_ != 0; }
+
+  void Swap(Mesh& to_swap) {
+    std::swap(mesh_vbo_, to_swap.mesh_vbo_);
+    std::swap(mesh_vao_, to_swap.mesh_vao_);
+    std::swap(number_of_vertices_, to_swap.number_of_vertices_);
+    std::swap(element_type_, to_swap.element_type_);
+  }
+
+  void Draw(uint32_t number_of_vertices) {
+    if (!mesh_vbo_)
+      return;
+
+    glBindVertexArray(mesh_vao_);
+    glDrawArrays(element_type_, 0, number_of_vertices);
+    glBindVertexArray(0);
+  }
+
+  void Draw() { Draw(number_of_vertices_); }
+
+  void SetVertices(uint32_t number_of_vertices, const void* vertices,
+                   GLenum element_type, GLenum usage) {
+    DeleteGLData();
+    element_type_ = element_type;
+    number_of_vertices_ = number_of_vertices;
+    glGenBuffers(1, &mesh_vbo_);
+    glGenVertexArrays(1, &mesh_vao_);
+    glBindVertexArray(mesh_vao_);
+
+    glBindBuffer(GL_ARRAY_BUFFER, mesh_vbo_);
+    glBufferData(GL_ARRAY_BUFFER, attribute_size * number_of_vertices, vertices,
+                 usage);
+
+    SetupAttributes();
+
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glBindVertexArray(0);
+  }
+
+  void SetVertices(uint32_t number_of_vertices, const void* vertices) {
+    SetVertices(number_of_vertices, vertices, element_type_, GL_STATIC_DRAW);
+  }
+
+  void SetVertices(uint32_t number_of_vertices, const void* vertices,
+                   GLenum element_type) {
+    SetVertices(number_of_vertices, vertices, element_type, GL_STATIC_DRAW);
+  }
+
+  std::tuple<Attributes...>* Map(GLbitfield access, int num_vertices) {
+    glBindBuffer(GL_ARRAY_BUFFER, mesh_vbo_);
+    void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0,
+                                 attribute_size * num_vertices, access);
+    return static_cast<std::tuple<Attributes...>*>(ptr);
+  }
+
+  void Unmap() {
+    glUnmapBuffer(GL_ARRAY_BUFFER);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+  }
+
+ private:
+  Mesh(const Mesh&) = delete;
+  Mesh& operator=(const Mesh&) = delete;
+
+  void DeleteGLData() {
+    if (mesh_vbo_) {
+      glDeleteBuffers(1, &mesh_vbo_);
+      glDeleteVertexArrays(1, &mesh_vao_);
+      mesh_vbo_ = 0;
+      mesh_vao_ = 0;
+      number_of_vertices_ = 0;
+    }
+  }
+
+  void SetupAttributes() {
+    const auto size = std::tuple_size<std::tuple<Attributes...>>::value;
+    Details::VertexAttribHelper<size - 1, Attributes...>{}();
+  }
+
+ private:
+  GLuint mesh_vbo_ = 0;
+  GLuint mesh_vao_ = 0;
+  uint32_t number_of_vertices_ = 0;
+
+  GLenum element_type_ = GL_TRIANGLES;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_GRAPHICS_MESH_H_
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/shader_program.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/shader_program.h
new file mode 100644
index 0000000..4218a73
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/shader_program.h
@@ -0,0 +1,75 @@
+#ifndef ANDROID_DVR_SHADER_PROGRAM_H_
+#define ANDROID_DVR_SHADER_PROGRAM_H_
+
+#include <EGL/egl.h>
+#include <GLES3/gl31.h>
+#include <sys/cdefs.h>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace dvr {
+
+// Helper function that allows you to write a shader as a Lambda.  This allows
+// an IDE to syntax highlight the contents of a shader, as well as preventing
+// quotations on each line. Usage: std::string vs = SHADER0([]() { ... });
+template <size_t size>
+std::string StripLambda(const char (&shader)[size]) {
+  return std::string(shader + 6, shader + size - 2);
+}
+
+#define SHADER0(Src) ::android::dvr::StripLambda(#Src)
+
+// Helper function that takes a shader source string containing %0, %1, %n,
+// tokens and replaces them with replacements[0], replacements[1],
+// replacements[n].  For example:
+// shader = "{
+//   uniform vec2 %0;
+//   %1
+//   ...
+//     %0.x = 1.0; ...
+//     %1(%0);
+// }"
+// -> %0 = "myVarName", %1 = "void f(vec2 v) { ... }"
+std::string ComposeShader(const std::string& shader_code,
+                          const std::vector<std::string>& replacements);
+
+class ShaderProgram {
+ public:
+  ShaderProgram();
+  ShaderProgram(const std::string& vertext_source,
+                const std::string& fragment_source);
+  ShaderProgram(ShaderProgram&&);
+  ~ShaderProgram();
+
+  ShaderProgram& operator=(ShaderProgram&&);
+
+  void Link(const std::string& vertext_source,
+            const std::string& fragment_source);
+
+  void Link(const std::string& compute_source);
+
+  void Use() const;
+
+  GLuint GetProgram() const { return program_; }
+  GLuint GetUniformLocation(const GLchar* name) const {
+    return glGetUniformLocation(program_, name);
+  }
+  GLuint GetAttribLocation(const GLchar* name) const {
+    return glGetAttribLocation(program_, name);
+  }
+
+  bool IsUsable() const { return program_ != 0; }
+  explicit operator bool() const { return IsUsable(); }
+
+ private:
+  ShaderProgram(const ShaderProgram&) = delete;
+  ShaderProgram& operator=(const ShaderProgram&) = delete;
+
+  GLuint program_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_SHADER_PROGRAM_H_
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/timer_query.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/timer_query.h
new file mode 100644
index 0000000..11d4d01
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/timer_query.h
@@ -0,0 +1,52 @@
+#ifndef ANDROID_DVR_GRAPHICS_TIMER_QUERY_H_
+#define ANDROID_DVR_GRAPHICS_TIMER_QUERY_H_
+
+#include <GLES3/gl3.h>
+
+namespace android {
+namespace dvr {
+
+// Class used to asynchronously query time between draw calls on gpu.
+class TimerQuery {
+ public:
+  TimerQuery();
+  ~TimerQuery();
+
+  // Marks the start of the timer on gpu.
+  void Begin();
+
+  // Marks the end of the timer on gpu.
+  void End();
+
+  // Gets the time that has passed from call to Begin to End.
+  // Should be called only after the frame has been presented (after the call to
+  // swapbuffers).
+  double GetTimeInMS();
+
+ private:
+  // Generates OpenGL query object.
+  void Init();
+  // Deletes OpenGL query object.
+  void Delete();
+
+  GLuint query_ = 0;
+
+  friend class SyncTimerQuery;
+};
+
+// Simplification of TimerQuery that allows to synchronously query time used
+// for draw calls on gpu by doing glFlush and stalling cpu.
+class SyncTimerQuery {
+ public:
+  SyncTimerQuery();
+
+  double FlushAndGetTimeInMS();  // Note: This WILL cause a glFlush()
+
+ private:
+  TimerQuery timer_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_GRAPHICS_TIMER_QUERY_H_
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/vertex_attributes.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/vertex_attributes.h
new file mode 100644
index 0000000..dac5b64
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/vertex_attributes.h
@@ -0,0 +1,64 @@
+#ifndef ANDROID_DVR_GRAPHICS_VERTEX_ATTRIBUTES_H_
+#define ANDROID_DVR_GRAPHICS_VERTEX_ATTRIBUTES_H_
+
+#include <private/dvr/types.h>
+
+#include <EGL/egl.h>
+#include <GLES3/gl3.h>
+#include <tuple>
+
+namespace android {
+namespace dvr {
+
+namespace Details {
+
+// Set up the vertex attributes by iterating over the variadic template
+// parameters.  The supported attributes are the GetSize and GetType
+// specializations.
+// clang-format off
+template<typename T> GLint GetSize();
+template<> inline GLint GetSize<vec2>() { return 2; }
+template<> inline GLint GetSize<vec3>() { return 3; }
+template<> inline GLint GetSize<vec4>() { return 4; }
+
+template<typename T> GLenum GetType();
+template<> inline GLenum GetType<vec2>() { return GL_FLOAT; }
+template<> inline GLenum GetType<vec3>() { return GL_FLOAT; }
+template<> inline GLenum GetType<vec4>() { return GL_FLOAT; }
+// clang-format on
+
+template <typename T>
+void VertexAttrib(GLuint index, GLsizei stride, const GLvoid* pointer) {
+  glVertexAttribPointer(index, GetSize<T>(), GetType<T>(), GL_FALSE, stride,
+                        pointer);
+  glEnableVertexAttribArray(index);
+}
+
+// Recursion variadic template parameter iterator.
+template <int index, typename... Ts>
+struct VertexAttribHelper {
+  using tuple = std::tuple<Ts...>;
+  size_t operator()() {
+    size_t offset = VertexAttribHelper<index - 1, Ts...>{}();
+    using type = typename std::tuple_element<index, tuple>::type;
+    VertexAttrib<type>(index, sizeof(tuple), reinterpret_cast<void*>(offset));
+    return offset + sizeof(type);
+  }
+};
+
+// Recursion stop point.
+template <typename... Ts>
+struct VertexAttribHelper<0, Ts...> {
+  using tuple = std::tuple<Ts...>;
+  size_t operator()() {
+    using type = typename std::tuple_element<0, tuple>::type;
+    VertexAttrib<type>(0, sizeof(tuple), nullptr);
+    return sizeof(type);
+  }
+};
+}  // namespace Details
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_GRAPHICS_VERTEX_ATTRIBUTES_H_
diff --git a/libs/vr/libdvrgraphics/include/private/dvr/graphics/vr_gl_extensions.h b/libs/vr/libdvrgraphics/include/private/dvr/graphics/vr_gl_extensions.h
new file mode 100644
index 0000000..9635dbb
--- /dev/null
+++ b/libs/vr/libdvrgraphics/include/private/dvr/graphics/vr_gl_extensions.h
@@ -0,0 +1,51 @@
+#ifndef ANDROID_DVR_VR_GL_EXTENSIONS_H_
+#define ANDROID_DVR_VR_GL_EXTENSIONS_H_
+
+// clang-format off
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl31.h>
+#include <GLES3/gl3ext.h>
+// clang-format on
+
+// GL_EXT_disjoint_timer_query API function declarations
+extern PFNGLGETQUERYOBJECTI64VEXTPROC glGetQueryObjecti64v;
+extern PFNGLGETQUERYOBJECTIVEXTPROC glGetQueryObjectiv;
+extern PFNGLQUERYCOUNTEREXTPROC glQueryCounter;
+
+// EXT_buffer_storage:
+extern PFNGLBUFFERSTORAGEEXTPROC glBufferStorage;
+
+typedef void(GL_APIENTRYP PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR)(
+    GLenum target, GLenum attachment, GLuint texture, GLint level,
+    GLint baseViewIndex, GLsizei numViews);
+typedef void(GL_APIENTRYP PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVR)(
+    GLenum target, GLenum attachement, GLuint texture, GLint level,
+    GLsizei samples, GLint baseViewIndex, GLsizei numViews);
+
+extern PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR glFramebufferTextureMultiview;
+extern PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVR
+    glFramebufferTextureMultisampleMultiview;
+
+// QCOM_gralloc_buffer_data and QCOM_shared_buffer
+typedef void(GL_APIENTRY* PFNGLGRALLOCBUFFERDATAQCOM)(GLenum target,
+                                                      GLsizeiptr sizeInBytes,
+                                                      GLvoid* hostPtr,
+                                                      GLint fd);
+typedef void(GL_APIENTRY* PFNGLSHAREDBUFFERCREATEQCOM)(GLsizeiptr sizeInBytes,
+                                                       GLint* outFd);
+typedef void(GL_APIENTRY* PFNGLSHAREDBUFFERDESTROYQCOM)(GLint fd);
+typedef void(GL_APIENTRY* PFNGLSHAREDBUFFERBINDQCOM)(GLenum target,
+                                                     GLsizeiptr sizeInBytes,
+                                                     GLint fd);
+
+extern PFNGLGRALLOCBUFFERDATAQCOM glGrallocBufferDataQCOM;
+extern PFNGLSHAREDBUFFERCREATEQCOM glCreateSharedBufferQCOM;
+extern PFNGLSHAREDBUFFERDESTROYQCOM glDestroySharedBufferQCOM;
+extern PFNGLSHAREDBUFFERBINDQCOM glBindSharedBufferQCOM;
+
+extern "C" void load_gl_extensions();
+
+#endif  // ANDROID_DVR_VR_GL_EXTENSIONS_H_
diff --git a/libs/vr/libdvrgraphics/shader_program.cpp b/libs/vr/libdvrgraphics/shader_program.cpp
new file mode 100644
index 0000000..bf36eff
--- /dev/null
+++ b/libs/vr/libdvrgraphics/shader_program.cpp
@@ -0,0 +1,165 @@
+#include "include/private/dvr/graphics/shader_program.h"
+
+#include <regex>
+#include <sstream>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+
+namespace {
+
+static bool CompileShader(GLuint shader, const std::string& shader_string) {
+  std::string prefix = "";
+  if (!base::StartsWith(shader_string, "#version",
+                        base::CompareCase::SENSITIVE)) {
+    prefix = "#version 310 es\n";
+  }
+  std::string string_with_prefix = prefix + shader_string;
+  const char* shader_str[] = {string_with_prefix.data()};
+  glShaderSource(shader, 1, shader_str, nullptr);
+  glCompileShader(shader);
+
+  GLint success;
+  glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
+  if (!success) {
+    GLchar infoLog[512];
+    glGetShaderInfoLog(shader, 512, nullptr, infoLog);
+    LOG(ERROR) << "Shader Failed to compile: " << *shader_str << " -- "
+               << infoLog;
+    return false;
+  }
+  return true;
+}
+
+static bool LinkProgram(GLuint program, GLuint vertex_shader,
+                        GLuint fragment_shader) {
+  glAttachShader(program, vertex_shader);
+  glAttachShader(program, fragment_shader);
+  glLinkProgram(program);
+
+  // Check for linking errors
+  GLint success;
+  glGetProgramiv(program, GL_LINK_STATUS, &success);
+  if (!success) {
+    GLchar infoLog[512];
+    glGetProgramInfoLog(program, 512, nullptr, infoLog);
+    LOG(ERROR) << "Shader failed to link: " << infoLog;
+    return false;
+  }
+
+  return true;
+}
+
+static bool LinkProgram(GLuint program, GLuint compute_shader) {
+  glAttachShader(program, compute_shader);
+  glLinkProgram(program);
+
+  // Check for linking errors
+  GLint success;
+  glGetProgramiv(program, GL_LINK_STATUS, &success);
+  if (!success) {
+    GLchar infoLog[512];
+    glGetProgramInfoLog(program, 512, nullptr, infoLog);
+    LOG(ERROR) << "Shader failed to link: " << infoLog;
+    return false;
+  }
+
+  return true;
+}
+
+}  // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+ShaderProgram::ShaderProgram() : program_(0) {}
+
+ShaderProgram::ShaderProgram(const std::string& vertext_source,
+                             const std::string& fragment_source)
+    : program_(0) {
+  Link(vertext_source, fragment_source);
+}
+
+ShaderProgram::ShaderProgram(ShaderProgram&& to_move) {
+  std::swap(program_, to_move.program_);
+}
+
+ShaderProgram::~ShaderProgram() {
+  if (program_)
+    glDeleteProgram(program_);
+}
+
+ShaderProgram& ShaderProgram::operator=(ShaderProgram&& to_move) {
+  std::swap(program_, to_move.program_);
+  return *this;
+}
+
+void ShaderProgram::Link(const std::string& vertext_source,
+                         const std::string& fragment_source) {
+  if (program_)
+    glDeleteProgram(program_);
+  program_ = glCreateProgram();
+  GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
+  GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
+
+  bool success = CompileShader(vertex_shader, vertext_source) &&
+                 CompileShader(fragment_shader, fragment_source) &&
+                 LinkProgram(program_, vertex_shader, fragment_shader);
+
+  glDeleteShader(vertex_shader);
+  glDeleteShader(fragment_shader);
+
+  if (!success) {
+    glDeleteProgram(program_);
+    program_ = 0;
+  }
+}
+
+void ShaderProgram::Link(const std::string& compute_source) {
+  if (program_)
+    glDeleteProgram(program_);
+  program_ = glCreateProgram();
+  GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
+
+  bool success =
+      CompileShader(shader, compute_source) && LinkProgram(program_, shader);
+
+  glDeleteShader(shader);
+
+  if (!success) {
+    glDeleteProgram(program_);
+    program_ = 0;
+  }
+}
+
+void ShaderProgram::Use() const { glUseProgram(program_); }
+
+std::string ComposeShader(const std::string& shader_code,
+                          const std::vector<std::string>& variables) {
+  std::stringstream result_stream;
+  std::regex expression("%([0-9]*)");
+  using reg_iter = std::regex_token_iterator<std::string::const_iterator>;
+  reg_iter rend;
+  // match the string and number (drop the %)
+  std::vector<int> submatches = {-1, 1};
+  reg_iter reg(shader_code.begin(), shader_code.end(), expression, submatches);
+  bool is_even = true;
+  while (reg != rend) {
+    if (is_even) {
+      // even entries is the code between the %n's
+      result_stream << *reg;
+    } else {
+      // odd entries are the index into the passed in variables.
+      size_t i = static_cast<size_t>(std::stoi(*reg));
+      if (i < variables.size()) {
+        result_stream << variables[i];
+      }
+    }
+    is_even = !is_even;
+    ++reg;
+  }
+  return result_stream.str();
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdvrgraphics/timer_query.cpp b/libs/vr/libdvrgraphics/timer_query.cpp
new file mode 100644
index 0000000..dcc6216
--- /dev/null
+++ b/libs/vr/libdvrgraphics/timer_query.cpp
@@ -0,0 +1,65 @@
+#include "include/private/dvr/graphics/timer_query.h"
+
+#include <GLES2/gl2ext.h>
+#include <base/logging.h>
+
+namespace android {
+namespace dvr {
+
+TimerQuery::TimerQuery() {}
+
+TimerQuery::~TimerQuery() { Delete(); }
+
+void TimerQuery::Init() { glGenQueriesEXT(1, &query_); }
+
+void TimerQuery::Delete() {
+  if (query_) {
+    glDeleteQueriesEXT(1, &query_);
+    query_ = 0;
+  }
+}
+
+void TimerQuery::Begin() {
+  if (query_ == 0) {
+    Init();
+  }
+  glBeginQueryEXT(GL_TIME_ELAPSED_EXT, query_);
+}
+
+void TimerQuery::End() { glEndQueryEXT(GL_TIME_ELAPSED_EXT); }
+
+double TimerQuery::GetTimeInMS() {
+  GLuint64 elapsed_time = 0;
+  glGetQueryObjectui64vEXT(query_, GL_QUERY_RESULT, &elapsed_time);
+  return static_cast<double>(elapsed_time) / 1000000.0;
+}
+
+SyncTimerQuery::SyncTimerQuery() { timer_.Begin(); }
+
+double SyncTimerQuery::FlushAndGetTimeInMS() {
+  if (timer_.query_ == 0) {
+    LOG(ERROR) << "Error: Only call FlushAndGetTimeInMS() once.";
+    return 0.0;
+  }
+  timer_.End();
+  glFlush();
+  GLint done = 0;
+  while (!done) {
+    glGetQueryObjectivEXT(timer_.query_, GL_QUERY_RESULT_AVAILABLE, &done);
+  }
+
+  GLint disjoint_occurred = 0;
+  glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_occurred);
+  if (disjoint_occurred) {
+    LOG(ERROR) << "Disjoint occurred.";
+    timer_.Delete();
+    return 0.0;
+  }
+
+  double elapsed_time = timer_.GetTimeInMS();
+  timer_.Delete();
+  return elapsed_time;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libdvrgraphics/vr_gl_extensions.cpp b/libs/vr/libdvrgraphics/vr_gl_extensions.cpp
new file mode 100644
index 0000000..2c5a698
--- /dev/null
+++ b/libs/vr/libdvrgraphics/vr_gl_extensions.cpp
@@ -0,0 +1,44 @@
+#include "include/private/dvr/graphics/vr_gl_extensions.h"
+
+PFNGLGETQUERYOBJECTI64VEXTPROC glGetQueryObjecti64v = NULL;
+PFNGLGETQUERYOBJECTIVEXTPROC glGetQueryObjectiv = NULL;
+PFNGLQUERYCOUNTEREXTPROC glQueryCounter = NULL;
+PFNGLBUFFERSTORAGEEXTPROC glBufferStorage = NULL;
+PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR glFramebufferTextureMultiview = NULL;
+PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVR
+glFramebufferTextureMultisampleMultiview = NULL;
+
+PFNGLSHAREDBUFFERCREATEQCOM glCreateSharedBufferQCOM = NULL;
+PFNGLSHAREDBUFFERDESTROYQCOM glDestroySharedBufferQCOM = NULL;
+PFNGLSHAREDBUFFERBINDQCOM glBindSharedBufferQCOM = NULL;
+PFNGLGRALLOCBUFFERDATAQCOM glGrallocBufferDataQCOM = NULL;
+
+extern "C" void load_gl_extensions() {
+  if (glGetQueryObjecti64v) {
+    return;
+  }
+  glGetQueryObjecti64v = reinterpret_cast<PFNGLGETQUERYOBJECTI64VEXTPROC>(
+      eglGetProcAddress("glGetQueryObjecti64vEXT"));
+  glGetQueryObjectiv = reinterpret_cast<PFNGLGETQUERYOBJECTIVEXTPROC>(
+      eglGetProcAddress("glGetQueryObjectivEXT"));
+  glQueryCounter = reinterpret_cast<PFNGLQUERYCOUNTEREXTPROC>(
+      eglGetProcAddress("glQueryCounterEXT"));
+  glBufferStorage = reinterpret_cast<PFNGLBUFFERSTORAGEEXTPROC>(
+      eglGetProcAddress("glBufferStorageEXT"));
+
+  glFramebufferTextureMultiview =
+      reinterpret_cast<PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR>(
+          eglGetProcAddress("glFramebufferTextureMultiviewOVR"));
+  glFramebufferTextureMultisampleMultiview =
+      reinterpret_cast<PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVR>(
+          eglGetProcAddress("glFramebufferTextureMultisampleMultiviewOVR"));
+
+  glGrallocBufferDataQCOM = reinterpret_cast<PFNGLGRALLOCBUFFERDATAQCOM>(
+      eglGetProcAddress("glGrallocBufferDataQCOM"));
+  glCreateSharedBufferQCOM = reinterpret_cast<PFNGLSHAREDBUFFERCREATEQCOM>(
+      eglGetProcAddress("glCreateSharedBufferQCOM"));
+  glBindSharedBufferQCOM = reinterpret_cast<PFNGLSHAREDBUFFERBINDQCOM>(
+      eglGetProcAddress("glBindSharedBufferQCOM"));
+  glDestroySharedBufferQCOM = reinterpret_cast<PFNGLSHAREDBUFFERDESTROYQCOM>(
+      eglGetProcAddress("glDestroySharedBufferQCOM"));
+}
diff --git a/libs/vr/libeds/Android.mk b/libs/vr/libeds/Android.mk
new file mode 100644
index 0000000..0345f6d
--- /dev/null
+++ b/libs/vr/libeds/Android.mk
@@ -0,0 +1,89 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	eds.cpp \
+	eds_mesh.cpp \
+	composite_hmd.cpp \
+	cpu_thread_pose_updater.cpp \
+	display_metrics.cpp \
+	distortion_renderer.cpp \
+	lucid_metrics.cpp \
+	lucid_pose_tracker.cpp \
+	lookup_radial_distortion.cpp \
+	polynomial_radial_distortion.cpp
+
+includeFiles += \
+	$(LOCAL_PATH)/include
+
+sharedLibraries := \
+	libbase \
+	libcutils \
+	liblog \
+	libEGL \
+	libGLESv1_CM \
+	libGLESv2 \
+	libvulkan
+
+staticLibraries := \
+	libchrome \
+	libdisplay \
+	libdvrcommon \
+	libdvrgraphics \
+	libsensor \
+	libpdx_default_transport \
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+LOCAL_CFLAGS += -Wno-unused-parameter
+# Enable debug options below to show GL errors and use gdb.
+# LOCAL_CFLAGS += -UNDEBUG -DDEBUG -O0 -g
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_MODULE := libeds
+include $(BUILD_STATIC_LIBRARY)
+
+
+testFiles := \
+  tests/eds_app_tests.cpp
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := eds_app_tests
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+  $(testFiles) \
+
+LOCAL_C_INCLUDES := \
+  $(includeFiles) \
+
+LOCAL_SHARED_LIBRARIES := \
+  libhardware \
+  libsync \
+  $(sharedLibraries) \
+
+LOCAL_STATIC_LIBRARIES := \
+  libgmock_main \
+  libgmock \
+  libdisplay \
+  libeds \
+  libbufferhub \
+  $(staticLibraries) \
+
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/vr/libeds/composite_hmd.cpp b/libs/vr/libeds/composite_hmd.cpp
new file mode 100644
index 0000000..d29cd65
--- /dev/null
+++ b/libs/vr/libeds/composite_hmd.cpp
@@ -0,0 +1,256 @@
+#include "include/private/dvr/composite_hmd.h"
+
+#include <base/logging.h>
+#include <private/dvr/numeric.h>
+
+namespace android {
+namespace dvr {
+
+CompositeHmd::CompositeHmd(const HeadMountMetrics& head_mount_metrics,
+                           const DisplayMetrics& display_metrics)
+    : head_mount_metrics_(head_mount_metrics),
+      display_metrics_(display_metrics) {
+  MetricsChanged();
+}
+
+float CompositeHmd::GetTargetFrameDuration() const {
+  return display_metrics_.GetFrameDurationSeconds();
+}
+
+vec2 CompositeHmd::ComputeDistortedPoint(EyeType eye, vec2 position,
+                                         RgbColorChannel channel) const {
+  position = TransformPoint(eye_tan_angle_from_norm_screen_matrix_[eye], position);
+  vec2 distorted =
+      head_mount_metrics_.GetColorChannelDistortion(channel).Distort(position);
+  return TransformPoint(eye_norm_texture_from_tan_angle_matrix_[eye], distorted);
+}
+
+vec2 CompositeHmd::ComputeInverseDistortedPoint(EyeType eye, vec2 position,
+                                                RgbColorChannel channel) const {
+  position = TransformPoint(eye_norm_texture_from_tan_angle_inv_matrix_[eye], position);
+  vec2 distorted =
+      head_mount_metrics_.GetColorChannelDistortion(channel).DistortInverse(
+          position);
+  return TransformPoint(eye_tan_angle_from_norm_screen_inv_matrix_[eye], distorted);
+}
+
+void CompositeHmd::ComputeDistortedVertex(EyeType eye, vec2 uv_in,
+                                          vec2* vertex_out,
+                                          vec2* uv_out) const {
+  // The mesh vertices holds the shape of the distortion.
+  vec2 vertex_position = ComputeInverseDistortedPoint(eye, uv_in, kRed);
+  *vertex_out = vec2(vertex_position.x() - 0.5f, vertex_position.y() - 0.5f);
+
+  if (uv_out) {
+    // Compute the texture coordinate for each vertex coordinate.
+    // Red's is the inverse of the inverse, skip the calculation and use uv_in.
+    uv_out[kRed] = uv_in;
+    uv_out[kGreen] = ComputeDistortedPoint(eye, vertex_position, kGreen);
+    uv_out[kBlue] = ComputeDistortedPoint(eye, vertex_position, kBlue);
+  }
+}
+
+vec2i CompositeHmd::GetRecommendedRenderTargetSize() const {
+  return recommended_render_target_size_;
+}
+
+Range2i CompositeHmd::GetDisplayRange() const { return display_range_; }
+
+mat4 CompositeHmd::GetEyeFromHeadMatrix(EyeType eye) const {
+  return eye_from_head_matrix_[eye];
+}
+
+FieldOfView CompositeHmd::GetEyeFov(EyeType eye) const { return eye_fov_[eye]; }
+
+Range2i CompositeHmd::GetEyeViewportBounds(EyeType eye) const {
+  return eye_viewport_range_[eye];
+}
+
+void CompositeHmd::SetHeadMountMetrics(
+    const HeadMountMetrics& head_mount_metrics) {
+  // Use the assignement operator to do memberwise copy.
+  head_mount_metrics_ = head_mount_metrics;
+  MetricsChanged();
+}
+
+const HeadMountMetrics& CompositeHmd::GetHeadMountMetrics() const {
+  return head_mount_metrics_;
+}
+
+void CompositeHmd::SetDisplayMetrics(const DisplayMetrics& display_metrics) {
+  // Use the assignment operator to do memberwise copy.
+  display_metrics_ = display_metrics;
+  MetricsChanged();
+}
+
+const DisplayMetrics& CompositeHmd::GetDisplayMetrics() const {
+  return display_metrics_;
+}
+
+void CompositeHmd::MetricsChanged() {
+  // Abbreviations in variable names:
+  //   "vp": viewport
+  //   "ta": tan-angle
+  const HeadMountMetrics& mount = head_mount_metrics_;
+  DisplayMetrics display = display_metrics_;
+
+  if (display.IsPortrait()) {
+    // If we're in portrait mode, toggle the orientation so that all
+    // calculations are done in landscape mode.
+    display.ToggleOrientation();
+  }
+
+  float display_width_meters = display.GetSizeMeters()[0];
+  float display_height_meters = display.GetSizeMeters()[1];
+
+  vec2 pixels_per_meter = vec2(1.0f / display.GetMetersPerPixel()[0],
+                               1.0f / display.GetMetersPerPixel()[1]);
+
+  // virtual_eye_to_screen_dist is the distance from the screen to the eye
+  // after it has been projected through the lens.  This would normally be
+  // slightly different from the distance to the actual eye.
+  float virtual_eye_to_screen_dist = mount.GetVirtualEyeToScreenDistance();
+  float meters_per_tan_angle = virtual_eye_to_screen_dist;
+  vec2 pixels_per_tan_angle = pixels_per_meter * meters_per_tan_angle;
+
+  CHECK_NE(0.0f, display_width_meters);
+  CHECK_NE(0.0f, display_height_meters);
+  CHECK_NE(0.0f, virtual_eye_to_screen_dist);
+
+  // Height of lenses from the bottom of the screen.
+  float lens_y_center = 0;
+  float bottom_dist = 0;
+  float top_dist = 0;
+
+  // bottom_display_dist and top_display_dist represent the distance from the
+  // lens center to the edge of the display.
+  float bottom_display_dist = 0;
+  float top_display_dist = 0;
+  switch (mount.GetVerticalAlignment()) {
+    case HeadMountMetrics::kBottom:
+      lens_y_center =
+          mount.GetTrayToLensDistance() - display.GetBorderSizeMeters();
+      bottom_dist = lens_y_center;
+      top_dist = lens_y_center;
+      bottom_display_dist = lens_y_center;
+      top_display_dist = display_height_meters - lens_y_center;
+      break;
+    case HeadMountMetrics::kCenter:
+      // TODO(hendrikw): This should respect the border size, but since we
+      //                 currently hard code the border size, it would break
+      //                 the distortion on some devices.  Revisit when border
+      //                 size is fixed.
+      lens_y_center = display_height_meters * 0.5f;
+      bottom_dist = lens_y_center;
+      top_dist = lens_y_center;
+      bottom_display_dist = lens_y_center;
+      top_display_dist = lens_y_center;
+      break;
+    case HeadMountMetrics::kTop:
+      lens_y_center = display_height_meters - (mount.GetTrayToLensDistance() -
+                                               display.GetBorderSizeMeters());
+      bottom_dist =
+          mount.GetTrayToLensDistance() - display.GetBorderSizeMeters();
+      top_dist = bottom_dist;
+      bottom_display_dist = lens_y_center;
+      top_display_dist = display_height_meters - lens_y_center;
+      break;
+  }
+
+  float inner_dist = mount.GetScreenCenterToLensDistance();
+  float outer_dist = display_width_meters * 0.5f - inner_dist;
+
+  // We don't take chromatic aberration into account yet for computing FOV,
+  // viewport, etc, so we only use the green channel for now. Note the actual
+  // Distort function *does* implement chromatic aberration.
+  const ColorChannelDistortion& distortion =
+      mount.GetColorChannelDistortion(kGreen);
+
+  vec2 outer_point(outer_dist / virtual_eye_to_screen_dist, 0.0f);
+  vec2 inner_point(inner_dist / virtual_eye_to_screen_dist, 0.0f);
+  vec2 bottom_point(0.0f, bottom_dist / virtual_eye_to_screen_dist);
+  vec2 top_point(0.0f, top_dist / virtual_eye_to_screen_dist);
+
+  float outer_angle = atanf(distortion.Distort(outer_point)[0]);
+  float inner_angle = atanf(distortion.Distort(inner_point)[0]);
+  float bottom_angle = atanf(distortion.Distort(bottom_point)[1]);
+  float top_angle = atanf(distortion.Distort(top_point)[1]);
+
+  for (EyeType eye : {kLeftEye, kRightEye}) {
+    const FieldOfView max_fov = mount.GetEyeMaxFov(eye);
+    float left_angle = (eye == kLeftEye) ? outer_angle : inner_angle;
+    float right_angle = (eye == kLeftEye) ? inner_angle : outer_angle;
+
+    eye_fov_[eye] = FieldOfView(std::min(left_angle, max_fov.GetLeft()),
+                                std::min(right_angle, max_fov.GetRight()),
+                                std::min(bottom_angle, max_fov.GetBottom()),
+                                std::min(top_angle, max_fov.GetTop()));
+
+    vec2 texture_vp_ta_p1 =
+        vec2(-tanf(eye_fov_[eye].GetLeft()), -tanf(eye_fov_[eye].GetBottom()));
+    vec2 texture_vp_ta_p2 =
+        vec2(tanf(eye_fov_[eye].GetRight()), tanf(eye_fov_[eye].GetTop()));
+    vec2 texture_vp_size_ta = texture_vp_ta_p2 - texture_vp_ta_p1;
+
+    vec2 texture_vp_sizef_pixels =
+        texture_vp_size_ta.array() * pixels_per_tan_angle.array();
+
+    vec2i texture_vp_size_pixels =
+        vec2i(static_cast<int32_t>(roundf(texture_vp_sizef_pixels[0])),
+              static_cast<int32_t>(roundf(texture_vp_sizef_pixels[1])));
+    int vp_start_x =
+        (eye == kLeftEye) ? 0 : eye_viewport_range_[kLeftEye].p2[0];
+
+    eye_viewport_range_[eye] =
+        Range2i::FromSize(vec2i(vp_start_x, 0), texture_vp_size_pixels);
+    float left_dist = (eye == kLeftEye) ? outer_dist : inner_dist;
+    float right_dist = (eye == kLeftEye) ? inner_dist : outer_dist;
+    vec2 screen_ta_p1(-left_dist / virtual_eye_to_screen_dist,
+                      -bottom_display_dist / virtual_eye_to_screen_dist);
+    vec2 screen_ta_p2(right_dist / virtual_eye_to_screen_dist,
+                      top_display_dist / virtual_eye_to_screen_dist);
+    vec2 screen_ta_size = screen_ta_p2 - screen_ta_p1;
+
+    // Align the tan angle coordinates to the nearest pixel.  This will ensure
+    // that the optical center doesn't straddle multiple pixels.
+    // TODO(hendrikw): verify that this works correctly for Daydream View.
+    vec2 tan_angle_per_pixel(screen_ta_size.array() /
+                             texture_vp_size_pixels.cast<float>().array());
+    vec2 pixel_p1(screen_ta_p1.array() / tan_angle_per_pixel.array());
+    vec2 pixel_shift(roundf(pixel_p1.x()) - pixel_p1.x(),
+                     roundf(pixel_p1.y()) - pixel_p1.y());
+    screen_ta_p1 +=
+        (tan_angle_per_pixel.array() * pixel_shift.array()).matrix();
+    screen_ta_p2 +=
+        (tan_angle_per_pixel.array() * pixel_shift.array()).matrix();
+
+    // Calculate the transformations needed for the distortions.
+    eye_tan_angle_from_norm_screen_matrix_[eye] =
+        TranslationMatrix(vec2(screen_ta_p1)) *
+        ScaleMatrix(screen_ta_size);
+    eye_tan_angle_from_norm_screen_inv_matrix_[eye] =
+        eye_tan_angle_from_norm_screen_matrix_[eye].inverse();
+
+    eye_norm_texture_from_tan_angle_inv_matrix_[eye] =
+        TranslationMatrix(texture_vp_ta_p1) *
+        ScaleMatrix(texture_vp_size_ta);
+    eye_norm_texture_from_tan_angle_matrix_[eye] =
+        eye_norm_texture_from_tan_angle_inv_matrix_[eye].inverse();
+  }
+  vec2i left_vp_size = eye_viewport_range_[kLeftEye].GetSize();
+  vec2i right_vp_size = eye_viewport_range_[kRightEye].GetSize();
+
+  recommended_render_target_size_ =
+      vec2i(left_vp_size[0] + right_vp_size[0],
+            std::max(left_vp_size[1], right_vp_size[1]));
+
+  display_range_ = Range2i::FromSize(vec2i(0, 0), display.GetSizePixels());
+
+  eye_from_head_matrix_[kLeftEye] = Eigen::Translation3f(
+      vec3(mount.GetScreenCenterToLensDistance(), 0.0f, 0.0f));
+  eye_from_head_matrix_[kRightEye] = Eigen::Translation3f(
+      vec3(-mount.GetScreenCenterToLensDistance(), 0.0f, 0.0f));
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libeds/cpu_thread_pose_updater.cpp b/libs/vr/libeds/cpu_thread_pose_updater.cpp
new file mode 100644
index 0000000..5b8a734
--- /dev/null
+++ b/libs/vr/libeds/cpu_thread_pose_updater.cpp
@@ -0,0 +1,86 @@
+#include "include/private/dvr/cpu_thread_pose_updater.h"
+
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#define ATRACE_TAG ATRACE_TAG_INPUT
+#include <utils/Trace.h>
+
+#include <private/dvr/clock_ns.h>
+#include <private/dvr/debug.h>
+
+namespace android {
+namespace dvr {
+
+CpuThreadPoseUpdater::CpuThreadPoseUpdater()
+    : stop_request_(false), update_period_us_(0), count_(0) {}
+
+CpuThreadPoseUpdater::~CpuThreadPoseUpdater() { StopAndJoin(); }
+
+void CpuThreadPoseUpdater::Start(volatile RawPosePair* mapped_pose_buffer,
+                                 int period_us) {
+  mapped_pose_buffer_ = mapped_pose_buffer;
+  update_period_us_ = period_us;
+  stop_request_ = false;
+
+  // First buffer is odd (starts at 1), second is even (starts at 2).
+  count_ = 0;
+  mapped_pose_buffer_->pose1.Reset(++count_);
+  mapped_pose_buffer_->pose2.Reset(++count_);
+
+  update_thread_ = std::thread(&CpuThreadPoseUpdater::UpdateThread, this);
+}
+
+void CpuThreadPoseUpdater::StopAndJoin() {
+  stop_request_ = true;
+  if (update_thread_.joinable()) {
+    update_thread_.join();
+  }
+}
+
+void CpuThreadPoseUpdater::UpdateThread() {
+  prctl(PR_SET_NAME, reinterpret_cast<intptr_t>("CpuPoseUpdater"),
+        0, 0, 0);
+
+  ATRACE_NAME(__PRETTY_FUNCTION__);
+  for (;;) {
+    if (stop_request_) {
+      break;
+    }
+
+    ++count_;
+
+    // Choose the writable pose based on whether count is odd or even.
+    volatile RawPose* out_pose = nullptr;
+    if (count_ & 1) {
+      out_pose = &mapped_pose_buffer_->pose1;
+    } else {
+      out_pose = &mapped_pose_buffer_->pose2;
+    }
+
+    {
+      ATRACE_NAME("GetPose");
+      Posef pose = pose_tracker_.GetPose(GetSystemClockNs());
+      out_pose->qx = pose.GetRotation().x();
+      out_pose->qy = pose.GetRotation().y();
+      out_pose->qz = pose.GetRotation().z();
+      out_pose->qw = pose.GetRotation().w();
+      out_pose->px = pose.GetPosition()[0];
+      out_pose->py = pose.GetPosition()[1];
+      out_pose->pz = pose.GetPosition()[2];
+      // Atomically store the count so that it hits memory last:
+      out_pose->count.store(count_, std::memory_order_release);
+    }
+
+    // Sleep to simulate the IMU update process.
+    usleep(update_period_us_);
+    // TODO(jbates) sleep_for returns immediately, we need to fix our toolchain!
+    // int64_t c1 = GetSystemClockNs();
+    // std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    // int64_t c2 = GetSystemClockNs();
+    // fprintf(stderr, "%lld us\n", (long long)(c2 - c1) / 1000);
+  }
+}
+
+}  // namesapce dvr
+}  // namesapce android
diff --git a/libs/vr/libeds/display_metrics.cpp b/libs/vr/libeds/display_metrics.cpp
new file mode 100644
index 0000000..e129395
--- /dev/null
+++ b/libs/vr/libeds/display_metrics.cpp
@@ -0,0 +1,30 @@
+#include "include/private/dvr/display_metrics.h"
+
+namespace android {
+namespace dvr {
+
+DisplayMetrics::DisplayMetrics(vec2i size_pixels, vec2 meters_per_pixel,
+                               float border_size_meters,
+                               float frame_duration_seconds,
+                               DisplayOrientation orientation)
+    : size_pixels_(size_pixels),
+      meters_per_pixel_(meters_per_pixel),
+      border_size_meters_(border_size_meters),
+      frame_duration_seconds_(frame_duration_seconds),
+      orientation_(orientation) {}
+
+void DisplayMetrics::ToggleOrientation() {
+  std::swap(size_pixels_[0], size_pixels_[1]);
+  std::swap(meters_per_pixel_[0], meters_per_pixel_[1]);
+  if (orientation_ == DisplayOrientation::kPortrait)
+    orientation_ = DisplayOrientation::kLandscape;
+  else
+    orientation_ = DisplayOrientation::kPortrait;
+}
+
+DisplayMetrics::DisplayMetrics()
+    : DisplayMetrics(vec2i(0, 0), vec2(0.0f, 0.0f), 0.0f, 0.0f,
+                     DisplayOrientation::kLandscape) {}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libeds/distortion_renderer.cpp b/libs/vr/libeds/distortion_renderer.cpp
new file mode 100644
index 0000000..a19843f
--- /dev/null
+++ b/libs/vr/libeds/distortion_renderer.cpp
@@ -0,0 +1,793 @@
+#include "include/private/dvr/distortion_renderer.h"
+
+#include <float.h>
+
+#include <string>
+
+#include <utils/Log.h>
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Trace.h>
+
+#include <base/logging.h>
+#include <private/dvr/clock_ns.h>
+#include <private/dvr/composite_hmd.h>
+#include <private/dvr/debug.h>
+#include <private/dvr/graphics/gpu_profiler.h>
+#include <private/dvr/ortho.h>
+#include <private/dvr/sensor_constants.h>
+
+#define STRINGIFY2(s) #s
+#define STRINGIFY(s) STRINGIFY2(s)
+
+#define POSITION_ATTR 0
+#define VIEWPORT_COORD_R_ATTR 1
+#define VIEWPORT_COORD_G_ATTR 2
+#define VIEWPORT_COORD_B_ATTR 3
+
+// Pose data uniform buffer bindings. Must be sequential.
+#define POSE_BINDING 0
+#define POSE_BINDING2 1
+
+// Texture unit bindings. Must be sequential.
+// Things break if we start at binding 0 (samples come back black).
+#define SAMPLER_BINDING 1
+#define SAMPLER_BINDING2 2
+
+#define GLSL_VIGNETTE_FUNC                                       \
+  "float vignette(vec2 texCoords) {\n"                           \
+  "  const float fadeDist = 0.01;\n"                             \
+  "  const float fadeDistInv = 1.0 / fadeDist;\n"                \
+  "  const float inset = 0.02;\n"                                \
+  "  vec2 lowEdge = vec2(inset - fadeDist);\n"                   \
+  "  vec2 highEdge = vec2(1.0 - inset + fadeDist);\n"            \
+  "  vec2 vignetteMin = "                                        \
+  "    clamp(-fadeDistInv * (lowEdge - texCoords), 0.0, 1.0);\n" \
+  "  vec2 vignetteMax = "                                        \
+  "    clamp(fadeDistInv * (highEdge - texCoords), 0.0, 1.0);\n" \
+  "  vec2 vignette = vignetteMin * vignetteMax;\n"               \
+  "  return vignette.x * vignette.y;\n"                          \
+  "}\n"
+
+namespace {
+
+// If enabled, the pixel shader will blend by reading back the current pixel
+// from the framebuffer.
+// TODO(jbates) With framebuffer read coherency disabled, this seems to perform
+//   well enough. That requires a GL extension, so for now we disable this path.
+constexpr bool kUseFramebufferReadback = false;
+
+static const char* kVertexShaderChromaticAberrationString =
+    "uniform mat4 uProjectionMatrix;\n"
+    "layout(binding = " STRINGIFY(POSE_BINDING) ", std140)\n"
+    "uniform LateLatchData {\n"
+    "  mat4 uTexFromRecommendedViewportMatrix;\n"
+    "};\n"
+    "#ifdef COMPOSITE_LAYER_2\n"
+    "layout(binding = " STRINGIFY(POSE_BINDING2) ", std140)\n"
+    "uniform LateLatchData2 {\n"
+    "  mat4 uTexFromRecommendedViewportMatrix2;\n"
+    "};\n"
+    "#endif\n"
+    "uniform vec4 uTexXMinMax;\n"
+    "layout(location = " STRINGIFY(POSITION_ATTR) ") in vec2 aPosition;\n"
+    "layout(location = " STRINGIFY(VIEWPORT_COORD_R_ATTR)
+           ") in vec2 aViewportCoordsR;\n"
+    "layout(location = " STRINGIFY(VIEWPORT_COORD_G_ATTR)
+           ") in vec2 aViewportCoordsG;\n"
+    "layout(location = " STRINGIFY(VIEWPORT_COORD_B_ATTR)
+           ") in vec2 aViewportCoordsB;\n"
+    "mediump out vec4 vTexCoordsRG;\n"
+    "mediump out vec2 vTexCoordsB;\n"
+    "#ifdef COMPOSITE_LAYER_2\n"
+    "mediump out vec4 vTexCoordsRG2;\n"
+    "mediump out vec2 vTexCoordsB2;\n"
+    "#endif\n"
+    "mediump out vec3 vVignette;\n"
+    "\n" GLSL_VIGNETTE_FUNC
+    "void main(void) {\n"
+    "  vVignette.r = vignette(aViewportCoordsR);\n"
+    "  vVignette.g = vignette(aViewportCoordsG);\n"
+    "  vVignette.b = vignette(aViewportCoordsB);\n"
+    "  vec4 redTexCoords = (uTexFromRecommendedViewportMatrix * \n"
+    "                       vec4(aViewportCoordsR, 0., 1.));\n"
+    "  vec4 greenTexCoords = (uTexFromRecommendedViewportMatrix * \n"
+    "                         vec4(aViewportCoordsG, 0., 1.));\n"
+    "  vec4 blueTexCoords = (uTexFromRecommendedViewportMatrix * \n"
+    "                        vec4(aViewportCoordsB, 0., 1.));\n"
+    "  vTexCoordsRG.xy = redTexCoords.xy / redTexCoords.w;\n"
+    "  vTexCoordsRG.zw = greenTexCoords.xy / greenTexCoords.w;\n"
+    "  vTexCoordsB = blueTexCoords.xy / blueTexCoords.w;\n"
+    "  vTexCoordsRG.x = clamp(vTexCoordsRG.x, uTexXMinMax.x, uTexXMinMax.y);\n"
+    "  vTexCoordsRG.z = clamp(vTexCoordsRG.z, uTexXMinMax.x, uTexXMinMax.y);\n"
+    "  vTexCoordsB.x = clamp(vTexCoordsB.x, uTexXMinMax.x, uTexXMinMax.y);\n"
+    "#ifdef COMPOSITE_LAYER_2\n"
+    "  redTexCoords = (uTexFromRecommendedViewportMatrix2 * \n"
+    "                  vec4(aViewportCoordsR, 0., 1.));\n"
+    "  greenTexCoords = (uTexFromRecommendedViewportMatrix2 * \n"
+    "                    vec4(aViewportCoordsG, 0., 1.));\n"
+    "  blueTexCoords = (uTexFromRecommendedViewportMatrix2 * \n"
+    "                   vec4(aViewportCoordsB, 0., 1.));\n"
+    "  vTexCoordsRG2.xy = redTexCoords.xy / redTexCoords.w;\n"
+    "  vTexCoordsRG2.zw = greenTexCoords.xy / greenTexCoords.w;\n"
+    "  vTexCoordsB2 = blueTexCoords.xy / blueTexCoords.w;\n"
+    "  vTexCoordsRG2.x = clamp(vTexCoordsRG2.x,\n"
+    "                          uTexXMinMax.z, uTexXMinMax.w);\n"
+    "  vTexCoordsRG2.z = clamp(vTexCoordsRG2.z, uTexXMinMax.z,\n"
+    "                          uTexXMinMax.w);\n"
+    "  vTexCoordsB2.x = clamp(vTexCoordsB2.x, uTexXMinMax.z, uTexXMinMax.w);\n"
+    "#endif\n"
+    "  gl_Position = uProjectionMatrix * vec4(aPosition, 0., 1.);\n"
+    "}\n";
+
+static const char* kFragmentShaderChromaticAberrationString =
+    "#ifdef GL_ES\n"
+    "precision mediump float;\n"
+    "#endif\n"
+    " \n"
+    "layout(binding = " STRINGIFY(SAMPLER_BINDING) ")\n"
+    "uniform sampler2D uDistortionTexture; \n"
+    "mediump in vec4 vTexCoordsRG;\n"
+    "mediump in vec2 vTexCoordsB;\n"
+    "#ifdef COMPOSITE_LAYER_2\n"
+    "layout(binding = " STRINGIFY(SAMPLER_BINDING2) ")\n"
+    "uniform sampler2D uDistortionTexture2; \n"
+    "mediump in vec4 vTexCoordsRG2;\n"
+    "mediump in vec2 vTexCoordsB2;\n"
+    "#endif\n"
+    "mediump in vec3 vVignette;\n"
+    "#ifdef BLEND_WITH_PREVIOUS_LAYER \n"
+    "inout vec4 fragColor; \n"
+    "#else \n"
+    "out vec4 fragColor; \n"
+    "#endif \n"
+    " \n"
+    "void main(void) { \n"
+    "  vec4 ra = texture(uDistortionTexture, vTexCoordsRG.xy); \n"
+    "  vec4 ga = texture(uDistortionTexture, vTexCoordsRG.zw); \n"
+    "  vec4 ba = texture(uDistortionTexture, vTexCoordsB); \n"
+    "#ifdef BLEND_WITH_PREVIOUS_LAYER \n"
+    "  vec3 alpha1 = vec3(ra.a, ga.a, ba.a); \n"
+    "  vec3 color = (vec3(1.0) - alpha1) * fragColor.rgb + \n"
+    "               alpha1 * vec3(ra.r, ga.g, ba.b); \n"
+    "#else // BLEND_WITH_PREVIOUS_LAYER \n"
+    "  vec3 color = vec3(ra.r, ga.g, ba.b); \n"
+    "#endif // BLEND_WITH_PREVIOUS_LAYER \n"
+    "#ifdef COMPOSITE_LAYER_2 \n"
+    "  // Alpha blend layer 2 onto layer 1. \n"
+    "  vec4 ra2 = texture(uDistortionTexture2, vTexCoordsRG2.xy); \n"
+    "  vec4 ga2 = texture(uDistortionTexture2, vTexCoordsRG2.zw); \n"
+    "  vec4 ba2 = texture(uDistortionTexture2, vTexCoordsB2); \n"
+    "  vec3 color2 = vec3(ra2.r, ga2.g, ba2.b); \n"
+    "  vec3 alpha2 = vec3(ra2.a, ga2.a, ba2.a); \n"
+    "  color = (vec3(1.0) - alpha2) * color + alpha2 * color2; \n"
+    "#endif \n"
+    "#ifdef ALPHA_VIGNETTE\n"
+    "  fragColor = vec4(color, vVignette.b * ga.a); \n"
+    "#else // ALPHA_VIGNETTE\n"
+    "  fragColor = vec4(vVignette.rgb * color, ga.a); \n"
+    "#endif // ALPHA_VIGNETTE\n"
+    "} \n";
+
+static const char* kVertexShaderNoChromaticAberrationString =
+    "uniform mat4 uProjectionMatrix;\n"
+    "layout(binding = " STRINGIFY(POSE_BINDING) ", std140)\n"
+    "uniform LateLatchData {\n"
+    "  mat4 uTexFromRecommendedViewportMatrix;\n"
+    "};\n"
+    "#ifdef COMPOSITE_LAYER_2\n"
+    "layout(binding = " STRINGIFY(POSE_BINDING2) ", std140)\n"
+    "uniform LateLatchData2 {\n"
+    "  mat4 uTexFromRecommendedViewportMatrix2;\n"
+    "};\n"
+    "#endif\n"
+    "uniform vec4 uTexXMinMax;\n"
+    "layout(location = " STRINGIFY(POSITION_ATTR) ") in vec2 aPosition;\n"
+    "layout(location = " STRINGIFY(VIEWPORT_COORD_G_ATTR)
+           ") in vec2 aViewportCoords;\n"
+    "mediump out vec2 vTexCoords;\n"
+    "#ifdef COMPOSITE_LAYER_2\n"
+    "mediump out vec2 vTexCoords2;\n"
+    "#endif\n"
+    "mediump out vec3 vVignette;\n"
+    "\n" GLSL_VIGNETTE_FUNC
+    "void main(void) {\n"
+    "  float fVignette = vignette(aViewportCoords);\n"
+    "  vVignette = vec3(fVignette, fVignette, fVignette);\n"
+    "  vec4 texCoords = (uTexFromRecommendedViewportMatrix * \n"
+    "                    vec4(aViewportCoords, 0., 1.));\n"
+    "  vTexCoords = texCoords.xy / texCoords.w;\n"
+    "  vTexCoords.x = clamp(vTexCoords.x, uTexXMinMax.x, uTexXMinMax.y);\n"
+    "#ifdef COMPOSITE_LAYER_2\n"
+    "  texCoords = (uTexFromRecommendedViewportMatrix2 * \n"
+    "               vec4(aViewportCoords, 0., 1.));\n"
+    "  vTexCoords2 = texCoords.xy / texCoords.w;\n"
+    "  vTexCoords2.x = clamp(vTexCoords2.x, uTexXMinMax.z, uTexXMinMax.w);\n"
+    "#endif\n"
+    "  gl_Position = uProjectionMatrix * vec4(aPosition, 0., 1.);\n"
+    "}\n";
+
+static const char* kFragmentShaderNoChromaticAberrationString =
+    "#ifdef GL_ES\n"
+    "precision mediump float;\n"
+    "#endif\n"
+    " \n"
+    "layout(binding = " STRINGIFY(SAMPLER_BINDING) ")\n"
+    "uniform sampler2D uDistortionTexture; \n"
+    "mediump in vec2 vTexCoords;\n"
+    "#ifdef COMPOSITE_LAYER_2\n"
+    "layout(binding = " STRINGIFY(SAMPLER_BINDING2) ")\n"
+    "uniform sampler2D uDistortionTexture2; \n"
+    "mediump in vec2 vTexCoords2;\n"
+    "#endif\n"
+    "mediump in vec3 vVignette;\n"
+    "out vec4 fragColor;\n"
+    " \n"
+    "void main(void) { \n"
+    "  vec4 color = texture(uDistortionTexture, vTexCoords); \n"
+    "#ifdef COMPOSITE_LAYER_2 \n"
+    "  // Alpha blend layer 2 onto layer 1. \n"
+    "  vec4 color2 = texture(uDistortionTexture2, vTexCoords2); \n"
+    "  float alpha2 = color2.a; \n"
+    "  color.rgb = (1.0 - alpha2) * color.rgb + alpha2 * color2.rgb; \n"
+    "#endif \n"
+    "  fragColor = vec4(vVignette * color.rgb, color.a); \n"
+    "} \n";
+
+static const char* kVertexShaderSimpleVideoQuadString =
+    "uniform mat4 uProjectionMatrix;\n"
+    "layout(binding = " STRINGIFY(POSE_BINDING) ", std140)\n"
+    "uniform LateLatchData {\n"
+    "  mat4 uEdsCorrection;\n"
+    "};\n"
+    "uniform mat4 uTexFromEyeMatrix;\n"
+    "uniform mat4 uEyeFromViewportMatrix;\n"
+    "layout(location = " STRINGIFY(POSITION_ATTR) ") in vec2 aPosition;\n"
+    "layout(location = " STRINGIFY(VIEWPORT_COORD_G_ATTR)
+           ") in vec2 aViewportCoords;\n"
+    "mediump out vec2 vTexCoords;\n"
+    "void main(void) {\n"
+    "  mat4 m = uTexFromEyeMatrix * inverse(uEdsCorrection) * uEyeFromViewportMatrix;\n"
+    "  mat3 uTexFromViewportMatrix = inverse(mat3(m[0].xyw, m[1].xyw, m[3].xyw)); \n"
+    "  vec3 texCoords = uTexFromViewportMatrix * vec3(aViewportCoords, 1.0);\n"
+    "  vTexCoords = texCoords.xy / texCoords.z;\n"
+    "  gl_Position = uProjectionMatrix * vec4(aPosition, 0.0, 1.0);\n"
+    "}\n";
+
+static const char* kFragmentShaderSimpleVideoQuadString =
+    "#extension GL_OES_EGL_image_external_essl3 : enable\n"
+    " \n"
+    "#ifdef GL_ES\n"
+    "precision mediump float;\n"
+    "#endif\n"
+    " \n"
+    "layout(binding = " STRINGIFY(SAMPLER_BINDING) ")\n"
+    "uniform samplerExternalOES uDistortionTexture; \n"
+    "mediump in vec2 vTexCoords;\n"
+    "out vec4 fragColor;\n"
+    " \n"
+    "void main(void) { \n"
+    "  if (clamp(vTexCoords, 0.0, 1.0) != vTexCoords) { \n"
+    "    fragColor = vec4(0.0, 0.0, 0.0, 0.0); \n"
+    "  } else { \n"
+    "    fragColor = texture(uDistortionTexture, vTexCoords); \n"
+    "  } \n"
+    "} \n";
+
+}  // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+// Note that converting from Clip Space ([-1,1]^3) to Viewport Space
+// for one eye ([0,1]x[0,1]) requires dividing by 2 in x and y.
+const mat4 DistortionRenderer::kViewportFromClipMatrix =
+    Eigen::Translation3f(vec3(0.5f, 0.5f, 0)) *
+    Eigen::DiagonalMatrix<float, 3>(vec3(0.5f, 0.5f, 1.0f));
+
+const mat4 DistortionRenderer::kClipFromViewportMatrix =
+    Eigen::DiagonalMatrix<float, 3>(vec3(2.0f, 2.0f, 1.0f)) *
+    Eigen::Translation3f(vec3(-0.5f, -0.5f, 0));
+
+void DistortionRenderer::EdsShader::load(const char* vertex,
+                                         const char* fragment, int num_layers,
+                                         bool use_alpha_vignette,
+                                         float rotation, bool flip_vertical,
+                                         bool blend_with_previous_layer) {
+  std::string vert_builder = "#version 310 es\n";
+  std::string frag_builder = "#version 310 es\n";
+  if (blend_with_previous_layer && kUseFramebufferReadback) {
+    frag_builder += "#extension GL_EXT_shader_framebuffer_fetch : require\n";
+  }
+
+  if (num_layers == 2) {
+    vert_builder += "#define COMPOSITE_LAYER_2\n";
+    frag_builder += "#define COMPOSITE_LAYER_2\n";
+  } else {
+    CHECK_EQ(num_layers, 1);
+  }
+  if (blend_with_previous_layer) {
+    // Check for unsupported shader combinations:
+    CHECK_EQ(num_layers, 1);
+    CHECK_EQ(use_alpha_vignette, false);
+    if (kUseFramebufferReadback)
+      frag_builder += "#define BLEND_WITH_PREVIOUS_LAYER\n";
+  }
+  if (use_alpha_vignette) {
+    vert_builder += "#define ALPHA_VIGNETTE\n";
+    frag_builder += "#define ALPHA_VIGNETTE\n";
+  }
+
+  vert_builder += vertex;
+  frag_builder += fragment;
+  pgm.Link(vert_builder, frag_builder);
+  CHECK(pgm.IsUsable());
+
+  pgm.Use();
+
+  uProjectionMatrix =
+      glGetUniformLocation(pgm.GetProgram(), "uProjectionMatrix");
+  uTexFromEyeMatrix =
+      glGetUniformLocation(pgm.GetProgram(), "uTexFromEyeMatrix");
+  uEyeFromViewportMatrix =
+      glGetUniformLocation(pgm.GetProgram(), "uEyeFromViewportMatrix");
+  uTexXMinMax = glGetUniformLocation(pgm.GetProgram(), "uTexXMinMax");
+  CHECK_GL();
+
+  float vertical_multiply = flip_vertical ? -1.0 : 1.0;
+  mat4 projectionMatrix = OrthoMatrix(-0.5f, 0.5f, vertical_multiply * -0.5f,
+                                      vertical_multiply * 0.5f, -1.0f, 1.0f);
+
+  // Rotate the mesh into the screen's orientation.
+  // TODO(hendrikw): Once the display is finalized, and perhaps not portrait,
+  //                 look into removing this matrix altogether.
+  projectionMatrix =
+      projectionMatrix * Eigen::AngleAxisf(rotation, vec3::UnitZ());
+
+  CHECK(sizeof(mat4) == 4 * 4 * 4);
+  glUniformMatrix4fv(uProjectionMatrix, 1, false, projectionMatrix.data());
+}
+
+DistortionRenderer::DistortionRenderer(
+    const CompositeHmd& hmd, vec2i display_size, int distortion_mesh_resolution,
+    bool flip_texture_horizontally, bool flip_texture_vertically,
+    bool separated_eye_buffers, bool eds_enabled, bool late_latch_enabled)
+    : shader_type_(kChromaticAberrationCorrection),
+      eds_enabled_(eds_enabled),
+      chromatic_aberration_correction_enabled_(true),
+      use_alpha_vignette_(false),
+      distortion_mesh_resolution_(distortion_mesh_resolution),
+      last_distortion_texture_id_(0),
+      app_texture_target_(GL_TEXTURE_2D),
+      display_size_(display_size),
+      separated_eye_buffers_(separated_eye_buffers) {
+  ATRACE_NAME("DistortionRenderer::DistortionRenderer");
+
+  float device_rotation = 0.0;
+
+  if (eds_enabled_) {
+    // Late latch must be on if eds_enabled_ is true.
+    if (!late_latch_enabled) {
+      LOG(ERROR) << "Cannot enable EDS without late latch. "
+                 << "Force enabling late latch.";
+      late_latch_enabled = true;
+    }
+  }
+
+  // TODO(hendrikw): Look into moving this logic into DisplayMetrics.
+  if (hmd.GetDisplayMetrics().IsPortrait()) {
+    device_rotation = -M_PI / 2.0f;
+  }
+
+  // Create shader programs.
+  shaders_[kNoChromaticAberrationCorrection].load(
+      kVertexShaderNoChromaticAberrationString,
+      kFragmentShaderNoChromaticAberrationString, 1, false, device_rotation,
+      flip_texture_horizontally, false);
+  shaders_[kNoChromaticAberrationCorrectionTwoLayers].load(
+      kVertexShaderNoChromaticAberrationString,
+      kFragmentShaderNoChromaticAberrationString, 2, false, device_rotation,
+      flip_texture_horizontally, false);
+  shaders_[kChromaticAberrationCorrection].load(
+      kVertexShaderChromaticAberrationString,
+      kFragmentShaderChromaticAberrationString, 1, false, device_rotation,
+      flip_texture_horizontally, false);
+  shaders_[kChromaticAberrationCorrectionTwoLayers].load(
+      kVertexShaderChromaticAberrationString,
+      kFragmentShaderChromaticAberrationString, 2, false, device_rotation,
+      flip_texture_horizontally, false);
+  shaders_[kChromaticAberrationCorrectionAlphaVignette].load(
+      kVertexShaderChromaticAberrationString,
+      kFragmentShaderChromaticAberrationString, 1, true, device_rotation,
+      flip_texture_horizontally, false);
+  shaders_[kChromaticAberrationCorrectionAlphaVignetteTwoLayers].load(
+      kVertexShaderChromaticAberrationString,
+      kFragmentShaderChromaticAberrationString, 2, true, device_rotation,
+      flip_texture_horizontally, false);
+  shaders_[kChromaticAberrationCorrectionWithBlend].load(
+      kVertexShaderChromaticAberrationString,
+      kFragmentShaderChromaticAberrationString, 1, false, device_rotation,
+      flip_texture_horizontally, true);
+  shaders_[kSimpleVideoQuad].load(
+      kVertexShaderSimpleVideoQuadString,
+      kFragmentShaderSimpleVideoQuadString, 1, false, device_rotation,
+      flip_texture_horizontally, true);
+  CHECK_GL();
+
+  mat4 tex_from_recommended_viewport_matrix[2][2][2];
+  for (int eye = 0; eye < 2; ++eye) {
+    // Near and far plane don't actually matter for the clip_from_eye_matrix
+    // below since it is only used (for EDS) to transform coordinates for
+    // which the Z has been dropped.
+    static const float kNear = 0.1f, kFar = 100.0f;
+    const FieldOfView& fov =
+        (eye == kLeftEye ? hmd.GetEyeFov(kLeftEye) : hmd.GetEyeFov(kRightEye));
+    mat4 c_clip_from_eye_matrix = fov.GetProjectionMatrix(kNear, kFar);
+    mat4 c_eye_from_clip_matrix = c_clip_from_eye_matrix.inverse();
+
+    // Compute tex_from_recommended_viewport_matrix.
+
+    // flip_texture_vertically defines the default flip behavior.
+    // do_flip[0] should be the default, while do_flip[1] should be the
+    // inverse of the default.
+    int do_flip[2] = {flip_texture_vertically ? 1 : 0,
+                      flip_texture_vertically ? 0 : 1};
+    for (int flip = 0; flip < 2; ++flip) {
+      vec2 flip_scale(1.0f, do_flip[flip] ? -1.0f : 1.0f);
+      vec2 flip_offset(0.0f, do_flip[flip] ? 1.0f : 0.0f);
+
+      for (int separate_eye = 0; separate_eye < 2; ++separate_eye) {
+        vec2 viewport_corner_offset = (eye == kLeftEye || separate_eye)
+                                          ? vec2(0.0f, 0.0f)
+                                          : vec2(0.5f, 0.0f);
+        const vec2 txy = viewport_corner_offset + flip_offset;
+        const vec2 scalexy = vec2(separate_eye ? 1.0f : 0.5f, 1.0f);
+        tex_from_recommended_viewport_matrix[eye][flip][separate_eye] =
+            Eigen::Translation3f(vec3(txy.x(), txy.y(), 0.0f)) *
+            Eigen::DiagonalMatrix<float, 3>(vec3(flip_scale.x() * scalexy.x(),
+                                                 flip_scale.y(), scalexy.y()));
+
+        tex_from_eye_matrix_[eye][flip][separate_eye] =
+            tex_from_recommended_viewport_matrix[eye][flip][separate_eye] *
+            kViewportFromClipMatrix * c_clip_from_eye_matrix;
+      }
+    }
+
+    eye_from_viewport_matrix_[eye] =
+        c_eye_from_clip_matrix * kClipFromViewportMatrix;
+  }
+
+  // Create UBO for setting the EDS matrix to identity when EDS is disabled.
+  glGenBuffers(2 * 2 * 2, &uTexFromRecommendedViewportMatrix[0][0][0]);
+  for (int eye = 0; eye < 2; ++eye) {
+    for (int flip = 0; flip < 2; ++flip) {
+      for (int separate_eye = 0; separate_eye < 2; ++separate_eye) {
+        glBindBuffer(
+            GL_UNIFORM_BUFFER,
+            uTexFromRecommendedViewportMatrix[eye][flip][separate_eye]);
+        glBufferData(GL_UNIFORM_BUFFER, sizeof(mat4), 0, GL_STATIC_DRAW);
+        CHECK_GL();
+        mat4* mat = static_cast<mat4*>(glMapBufferRange(
+            GL_UNIFORM_BUFFER, 0, sizeof(mat4), GL_MAP_WRITE_BIT));
+        CHECK_GL();
+        *mat = tex_from_recommended_viewport_matrix[eye][flip][separate_eye];
+        glUnmapBuffer(GL_UNIFORM_BUFFER);
+      }
+    }
+  }
+  glBindBuffer(GL_UNIFORM_BUFFER, 0);
+
+  // Create distortion meshes and associated GL resources.
+  glGenBuffers(2, mesh_vbo_);
+  glGenVertexArrays(2, mesh_vao_);
+  glGenBuffers(2, mesh_ibo_);
+  RecomputeDistortion(hmd);
+
+  SetDisplaySize(display_size);
+
+  if (hmd.GetDisplayMetrics().IsPortrait()) {
+    eye_viewport_origin_[0] =
+        vec2i(0, flip_texture_horizontally ? 0 : display_size_[1] / 2);
+    eye_viewport_origin_[1] =
+        vec2i(0, flip_texture_horizontally ? display_size_[1] / 2 : 0);
+    eye_viewport_size_ = vec2i(display_size_[0], display_size_[1] / 2);
+  } else {
+    eye_viewport_origin_[0] = vec2i(0, 0);
+    eye_viewport_origin_[1] = vec2i(display_size_[0] / 2, 0);
+    eye_viewport_size_ = vec2i(display_size_[0] / 2, display_size_[1]);
+  }
+
+  CHECK_GL();
+}
+
+DistortionRenderer::~DistortionRenderer() {
+  glDeleteBuffers(2 * 2 * 2, &uTexFromRecommendedViewportMatrix[0][0][0]);
+  glDeleteBuffers(2, mesh_vbo_);
+  glDeleteVertexArrays(2, mesh_vao_);
+  glDeleteBuffers(2, mesh_ibo_);
+}
+
+void DistortionRenderer::ApplyDistortionCorrectionToTexture(
+    EyeType eye, const GLuint* texture_ids, const bool* vertical_flip,
+    const bool* separate_eye, const int* late_latch_layer, int num_textures,
+    bool blend_with_previous_layer, bool do_gl_state_prep) {
+  ATRACE_NAME(__PRETTY_FUNCTION__);
+
+  bool use_gl_blend = use_alpha_vignette_ ||
+                      (blend_with_previous_layer && !kUseFramebufferReadback);
+  if (use_gl_blend) {
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+  }
+  DrawEye(eye, texture_ids, vertical_flip, separate_eye, late_latch_layer,
+          num_textures, blend_with_previous_layer, do_gl_state_prep);
+  if (use_gl_blend) {
+    glDisable(GL_BLEND);
+  }
+  CHECK_GL();
+}
+
+void DistortionRenderer::DrawVideoQuad(EyeType eye, int layer_i,
+                                       GLuint texture_id,
+                                       const mat4& transform) {
+  shaders_[kSimpleVideoQuad].use();
+
+  shaders_[kSimpleVideoQuad].SetTexFromEyeTransform(
+      tex_from_eye_matrix_[eye][0][1]);
+  shaders_[kSimpleVideoQuad].SetEyeFromViewportTransform(
+      transform * kClipFromViewportMatrix);
+
+  if (eds_enabled_) {
+    // Bind late latch view-projection UBO that is produced by AddEdsLateLatch.
+    late_latch_[layer_i]->BindUniformBuffer(
+        POSE_BINDING, LateLatch::kViewMatrix, eye);
+    CHECK_GL();
+  } else {
+    // When EDS is disabled we just set the matrix here with no pose offset.
+    glBindBufferBase(GL_UNIFORM_BUFFER, POSE_BINDING + layer_i,
+                     uTexFromRecommendedViewportMatrix[eye][0][1]);
+    CHECK_GL();
+  }
+
+  glActiveTexture(GL_TEXTURE0 + SAMPLER_BINDING);
+  glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id);
+  CHECK_GL();
+
+  glDrawElements(GL_TRIANGLE_STRIP, mesh_node_[eye].indices.size(),
+                 GL_UNSIGNED_SHORT, nullptr);
+
+  CHECK_GL();
+}
+
+void DistortionRenderer::DoLateLatch(uint32_t target_vsync_count,
+                                     const uint32_t* render_buffer_index,
+                                     const GLuint* render_pose_buffer_objects,
+                                     const bool* vertical_flip,
+                                     const bool* separate_eye,
+                                     int num_textures) {
+  if (eds_enabled_) {
+    LateLatchInput data;
+    memset(&data, 0, sizeof(data));
+    for (int ti = 0; ti < num_textures; ++ti) {
+      if (late_latch_[ti] == nullptr)
+        late_latch_[ti].reset(new LateLatch(false));
+
+      int flip_index = vertical_flip[ti] ? 1 : 0;
+      int separate_eye_i = separate_eye[ti] ? 1 : 0;
+      // Copy data into late latch input struct.
+      for (int eye = 0; eye < 2; ++eye) {
+        data.eds_mat1[eye] =
+            tex_from_eye_matrix_[eye][flip_index][separate_eye_i];
+        data.eds_mat2[eye] = eye_from_viewport_matrix_[eye];
+      }
+      data.pose_index = target_vsync_count & kPoseAsyncBufferIndexMask;
+      data.render_pose_index = render_buffer_index[ti];
+
+      late_latch_[ti]->AddEdsLateLatch(data, render_pose_buffer_objects[ti]);
+    }
+  }
+}
+
+void DistortionRenderer::PrepGlState(EyeType eye) {
+  glViewport(eye_viewport_origin_[eye].x(), eye_viewport_origin_[eye].y(),
+             eye_viewport_size_.x(), eye_viewport_size_.y());
+
+  glBindVertexArray(mesh_vao_[eye]);
+  CHECK_GL();
+
+  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh_ibo_[eye]);
+  CHECK_GL();
+
+  if (!eds_enabled_) {
+    glMemoryBarrier(GL_UNIFORM_BARRIER_BIT);
+  }
+}
+
+void DistortionRenderer::ResetGlState(int num_textures) {
+  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+  glBindBuffer(GL_ARRAY_BUFFER, 0);
+  glBindVertexArray(0);
+  if (eds_enabled_) {
+    for (int ti = 0; ti < num_textures; ++ti)
+      glBindBufferBase(GL_UNIFORM_BUFFER, POSE_BINDING + ti, 0);
+  } else {
+    glBindBuffer(GL_UNIFORM_BUFFER, 0);
+  }
+
+  CHECK_GL();
+
+  // Unbind all texture inputs.
+  for (int ti = 0; ti < num_textures; ++ti) {
+    glActiveTexture(GL_TEXTURE0 + SAMPLER_BINDING + ti);
+    glBindTexture(app_texture_target_, 0);
+  }
+  glActiveTexture(GL_TEXTURE0);
+}
+
+void DistortionRenderer::DrawEye(EyeType eye, const GLuint* texture_ids,
+                                 const bool* vertical_flip,
+                                 const bool* separate_eye,
+                                 const int* late_latch_layer, int num_textures,
+                                 bool blend_with_previous_layer,
+                                 bool do_gl_state_prep) {
+  if (do_gl_state_prep)
+    PrepGlState(eye);
+
+  if (num_textures > kMaxLayers) {
+    LOG(ERROR) << "Too many textures for DistortionRenderer";
+    num_textures = kMaxLayers;
+  }
+
+  CHECK(num_textures == 1 || num_textures == 2);
+
+  if (num_textures == 2) {
+    if (chromatic_aberration_correction_enabled_) {
+      if (use_alpha_vignette_) {
+        shader_type_ = kChromaticAberrationCorrectionAlphaVignetteTwoLayers;
+      } else {
+        shader_type_ = kChromaticAberrationCorrectionTwoLayers;
+      }
+    } else {
+      shader_type_ = kNoChromaticAberrationCorrectionTwoLayers;
+    }
+  } else {
+    if (chromatic_aberration_correction_enabled_) {
+      if (blend_with_previous_layer) {
+        shader_type_ = kChromaticAberrationCorrectionWithBlend;
+      } else if (use_alpha_vignette_) {
+        shader_type_ = kChromaticAberrationCorrectionAlphaVignette;
+      } else {
+        shader_type_ = kChromaticAberrationCorrection;
+      }
+    } else {
+      shader_type_ = kNoChromaticAberrationCorrection;
+    }
+  }
+  shaders_[shader_type_].use();
+
+  for (int ti = 0; ti < num_textures; ++ti) {
+    int flip_index = vertical_flip[ti] ? 1 : 0;
+    if (eds_enabled_) {
+      // Bind late latch view-projection UBO that is produced by
+      // AddEdsLateLatch.
+      late_latch_[late_latch_layer[ti]]->BindUniformBuffer(
+          POSE_BINDING + ti, LateLatch::kViewProjMatrix, eye);
+      CHECK_GL();
+    } else {
+      // When EDS is disabled we just set the matrix here with no pose offset.
+      // With app late-latching, we can't know the pose that the app used
+      // because it's in the app's framebuffer.
+      int separate_eye_i = separate_eye[ti] ? 1 : 0;
+      glBindBufferBase(
+          GL_UNIFORM_BUFFER, POSE_BINDING + ti,
+          uTexFromRecommendedViewportMatrix[eye][flip_index][separate_eye_i]);
+      CHECK_GL();
+    }
+
+    glActiveTexture(GL_TEXTURE0 + SAMPLER_BINDING + ti);
+    glBindTexture(app_texture_target_, texture_ids[ti]);
+    CHECK_GL();
+  }
+
+  // Prevents left eye data from bleeding into right eye and vice-versa.
+  vec2 layer_min_max[kMaxLayers];
+  for (int i = 0; i < kMaxLayers; ++i)
+    layer_min_max[i] = vec2(0.0f, 0.0f);
+  for (int ti = 0; ti < num_textures; ++ti) {
+    if (separate_eye[ti]) {
+      layer_min_max[ti] = vec2(0.0f, 1.0f);  // Use the whole texture.
+    } else if (eye == kLeftEye) {
+      layer_min_max[ti] = vec2(0.0f, 0.499f);
+    } else {
+      layer_min_max[ti] = vec2(0.501f, 1.0f);
+    }
+  }
+  // The second layer stores its x min and max in the z,w slots of the vec4.
+  vec4 xTexMinMax(layer_min_max[0].x(), layer_min_max[0].y(),
+                  layer_min_max[1].x(), layer_min_max[1].y());
+
+  glUniform4fv(shaders_[shader_type_].uTexXMinMax, 1, &xTexMinMax[0]);
+  CHECK_GL();
+
+  glDrawElements(GL_TRIANGLE_STRIP, mesh_node_[eye].indices.size(),
+                 GL_UNSIGNED_SHORT, nullptr);
+  CHECK_GL();
+  if (do_gl_state_prep)
+    ResetGlState(num_textures);
+}
+
+void DistortionRenderer::SetDisplaySize(vec2i display_size) {
+  display_size_ = display_size;
+}
+
+void DistortionRenderer::SetEdsEnabled(bool enabled) { eds_enabled_ = enabled; }
+
+void DistortionRenderer::RecomputeDistortion(const CompositeHmd& hmd) {
+  using std::placeholders::_1;
+  using std::placeholders::_2;
+  using std::placeholders::_3;
+  using std::placeholders::_4;
+  DistortionFunction distortion_function =
+      std::bind(&CompositeHmd::ComputeDistortedVertex, &hmd, _1, _2, _3, _4);
+
+  for (int i = 0; i < 2; ++i) {
+    mesh_node_[i] =
+        BuildDistortionMesh(static_cast<EyeType>(i),
+                            distortion_mesh_resolution_, distortion_function);
+
+    glBindVertexArray(mesh_vao_[i]);
+
+    glBindBuffer(GL_ARRAY_BUFFER, mesh_vbo_[i]);
+    glBufferData(GL_ARRAY_BUFFER,
+                 sizeof(EdsVertex) * mesh_node_[i].vertices.size(),
+                 &mesh_node_[i].vertices.front(), GL_STATIC_DRAW);
+
+    glEnableVertexAttribArray(POSITION_ATTR);
+    glEnableVertexAttribArray(VIEWPORT_COORD_R_ATTR);
+    glEnableVertexAttribArray(VIEWPORT_COORD_G_ATTR);
+    glEnableVertexAttribArray(VIEWPORT_COORD_B_ATTR);
+
+    glVertexAttribPointer(
+        POSITION_ATTR, 2, GL_FLOAT, GL_FALSE, sizeof(EdsVertex),
+        reinterpret_cast<void*>(offsetof(EdsVertex, position)));
+
+    glVertexAttribPointer(
+        VIEWPORT_COORD_R_ATTR, 2, GL_FLOAT, GL_FALSE, sizeof(EdsVertex),
+        reinterpret_cast<void*>(offsetof(EdsVertex, red_viewport_coords)));
+
+    glVertexAttribPointer(
+        VIEWPORT_COORD_G_ATTR, 2, GL_FLOAT, GL_FALSE, sizeof(EdsVertex),
+        reinterpret_cast<void*>(offsetof(EdsVertex, green_viewport_coords)));
+
+    glVertexAttribPointer(
+        VIEWPORT_COORD_B_ATTR, 2, GL_FLOAT, GL_FALSE, sizeof(EdsVertex),
+        reinterpret_cast<void*>(offsetof(EdsVertex, blue_viewport_coords)));
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh_ibo_[i]);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+                 sizeof(uint16_t) * mesh_node_[i].indices.size(),
+                 &mesh_node_[i].indices.front(), GL_STATIC_DRAW);
+    CHECK_GL();
+  }
+  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+  glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+  glBindVertexArray(0);
+}
+
+bool DistortionRenderer::GetLastEdsPose(LateLatchOutput* out_data, int layer_id) const {
+  if (layer_id >= kMaxLayers) {
+    LOG(ERROR) << "Accessing invalid layer " << layer_id << std::endl;
+    return false;
+  }
+
+  if (late_latch_[layer_id] != nullptr) {
+    late_latch_[layer_id]->CaptureOutputData(out_data);
+    return true;
+  } else {
+    LOG(ERROR) << "Late latch shader not enabled." << std::endl;
+    return false;
+  }
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libeds/eds.cpp b/libs/vr/libeds/eds.cpp
new file mode 100644
index 0000000..8af5b27
--- /dev/null
+++ b/libs/vr/libeds/eds.cpp
@@ -0,0 +1,35 @@
+#include <dvr/eds.h>
+
+#include <private/dvr/graphics/vr_gl_extensions.h>
+#include <private/dvr/late_latch.h>
+#include <private/dvr/types.h>
+
+// TODO(jbates) delete this file and eds.h
+
+extern "C" int dvrEdsInit(bool with_late_latch) { return 0; }
+
+extern "C" void dvrEdsDeinit() {}
+
+extern "C" int dvrEdsCapturePoseAsync(int eye, uint32_t target_vsync_count,
+                                      const float* projection_matrix,
+                                      const float* eye_from_head_matrix,
+                                      const float* pose_offset_matrix) {
+  return 0;
+}
+
+extern "C" int dvrEdsBindPose(int eye, uint32_t ubo_binding, intptr_t offset,
+                              ssize_t size) {
+  return 0;
+}
+
+extern "C" int dvrEdsBlitPose(int eye, int viewport_width,
+                              int viewport_height) {
+  return 0;
+}
+
+extern "C" int dvrEdsBlitPoseFromCpu(int eye, int viewport_width,
+                                     int viewport_height,
+                                     const float* pose_quaternion,
+                                     const float* pose_position) {
+  return 0;
+}
diff --git a/libs/vr/libeds/eds_mesh.cpp b/libs/vr/libeds/eds_mesh.cpp
new file mode 100644
index 0000000..2c7dc2f
--- /dev/null
+++ b/libs/vr/libeds/eds_mesh.cpp
@@ -0,0 +1,136 @@
+#include "include/private/dvr/eds_mesh.h"
+
+#include <math.h>
+
+#include <base/logging.h>
+#include <private/dvr/types.h>
+
+namespace {
+
+using android::dvr::EdsVertex;
+using android::dvr::EyeType;
+using android::dvr::DistortionFunction;
+using android::dvr::vec2;
+
+// Computes the vertices for a distortion mesh with resolution |resolution| and
+// distortion provided by |hmd| and stores them in |vertices|.
+static void ComputeDistortionMeshVertices(
+    EdsVertex* vertices, int resolution,
+    const DistortionFunction& distortion_function, EyeType eye) {
+  for (int row = 0; row < resolution; row++) {
+    for (int col = 0; col < resolution; col++) {
+      const float x_norm =
+          static_cast<float>(col) / (static_cast<float>(resolution - 1U));
+      const float y_norm =
+          static_cast<float>(row) / (static_cast<float>(resolution - 1U));
+
+      const vec2 xy_norm(x_norm, y_norm);
+      const size_t index = col * resolution + row;
+
+      // Evaluate distortion function to get the new coordinates for each color
+      // channel. The distortion function returns the new coordinates relative
+      // to a full viewport with 0 <= x <= 1 for each eye.
+      vec2 coords[3];
+      distortion_function(eye, xy_norm, &vertices[index].position, coords);
+
+      // Store distortion mapping in texture coordinates.
+      vertices[index].red_viewport_coords = coords[0];
+      vertices[index].green_viewport_coords = coords[1];
+      vertices[index].blue_viewport_coords = coords[2];
+    }
+  }
+}
+
+// Computes the triangle strip indices for a distortion mesh with resolution
+// |resolution| and stores them in |indices|.
+static void ComputeDistortionMeshIndices(uint16_t* indices, int resolution) {
+  // The following strip method has been used in the Cardboard SDK
+  // (java/com/google/vrtoolkit/cardboard/DistortionRenderer.java) and has
+  // originally been described at:
+  //
+  // http://dan.lecocq.us/wordpress/2009/12/25/triangle-strip-for-grids-a-construction/
+  //
+  // For a grid with 4 rows and 4 columns of vertices, the strip would
+  // look like:
+  //                             ↻
+  //         0    -    4    -    8    -   12
+  //         ↓    ↗    ↓    ↗    ↓    ↗    ↓
+  //         1    -    5    -    9    -   13
+  //         ↓    ↖    ↓    ↖    ↓    ↖    ↓
+  //         2    -    6    -   10    -   14
+  //         ↓    ↗    ↓    ↗    ↓    ↗    ↓
+  //         3    -    7    -   11    -   15
+  //                   ↺
+  //
+  // Note the little circular arrows next to 7 and 8 that indicate
+  // repeating that vertex once so as to produce degenerate triangles.
+  //
+  // To facilitate scanline racing, the vertex order is left to right.
+
+  int16_t index_offset = 0;
+  int16_t vertex_offset = 0;
+  for (int row = 0; row < resolution - 1; ++row) {
+    if (row > 0) {
+      indices[index_offset] = indices[index_offset - 1];
+      ++index_offset;
+    }
+    for (int col = 0; col < resolution; ++col) {
+      if (col > 0) {
+        if (row % 2 == 0) {
+          // Move right on even rows.
+          ++vertex_offset;
+        } else {
+          --vertex_offset;
+        }
+      }
+      // A cast to uint16_t is safe here as |vertex_offset| will not drop below
+      // zero in this loop. As col is initially equal to zero |vertex_offset| is
+      // always incremented before being decremented, is initialized to zero and
+      // is only incremented outside of the loop.
+      indices[index_offset++] = static_cast<uint16_t>(vertex_offset);
+      indices[index_offset++] = static_cast<uint16_t>(
+          vertex_offset + static_cast<int16_t>(resolution));
+    }
+    vertex_offset =
+        static_cast<int16_t>(static_cast<int>(resolution) + vertex_offset);
+  }
+}
+
+}  // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+// Builds a distortion mesh of resolution |resolution| using the distortion
+// provided by |hmd| for |eye|.
+EdsMesh BuildDistortionMesh(EyeType eye, int resolution,
+                            const DistortionFunction& distortion_function) {
+  CHECK_GT(resolution, 2);
+
+  // Number of indices produced by the strip method
+  // (see comment in ComputeDistortionMeshIndices):
+  //
+  //     1 vertex per triangle
+  //     2 triangles per quad, (rows - 1) * (cols - 1) quads
+  //     2 vertices at the start of each row for the first triangle
+  //     1 extra vertex per row (except first and last) for a
+  //       degenerate triangle
+  //
+  const uint16_t index_count =
+      static_cast<uint16_t>(resolution * (2 * resolution - 1U) - 2U);
+  const uint16_t vertex_count = static_cast<uint16_t>(resolution * resolution);
+
+  EdsMesh mesh;
+  mesh.vertices.resize(vertex_count);
+  mesh.indices.resize(index_count);
+
+  // Populate vertex and index buffer.
+  ComputeDistortionMeshVertices(&mesh.vertices[0], resolution,
+                                distortion_function, eye);
+  ComputeDistortionMeshIndices(&mesh.indices[0], resolution);
+
+  return mesh;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libeds/include/CPPLINT.cfg b/libs/vr/libeds/include/CPPLINT.cfg
new file mode 100644
index 0000000..2f8a3c0
--- /dev/null
+++ b/libs/vr/libeds/include/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-build/header_guard
diff --git a/libs/vr/libeds/include/dvr/eds.h b/libs/vr/libeds/include/dvr/eds.h
new file mode 100644
index 0000000..37b1297
--- /dev/null
+++ b/libs/vr/libeds/include/dvr/eds.h
@@ -0,0 +1,150 @@
+#ifndef ANDROID_DVR_EDS_H_
+#define ANDROID_DVR_EDS_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+// This struct aligns with GLSL uniform blocks with std140 layout.
+// std140 allows padding between certain types, so padding must be explicitly
+// added as struct members.
+struct __attribute__((__packed__)) DvrLateLatchData {
+  // Column-major order.
+  float view_proj_matrix[16];
+  // Column-major order.
+  float view_matrix[16];
+  float pose_quaternion[4];
+  float pose_position[4];
+};
+
+//
+// These APIs are not thread safe and must be called on a single thread with an
+// actively bound GL context corresponding to a display surface.
+//
+
+// Prepares EDS and Late Latching system. Idempotent if called more than once.
+// The target GL context must be created and bound.
+//
+// If |with_late_latch| is true, a thread will be created that asynchronously
+// updates the pose in memory.
+//
+// The following GL states are modified as follows:
+// glBindBuffer(GL_ARRAY_BUFFER, 0);
+// glBindBuffer(GL_UNIFORM_BUFFER, 0);
+//
+// Returns 0 on success, negative error code on failure.
+// Check GL errors with glGetError for other error conditions.
+int dvrEdsInit(bool with_late_latch);
+
+// Stops and destroys the EDS Late Latching system.
+void dvrEdsDeinit();
+
+// Submits GL draw command that will capture the latest head pose into a uniform
+// buffer object. This should be called twice per frame, before the app begins
+// drawing for each eye.
+// For each eye, a later call to dvrEdsBlitPose will write this pose into
+// the application framebuffer corner so that the EDS service knows what pose
+// the frame was rendered with.
+//
+// |eye| is 0 for left eye and 1 for right eye.
+//
+// The following GL states are modified as follows:
+// glUseProgram(0);
+// glBindBuffer(GL_UNIFORM_BUFFER, 0);
+// glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, id);
+// glDisable(GL_RASTERIZER_DISCARD);
+//
+// Returns 0 on success, negative error code on failure:
+//   EPERM - dvrEdsInit(true) was not called.
+// Check GL errors with glGetError for other error conditions.
+int dvrEdsCapturePoseAsync(int eye, uint32_t target_vsync_count,
+                           const float* projection_matrix,
+                           const float* eye_from_head_matrix,
+                           const float* pose_offset_matrix);
+
+// Binds the late-latch output data as a GL_UNIFORM_BUFFER so that your vertex
+// shaders can use the latest head pose. For example, to bind just the
+// view_matrix from the output:
+//
+// dvrEdsBindPose(eye, BINDING,
+//                       offsetof(DvrLateLatchData, view_matrix),
+//                       sizeof(DvrLateLatchData::view_matrix));
+//
+// Or more commonly, bind the view projection matrix:
+//
+// dvrEdsBindPose(eye, BINDING,
+//                       offsetof(DvrLateLatchData, view_proj_matrix),
+//                       sizeof(DvrLateLatchData::view_proj_matrix));
+//
+// BINDING in the above examples is the binding location of the uniform
+// interface block in the GLSL shader.
+//
+// Shader example (3 would be the |ubo_binding| passed to this function):
+//  layout(binding = 3, std140) uniform LateLatchData {
+//    mat4 uViewProjection;
+//  };
+//
+// |eye| is 0 for left eye and 1 for right eye.
+//
+// The following GL states are modified as follows:
+// glBindBuffer(GL_UNIFORM_BUFFER, ...);
+// glBindBufferRange(GL_UNIFORM_BUFFER, ...);
+//
+// To clear the binding, call glBindBuffer(GL_UNIFORM_BUFFER, 0);
+//
+// Returns 0 on success, negative error code on failure:
+//   EPERM - dvrEdsInit(true) was not called.
+// Check GL errors with glGetError for other error conditions.
+int dvrEdsBindPose(int eye, uint32_t ubo_binding, intptr_t offset,
+                   ssize_t size);
+
+// DEPRECATED
+//
+// Blits the pose captured previously into the currently bound framebuffer.
+// The current framebuffer is assumed to be the default framebuffer 0, the
+// surface that will be sent to the display and have EDS and lens warp applied
+// to it.
+//
+// |eye| is 0 for left eye and 1 for right eye.
+// |viewport_width| is the width of the viewport for this eye, which is
+//                  usually half the width of the framebuffer.
+// |viewport_height| is the height of the viewport for this eye, which is
+//                   usually the height of the framebuffer.
+//
+// The following GL states are modified as follows:
+// glUseProgram(0);
+// glBindBuffer(GL_UNIFORM_BUFFER, 0);
+// glBindBufferRange(GL_UNIFORM_BUFFER, 23, ...);
+//
+// Returns 0 on success, negative error code on failure:
+//   EPERM - dvrEdsInit was not called.
+// Check GL errors with glGetError for other error conditions.
+int dvrEdsBlitPose(int eye, int viewport_width, int viewport_height);
+
+// DEPRECATED
+//
+// Same as dvrEdsBlitPose except that the pose is provided as an
+// parameter instead of getting it from dvrEdsBindPose. This is for
+// applications that want EDS but do not want late-latching.
+//
+// |pose_quaternion| should point to 4 floats that represent a quaternion.
+// |pose_position| should point to 3 floats that represent x,y,z position.
+//
+// GL states are modified as follows:
+// glUseProgram(0);
+// glBindBuffer(GL_UNIFORM_BUFFER, 0);
+// glBindBufferBase(GL_UNIFORM_BUFFER, 23, ...);
+//
+// Returns 0 on success, negative error code on failure:
+//   EPERM - dvrEdsInit was not called.
+// Check GL errors with glGetError for other error conditions.
+int dvrEdsBlitPoseFromCpu(int eye, int viewport_width, int viewport_height,
+                          const float* pose_quaternion,
+                          const float* pose_position);
+
+__END_DECLS
+
+#endif  // ANDROID_DVR_EDS_H_
diff --git a/libs/vr/libeds/include/private/dvr/color_channel_distortion.h b/libs/vr/libeds/include/private/dvr/color_channel_distortion.h
new file mode 100644
index 0000000..4e612cd
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/color_channel_distortion.h
@@ -0,0 +1,30 @@
+#ifndef ANDROID_DVR_COLOR_CHANNEL_DISTORTION_H_
+#define ANDROID_DVR_COLOR_CHANNEL_DISTORTION_H_
+
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+// ColorChannelDistortion encapsulates the way one color channel (wavelength)
+// is distorted optically when an image is viewed through a lens.
+class ColorChannelDistortion {
+ public:
+  virtual ~ColorChannelDistortion() {}
+
+  // Given a 2d point p, returns the corresponding distorted point.
+  // The units of both the input and output points are tan-angle units,
+  // which can be computed as the distance on the screen divided by
+  // distance from the virtual eye to the screen.  For both the input
+  // and output points, the intersection of the optical axis of the lens
+  // with the screen defines the origin, the x axis points right, and
+  // the y axis points up.
+  virtual vec2 Distort(vec2 p) const = 0;
+
+  virtual vec2 DistortInverse(vec2 p) const = 0;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_COLOR_CHANNEL_DISTORTION_H_
diff --git a/libs/vr/libeds/include/private/dvr/composite_hmd.h b/libs/vr/libeds/include/private/dvr/composite_hmd.h
new file mode 100644
index 0000000..70727e0
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/composite_hmd.h
@@ -0,0 +1,89 @@
+#ifndef ANDROID_DVR_COMPOSITE_HMD_H_
+#define ANDROID_DVR_COMPOSITE_HMD_H_
+
+#include <private/dvr/display_metrics.h>
+#include <private/dvr/head_mount_metrics.h>
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+// An intermediate structure composed of a head mount (described by
+// HeadMountMetrics) and a display (described by DisplayMetrics).
+class CompositeHmd {
+ public:
+  // Constructs a new CompositeHmd given a HeadMountMetrics and a
+  // DisplayMetrics.
+  CompositeHmd(const HeadMountMetrics& head_mount_metrics,
+               const DisplayMetrics& display_metrics);
+
+  CompositeHmd(CompositeHmd&& composite_hmd) = delete;
+  CompositeHmd(const CompositeHmd& composite_hmd) = delete;
+  CompositeHmd& operator=(CompositeHmd&& composite_hmd) = delete;
+  CompositeHmd& operator=(const CompositeHmd& composite_hmd) = delete;
+
+  // Headset metadata.
+  float GetTargetFrameDuration() const;
+  void ComputeDistortedVertex(EyeType eye, vec2 uv_in, vec2* vertex_out,
+                              vec2* uv_out) const;
+
+  // Eye-unspecific view accessors.
+  vec2i GetRecommendedRenderTargetSize() const;
+  Range2i GetDisplayRange() const;
+
+  // Eye-specific view accessors.
+  mat4 GetEyeFromHeadMatrix(EyeType eye) const;
+  FieldOfView GetEyeFov(EyeType eye) const;
+  Range2i GetEyeViewportBounds(EyeType eye) const;
+
+  // Set HeadMountMetrics and recompute everything that depends on
+  // HeadMountMetrics.
+  void SetHeadMountMetrics(const HeadMountMetrics& head_mount_metrics);
+
+  // Returns a reference to the |head_mount_metrics_| member.
+  const HeadMountMetrics& GetHeadMountMetrics() const;
+
+  // Set DisplayMetrics and recompute everything that depends on DisplayMetrics.
+  void SetDisplayMetrics(const DisplayMetrics& display_metrics);
+
+  // Returns a reference to the current display metrics.
+  const DisplayMetrics& GetDisplayMetrics() const;
+
+  // Compute the distorted point for a single channel.
+  vec2 ComputeDistortedPoint(EyeType eye, vec2 position,
+                             RgbColorChannel channel) const;
+
+  // Compute the inverse distorted point for a single channel.
+  vec2 ComputeInverseDistortedPoint(EyeType eye, vec2 position,
+                                    RgbColorChannel channel) const;
+
+ private:
+  FieldOfView eye_fov_[2];
+  Range2i eye_viewport_range_[2];
+  mat4 eye_from_head_matrix_[2];
+  Range2i display_range_;
+  vec2i recommended_render_target_size_;
+
+  // Per-eye scale and translation to convert from normalized Screen Space
+  // ([0:1]x[0:1]) to tan-angle space.
+  mat3 eye_tan_angle_from_norm_screen_matrix_[2];
+  mat3 eye_tan_angle_from_norm_screen_inv_matrix_[2];
+
+  // Per-eye scale and translation to convert from tan-angle space to normalized
+  // Texture Space ([0:1]x[0:1]).
+  mat3 eye_norm_texture_from_tan_angle_matrix_[2];
+  mat3 eye_norm_texture_from_tan_angle_inv_matrix_[2];
+
+  HeadMountMetrics head_mount_metrics_;
+  DisplayMetrics display_metrics_;
+
+  // Called by SetHeadMountMetrics/SetDisplayMetrics after metrics get changed.
+  // This function will update head_mount_metrics_/display_metrics_ based on the
+  // metrics supplied in the above two methods.
+  void MetricsChanged();
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_COMPOSITE_HMD_H_
diff --git a/libs/vr/libeds/include/private/dvr/cpu_thread_pose_updater.h b/libs/vr/libeds/include/private/dvr/cpu_thread_pose_updater.h
new file mode 100644
index 0000000..6a2c8a6
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/cpu_thread_pose_updater.h
@@ -0,0 +1,48 @@
+#ifndef ANDROID_DVR_CPU_THREAD_POSE_UPDATER_H_
+#define ANDROID_DVR_CPU_THREAD_POSE_UPDATER_H_
+
+#include <atomic>
+#include <thread>
+
+#include <private/dvr/lucid_pose_tracker.h>
+#include <private/dvr/raw_pose.h>
+
+namespace android {
+namespace dvr {
+
+// Temporary version of pose updater that uses a CPU thread to update
+// the pose buffer. Note that this thread starts and runs indefinitely
+class CpuThreadPoseUpdater {
+ public:
+  CpuThreadPoseUpdater();
+  ~CpuThreadPoseUpdater();
+
+  // Start the thread to update the given buffer with the given number of
+  // microseconds between updates.
+  void Start(volatile RawPosePair* mapped_pose_buffer, int period_us);
+
+  void StopAndJoin();
+
+ private:
+  void UpdateThread();
+
+  volatile RawPosePair* mapped_pose_buffer_;
+
+  // Pose update thread.
+  std::thread update_thread_;
+
+  volatile bool stop_request_;
+
+  // Update period in microseconds.
+  int update_period_us_;
+
+  // Current pose count, used to avoid writing to the same buffer that is being
+  // read by the GPU.
+  uint32_t count_;
+  LucidPoseTracker pose_tracker_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_CPU_THREAD_POSE_UPDATER_H_
diff --git a/libs/vr/libeds/include/private/dvr/display_metrics.h b/libs/vr/libeds/include/private/dvr/display_metrics.h
new file mode 100644
index 0000000..87d9d04
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/display_metrics.h
@@ -0,0 +1,79 @@
+#ifndef ANDROID_DVR_DISPLAY_METRICS_H_
+#define ANDROID_DVR_DISPLAY_METRICS_H_
+
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+enum class DisplayOrientation { kPortrait, kLandscape };
+
+// DisplayMetrics encapsulates metrics describing a display to be used
+// with a head mount to create a head mounted display.
+class DisplayMetrics {
+ public:
+  DisplayMetrics();
+  // Constructs a DisplayMetrics given a display size in pixels,
+  // meters per pixel, border size in meters, and frame duration in
+  // seconds.
+  //
+  // size_pixels The size of the display in pixels.
+  // meters_per_pixel The meters per pixel in each dimension.
+  // border_size_meters The size of the border around the display
+  //     in meters.  When the device sits on a surface in the proper
+  //     orientation this is the distance from the surface to the edge
+  //     of the display.
+  // frame_duration_seconds The duration in seconds of each frame
+  //     (i.e., 1 / framerate).
+  DisplayMetrics(vec2i size_pixels, vec2 meters_per_pixel,
+                 float border_size_meters, float frame_duration_seconds,
+                 DisplayOrientation orientation);
+
+  // Gets the size of the display in physical pixels (not logical pixels).
+  vec2i GetSizePixels() const { return size_pixels_; }
+
+  DisplayOrientation GetOrientation() const { return orientation_; }
+  bool IsPortrait() const {
+    return orientation_ == DisplayOrientation::kPortrait;
+  }
+
+  // Gets the size of the display in meters.
+  vec2 GetSizeMeters() const {
+    return vec2(static_cast<float>(size_pixels_[0]),
+                static_cast<float>(size_pixels_[1]))
+               .array() *
+           meters_per_pixel_.array();
+  }
+
+  // Gets the meters per pixel.
+  vec2 GetMetersPerPixel() const { return meters_per_pixel_; }
+
+  // Gets the size of the border around the display.
+  // For a phone in landscape position this would be the distance from
+  // the bottom the edge of the phone to the bottom of the screen.
+  float GetBorderSizeMeters() const { return border_size_meters_; }
+
+  // Gets the frame duration in seconds for the display.
+  float GetFrameDurationSeconds() const { return frame_duration_seconds_; }
+
+  // Toggles the orientation and swaps all of the settings such that the
+  // display is being held in the other orientation.
+  void ToggleOrientation();
+
+  // Override the meters per pixel.
+  void SetMetersPerPixel(const vec2& meters_per_pixel) {
+    meters_per_pixel_ = meters_per_pixel;
+  }
+
+ private:
+  vec2i size_pixels_;
+  vec2 meters_per_pixel_;
+  float border_size_meters_;
+  float frame_duration_seconds_;
+  DisplayOrientation orientation_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_DISPLAY_METRICS_H_
diff --git a/libs/vr/libeds/include/private/dvr/distortion_renderer.h b/libs/vr/libeds/include/private/dvr/distortion_renderer.h
new file mode 100644
index 0000000..e1c8114
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/distortion_renderer.h
@@ -0,0 +1,233 @@
+#ifndef ANDROID_DVR_DISTORTION_RENDERER_H_
+#define ANDROID_DVR_DISTORTION_RENDERER_H_
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <array>
+#include <functional>
+
+#include <private/dvr/eds_mesh.h>
+#include <private/dvr/graphics/shader_program.h>
+#include <private/dvr/late_latch.h>
+#include <private/dvr/lucid_pose_tracker.h>
+#include <private/dvr/render_texture_params.h>
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+class CompositeHmd;
+
+// Encapsulates the rendering operations to correct for the HMD's lens
+// distortion.
+class DistortionRenderer {
+ public:
+  static constexpr int kMaxLayers = 2;
+  static constexpr int kMaxLatchedLayers = 4;
+
+  static const mat4 kViewportFromClipMatrix;
+  static const mat4 kClipFromViewportMatrix;
+
+  // Creates a distortion renderer for distortion function.
+  //
+  // distortion_function the black-box distortion function to apply.
+  // display_size the resolution of the output of the distortion renderer.
+  // distortion_mesh_resolution the amount of subdivision in the
+  //     distortion mesh.
+  DistortionRenderer(const CompositeHmd& hmd, vec2i display_size,
+                     int distortion_mesh_resolution,
+                     bool flip_texture_horizontally,
+                     bool flip_texture_vertically, bool separated_eye_buffers,
+                     bool eds_enabled, bool late_latch_enabled);
+  ~DistortionRenderer();
+
+  // Returns the distortion factor array for the distortion function that was
+  // passed in at creation time. The distortion factor array contains the
+  // magnification factor induced by the distortion mesh at every vertex. There
+  // is one entry per vertex, and entries are ordered in row-major major. The
+  // array contains the magnification for both eyes averaged.
+  const std::vector<float>& GetDistortionFactorArray();
+
+  // |render_pose_buffer_object| is the per-texture pose array buffer object.
+  // |render_buffer_index| is the per-texture index into the pose array buffer
+  //                       object. This selects which pose was rendered into the
+  //                       corresponding texture.
+  void DoLateLatch(uint32_t target_vsync_count,
+                   const uint32_t* render_buffer_index,
+                   const GLuint* render_pose_buffer_objects,
+                   const bool* vertical_flip, const bool* separate_eye,
+                   int num_textures);
+
+  // Convenience method that does no flipping.
+  void DoLateLatch(uint32_t target_vsync_count,
+                   const uint32_t* render_buffer_index,
+                   const GLuint* render_pose_buffer_objects, int num_textures) {
+    bool flip[kMaxLayers] = {false};
+    bool separate[kMaxLayers] = {separated_eye_buffers_};
+    DoLateLatch(target_vsync_count, render_buffer_index,
+                render_pose_buffer_objects, flip, separate, num_textures);
+  }
+
+  void PrepGlState(EyeType eye);
+  void ResetGlState(int num_textures);
+
+  // Applies distortion correction to the given textures by rendering into the
+  // current output target.
+  //
+  // eye Which eye is being corrected.
+  // texture_ids The OpenGL texture IDs of the texture layers.
+  // texture_sizes Dimensions of the corresponding textures.
+  // vertical_flip Whether to flip each input texture vertically.
+  // separate_eye Whether the correspending texture is a separate texture for
+  //              left and right eyes. If false, it is a shared texture with
+  //              the left view on the left half and right on the right half.
+  // late_latch_layer Which late latch layer index to use for each texture.
+  //     Typically this is just {0, 1} unless blend_with_previous_layer is used.
+  // num_textures Number of textures in texture_ids and texture_sizes.
+  // blend_with_previous_layer If enabled, blend this single layer with the
+  //     existing framebuffer contents.
+  void ApplyDistortionCorrectionToTexture(
+      EyeType eye, const GLuint* texture_ids, const bool* vertical_flip,
+      const bool* separate_eye, const int* late_latch_layer, int num_textures,
+      bool blend_with_previous_layer, bool do_gl_state_prep);
+
+  // Convenience method that does no flipping.
+  void ApplyDistortionCorrectionToTexture(EyeType eye,
+                                          const GLuint* texture_ids,
+                                          int num_textures) {
+    bool flip[kMaxLayers] = {false};
+    bool separate[kMaxLayers] = {separated_eye_buffers_,
+                                 separated_eye_buffers_};
+    int latch_layer[kMaxLayers] = {0, 1};
+    ApplyDistortionCorrectionToTexture(eye, texture_ids, flip, separate,
+                                       latch_layer, num_textures, false, true);
+  }
+
+  // Draw a video quad based on the given video texture by rendering into the
+  // current output target.
+  //
+  // eye Which eye is being corrected.
+  // layer_id Which compositor layer the video mesh should be drawn into.
+  // texture_ids The OpenGL texture IDs of the texture layers.
+  // transform The transformation matrix that transforms the video mesh to its
+  //           desired eye space position for the target eye.
+  void DrawVideoQuad(EyeType eye, int layer_id, GLuint texture_id,
+                     const mat4& transform);
+
+  // Modifies the size of the output display. This is the number of physical
+  // pixels per dimension covered by the display on the output device. Calling
+  // this method is cheap; it only updates the state table of the two
+  // eye-specific mesh nodes.
+  void SetDisplaySize(vec2i size);
+
+  void SetEdsEnabled(bool enabled);
+  void SetChromaticAberrationCorrectionEnabled(bool enabled) {
+    chromatic_aberration_correction_enabled_ = enabled;
+  }
+  void SetUseAlphaVignette(bool enabled) { use_alpha_vignette_ = enabled; }
+
+  bool GetLastEdsPose(LateLatchOutput* out_data, int layer_id = 0) const;
+
+ private:
+  enum ShaderProgramType {
+    kNoChromaticAberrationCorrection,
+    kNoChromaticAberrationCorrectionTwoLayers,
+    kChromaticAberrationCorrection,
+    kChromaticAberrationCorrectionTwoLayers,
+    kChromaticAberrationCorrectionAlphaVignette,
+    kChromaticAberrationCorrectionAlphaVignetteTwoLayers,
+    kChromaticAberrationCorrectionWithBlend,
+    kSimpleVideoQuad,
+    kNumShaderPrograms,
+  };
+
+  struct EdsShader {
+    EdsShader() {}
+    ~EdsShader() {
+    }
+
+    void load(const char* vertex, const char* fragment, int num_layers,
+              bool use_alpha_vignette, float rotation, bool flip_vertical,
+              bool blend_with_previous_layer);
+    void use() { pgm.Use(); }
+
+    // Update uTexFromEyeMatrix and uEyeFromViewportMatrix by the distortion
+    // renderer with the transform matrix.
+    void SetTexFromEyeTransform(const mat4& transform) {
+      glUniformMatrix4fv(uTexFromEyeMatrix, 1, false, transform.data());
+    }
+
+    void SetEyeFromViewportTransform(const mat4& transform) {
+      glUniformMatrix4fv(uEyeFromViewportMatrix, 1, false, transform.data());
+    }
+
+    ShaderProgram pgm;
+
+    // Texture variables, named to match shader strings for convenience.
+    GLint uProjectionMatrix;
+    GLint uTexFromEyeMatrix;
+    GLint uEyeFromViewportMatrix;
+    GLint uTexXMinMax;
+  };
+
+  void DrawEye(EyeType eye, const GLuint* texture_ids,
+               const bool* vertical_flip, const bool* separate_eye,
+               const int* late_latch_layer, int num_textures,
+               bool blend_with_previous_layer, bool do_gl_state_prep);
+
+  // This function is called when there is an update on Hmd and distortion mesh
+  // vertices and factor array will be updated.
+  void RecomputeDistortion(const CompositeHmd& hmd);
+
+  // Per-eye, per flip, per separate eye mode buffers for setting EDS matrix
+  // when EDS is disabled.
+  GLuint uTexFromRecommendedViewportMatrix[2][2][2];
+
+  // Distortion mesh for the each eye.
+  EdsMesh mesh_node_[2];
+  // VBO (vertex buffer object) for distortion mesh vertices.
+  GLuint mesh_vbo_[2];
+  // VAO (vertex array object) for distortion mesh vertex array data.
+  GLuint mesh_vao_[2];
+  // IBO (index buffer object) for distortion mesh indices.
+  GLuint mesh_ibo_[2];
+
+  EdsShader shaders_[kNumShaderPrograms];
+
+  // Enum to indicate which shader program is being used.
+  ShaderProgramType shader_type_;
+
+  bool eds_enabled_;
+  bool chromatic_aberration_correction_enabled_;
+  bool use_alpha_vignette_;
+
+  // This keeps track of what distortion mesh resolution we are using currently.
+  // When there is an update on Hmd, the distortion mesh vertices/factor array
+  // will be re-computed with the old resolution that is stored here.
+  int distortion_mesh_resolution_;
+
+  // The OpenGL ID of the last texture passed to
+  // ApplyDistortionCorrectionToTexture().
+  GLuint last_distortion_texture_id_;
+
+  // GL texture 2D target for application texture.
+  GLint app_texture_target_;
+
+  // Precomputed matrices for EDS and viewport transforms.
+  mat4 tex_from_eye_matrix_[2][2][2];
+  mat4 eye_from_viewport_matrix_[2];
+
+  // Eye viewport locations.
+  vec2i eye_viewport_origin_[2];
+  vec2i eye_viewport_size_;
+
+  vec2i display_size_;
+
+  std::unique_ptr<LateLatch> late_latch_[kMaxLatchedLayers];
+  bool separated_eye_buffers_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_DISTORTION_RENDERER_H_
diff --git a/libs/vr/libeds/include/private/dvr/eds_mesh.h b/libs/vr/libeds/include/private/dvr/eds_mesh.h
new file mode 100644
index 0000000..d2c901e
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/eds_mesh.h
@@ -0,0 +1,38 @@
+#ifndef ANDROID_DVR_EDS_MESH_H_
+#define ANDROID_DVR_EDS_MESH_H_
+
+#include <stdint.h>
+#include <functional>
+#include <vector>
+
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+struct EdsVertex {
+  vec2 position;
+  vec2 red_viewport_coords;
+  vec2 green_viewport_coords;
+  vec2 blue_viewport_coords;
+};
+
+struct EdsMesh {
+  std::vector<EdsVertex> vertices;
+  std::vector<uint16_t> indices;
+};
+
+// Distortion function takes in a point in the range [0..1, 0..1] and returns
+// the vertex position and the three distorted points for separate R, G and B
+// channels.
+typedef std::function<void(EyeType, vec2, vec2*, vec2*)> DistortionFunction;
+
+// Builds a distortion mesh of resolution |resolution| using
+// the distortion provided by |hmd| for |eye|.
+EdsMesh BuildDistortionMesh(EyeType eye, int resolution,
+                            const DistortionFunction& distortion_function);
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_EDS_MESH_H_
diff --git a/libs/vr/libeds/include/private/dvr/head_mount_metrics.h b/libs/vr/libeds/include/private/dvr/head_mount_metrics.h
new file mode 100644
index 0000000..f3e63a6
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/head_mount_metrics.h
@@ -0,0 +1,134 @@
+#ifndef ANDROID_DVR_HEAD_MOUNT_METRICS_H_
+#define ANDROID_DVR_HEAD_MOUNT_METRICS_H_
+
+#include <array>
+
+#include <private/dvr/color_channel_distortion.h>
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+// HeadMountMetrics encapsulates metrics describing a head mount to be used
+// with a display to create a head mounted display.
+class HeadMountMetrics {
+ public:
+  // The vertical point of the HMD where the lens distance is measured from.
+  enum VerticalAlignment { kBottom = 0, kCenter = 1, kTop = 2 };
+
+  enum EyeOrientation {
+    kCCW0Degrees = 0,
+    kCCW90Degrees = 1,
+    kCCW180Degrees = 2,
+    kCCW270Degrees = 3,
+    kCCW0DegreesMirrored = 4,
+    kCCW90DegreesMirrored = 5,
+    kCCW180DegreesMirrored = 6,
+    kCCW270DegreesMirrored = 7,
+
+    // Rotations that consist of an odd number of 90 degree rotations will swap
+    // the height and width of any bounding boxes/viewports. This bit informs
+    // any viewport manipulating code to perform the appropriate transformation.
+    kRightAngleBit = 0x01,
+    // Viewports are represented as four floating point values (four half
+    // angles). Rotating this structure can be done through a shift operation.
+    // This mask extracts the rotation portion of the orientation.
+    kRotationMask = 0x03,
+    // This mask specifies whether the output is mirrored.
+    kMirroredBit = 0x04
+  };
+
+  HeadMountMetrics(
+      float inter_lens_distance, float tray_to_lens_distance,
+      float virtual_eye_to_screen_distance,
+      VerticalAlignment vertical_alignment, const FieldOfView& left_eye_max_fov,
+      const FieldOfView& right_eye_max_fov,
+      const std::shared_ptr<ColorChannelDistortion>& red_distortion,
+      const std::shared_ptr<ColorChannelDistortion>& green_distortion,
+      const std::shared_ptr<ColorChannelDistortion>& blue_distortion,
+      EyeOrientation left_eye_orientation, EyeOrientation right_eye_orientation,
+      float screen_center_to_lens_distance)
+      : inter_lens_distance_(inter_lens_distance),
+        tray_to_lens_distance_(tray_to_lens_distance),
+        virtual_eye_to_screen_distance_(virtual_eye_to_screen_distance),
+        screen_center_to_lens_distance_(screen_center_to_lens_distance),
+        vertical_alignment_(vertical_alignment),
+        eye_max_fov_({{left_eye_max_fov, right_eye_max_fov}}),
+        color_channel_distortion_(
+            {{red_distortion, green_distortion, blue_distortion}}),
+        supports_chromatic_aberration_correction_(true),
+        eye_orientation_({{left_eye_orientation, right_eye_orientation}}) {
+    // If we're missing the green or blur distortions, assume that we don't
+    // correct for chromatic aberration.
+    if (!green_distortion || !blue_distortion) {
+      color_channel_distortion_[1] = red_distortion;
+      color_channel_distortion_[2] = red_distortion;
+      supports_chromatic_aberration_correction_ = false;
+    }
+  }
+
+  // Returns the distance in meters between the optical centers of the two
+  // lenses.
+  float GetInterLensDistance() const { return inter_lens_distance_; }
+
+  // Returns the distance in meters from the "tray" upon which the display
+  // rests to the optical center of a lens.
+  float GetTrayToLensDistance() const { return tray_to_lens_distance_; }
+
+  // Returns the distance in meters from the virtual eye to the screen.
+  // See http://go/vr-distortion-correction for an explanation of what
+  // this distance is.
+  float GetVirtualEyeToScreenDistance() const {
+    return virtual_eye_to_screen_distance_;
+  }
+
+  // Returns the horizontal distance from the center of the screen to the center
+  // of the lens, in meters.
+  float GetScreenCenterToLensDistance() const {
+    return screen_center_to_lens_distance_;
+  }
+
+  // Returns the vertical alignment of the HMD.  The tray-to-lens distance
+  // is relative to this position.  Exception: if the alignment is kCenter,
+  // then the offset has no meaning.
+  VerticalAlignment GetVerticalAlignment() const { return vertical_alignment_; }
+
+  // Returns the given eye's maximum field of view visible through the lens.
+  // The actual rendered field of view will be limited by this and also by
+  // the size of the screen.
+  const FieldOfView& GetEyeMaxFov(EyeType eye) const {
+    return eye_max_fov_[eye];
+  }
+
+  // Returns the ColorChannelDistortion object representing the distortion
+  // caused by the lenses for the given color channel.
+  const ColorChannelDistortion& GetColorChannelDistortion(
+      RgbColorChannel channel) const {
+    return *color_channel_distortion_[channel];
+  }
+
+  bool supports_chromatic_aberration_correction() const {
+    return supports_chromatic_aberration_correction_;
+  }
+
+  EyeOrientation GetEyeOrientation(EyeType eye) const {
+    return eye_orientation_[eye];
+  }
+
+ private:
+  float inter_lens_distance_;
+  float tray_to_lens_distance_;
+  float virtual_eye_to_screen_distance_;
+  float screen_center_to_lens_distance_;
+  VerticalAlignment vertical_alignment_;
+  std::array<FieldOfView, 2> eye_max_fov_;
+  std::array<std::shared_ptr<ColorChannelDistortion>, 3>
+      color_channel_distortion_;
+  bool supports_chromatic_aberration_correction_;
+  std::array<EyeOrientation, 2> eye_orientation_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_HEAD_MOUNT_METRICS_H_
diff --git a/libs/vr/libeds/include/private/dvr/identity_distortion.h b/libs/vr/libeds/include/private/dvr/identity_distortion.h
new file mode 100644
index 0000000..b9c5cf6
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/identity_distortion.h
@@ -0,0 +1,23 @@
+#ifndef ANDROID_DVR_IDENTITY_DISTORTION_H_
+#define ANDROID_DVR_IDENTITY_DISTORTION_H_
+
+#include <private/dvr/color_channel_distortion.h>
+
+namespace android {
+namespace dvr {
+
+// Provides an identity distortion operation if running the device without any
+// lenses.
+class IdentityDistortion : public ColorChannelDistortion {
+ public:
+  IdentityDistortion() {}
+
+  vec2 Distort(vec2 p) const override { return p; }
+
+  vec2 DistortInverse(vec2 p) const override { return p; }
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_IDENTITY_DISTORTION_H_
diff --git a/libs/vr/libeds/include/private/dvr/lookup_radial_distortion.h b/libs/vr/libeds/include/private/dvr/lookup_radial_distortion.h
new file mode 100644
index 0000000..56fc5db
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/lookup_radial_distortion.h
@@ -0,0 +1,31 @@
+#ifndef ANDROID_DVR_LOOKUP_RADIAL_DISTORTION_H_
+#define ANDROID_DVR_LOOKUP_RADIAL_DISTORTION_H_
+
+#include <vector>
+
+#include <private/dvr/color_channel_distortion.h>
+
+namespace android {
+namespace dvr {
+
+// LookupRadialDistortion implements a radial distortion based using using a
+// vector of tan(angle) -> multipliers.  This can use measured data directly.
+class LookupRadialDistortion : public ColorChannelDistortion {
+ public:
+  // lookup.x = tan(angle), lookup.y = distance from center multiplier.
+  explicit LookupRadialDistortion(const vec2* lookup, size_t count);
+
+  vec2 Distort(vec2 p) const override;
+  vec2 DistortInverse(vec2 p) const override;
+
+ private:
+  float DistortionFactor(float r) const;
+  float DistortRadius(float r) const;
+
+  std::vector<vec2> lookup_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_LOOKUP_RADIAL_DISTORTION_H_
diff --git a/libs/vr/libeds/include/private/dvr/lucid_metrics.h b/libs/vr/libeds/include/private/dvr/lucid_metrics.h
new file mode 100644
index 0000000..0e4ada4
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/lucid_metrics.h
@@ -0,0 +1,22 @@
+#ifndef ANDROID_DVR_LUCID_METRICS_H_
+#define ANDROID_DVR_LUCID_METRICS_H_
+
+#include <private/dvr/display_metrics.h>
+#include <private/dvr/head_mount_metrics.h>
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+HeadMountMetrics CreateHeadMountMetrics();
+HeadMountMetrics CreateHeadMountMetrics(const FieldOfView& l_fov,
+                                        const FieldOfView& r_fov);
+HeadMountMetrics CreateUndistortedHeadMountMetrics();
+HeadMountMetrics CreateUndistortedHeadMountMetrics(const FieldOfView& l_fov,
+                                                   const FieldOfView& r_fov);
+DisplayMetrics CreateDisplayMetrics(vec2i screen_size);
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_LUCID_METRICS_H_
diff --git a/libs/vr/libeds/include/private/dvr/lucid_pose_tracker.h b/libs/vr/libeds/include/private/dvr/lucid_pose_tracker.h
new file mode 100644
index 0000000..4ceda5a
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/lucid_pose_tracker.h
@@ -0,0 +1,49 @@
+#ifndef ANDROID_DVR_LUCID_POSE_TRACKER_H_
+#define ANDROID_DVR_LUCID_POSE_TRACKER_H_
+
+#include <memory>
+
+#include <dvr/pose_client.h>
+
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+// Provides pose tracking via the system pose service.
+class LucidPoseTracker {
+ public:
+  // When set, the pose service is ignored and the given pose is always returned
+  // by GetPose. As long as this is called before any LucidPoseTracker is
+  // used, the pose service will not be created.
+  // Threading: this is not thread safe.
+  static void SetPoseOverride(const Posef& pose);
+
+  // Reset prior override pose.
+  static void ClearPoseOverride();
+
+  LucidPoseTracker();
+  ~LucidPoseTracker();
+
+  // Currently GetPose() will ignore timestamp_ns and always return the most
+  // recent orientation.
+  // TODO(stefanus): support prediction.
+  Posef GetPose(uint64_t timestamp_ns);
+
+ private:
+  static bool is_override_pose_;
+  static Posef override_pose_;
+
+  DvrPose* pose_client_;
+
+  // The most recent pose.
+  Posef latest_pose_;
+
+  // The time stamp corresponding to when latest_pose_ was last updated.
+  uint64_t latest_timestamp_ns_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_LUCID_POSE_TRACKER_H_
diff --git a/libs/vr/libeds/include/private/dvr/polynomial_radial_distortion.h b/libs/vr/libeds/include/private/dvr/polynomial_radial_distortion.h
new file mode 100644
index 0000000..8f080aa
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/polynomial_radial_distortion.h
@@ -0,0 +1,60 @@
+#ifndef ANDROID_DVR_POLYNOMIAL_RADIAL_DISTORTION_H_
+#define ANDROID_DVR_POLYNOMIAL_RADIAL_DISTORTION_H_
+
+#include <vector>
+
+#include <private/dvr/color_channel_distortion.h>
+
+namespace android {
+namespace dvr {
+
+// PolynomialRadialDistortion implements a radial distortion based using
+// a set of coefficients describing a polynomial function.
+// See http://en.wikipedia.org/wiki/Distortion_(optics).
+//
+// Unless otherwise stated, the units used in this class are tan-angle units
+// which can be computed as distance on the screen divided by distance from the
+// virtual eye to the screen.
+class PolynomialRadialDistortion : public ColorChannelDistortion {
+ public:
+  // Construct a PolynomialRadialDistortion with coefficients for
+  // the radial distortion equation:
+  //
+  //   p' = p (1 + K1 r^2 + K2 r^4 + ... + Kn r^(2n))
+  //
+  // where r is the distance in tan-angle units from the optical center,
+  // p the input point and p' the output point.
+  // The provided vector contains the coefficients for the even monomials
+  // in the distortion equation: coefficients[0] is K1, coefficients[1] is K2,
+  // etc.  Thus the polynomial used for distortion has degree
+  // (2 * coefficients.size()).
+  explicit PolynomialRadialDistortion(const std::vector<float>& coefficients);
+
+  // Given a radius (measuring distance from the optical axis of the lens),
+  // returns the distortion factor for that radius.
+  float DistortionFactor(float r_squared) const;
+
+  // Given a radius (measuring distance from the optical axis of the lens),
+  // returns the corresponding distorted radius.
+  float DistortRadius(float r) const;
+
+  // Given a 2d point p, returns the corresponding distorted point.
+  // distance from the virtual eye to the screen.  The optical axis
+  // of the lens defines the origin for both input and output points.
+  vec2 Distort(vec2 p) const override;
+
+  // Given a 2d point p, returns the point that would need to be passed to
+  // Distort to get point p (approximately).
+  vec2 DistortInverse(vec2 p) const override;
+
+  // Returns the distortion coefficients.
+  const std::vector<float>& GetCoefficients() const;
+
+ private:
+  std::vector<float> coefficients_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_POLYNOMIAL_RADIAL_DISTORTION_H_
diff --git a/libs/vr/libeds/include/private/dvr/raw_pose.h b/libs/vr/libeds/include/private/dvr/raw_pose.h
new file mode 100644
index 0000000..7058f1a
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/raw_pose.h
@@ -0,0 +1,54 @@
+#ifndef ANDROID_DVR_RAW_POSE_H_
+#define ANDROID_DVR_RAW_POSE_H_
+
+#include <atomic>
+
+namespace android {
+namespace dvr {
+
+// POD raw data of a head pose with a count field for read consistency checking.
+// Warning: The layout of this struct and RawPosePair are specific to match the
+// corresponding buffer type in the shader in late_latch.cpp.
+struct RawPose {
+  void Reset(uint32_t new_count) volatile {
+    qx = qy = qz = 0.0f;
+    qw = 1.0f;
+    px = py = pz = 0.0f;
+    count = new_count;
+  }
+
+  float qx, qy, qz, qw;
+  float px, py, pz;
+  std::atomic<uint32_t> count;
+};
+
+// RawPosePair is used for lock-free writing at about 1khz by the CPU/DSP
+// and reading by the GPU. At creation time, pose1 is given count = 1 and
+// pose2 is given count = 2.
+//
+// The lock-free write pattern is:
+// - write to pose with least count.
+// - memory write barrier.
+// - write count = count + 2.
+//
+// For reads, there is an important assumption about the GPU: it generally
+// processes things contiguously, without arbitrary preemptions that save and
+// restore full cache states. In other words, if the GPU is preempted and then
+// later resumed, any data that was read from memory before the preemption will
+// be re-read from memory after resume. This allows the following read trick to
+// work:
+// - read the full RawPosePair into a shader.
+// - select the pose with the newest count.
+//
+// The older pose may be partially written by the async stores from CPU/DSP, but
+// because of the memory barrier and GPU characteristics, the highest count pose
+// should always be a fully consistent RawPose.
+struct RawPosePair {
+  RawPose pose1;
+  RawPose pose2;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_RAW_POSE_H_
diff --git a/libs/vr/libeds/include/private/dvr/render_texture_params.h b/libs/vr/libeds/include/private/dvr/render_texture_params.h
new file mode 100644
index 0000000..71aebef
--- /dev/null
+++ b/libs/vr/libeds/include/private/dvr/render_texture_params.h
@@ -0,0 +1,55 @@
+#ifndef ANDROID_DVR_RENDER_TEXTURE_PARAMS_H_
+#define ANDROID_DVR_RENDER_TEXTURE_PARAMS_H_
+
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+// Encapsulates information about the render texture, includes the size
+// of the render texture, and the left/right viewport which define the
+// portion each eye is rendering onto. This struct will be passed to
+// PresentFrame every frame before the client actually drawing the scene.
+struct RenderTextureParams {
+  RenderTextureParams() {}
+
+  RenderTextureParams(vec2i target_texture_size,
+                      const Range2i& eye_viewport_bounds_left,
+                      const Range2i& eye_viewport_bounds_right,
+                      const FieldOfView& eye_fov_left,
+                      const FieldOfView& eye_fov_right)
+      : texture_size(target_texture_size) {
+    eye_viewport_bounds[kLeftEye] = eye_viewport_bounds_left;
+    eye_viewport_bounds[kRightEye] = eye_viewport_bounds_right;
+    eye_fov[kLeftEye] = eye_fov_left;
+    eye_fov[kRightEye] = eye_fov_right;
+  }
+
+  explicit RenderTextureParams(vec2i target_texture_size,
+                               const FieldOfView& eye_fov_left,
+                               const FieldOfView& eye_fov_right) {
+    texture_size = target_texture_size;
+    eye_viewport_bounds[0] = Range2i::FromSize(
+        vec2i(0, 0), vec2i(texture_size[0] / 2, texture_size[1]));
+    eye_viewport_bounds[1] =
+        Range2i::FromSize(vec2i(texture_size[0] / 2, 0),
+                          vec2i(texture_size[0] / 2, texture_size[1]));
+
+    eye_fov[kLeftEye] = eye_fov_left;
+    eye_fov[kRightEye] = eye_fov_right;
+  }
+
+  // The render texture size.
+  vec2i texture_size;
+
+  // The viewport bounds on the render texture for each eye.
+  Range2i eye_viewport_bounds[2];
+
+  // The field of view for each eye in degrees.
+  FieldOfView eye_fov[2];
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_RENDER_TEXTURE_PARAMS_H_
diff --git a/libs/vr/libeds/lookup_radial_distortion.cpp b/libs/vr/libeds/lookup_radial_distortion.cpp
new file mode 100644
index 0000000..2cee863
--- /dev/null
+++ b/libs/vr/libeds/lookup_radial_distortion.cpp
@@ -0,0 +1,47 @@
+#include "include/private/dvr/lookup_radial_distortion.h"
+
+namespace android {
+namespace dvr {
+
+LookupRadialDistortion::LookupRadialDistortion(const vec2* lookup, size_t count)
+    : lookup_(lookup, lookup + count) {}
+
+float LookupRadialDistortion::DistortionFactor(float r) const {
+  for (size_t i = 1; i < lookup_.size(); ++i) {
+    if (lookup_[i].x() > r) {
+      float t =
+          (r - lookup_[i - 1].x()) / (lookup_[i].x() - lookup_[i - 1].x());
+      return lookup_[i - 1].y() + t * (lookup_[i].y() - lookup_[i - 1].y());
+    }
+  }
+  return lookup_.back().y();
+}
+
+float LookupRadialDistortion::DistortRadius(float r) const {
+  return r * DistortionFactor(r);
+}
+
+vec2 LookupRadialDistortion::Distort(vec2 p) const {
+  return p * DistortionFactor(p.norm());
+}
+
+vec2 LookupRadialDistortion::DistortInverse(vec2 p) const {
+  // Secant method.
+  const float radius = p.norm();
+  float r0 = radius / 0.9f;
+  float r1 = radius * 0.9f;
+  float r2;
+  float dr0 = radius - DistortRadius(r0);
+  float dr1;
+  while (fabsf(r1 - r0) > 0.0001f /** 0.1mm */) {
+    dr1 = radius - DistortRadius(r1);
+    r2 = r1 - dr1 * ((r1 - r0) / (dr1 - dr0));
+    r0 = r1;
+    r1 = r2;
+    dr0 = dr1;
+  }
+  return (r1 / radius) * p;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libeds/lucid_metrics.cpp b/libs/vr/libeds/lucid_metrics.cpp
new file mode 100644
index 0000000..690c326
--- /dev/null
+++ b/libs/vr/libeds/lucid_metrics.cpp
@@ -0,0 +1,327 @@
+#include "include/private/dvr/display_metrics.h"
+#include <private/dvr/head_mount_metrics.h>
+#include <private/dvr/identity_distortion.h>
+#include <private/dvr/lookup_radial_distortion.h>
+#include <private/dvr/lucid_metrics.h>
+#include <private/dvr/types.h>
+
+namespace {
+
+// These numbers are specific to the OnePlus One and therefore
+// temporary until we advance to the next Lucid development platform.
+
+// Head mount metrics for Lucid A00
+static const float kDefaultInterLensDistance = 0.064f;  // 64mm
+static const float kDefaultTrayToLensDistance = 0.035f;
+static const float kDefaultVirtualEyeToScreenDistance = 0.042f;
+static const android::dvr::HeadMountMetrics::VerticalAlignment
+    kDefaultVerticalAlignment = android::dvr::HeadMountMetrics::kCenter;
+static const float kDefaultFovHalfAngleInsideH = 43.7f * M_PI / 180.0f;
+static const float kDefaultFovHalfAngleOutsideH = 47.8f * M_PI / 180.0f;
+static const float kDefaultFovHalfAngleV = 54.2f * M_PI / 180.0f;
+
+// Screen size in meters for Lucid (Nexus 6 display in portrait mode).
+static const android::dvr::vec2 kScreenSizeInMeters(0.0742177f, 0.131943f);
+
+// Border size in meters for the OnePlus One.
+static const float kScreenBorderSize = 0.004f;
+
+// Refresh rate.
+static const float kScreenRefreshRate = 60.0f;
+
+// Lucid display orientation is portrait.
+static const android::dvr::DisplayOrientation kDisplayOrientation =
+    android::dvr::DisplayOrientation::kPortrait;
+
+}  // anonymous namespace
+
+namespace android {
+namespace dvr {
+
+// The distortion lookup tables were generated via a raytraced lens simulation.
+// Please see for full calculations:
+// https://docs.google.com/a/google.com/spreadsheets/d/
+//       15cfHmCw5mHVOQ1rAJxMhta4q0e8zzcUDka1nRkfl7pY/edit?usp=sharing
+LookupRadialDistortion* GetBlueDistortionLookup() {
+  // clang-format off
+  vec2 kBlueDistortionLookup[] = {
+    {0.00000000000f, 1.00000000000f},
+    {0.01888626190f, 1.00096958278f},
+    {0.03777223810f, 1.00133301793f},
+    {0.05665761905f, 1.00193985168f},
+    {0.07554214286f, 1.00279048731f},
+    {0.09442542857f, 1.00388751781f},
+    {0.11330704762f, 1.00523363045f},
+    {0.13218657143f, 1.00683149424f},
+    {0.15106340476f, 1.00868516849f},
+    {0.16993695238f, 1.01079861126f},
+    {0.18880640476f, 1.01317712726f},
+    {0.20767092857f, 1.01582607321f},
+    {0.22652945238f, 1.01875203063f},
+    {0.24538078571f, 1.02196207850f},
+    {0.26422352381f, 1.02546421601f},
+    {0.28305602381f, 1.02926737969f},
+    {0.30187640476f, 1.03338139216f},
+    {0.32068252381f, 1.03781702504f},
+    {0.33947190476f, 1.04258620905f},
+    {0.35824171429f, 1.04770206653f},
+    {0.37698869048f, 1.05317909331f},
+    {0.39570916667f, 1.05903306635f},
+    {0.41439900000f, 1.06528124790f},
+    {0.43305350000f, 1.07194257391f},
+    {0.45166738095f, 1.07903777957f},
+    {0.47023471429f, 1.08658953759f},
+    {0.48874897619f, 1.09462239798f},
+    {0.50720285714f, 1.10316330018f},
+    {0.52558835714f, 1.11224144183f},
+    {0.54389669048f, 1.12188861421f},
+    {0.56211826190f, 1.13213939967f},
+    {0.58024261905f, 1.14303145047f},
+    {0.59825847619f, 1.15460566091f},
+    {0.61615335714f, 1.16690711338f},
+    {0.63391345238f, 1.17998560444f},
+    {0.65152300000f, 1.19389708987f},
+    {0.66896328571f, 1.20870580446f},
+    {0.68621100000f, 1.22448751087f},
+    {0.70323578571f, 1.24133415620f},
+    {0.71999716667f, 1.25935962776f},
+    {0.73643969048f, 1.27870875648f},
+    {0.75250778571f, 1.29953256670f},
+    {0.76817614286f, 1.32193822000f},
+    {0.78342009524f, 1.34604270338f},
+    {0.79828314286f, 1.37185833833f},
+    {0.81267376190f, 1.39964322604f},
+    {0.82656559524f, 1.42955958262f},
+    {0.83983054762f, 1.46196539657f},
+    {0.85234333333f, 1.49724142650f},
+    {0.86394971429f, 1.53585530271f},
+    {0.87422461905f, 1.57881139444f},
+    {0.88382583095f, 1.62091537826f},
+    {0.89571361286f, 1.67610209261f},
+    {0.90490389167f, 1.72118819668f},
+    {0.91526452143f, 1.77496904481f},
+    {0.92651365452f, 1.83722833673f},
+    {0.93437489976f, 1.88337590145f},
+    {0.94654105500f, 1.95937892848f},
+    {0.95476685095f, 2.01469745492f},
+    {0.96720383310f, 2.10451495481f},
+    {0.97546726405f, 2.16904926656f},
+    {0.98774046786f, 2.27302748020f},
+    {0.99579206762f, 2.34720582421f},
+    {1.00763328857f, 2.46603526105f},
+    {1.01533118405f, 2.55049232288f},
+    {1.02287120929f, 2.63936582235f}
+  };
+  // clang-format on
+  return new LookupRadialDistortion(
+      kBlueDistortionLookup, sizeof(kBlueDistortionLookup) / sizeof(vec2));
+}
+
+LookupRadialDistortion* GetGreenDistortionLookup() {
+  // clang-format off
+  vec2 kGreenDistortionLookup[] = {
+    {0.00000000000f, 1.00000000000f},
+    {0.01898883333f, 1.00000000000f},
+    {0.03797750000f, 1.00000000000f},
+    {0.05696585714f, 1.00000000000f},
+    {0.07595369048f, 1.00000000000f},
+    {0.09494078571f, 1.00000000000f},
+    {0.11392685714f, 1.00000000000f},
+    {0.13291157143f, 1.00000000000f},
+    {0.15189450000f, 1.00176560670f},
+    {0.17087511905f, 1.00384553961f},
+    {0.18985280952f, 1.00618614484f},
+    {0.20882680952f, 1.00879302066f},
+    {0.22779623810f, 1.01167234096f},
+    {0.24675997619f, 1.01483135203f},
+    {0.26571680952f, 1.01827767641f},
+    {0.28466519048f, 1.02202026825f},
+    {0.30360342857f, 1.02606859705f},
+    {0.32252950000f, 1.03043334057f},
+    {0.34144104762f, 1.03512630376f},
+    {0.36033538095f, 1.04016038545f},
+    {0.37920942857f, 1.04554970984f},
+    {0.39805966667f, 1.05130981266f},
+    {0.41688209524f, 1.05745768999f},
+    {0.43567214286f, 1.06401204155f},
+    {0.45442473810f, 1.07099310305f},
+    {0.47313411905f, 1.07842314596f},
+    {0.49179388095f, 1.08632639514f},
+    {0.51039692857f, 1.09472920992f},
+    {0.52893538095f, 1.10366038032f},
+    {0.54740061905f, 1.11315113705f},
+    {0.56578326190f, 1.12323535769f},
+    {0.58407300000f, 1.13395008040f},
+    {0.60225871429f, 1.14533547370f},
+    {0.62032809524f, 1.15743581542f},
+    {0.63826750000f, 1.17030000749f},
+    {0.65606135714f, 1.18398295206f},
+    {0.67369107143f, 1.19854780583f},
+    {0.69113350000f, 1.21406895255f},
+    {0.70835842857f, 1.23063670464f},
+    {0.72532545238f, 1.24836302903f},
+    {0.74197478571f, 1.26739777609f},
+    {0.75822164286f, 1.28793886907f},
+    {0.77407361905f, 1.31003521318f},
+    {0.78948523810f, 1.33383710115f},
+    {0.80448471429f, 1.35938255065f},
+    {0.81901733333f, 1.38686361242f},
+    {0.83305214286f, 1.41644808409f},
+    {0.84646438095f, 1.44848277406f},
+    {0.85912733333f, 1.48334485259f},
+    {0.87088369048f, 1.52149970074f},
+    {0.88131250000f, 1.56392750036f},
+    {0.89105132929f, 1.60552684742f},
+    {0.90312479476f, 1.66002695068f},
+    {0.91244067452f, 1.70458805205f},
+    {0.92297971714f, 1.75767475825f},
+    {0.93440940905f, 1.81916050294f},
+    {0.94237194976f, 1.86478635937f},
+    {0.95471202405f, 1.93989738862f},
+    {0.96305355738f, 1.99457325750f},
+    {0.97567372071f, 2.08333293385f},
+    {0.98407229071f, 2.14708073108f},
+    {0.99653762071f, 2.24981649552f},
+    {1.00471276167f, 2.32311751786f},
+    {1.01672394000f, 2.44057411530f},
+    {1.02452363381f, 2.52407947994f},
+    {1.03216732667f, 2.61194301580f}
+  };
+  // clang-format on
+  return new LookupRadialDistortion(
+      kGreenDistortionLookup, sizeof(kGreenDistortionLookup) / sizeof(vec2));
+}
+
+LookupRadialDistortion* GetRedDistortionLookup() {
+  // clang-format off
+  vec2 kRedDistortionLookup[] = {
+    {0.00000000000f, 1.00000000000f},
+    {0.01906776190f, 1.00000000000f},
+    {0.03813547619f, 1.00000000000f},
+    {0.05720304762f, 1.00000000000f},
+    {0.07627040476f, 1.00000000000f},
+    {0.09533740476f, 1.00000000000f},
+    {0.11440385714f, 1.00000000000f},
+    {0.13346952381f, 1.00000000000f},
+    {0.15253409524f, 1.00000000000f},
+    {0.17159714286f, 1.00000000000f},
+    {0.19065814286f, 1.00053530030f},
+    {0.20971645238f, 1.00310924426f},
+    {0.22877123810f, 1.00595236192f},
+    {0.24782154762f, 1.00907150786f},
+    {0.26686623810f, 1.01247435420f},
+    {0.28590388095f, 1.01616968529f},
+    {0.30493288095f, 1.02016688932f},
+    {0.32395133333f, 1.02447646681f},
+    {0.34295697619f, 1.02911011406f},
+    {0.36194726190f, 1.03408046560f},
+    {0.38091921429f, 1.03940151599f},
+    {0.39986942857f, 1.04508858434f},
+    {0.41879402381f, 1.05115843585f},
+    {0.43768857143f, 1.05762946333f},
+    {0.45654809524f, 1.06452169646f},
+    {0.47536695238f, 1.07185711363f},
+    {0.49413888095f, 1.07965956927f},
+    {0.51285690476f, 1.08795508025f},
+    {0.53151326190f, 1.09677206014f},
+    {0.55009952381f, 1.10614118417f},
+    {0.56860633333f, 1.11609607621f},
+    {0.58702361905f, 1.12667304464f},
+    {0.60534028571f, 1.13791190276f},
+    {0.62354421429f, 1.14985618930f},
+    {0.64162188095f, 1.16255413653f},
+    {0.65955780952f, 1.17605992962f},
+    {0.67733352381f, 1.19043584317f},
+    {0.69492602381f, 1.20575517508f},
+    {0.71230514286f, 1.22210708787f},
+    {0.72943057143f, 1.23960199799f},
+    {0.74623921429f, 1.25839340501f},
+    {0.76262400000f, 1.27871385661f},
+    {0.77861754762f, 1.30056919119f},
+    {0.79415866667f, 1.32413401001f},
+    {0.80926385714f, 1.34946540639f},
+    {0.82390640476f, 1.37670655635f},
+    {0.83805190476f, 1.40602920817f},
+    {0.85157807143f, 1.43777181543f},
+    {0.86435700000f, 1.47230885729f},
+    {0.87622914286f, 1.51010361811f},
+    {0.88677650000f, 1.55211817236f},
+    {0.89663317738f, 1.59330127207f},
+    {0.90883197952f, 1.64729627820f},
+    {0.91827594357f, 1.69138814689f},
+    {0.92892199405f, 1.74398939784f},
+    {0.94047261548f, 1.80490554711f},
+    {0.94852659262f, 1.85009630648f},
+    {0.96099790167f, 1.92451421938f},
+    {0.96945317500f, 1.97863645920f},
+    {0.98221554286f, 2.06656418112f},
+    {0.99069599476f, 2.12974390154f},
+    {1.00331392976f, 2.23149730290f},
+    {1.01157138762f, 2.30414058939f},
+    {1.02372409452f, 2.42049694265f},
+    {1.03162992905f, 2.50318810924f},
+    {1.03934762000f, 2.59027212626f}
+  };
+  // clang-format on
+  return new LookupRadialDistortion(
+      kRedDistortionLookup, sizeof(kRedDistortionLookup) / sizeof(vec2));
+}
+
+HeadMountMetrics CreateHeadMountMetrics(const FieldOfView& l_fov,
+                                        const FieldOfView& r_fov) {
+  std::shared_ptr<ColorChannelDistortion> default_distortion_r(
+      GetRedDistortionLookup());
+  std::shared_ptr<ColorChannelDistortion> default_distortion_g(
+      GetGreenDistortionLookup());
+  std::shared_ptr<ColorChannelDistortion> default_distortion_b(
+      GetBlueDistortionLookup());
+
+  return HeadMountMetrics(
+      kDefaultInterLensDistance, kDefaultTrayToLensDistance,
+      kDefaultVirtualEyeToScreenDistance, kDefaultVerticalAlignment, l_fov,
+      r_fov, default_distortion_r, default_distortion_g, default_distortion_b,
+      HeadMountMetrics::EyeOrientation::kCCW0Degrees,
+      HeadMountMetrics::EyeOrientation::kCCW0Degrees,
+      kDefaultInterLensDistance / 2.0f);
+}
+
+HeadMountMetrics CreateHeadMountMetrics() {
+  FieldOfView l_fov(kDefaultFovHalfAngleOutsideH, kDefaultFovHalfAngleInsideH,
+                    kDefaultFovHalfAngleV, kDefaultFovHalfAngleV);
+  FieldOfView r_fov(kDefaultFovHalfAngleInsideH, kDefaultFovHalfAngleOutsideH,
+                    kDefaultFovHalfAngleV, kDefaultFovHalfAngleV);
+
+  return CreateHeadMountMetrics(l_fov, r_fov);
+}
+
+DisplayMetrics CreateDisplayMetrics(vec2i screen_size) {
+  vec2 meters_per_pixel(
+      kScreenSizeInMeters[0] / static_cast<float>(screen_size[0]),
+      kScreenSizeInMeters[1] / static_cast<float>(screen_size[1]));
+  return DisplayMetrics(screen_size, meters_per_pixel, kScreenBorderSize,
+                        1000.0f / kScreenRefreshRate, kDisplayOrientation);
+}
+
+HeadMountMetrics CreateUndistortedHeadMountMetrics() {
+  FieldOfView l_fov(kDefaultFovHalfAngleOutsideH, kDefaultFovHalfAngleInsideH,
+                    kDefaultFovHalfAngleV, kDefaultFovHalfAngleV);
+  FieldOfView r_fov(kDefaultFovHalfAngleInsideH, kDefaultFovHalfAngleOutsideH,
+                    kDefaultFovHalfAngleV, kDefaultFovHalfAngleV);
+  return CreateUndistortedHeadMountMetrics(l_fov, r_fov);
+}
+
+HeadMountMetrics CreateUndistortedHeadMountMetrics(const FieldOfView& l_fov,
+                                                   const FieldOfView& r_fov) {
+  auto distortion_all = std::make_shared<IdentityDistortion>();
+
+  return HeadMountMetrics(kDefaultInterLensDistance, kDefaultTrayToLensDistance,
+                          kDefaultVirtualEyeToScreenDistance,
+                          kDefaultVerticalAlignment, l_fov, r_fov,
+                          distortion_all, distortion_all, distortion_all,
+                          HeadMountMetrics::EyeOrientation::kCCW0Degrees,
+                          HeadMountMetrics::EyeOrientation::kCCW0Degrees,
+                          kDefaultInterLensDistance / 2.0f);
+}
+
+}  // namespace dvr
+}  // namespace dvr
diff --git a/libs/vr/libeds/lucid_pose_tracker.cpp b/libs/vr/libeds/lucid_pose_tracker.cpp
new file mode 100644
index 0000000..c321bb0
--- /dev/null
+++ b/libs/vr/libeds/lucid_pose_tracker.cpp
@@ -0,0 +1,90 @@
+#include "include/private/dvr/lucid_pose_tracker.h"
+
+#define LOG_TAG "LucidPoseTracker"
+#include <cutils/log.h>
+
+#include <private/dvr/clock_ns.h>
+
+namespace android {
+namespace dvr {
+
+bool LucidPoseTracker::is_override_pose_ = false;
+Posef LucidPoseTracker::override_pose_ = Posef();
+
+void LucidPoseTracker::SetPoseOverride(const Posef& pose) {
+  is_override_pose_ = true;
+  override_pose_ = pose;
+}
+
+void LucidPoseTracker::ClearPoseOverride() {
+  is_override_pose_ = false;
+  override_pose_ = Posef();
+}
+
+LucidPoseTracker::LucidPoseTracker() : pose_client_(NULL) {}
+
+LucidPoseTracker::~LucidPoseTracker() {
+  if (pose_client_) {
+    dvrPoseDestroy(pose_client_);
+  }
+}
+
+Posef LucidPoseTracker::GetPose(uint64_t timestamp_ns) {
+  if (is_override_pose_) {
+    return override_pose_;
+  }
+
+  if (!pose_client_) {
+    pose_client_ = dvrPoseCreate();
+
+    if (!pose_client_) {
+      ALOGE("No pose service, returning identity pose");
+      return Posef();
+    }
+  }
+
+  DvrPoseState state;
+  dvrPosePoll(pose_client_, &state);
+
+  const vec4 head_rotation_in_start_quat(
+      state.head_from_start_rotation.x, state.head_from_start_rotation.y,
+      state.head_from_start_rotation.z, state.head_from_start_rotation.w);
+
+  // When the pose service hasn't computed a pose yet, it returns a zero
+  // quaternion; just use the identity rotation in that case.
+  // TODO(stefanus): Find a better way to signal and check this.
+  if (head_rotation_in_start_quat.squaredNorm() < 0.5f) {
+    latest_pose_.SetRotation(quat::Identity());
+  } else {
+    latest_pose_.SetRotation(
+        quat(head_rotation_in_start_quat.w(), head_rotation_in_start_quat.x(),
+             head_rotation_in_start_quat.y(), head_rotation_in_start_quat.z())
+            .normalized());
+  }
+
+  const vec3 head_position_in_start(state.head_from_start_translation.x,
+                                    state.head_from_start_translation.y,
+                                    state.head_from_start_translation.z);
+  latest_pose_.SetPosition(head_position_in_start);
+
+  latest_timestamp_ns_ = GetSystemClockNs();
+
+  // PoseState pose_state;
+  // pose_state.timestamp_ns = latest_timestamp_ns_;
+  // pose_state.sensor_from_start_rotation =
+  //    ion::math::Rotationd::FromQuaternion(ion::math::Vector4d(
+  //        state.head_from_start_rotation.x, state.head_from_start_rotation.y,
+  //        state.head_from_start_rotation.z,
+  //        state.head_from_start_rotation.w));
+  //// TODO(stefanus): Determine the first derivative of the rotation and set it
+  //// here.
+  // pose_state.sensor_from_start_rotation_velocity =
+  // ion::math::Vector3d::Zero();
+
+  // TODO(stefanus): perform prediction.
+
+  return latest_pose_;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libeds/polynomial_radial_distortion.cpp b/libs/vr/libeds/polynomial_radial_distortion.cpp
new file mode 100644
index 0000000..fa01bb4
--- /dev/null
+++ b/libs/vr/libeds/polynomial_radial_distortion.cpp
@@ -0,0 +1,53 @@
+#include "include/private/dvr/polynomial_radial_distortion.h"
+
+namespace android {
+namespace dvr {
+
+PolynomialRadialDistortion::PolynomialRadialDistortion(
+    const std::vector<float>& coefficients)
+    : coefficients_(coefficients) {}
+
+float PolynomialRadialDistortion::DistortionFactor(float r_squared) const {
+  float r_factor = 1.0f;
+  float distortion_factor = 1.0f;
+
+  for (float ki : coefficients_) {
+    r_factor *= r_squared;
+    distortion_factor += ki * r_factor;
+  }
+
+  return distortion_factor;
+}
+
+float PolynomialRadialDistortion::DistortRadius(float r) const {
+  return r * DistortionFactor(r * r);
+}
+
+vec2 PolynomialRadialDistortion::Distort(vec2 p) const {
+  return p * DistortionFactor(p.squaredNorm());
+}
+
+vec2 PolynomialRadialDistortion::DistortInverse(vec2 p) const {
+  // Secant method.
+  const float radius = p.norm();
+  float r0 = radius / 0.9f;
+  float r1 = radius * 0.9f;
+  float r2;
+  float dr0 = radius - DistortRadius(r0);
+  float dr1;
+  while (fabsf(r1 - r0) > 0.0001f /** 0.1mm */) {
+    dr1 = radius - DistortRadius(r1);
+    r2 = r1 - dr1 * ((r1 - r0) / (dr1 - dr0));
+    r0 = r1;
+    r1 = r2;
+    dr0 = dr1;
+  }
+  return (r1 / radius) * p;
+}
+
+const std::vector<float>& PolynomialRadialDistortion::GetCoefficients() const {
+  return coefficients_;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libeds/tests/eds_app_tests.cpp b/libs/vr/libeds/tests/eds_app_tests.cpp
new file mode 100644
index 0000000..1742736
--- /dev/null
+++ b/libs/vr/libeds/tests/eds_app_tests.cpp
@@ -0,0 +1,141 @@
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+#include <base/logging.h>
+#include <dvr/graphics.h>
+#include <dvr/pose_client.h>
+#include <gtest/gtest.h>
+#include <private/dvr/graphics/shader_program.h>
+#include <private/dvr/types.h>
+
+namespace {
+
+#define POSE_BINDING 0
+
+#ifndef STRINGIFY
+#define STRINGIFY2(s) #s
+#define STRINGIFY(s) STRINGIFY2(s)
+#endif
+
+static const char g_vert_shader[] =
+    "layout(binding = " STRINGIFY(POSE_BINDING) ", std140)\n"
+    "uniform LateLatchData {\n"
+    "  mat4 uViewProjection;\n"
+    "};\n"
+    "void main() {\n"
+    "  vec2 verts[4];\n"
+    "  verts[0] = vec2(-1, -1);\n"
+    "  verts[1] = vec2(-1, 1);\n"
+    "  verts[2] = vec2(1, -1);\n"
+    "  verts[3] = vec2(1, 1);\n"
+    "  gl_Position = uViewProjection * vec4(verts[gl_VertexID], 0.0, 1.0);\n"
+    "}\n";
+
+static const char g_frag_shader[] =
+    "precision mediump float;\n"
+    "out vec4 outColor;\n"
+    "void main() {\n"
+    "  outColor = vec4(1.0);\n"
+    "}\n";
+
+DvrGraphicsContext* CreateContext(int* surface_width, int* surface_height) {
+  DvrGraphicsContext* context = nullptr;
+  int display_width = 0, display_height = 0;
+  float inter_lens_meters = 0.0f;
+  float left_fov[4] = {0.0f};
+  float right_fov[4] = {0.0f};
+  int disable_warp = 0;
+  int enable_late_latch = 1;
+  DvrSurfaceParameter surface_params[] = {
+      DVR_SURFACE_PARAMETER_IN(DISABLE_DISTORTION, disable_warp),
+      DVR_SURFACE_PARAMETER_IN(ENABLE_LATE_LATCH, enable_late_latch),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_WIDTH, &display_width),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_HEIGHT, &display_height),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_WIDTH, surface_width),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_HEIGHT, surface_height),
+      DVR_SURFACE_PARAMETER_OUT(INTER_LENS_METERS, &inter_lens_meters),
+      DVR_SURFACE_PARAMETER_OUT(LEFT_FOV_LRBT, left_fov),
+      DVR_SURFACE_PARAMETER_OUT(RIGHT_FOV_LRBT, right_fov),
+      DVR_SURFACE_PARAMETER_LIST_END,
+  };
+  dvrGraphicsContextCreate(surface_params, &context);
+  return context;
+}
+
+}  // namespace
+
+TEST(SensorAppTests, EdsWithLateLatch) {
+  int surface_width = 0, surface_height = 0;
+  DvrGraphicsContext* context = CreateContext(&surface_width, &surface_height);
+  ASSERT_NE(nullptr, context);
+
+  android::dvr::ShaderProgram shader(g_vert_shader, g_frag_shader);
+
+  for (int i = 0; i < 5; ++i) {
+    DvrFrameSchedule schedule;
+    dvrGraphicsWaitNextFrame(context, 0, &schedule);
+
+    const auto ident_mat = android::dvr::mat4::Identity();
+    const float* ident_mats[] = { ident_mat.data(), ident_mat.data() };
+    GLuint late_latch_buffer_id = 0;
+    int ret = dvrBeginRenderFrameLateLatch(context, 0, schedule.vsync_count, 2,
+                                           ident_mats, ident_mats, ident_mats,
+                                           &late_latch_buffer_id);
+    EXPECT_EQ(0, ret);
+    for (int eye = 0; eye < 2; ++eye) {
+      if (eye == 0)
+        glViewport(0, 0, surface_width / 2, surface_height);
+      else
+        glViewport(surface_width / 2, 0, surface_width / 2, surface_height);
+
+      glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+      shader.Use();
+
+      // Bind late latch pose matrix buffer.
+      glBindBufferRange(
+          GL_UNIFORM_BUFFER, POSE_BINDING, late_latch_buffer_id,
+          offsetof(DvrGraphicsLateLatchData, view_proj_matrix[eye]),
+          16 * sizeof(float));
+
+      // TODO(jbates): use transform feedback here to grab the vertex output
+      // and verify that it received late-latch pose data. Combine this with
+      // mocked pose data to verify that late-latching is working.
+      glDrawArrays(GL_POINTS, 0, 4);
+    }
+    dvrPresent(context);
+  }
+
+  glFinish();
+  dvrGraphicsContextDestroy(context);
+}
+
+TEST(SensorAppTests, EdsWithoutLateLatch) {
+  int surface_width = 0, surface_height = 0;
+  DvrGraphicsContext* context = CreateContext(&surface_width, &surface_height);
+  ASSERT_NE(nullptr, context);
+  DvrPose* client = dvrPoseCreate();
+  ASSERT_NE(nullptr, client);
+
+  for (int i = 0; i < 5; ++i) {
+    DvrFrameSchedule schedule;
+    dvrGraphicsWaitNextFrame(context, 0, &schedule);
+    DvrPoseAsync pose;
+    int ret = dvrPoseGet(client, schedule.vsync_count, &pose);
+    ASSERT_EQ(0, ret);
+
+    dvrBeginRenderFrameEds(context, pose.orientation, pose.translation);
+    for (int eye = 0; eye < 2; ++eye) {
+      if (eye == 0)
+        glViewport(0, 0, surface_width / 2, surface_height);
+      else
+        glViewport(surface_width / 2, 0, surface_width / 2, surface_height);
+
+      glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+      EXPECT_EQ(0, ret);
+    }
+    dvrPresent(context);
+  }
+
+  dvrPoseDestroy(client);
+  dvrGraphicsContextDestroy(context);
+}
diff --git a/libs/vr/libgvr/.clang-format b/libs/vr/libgvr/.clang-format
new file mode 100644
index 0000000..3287160
--- /dev/null
+++ b/libs/vr/libgvr/.clang-format
@@ -0,0 +1,2 @@
+Language: Cpp
+DisableFormat: true
diff --git a/libs/vr/libgvr/Android.mk b/libs/vr/libgvr/Android.mk
new file mode 100644
index 0000000..bcb3961
--- /dev/null
+++ b/libs/vr/libgvr/Android.mk
@@ -0,0 +1,126 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+LOCAL_PATH := $(call my-dir)
+
+include_dirs := \
+  $(LOCAL_PATH)/include \
+  $(LOCAL_PATH)/prebuilt/include
+
+# Java platform library for the system implementation of the GVR API.
+include $(CLEAR_VARS)
+LOCAL_MODULE := gvr_platform
+LOCAL_MODULE_STEM := com.google.vr.gvr.platform
+LOCAL_REQUIRED_MODULES := libgvr_system_loader libgvr_system
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+include $(BUILD_JAVA_LIBRARY)
+
+# Library to perform dlopen on the actual platform library.
+include $(CLEAR_VARS)
+LOCAL_MODULE := libgvr_system_loader
+LOCAL_SRC_FILES := library_loader.cpp
+include $(BUILD_SHARED_LIBRARY)
+
+# Shared library implementing the GVR API.
+include $(CLEAR_VARS)
+LOCAL_MODULE := libgvr_system
+
+LOCAL_SRC_FILES := \
+    shim_gvr.cpp \
+    shim_gvr_controller.cpp \
+    shim_gvr_private.cpp \
+    deviceparams/CardboardDevice.nolite.proto
+
+LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+
+LOCAL_C_INCLUDES := $(include_dirs)
+LOCAL_C_INCLUDES += $(call local-generated-sources-dir)/proto/$(LOCAL_PATH)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(include_dirs)
+
+gvr_api_linker_script := $(LOCAL_PATH)/exported_apis.lds
+LOCAL_ADDITIONAL_DEPENDENCIES := $(gvr_api_linker_script)
+
+LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES
+LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES
+LOCAL_LDFLAGS += -Wl,-version-script,$(gvr_api_linker_script)
+
+LOCAL_SHARED_LIBRARIES := \
+    libandroid_runtime \
+    libbase \
+    libbinder \
+    libcutils \
+    libutils \
+    libgui \
+    libui \
+    libEGL \
+    libGLESv2 \
+    libvulkan \
+    libhardware \
+    liblog \
+    libsync \
+    libevent \
+    libprotobuf-cpp-full
+
+LOCAL_STATIC_LIBRARIES := \
+    libdisplay \
+    libbufferhub \
+    libbufferhubqueue \
+    libchrome \
+    libdvrcommon \
+    libeds \
+    libdvrgraphics \
+    libsensor \
+    libperformance \
+    libpdx_default_transport \
+
+include $(BUILD_SHARED_LIBRARY)
+
+# Prebuilt shared library for libgvr_audio.so
+include $(CLEAR_VARS)
+LOCAL_MODULE := libgvr_audio
+LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+LOCAL_MODULE_SUFFIX := .so
+LOCAL_MULTILIB := both
+LOCAL_SRC_FILES_arm := prebuilt/lib/android_arm/libgvr_audio.so
+LOCAL_SRC_FILES_arm64 := prebuilt/lib/android_arm64/libgvr_audio.so
+include $(BUILD_PREBUILT)
+
+# Prebuilt shared library for libgvr.so
+include $(CLEAR_VARS)
+LOCAL_MODULE := libgvr
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/prebuilt/include
+LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+LOCAL_MODULE_SUFFIX := .so
+LOCAL_MULTILIB := both
+LOCAL_SRC_FILES_arm := prebuilt/lib/android_arm/libgvr.so
+LOCAL_SRC_FILES_arm64 := prebuilt/lib/android_arm64/libgvr.so
+include $(BUILD_PREBUILT)
+
+# Prebuilt Java static library for common_library.aar
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+    gvr_common_library_aar:prebuilt/lib/common_library.aar
+include $(BUILD_MULTI_PREBUILT)
+
+# Dummy libgvr_ext to be used along side libgvr.so prebuilt.
+# This shall be replaced with Google3 prebuilts in future.
+include $(CLEAR_VARS)
+LOCAL_MODULE := libgvr_ext
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_SRC_FILES := dummy_gvr_ext.cpp
+LOCAL_STATIC_LIBRARIES := libchrome
+LOCAL_LDLIBS := -llog
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES += libgvr
+include $(BUILD_STATIC_LIBRARY)
diff --git a/libs/vr/libgvr/CPPLINT.cfg b/libs/vr/libgvr/CPPLINT.cfg
new file mode 100644
index 0000000..2da1c50
--- /dev/null
+++ b/libs/vr/libgvr/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-build/header_guard,-build/include_alpha
diff --git a/libs/vr/libgvr/README.dreamos b/libs/vr/libgvr/README.dreamos
new file mode 100644
index 0000000..d847210
--- /dev/null
+++ b/libs/vr/libgvr/README.dreamos
@@ -0,0 +1 @@
+Files under public/vr/gvr were imported from the public Google VR SDK.
diff --git a/libs/vr/libgvr/com.google.vr.gvr.platform.xml b/libs/vr/libgvr/com.google.vr.gvr.platform.xml
new file mode 100644
index 0000000..9297c7f
--- /dev/null
+++ b/libs/vr/libgvr/com.google.vr.gvr.platform.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<permissions>
+    <library name="com.google.vr.gvr.platform"
+             file="/system/framework/com.google.vr.gvr.platform.jar" />
+</permissions>
diff --git a/libs/vr/libgvr/deviceparams/CardboardDevice.nolite.proto b/libs/vr/libgvr/deviceparams/CardboardDevice.nolite.proto
new file mode 100644
index 0000000..77b5d72
--- /dev/null
+++ b/libs/vr/libgvr/deviceparams/CardboardDevice.nolite.proto
@@ -0,0 +1,299 @@
+
+syntax = "proto2";
+
+option java_package = "com.google.vrtoolkit.cardboard.proto";
+option java_outer_classname = "CardboardDevice";
+option optimize_for = SPEED;
+
+package proto;
+
+
+/**
+ * Message describing properties of a VR head mount device (HMD) which uses an
+ * interchangeable smartphone as a display (e.g. Google Cardboard).
+ *
+ * While some properties are certain (e.g. inter_lens_distance), others
+ * represent nominal values which may be refined depending on context (e.g.
+ * viewport_angles).
+ *
+ * Lengths are in meters unless noted otherwise.  Fields are _required_
+ * unless noted otherwise.
+ *
+ * Some context on why this set of parameters are deemed necessary and
+ * sufficient:
+ *    * FOV scale can be reasonably approximated from lens-to-screen distance
+ *      and display size (i.e. knowing lens focal length isn't crucial).
+ *    * Lenses are assumed to be horizontally centered with respect to
+ *      display.
+ *    * The display is not necessarily vertically centered.  For interchangeable
+ *      phones where the device rests against a tray, we can derive
+ *      the vertical offset from tray-to-lens height along with phone-specific
+ *      bezel and screen sizes (supplied separately).
+ */
+message DeviceParams {
+  // String identifying the device's vendor (e.g. "Google, Inc.").
+  // A device's [vendor, model] pair is expected to be globally unique.
+  optional string vendor = 1;
+
+  // String identifying the device's model, including revision info if
+  // needed (e.g. "Cardboard v1").  A device's [vendor, model] pair is
+  // expected to be globally unique.
+  optional string model = 2;
+
+  // Distance from the display screen to the optical center of lenses.
+  // This is a required field for distortion rendering, and must be positive.
+  optional float screen_to_lens_distance = 3;
+
+  // Horizontal distance between optical center of the lenses.
+  // This is a required field for distortion rendering, and must be positive.
+  optional float inter_lens_distance = 4;
+
+  // Four-element tuple (left, right, bottom, top) of left eye's view extent
+  // angles relative to center, assuming the following:
+  //     * eye is aligned with optical center of lens
+  //     * display screen is equal or larger than extents viewable through lens
+  //     * nominal eye-to-lens distance
+  //     * mirrored field of view will be applied to the right eye
+  // These values are essentially used as an optimization to avoid rendering
+  // pixels which can't be seen.
+  // This is a required field for distortion rendering, and angles must be
+  // positive.
+  repeated float left_eye_field_of_view_angles = 5 [packed = true];
+
+  enum VerticalAlignmentType {
+    BOTTOM = 0;  // phone rests against a fixed bottom tray
+    CENTER = 1;  // phone screen assumed to be centered w.r.t. lenses
+    TOP = 2;     // phone rests against a fixed top tray
+  }
+
+  // Set according to vertical alignment strategy-- see enum comments above.
+  // NOTE: If you set this to CENTER, see special instructions for the
+  // tray_to_lens_distance field below.
+  optional VerticalAlignmentType vertical_alignment = 11 [default = BOTTOM];
+
+  // If the phone is aligned vertically within the device by resting against
+  // a fixed top or bottom tray, this is the distance from the tray to
+  // optical center of the lenses.
+  // This is a required field for distortion rendering, and must be positive.
+  // NOTE: Due to a bug in initial versions of the SDK's, this field
+  // must be set explicitly to .035 when vertical_alignment = CENTER.
+  optional float tray_to_lens_distance = 6;
+
+  // Coefficients Ki for pincushion distortion function which maps
+  // from position on real screen to virtual screen (i.e. texture) relative
+  // to optical center:
+  //
+  //    p' = p (1 + K1 r^2 + K2 r^4 + ... + Kn r^(2n))
+  //
+  // where r is the distance in tan-angle units from the optical center,
+  // p the input point, and p' the output point.  Tan-angle units can be
+  // computed as distance on the screen divided by distance from the
+  // virtual eye to the screen.
+  repeated float distortion_coefficients = 7 [packed = true];
+  // Slots 8, 9 reserved for per-color channel distortion.
+
+  // Optionally, whether the head mount uses a magnet in any part of its
+  // design.  Intended as hint as to whether phone's magnetometer is
+  // available for tasks such as orientation tracking.
+  optional bool has_magnet = 10;
+
+  enum ButtonType {
+    // No physical button, and touch screen is not easily accessible.
+    NONE = 0;
+    // HMD has integrated magnet switch similar to original Cardboard.
+    MAGNET = 1;
+    // At least a portion of touch screen is easily accessible to user for taps.
+    TOUCH = 2;
+    // Touch screen is triggered indirectly via integrated button on the HMD.
+    INDIRECT_TOUCH = 3;
+  }
+
+  // Specify primary input mechanism of the HMD.  Intended for advisory
+  // purposes only, to address simple questions such as "can HMD
+  // be used with apps requiring a physical button event?" or "what icon
+  // should be used to represent button action to the user?".
+  optional ButtonType primary_button = 12 [default = MAGNET];
+
+  // Some internal data for Cardboard.  This data is not intended to be
+  // set or used by developers, and any data in this proto is not guaranteed
+  // to be supported with new SDK updates.
+  optional CardboardInternalParams internal = 1729;
+
+  // Optionally, specifies the additional parameters that are necessary for
+  // a Daydream-ready headset. This field is non-null if the headset is
+  // Daydream-ready.
+  // TODO(b/30112366) The inclusion of this message inside a DeviceParams is a
+  // somewhat ugly result of some historical choices in the SDK. We should
+  // consider refactoring our code to allow us to remove this, and the
+  // CardboardInternalParams messages from this proto.
+  optional DaydreamInternalParams daydream_internal = 196883;
+}
+
+// TODO(b/27108179): CardboardInternalParams should be migrated into its own
+// file, and not live in this file.
+
+/**
+ * Message describing parameters that are used internally by Cardboard
+ * and VRToolkit. These parameters don't necessarily fit into the DeviceParams
+ * notion of a VR viewer combined with user's phone (e.g. case of viewer with
+ * dedicated display, etc.) and are not intended to be used by developers
+ * and may or may not be supported or changed without notice on new releases
+ * of the Cardboard SDK or VR Toolkit.
+ */
+message CardboardInternalParams {
+  // Used to specify a per-eye post-process transformation -- an optional
+  // rotation and x-axis reflection -- to be applied after distortion
+  // correction.
+  enum OrientationType {
+    CCW_0_DEGREES = 0;
+    CCW_90_DEGREES = 1;
+    CCW_180_DEGREES = 2;
+    CCW_270_DEGREES = 3;
+    CCW_0_DEGREES_MIRRORED = 4;
+    CCW_90_DEGREES_MIRRORED = 5;
+    CCW_180_DEGREES_MIRRORED = 6;
+    CCW_270_DEGREES_MIRRORED = 7;
+  }
+
+  // Specify a post-process transformation that is applied after the distortion
+  // function. This field is optional, if not specified, CCW_0_DEGREES is
+  // assumed. If repeated, the first orientation is for the left eye, the second
+  // is for the right eye.
+  //
+  // For example, if [CCW_90_DEGREES, CCW_270_DEGREES_MIRRORED] is specified,
+  //
+  // this input:
+  //
+  // ***************** *****************
+  // *1             2* *1             2*
+  // *      *        * *      ***      *
+  // *      *        * *      * *      *
+  // *      ****     * *      *  *     *
+  // *4             3* *4             3*
+  // ***************** *****************
+  //
+  // is rendered on the screen like this:
+  //
+  // ***************** *****************
+  // *2       *     3* *3     *       2*
+  // *        *      * *       **      *
+  // *        *      * *        *      *
+  // *      ***      * *      ***      *
+  // *1             4* *4             1*
+  // ***************** *****************
+  repeated OrientationType eye_orientations = 1 [packed = true];
+
+  // Specify a horizontal offset from the middle of the screen to the center of
+  // the lens, in meters. If one is not provided, half of the inter lens
+  // distance is used.
+  //
+  // This is only necessary if the HMD has some sort of periscope effect, where
+  // the position of the lenses, relative to the screen, is different than
+  // their position relative to the user.
+  //
+  // For example, in the HMD below, two mirrors reflect the image from the
+  // screen to the user, creating a larger inter lens distance than the screen
+  // can support.
+  //
+  // [In the diagram below, S = screen, L = lens]
+  //
+  // screen_center_to_lens_distance
+  //             |--|
+  //
+  // -------------------------
+  // |     SSSSSSSSSSSS      |
+  // |        |  |  |        |
+  // |   /----/  |  \----\   |
+  // |   |       |       |   |
+  // |  LLL             LLL  |
+  //
+  //     |---------------|
+  //    inter_lens_distance
+  //
+  optional float screen_center_to_lens_distance = 2;
+
+  // Optional x-dimension physical pixels per inch of the external display,
+  // assuming landscape orientation. If set, this will override OS-reported
+  // values.
+  optional float x_ppi_override = 3;
+
+  // Optional y-dimension physical pixels per inch of the external display,
+  // assuming landscape orientation.  If set, this will override OS-reported
+  // values.
+  optional float y_ppi_override = 4;
+
+  // Optional string identifying the device's accelerometer and gyroscope.
+  // If either field is filled out, the corresponding sensor (gyroscope or
+  // accelerometer) will be used for head tracking.
+  //
+  // Valid strings are usually found in:
+  // vendor/<vendorname>/<devicename>/xxx/sensors.cpp
+  //
+  // For dynamic sensors, this string will be provided in a separate way.
+  //
+  // NB: Vendors and manufacturers should make the name of the sensor as
+  // specific as possible, since if multiple sensors with the same name are
+  // connected, the first will be used.
+  optional string accelerometer = 5;
+  optional string gyroscope = 6;
+}
+
+/**
+ * Message describing the additional properties of a Daydream-ready headset that
+ * are not used for a normal cardboard viewer. These parameters are not intended
+ * to be used, or consumed, by developers and may or may not be supported or
+ * changed without notice on new releases of the Cardboard SDK or VR Toolkit.
+ */
+message DaydreamInternalParams {
+  // The version of the Daydream-ready specification to which this device
+  // conforms.
+  optional int32 version = 1;
+
+  // Optionally, specifies the collection of screen alignment markers in the
+  // headset.
+  repeated ScreenAlignmentMarker alignment_markers = 2;
+}
+
+/**
+ * Message describing a single screen alignment marker.
+ *
+ * A screen alignment marker is a capacitive touch point affixed to the headset
+ * which is capable of making contact with the screen. The location of the touch
+ * point is given in meters, measured along a horizontal axis which passes
+ * through the center of both lenses, and a vertical axis which is equidistant
+ * from the centers of the lenses. A positive vertical value indicates that the
+ * point lies above the horizontal axis, and a positive horizontal value
+ * indicates that the point lies to the right, as seen by a user of the headset,
+ * of the vertical axis. For example, if the following is a representation of a
+ * headset, viewed from the point of view of a user, with three points marked by
+ * the numbers 1, 2, and 3.
+ *
+ * *****************************************************************************
+ * *                                    ^                                      *
+ * *               _____                |                _____                 *
+ * *              /     \               1               /     \                *
+ * *             /       \              |              /       \               *
+ * *            /         \             |             /         \              *
+ * *           /           \            |            /           \             *
+ * *          /             \           |           /             \            *
+ * *---------|-------*-------|----------+------2---|-------*-------|---------->*
+ * *          \             /           |           \             /            *
+ * *           \           /            |            \           /             *
+ * *            \         /         3   |             \         /              *
+ * *             \       /              |              \       /               *
+ * *              \_____/               |               \_____/                *
+ * *                                    |                                      *
+ * *                                    |                                      *
+ * *****************************************************************************
+ *
+ * Then the coordinates of point 1 could be horizontal = 0.0, vertical = 0.035;
+ * point 2 could be horizontal = 0.02; and point 3 could be horizontal = -0.01
+ * vertical = -0.012
+ */
+message ScreenAlignmentMarker {
+  // The horizontal coordinate of the touch point.
+  optional float horizontal = 1;
+
+  // The vertical coordinate of the touch point.
+  optional float vertical = 2;
+}
diff --git a/libs/vr/libgvr/dummy_gvr_ext.cpp b/libs/vr/libgvr/dummy_gvr_ext.cpp
new file mode 100644
index 0000000..c507038
--- /dev/null
+++ b/libs/vr/libgvr/dummy_gvr_ext.cpp
@@ -0,0 +1,29 @@
+#include <base/logging.h>
+#include <vr/gvr/capi/include/gvr.h>
+#include <vr/gvr/capi/include/gvr_ext.h>
+#include <vr/gvr/capi/include/gvr_types.h>
+
+gvr_frame_schedule* gvr_frame_schedule_create() { return NULL; }
+
+void gvr_frame_schedule_destroy(gvr_frame_schedule** /* schedule */) {}
+
+uint32_t gvr_frame_schedule_get_vsync_count(
+    gvr_frame_schedule* /* schedule */) {
+  return 0;
+}
+
+gvr_mat4f gvr_get_6dof_head_pose_in_start_space(gvr_context* gvr,
+                                                uint32_t /* vsync_count */) {
+  LOG(FATAL) << "gvr_get_6dof_head_pose_in_start_space is not implemented. "
+             << "Use gvr_get_head_space_from_start_space_pose instead.";
+  return gvr_mat4f({{{1.0f, 0.0f, 0.0f, 0.0f},
+                     {0.0f, 1.0f, 0.0f, 0.0f},
+                     {0.0f, 0.0f, 1.0f, 0.0f},
+                     {0.0f, 0.0f, 0.0f, 1.0f}}});
+}
+
+void gvr_wait_next_frame(gvr_swap_chain* /* swap_chain */,
+                         int64_t /* sched_offset_nanos */,
+                         gvr_frame_schedule* /* out_next_frame_schedule */) {
+  LOG(FATAL) << "gvr_wait_next_frame is not implemented.";
+}
diff --git a/libs/vr/libgvr/exported_apis.lds b/libs/vr/libgvr/exported_apis.lds
new file mode 100644
index 0000000..2d19303
--- /dev/null
+++ b/libs/vr/libgvr/exported_apis.lds
@@ -0,0 +1,16 @@
+{
+  global:
+    # Export GVR APIs, both public and private.
+    gvr_*;
+
+    # Whitelist of DVR APIs required by VrCore for Ambrosia controller support.
+    dvrPoseCreate;
+    dvrPoseDestroy;
+    dvrPoseGet;
+    dvrPoseGetController;
+    dvrPoseGetVsyncCount;
+
+  local:
+    # Hide everything else.
+    *;
+};
diff --git a/libs/vr/libgvr/include/private/dvr/internal_types.h b/libs/vr/libgvr/include/private/dvr/internal_types.h
new file mode 100644
index 0000000..dafdb73
--- /dev/null
+++ b/libs/vr/libgvr/include/private/dvr/internal_types.h
@@ -0,0 +1,170 @@
+#ifndef ANDROID_DVR_INTERNAL_TYPES_H_
+#define ANDROID_DVR_INTERNAL_TYPES_H_
+
+#include <GLES2/gl2.h>
+#include <atomic>
+#include <memory>
+#include <vector>
+#include <unordered_map>
+
+#include <dvr/graphics.h>
+#include <dvr/pose_client.h>
+#include <gui/Surface.h>
+#include <private/dvr/buffer_hub_queue_core.h>
+#include <private/dvr/display_client.h>
+#include <vr/gvr/capi/include/gvr.h>
+#include <vr/gvr/capi/include/gvr_types.h>
+#include <vr/gvr/capi/src/gvr_types_experimental.h>
+
+typedef struct gvr_user_prefs_ {
+} gvr_user_prefs;
+
+typedef struct gvr_context_ {
+  int32_t last_error_;
+  JNIEnv* jni_env_;
+  DvrPose* pose_client_;
+  std::unique_ptr<android::dvr::DisplayClient> display_client_;
+  android::dvr::SystemDisplayMetrics display_metrics_;
+  gvr_mat4f left_eye_viewport_transform_;
+  gvr_mat4f right_eye_viewport_transform_;
+  gvr_mat4f next_frame_6dof_pose_;
+  gvr_mat4f next_frame_controller_pose_[2];
+  gvr_user_prefs user_prefs_;
+  bool force_6dof_;
+  std::vector<gvr_swap_chain*> swap_chains_;
+
+  gvr_context_() :
+      last_error_(GVR_ERROR_NONE),
+      jni_env_(nullptr),
+      pose_client_(nullptr),
+      force_6dof_(false) {}
+
+  ~gvr_context_();
+} gvr_context;
+
+typedef struct gvr_buffer_spec_ {
+  gvr_sizei size;
+  int32_t msaa_samples;
+  int32_t color_format;
+  int32_t depth_stencil_format;
+  bool blur_behind;
+  bool initially_visible;
+  int z_order;
+
+  // The default values are configured to match SVR defaults
+  gvr_buffer_spec_()
+      : size{0, 0},
+        msaa_samples(0),
+        color_format(GVR_COLOR_FORMAT_RGBA_8888),
+        depth_stencil_format(GVR_DEPTH_STENCIL_FORMAT_DEPTH_16),
+        blur_behind(true),
+        initially_visible(true),
+        z_order(0) {}
+} gvr_buffer_spec;
+
+// This isn't a public gvr type
+struct gvr_buffer {
+  gvr_buffer_spec spec;
+  GLuint frame_buffer;
+  GLuint color_render_buffer;
+  GLuint depth_stencil_render_buffer;
+
+  // requested_size is used for resizing. It will be {-1, -1} when no resize is
+  // requested. Any other value indicates the app changed the size.
+  gvr_sizei requested_size;
+
+  gvr_buffer();
+  // If creation fails frame_buffer will be 0
+  gvr_buffer(gvr_context* gvr, const gvr_buffer_spec& spec,
+             GLuint texture_id, GLenum texture_target);
+  ~gvr_buffer();
+
+  gvr_buffer(gvr_buffer&& other);
+  gvr_buffer& operator=(gvr_buffer&& other);
+  gvr_buffer(const gvr_buffer& other) = delete;
+  gvr_buffer& operator=(const gvr_buffer& other) = delete;
+
+  // Set default values. Doesn't free GL resources first.
+  void SetDefaults();
+
+  // Frees all GL resources associated with the buffer
+  void FreeGl();
+};
+
+typedef struct gvr_swap_chain_ {
+  gvr_context* context;
+  DvrGraphicsContext* graphics_context_;
+  std::vector<gvr_buffer> buffers_;
+  bool frame_acquired_;
+  bool wait_next_frame_called_by_app_;
+  std::atomic<int32_t> next_external_surface_id_;
+  std::unordered_map<int32_t, gvr_external_surface*> external_surfaces_;
+
+  explicit gvr_swap_chain_(gvr_context* context)
+      : context(context),
+        graphics_context_(nullptr),
+        frame_acquired_(false),
+        wait_next_frame_called_by_app_(false),
+        next_external_surface_id_(0) {}
+  ~gvr_swap_chain_();
+} gvr_swap_chain;
+
+typedef struct gvr_buffer_viewport_ {
+  int32_t buffer_index;
+  gvr_rectf uv;
+  gvr_mat4f transform;
+  int32_t eye;
+  int32_t external_surface_id;
+  gvr_reprojection reprojection;
+
+  gvr_buffer_viewport_()
+      : buffer_index(0),
+        uv{0, 0, 0, 0},
+        transform{{{1.f, 0.f, 0.f, 0.f},
+                   {0.f, 1.f, 0.f, 0.f},
+                   {0.f, 0.f, 1.f, 0.f},
+                   {0.f, 0.f, 0.f, 1.f}}},
+        eye(0),
+        external_surface_id(-1),
+        reprojection(GVR_REPROJECTION_FULL) {}
+
+  gvr_buffer_viewport_(int32_t /* buffer_index */, gvr_rectf uv,
+                       const gvr_mat4f& transform, int32_t eye,
+                       int32_t external_surface_id,
+                       gvr_reprojection reprojection)
+      : buffer_index(0),
+        uv(uv),
+        transform(transform),
+        eye(eye),
+        external_surface_id(external_surface_id),
+        reprojection(reprojection) {}
+
+  bool operator==(const gvr_buffer_viewport_& other) const;
+
+  bool operator!=(const gvr_buffer_viewport_& other) const {
+    return !operator==(other);
+  }
+} gvr_buffer_viewport;
+
+typedef struct gvr_buffer_viewport_list_ {
+  std::vector<gvr_buffer_viewport> viewports;
+} gvr_buffer_viewport_list;
+
+typedef struct gvr_frame_schedule_ {
+  uint32_t vsync_count;
+  gvr_clock_time_point scheduled_finish;
+
+  gvr_frame_schedule_() : vsync_count(0) {
+    scheduled_finish.monotonic_system_time_nanos = 0;
+  }
+} gvr_frame_schedule;
+
+typedef struct gvr_display_synchronizer_ {} gvr_display_synchronizer;
+
+typedef struct gvr_external_surface_ {
+  int32_t id;
+  gvr_swap_chain* swap_chain;
+  DvrVideoMeshSurface* video_surface;
+} gvr_external_surface;
+
+#endif  // ANDROID_DVR_INTERNAL_TYPES_H_
diff --git a/libs/vr/libgvr/include/vr/gvr/capi/include/gvr_ext.h b/libs/vr/libgvr/include/vr/gvr/capi/include/gvr_ext.h
new file mode 100644
index 0000000..8af434f
--- /dev/null
+++ b/libs/vr/libgvr/include/vr/gvr/capi/include/gvr_ext.h
@@ -0,0 +1,119 @@
+/* Copyright 2016 Google Inc. All rights reserved.
+ *
+ * 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 VR_GVR_CAPI_INCLUDE_GVR_EXT_H_
+#define VR_GVR_CAPI_INCLUDE_GVR_EXT_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// Constants that represent GVR error codes.
+typedef enum {
+  // TODO(steventhomas): All errors should be switched to something more
+  // meaningful and this should eventually go away.
+  GVR_ERROR_INTERNAL = 9000,
+} gvr_error_ext;
+
+typedef struct gvr_frame_schedule_ gvr_frame_schedule;
+
+gvr_frame_schedule* gvr_frame_schedule_create();
+
+void gvr_frame_schedule_destroy(gvr_frame_schedule** schedule);
+
+uint32_t gvr_frame_schedule_get_vsync_count(gvr_frame_schedule* schedule);
+
+gvr_clock_time_point gvr_frame_schedule_get_scheduled_finish(
+    gvr_frame_schedule* schedule);
+
+/// Sleep until it's time to render the next frame.
+// |start_delay_ns| adjusts how long this function blocks the app from starting
+// its next frame. If |start_delay_ns| is 0, the function waits until the
+// scheduled frame finish time for the current frame, which gives the app one
+// full vsync period to render the next frame. If the app needs less than a full
+// vysnc period to render the frame, pass in a non-zero |start_delay_ns| to
+// delay the start of frame rendering further. For example, if the vsync period
+// is 11.1ms and the app takes 6ms to render a frame, consider setting this to
+// 5ms (note that the value is in nanoseconds, so 5,000,000ns) so that the app
+// finishes the frame closer to the scheduled frame finish time. Delaying the
+// start of rendering allows the app to use a more up-to-date pose for
+// rendering.
+// |start_delay_ns| must be a positive value or 0. If you're unsure what to set
+// for |start_delay_ns|, use 0.
+/// |out_next_frame_schedule| is an output parameter that will contain the
+/// schedule for the next frame. It can be null.
+void gvr_wait_next_frame(gvr_swap_chain* swap_chain, int64_t start_delay_nanos,
+                         gvr_frame_schedule* out_next_frame_schedule);
+
+gvr_mat4f gvr_get_6dof_head_pose_in_start_space(gvr_context* gvr,
+                                                uint32_t vsync_count);
+
+gvr_mat4f gvr_get_head_space_from_start_space_pose(
+    gvr_context* gvr, const gvr_clock_time_point time);
+
+gvr_mat4f gvr_get_start_space_from_controller_space_pose(
+    gvr_context* gvr, int controller_id, const gvr_clock_time_point time);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+#include <utility>
+
+namespace gvr {
+
+/// Convenience C++ wrapper for gvr_frame_schedule. Frees the underlying
+/// gvr_frame_schedule on destruction.
+class FrameSchedule {
+ public:
+  FrameSchedule() { schedule_ = gvr_frame_schedule_create(); }
+
+  ~FrameSchedule() {
+    if (schedule_)
+      gvr_frame_schedule_destroy(&schedule_);
+  }
+
+  FrameSchedule(FrameSchedule&& other) {
+    std::swap(schedule_, other.schedule_);
+  }
+
+  FrameSchedule& operator=(FrameSchedule&& other) {
+    std::swap(schedule_, other.schedule_);
+    return *this;
+  }
+
+  gvr_frame_schedule* cobj() { return schedule_; }
+  const gvr_frame_schedule* cobj() const { return schedule_; }
+
+  uint32_t GetVsyncCount() const {
+    return gvr_frame_schedule_get_vsync_count(schedule_);
+  }
+
+  gvr_clock_time_point GetScheduledFinish() const {
+    return gvr_frame_schedule_get_scheduled_finish(schedule_);
+  }
+
+ private:
+  gvr_frame_schedule* schedule_;
+};
+
+}  // namespace gvr
+#endif  // #if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+
+#endif  // VR_GVR_CAPI_INCLUDE_GVR_EXT_H_
diff --git a/libs/vr/libgvr/include/vr/gvr/capi/include/gvr_util.h b/libs/vr/libgvr/include/vr/gvr/capi/include/gvr_util.h
new file mode 100644
index 0000000..1fae611
--- /dev/null
+++ b/libs/vr/libgvr/include/vr/gvr/capi/include/gvr_util.h
@@ -0,0 +1,123 @@
+#ifndef VR_GVR_CAPI_INCLUDE_GVR_UTIL_H_
+#define VR_GVR_CAPI_INCLUDE_GVR_UTIL_H_
+
+#include <private/dvr/eigen.h>
+#include <private/dvr/numeric.h>
+#include <private/dvr/types.h>
+#include <vr/gvr/capi/include/gvr_types.h>
+
+namespace android {
+namespace dvr {
+
+inline gvr_rectf FovRadiansToDegrees(const gvr_rectf& fov) {
+  return gvr_rectf{ToDeg(fov.left), ToDeg(fov.right), ToDeg(fov.bottom),
+                   ToDeg(fov.top)};
+}
+
+inline gvr_rectf FovDegreesToRadians(const gvr_rectf& fov) {
+  return gvr_rectf{ToRad(fov.left), ToRad(fov.right), ToRad(fov.bottom),
+                   ToRad(fov.top)};
+}
+
+inline FieldOfView GvrToDvrFov(const gvr_rectf& fov) {
+  gvr_rectf fov_rad = FovDegreesToRadians(fov);
+  return FieldOfView(fov_rad.left, fov_rad.right, fov_rad.bottom, fov_rad.top);
+}
+
+inline gvr_rectf DvrToGvrFov(const FieldOfView& fov) {
+  return FovRadiansToDegrees(
+      gvr_rectf{fov.GetLeft(), fov.GetRight(), fov.GetBottom(), fov.GetTop()});
+}
+
+inline gvr_mat4f GvrIdentityMatrix() {
+  gvr_mat4f identity;
+  memset(&identity.m, 0, sizeof(identity.m));
+  for (int i = 0; i < 4; i++)
+    identity.m[i][i] = 1;
+  return identity;
+}
+
+inline gvr_mat4f GvrTranslationMatrix(float x, float y, float z) {
+  gvr_mat4f trans = GvrIdentityMatrix();
+  trans.m[0][3] = x;
+  trans.m[1][3] = y;
+  trans.m[2][3] = z;
+  return trans;
+}
+
+inline gvr_mat4f EigenToGvrMatrix(const mat4& m) {
+  gvr_mat4f ret;
+  for (int i = 0; i < 4; ++i)
+    for (int j = 0; j < 4; ++j)
+      ret.m[i][j] = m(i, j);
+  return ret;
+}
+
+inline mat4 GvrToEigenMatrix(const gvr::Mat4f& m) {
+  mat4 ret;
+  for (int i = 0; i < 4; ++i)
+    for (int j = 0; j < 4; ++j)
+      ret(i, j) = m.m[i][j];
+  return ret;
+}
+
+inline quat GvrToEigenRotation(const gvr_mat4f& m) {
+  mat3 ret;
+  for (int r = 0; r < 3; ++r)
+    for (int c = 0; c < 3; ++c)
+      ret(r, c) = m.m[r][c];
+  return quat(ret.matrix());
+}
+
+inline vec3 GvrToEigenTranslation(const gvr_mat4f& m) {
+  return vec3(m.m[0][3], m.m[1][3], m.m[2][3]);
+}
+
+// Converts a DVR pose to 6DOF head transform as a GVR matrix.
+inline gvr_mat4f PosefToGvrMatrix(const Posef& pose) {
+  return EigenToGvrMatrix(pose.GetObjectFromReferenceMatrix());
+}
+
+// Converts a DVR pose to 3DOF head transform as a GVR matrix by stripping out
+// position translation.
+inline gvr_mat4f PosefTo3DofGvrMatrix(const Posef& pose) {
+  gvr_mat4f ret = PosefToGvrMatrix(pose);
+  ret.m[0][3] = 0;
+  ret.m[1][3] = 0;
+  ret.m[2][3] = 0;
+  return ret;
+}
+
+// Converts a GVR matrix to a DVR pose.
+inline Posef GvrMatrixToPosef(const gvr_mat4f& m) {
+  return Posef(GvrToEigenRotation(m), GvrToEigenTranslation(m)).Inverse();
+}
+
+// Calculates an transform with only the yaw and position components of |pose|.
+// The inverse of this matrix cancels yaw and position without affecting roll or
+// pitch.
+inline mat4 CalculateRecenterTransform(const mat4& pose) {
+  const vec4 z_axis = pose * vec4::UnitZ();
+  const float yaw = std::atan2(z_axis[0], z_axis[2]);
+  const vec3 position = pose.translation();
+  return mat4(Eigen::AngleAxis<float>(yaw, vec3::UnitY())).translate(position);
+}
+
+// Calculates a transform that negates the position component of |pose| and
+// offsets the pose by |position|. The inverse of this matrix cancels the
+// position component of pose and translates by |position| without affecting
+// orientation.
+inline mat4 CalculateOffsetTransform(const mat4& pose, const vec3& position) {
+  // Transform the origin by the pose matrix to produce the offset that cancels
+  // only the position of the pose.
+  //          -1          T
+  // [ R | t ]  [ 0 ] = -R * t
+  // [ 0   1 ]  [ 1 ]
+  const vec3 position_offset = (pose.inverse() * vec4(0, 0, 0, 1)).head<3>();
+  return mat4(mat4::Identity()).translate(position - position_offset);
+}
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // VR_GVR_CAPI_INCLUDE_GVR_UTIL_H_
diff --git a/libs/vr/libgvr/java/com/google/vr/gvr/platform/Loader.java b/libs/vr/libgvr/java/com/google/vr/gvr/platform/Loader.java
new file mode 100644
index 0000000..5c5cc62
--- /dev/null
+++ b/libs/vr/libgvr/java/com/google/vr/gvr/platform/Loader.java
@@ -0,0 +1,32 @@
+package com.google.vr.gvr.platform;
+
+/**
+ * Auxiliary class to load the system implementation of the GVR API.
+ */
+public class Loader {
+
+  /**
+   * Opens a shared library containing the system implementation for the GVR
+   * API and returns the handle to it.
+   *
+   * @return A Long object describing the handle returned by dlopen.
+   */
+  public static Long loadLibrary() {
+    // Note: we cannot safely do caller verifications here, so instead we do
+    // them in the service side. This means that private API symbols will be
+    // visible to any app adding the appropriate <uses-library> in their
+    // manifest, but any requests to such APIs will fail if not done from a
+    // trusted package like VrCore.
+    //
+    // Trusted packages are defined by (package name, signature) pairs in within
+    // a system service, and both must match.
+
+    // Load a thin JNI library that runs dlopen on request.
+    System.loadLibrary("gvr_system_loader");
+
+    // Performs dlopen on the library and returns the handle.
+    return nativeLoadLibrary("libgvr_system.so");
+  }
+
+  private static native long nativeLoadLibrary(String library);
+}
diff --git a/libs/vr/libgvr/library_loader.cpp b/libs/vr/libgvr/library_loader.cpp
new file mode 100644
index 0000000..3cdc7d6
--- /dev/null
+++ b/libs/vr/libgvr/library_loader.cpp
@@ -0,0 +1,25 @@
+#include <dlfcn.h>
+#include <jni.h>
+
+#include <string>
+
+extern "C" {
+
+JNIEXPORT jlong JNICALL
+Java_com_google_vr_gvr_platform_Loader_nativeLoadLibrary(
+    JNIEnv* env, jclass, jstring java_library) {
+  if (!java_library)
+    return 0;
+
+  // Convert the Java String object to a C++ null-terminated string.
+  const char* data = env->GetStringUTFChars(java_library, NULL);
+  size_t size = env->GetStringUTFLength(java_library);
+  std::string library(data, size);
+  env->ReleaseStringUTFChars(java_library, data);
+
+  // Return the handle to the requested library.
+  return reinterpret_cast<jlong>(
+      dlopen(library.c_str(), RTLD_NOW | RTLD_LOCAL));
+}
+
+}  // extern "C"
diff --git a/libs/vr/libgvr/prebuilt/build_gvr_prebuilts.sh b/libs/vr/libgvr/prebuilt/build_gvr_prebuilts.sh
new file mode 100644
index 0000000..5d5076b
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/build_gvr_prebuilts.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+# Build and copy libgvr from Google3. Make sure that your local Google3 client
+# is up-to-date by running `p4 sync` before executing this script.
+#
+# Usage:
+# build_gvr_prebuilts.sh --google3_dir=<path_google3_client_root>
+
+source gbash.sh || exit
+
+DEFINE_string --required "google3_dir" "" \
+  "Path to the root directory of Google3 client"
+
+BLAZE_COMMON_OPTS=(
+  --compilation_mode=opt
+  --copt=-fdata-sections
+  --copt=-ffunction-sections
+  --define='prod=1'
+  --define='enable_experimental_sdk=1'
+  --linkopt=-Wl,--gc-sections
+)
+
+function copy_file() {
+  cp -v "${1}" ${CURRENT_DIR}/"${2}"
+}
+
+function copy_gvr_headers() {
+  echo "Copy GVR headers ..."
+
+  GVR_HEADER_DIR="include/vr/gvr/capi/include"
+  GVR_SOURCE_DIR="include/vr/gvr/capi/src"
+
+  # GVR public headers
+  copy_file "vr/gvr/capi/include/gvr.h" ${GVR_HEADER_DIR}
+  copy_file "vr/gvr/capi/include/gvr_audio.h" ${GVR_HEADER_DIR}
+  copy_file "vr/gvr/capi/include/gvr_controller.h" ${GVR_HEADER_DIR}
+  copy_file "vr/gvr/capi/include/gvr_types.h" ${GVR_HEADER_DIR}
+
+  # GVR private and experimental headers
+  copy_file "vr/gvr/capi/src/gvr_experimental.h" ${GVR_SOURCE_DIR}
+  copy_file "vr/gvr/capi/src/gvr_private.h" ${GVR_SOURCE_DIR}
+  copy_file "vr/gvr/capi/src/gvr_types_experimental.h" ${GVR_SOURCE_DIR}
+}
+
+function build_gvr_libs() {
+  echo "Build GVR libraries ..."
+
+  blaze build \
+    //java/com/google/vr/sdk/release:common_library.aar \
+    //vr/gvr/platform:libgvr.so \
+    //vr/gvr/platform:libgvr_audio.so \
+    ${BLAZE_COMMON_OPTS[@]} --config=android_arm --symlink_prefix blaze-arm-
+
+  blaze build \
+    //vr/gvr/platform:libgvr.so \
+    //vr/gvr/platform:libgvr_audio.so \
+    ${BLAZE_COMMON_OPTS[@]} --config=android_arm64 --symlink_prefix blaze-arm64-
+
+  copy_file "blaze-arm-genfiles/java/com/google/vr/sdk/release/common_library.aar" \
+    "lib/common_library.aar"
+  copy_file "blaze-arm-genfiles/vr/gvr/platform/libgvr.so" "lib/android_arm"
+  copy_file "blaze-arm-genfiles/vr/gvr/platform/libgvr_audio.so" "lib/android_arm"
+  copy_file "blaze-arm64-genfiles/vr/gvr/platform/libgvr.so" "lib/android_arm64"
+  copy_file "blaze-arm64-genfiles/vr/gvr/platform/libgvr_audio.so" "lib/android_arm64"
+}
+
+function main() {
+  set -ex
+
+  CURRENT_DIR=$(pwd)
+  GOOGLE3_DIR=${FLAGS_google3_dir}
+
+  cd ${GOOGLE3_DIR}
+  copy_gvr_headers
+  build_gvr_libs
+}
+
+gbash::init_google "$@"
+main "$@"
diff --git a/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr.h b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr.h
new file mode 100644
index 0000000..5054ebd
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr.h
@@ -0,0 +1,1737 @@
+/* Copyright 2016 Google Inc. All rights reserved.
+ *
+ * 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 VR_GVR_CAPI_INCLUDE_GVR_H_
+#define VR_GVR_CAPI_INCLUDE_GVR_H_
+
+#ifdef __ANDROID__
+#include <jni.h>
+#endif
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+#include <array>
+#include <memory>
+#include <vector>
+#endif
+
+#include "vr/gvr/capi/include/gvr_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @defgroup base Google VR Base C API
+/// @brief This is the Google VR C API. It supports clients writing VR
+/// experiences for head mounted displays that consist of a mobile phone and a
+/// VR viewer.
+///
+/// Example API usage:
+///
+///     #ifdef __ANDROID__
+///     // On Android, the gvr_context should almost always be obtained from
+///     // the Java GvrLayout object via
+///     // GvrLayout.getGvrApi().getNativeGvrContext().
+///     gvr_context* gvr = ...;
+///     #else
+///     gvr_context* gvr = gvr_create();
+///     #endif
+///
+///     gvr_initialize_gl(gvr);
+///
+///     gvr_buffer_viewport_list* viewport_list =
+///         gvr_buffer_viewport_list_create(gvr);
+///     gvr_get_recommended_buffer_viewports(gvr, viewport_list);
+///     gvr_buffer_viewport* left_eye_vp = gvr_buffer_viewport_create(gvr);
+///     gvr_buffer_viewport* right_eye_vp = gvr_buffer_viewport_create(gvr);
+///     gvr_buffer_viewport_list_get_item(viewport_list, 0, left_eye_vp);
+///     gvr_buffer_viewport_list_get_item(viewport_list, 1, right_eye_vp);
+///
+///     while (client_app_should_render) {
+///       // A client app should be ready for the render target size to change
+///       // whenever a new QR code is scanned, or a new viewer is paired.
+///       gvr_sizei render_target_size =
+///           gvr_get_maximum_effective_render_target_size(gvr);
+///       // The maximum effective render target size can be very large, most
+///       // applications need to scale down to compensate.
+///       render_target_size.width /= 2;
+///       render_target_size.height /= 2;
+///       gvr_swap_chain_resize_buffer(swap_chain, 0, render_target_size);
+///
+///       // This function will depend on your render loop's implementation.
+///       gvr_clock_time_point next_vsync = AppGetNextVsyncTime();
+///
+///       const gvr_mat4f head_view =
+///           gvr_get_head_space_from_start_space_rotation(gvr, next_vsync);
+///       const gvr_mat4f left_eye_view = MatrixMultiply(
+///           gvr_get_eye_from_head_matrix(gvr, GVR_LEFT_EYE), head_view);
+///       const gvr::Mat4f right_eye_view = MatrixMultiply(
+///           gvr_get_eye_from_head_matrix(gvr, GVR_RIGHT_EYE), head_view);
+///
+///       // Insert client rendering code here.
+///
+///       AppSetRenderTarget(offscreen_texture_id);
+///
+///       AppDoSomeRenderingForEye(
+///           gvr_buffer_viewport_get_source_uv(left_eye_view),
+///           left_eye_matrix);
+///       AppDoSomeRenderingForEye(
+///           gvr_buffer_viewport_get_source_uv(right_eye_view),
+///           right_eye_matrix);
+///       AppSetRenderTarget(primary_display);
+///
+///       gvr_frame_submit(&frame, viewport_list, head_matrix);
+///     }
+///
+///     // Cleanup memory.
+///     gvr_buffer_viewport_list_destroy(&viewport_list);
+///     gvr_buffer_viewport_destroy(&left_eye_vp);
+///     gvr_buffer_viewport_destroy(&right_eye_vp);
+///
+///     #ifdef __ANDROID__
+///     // On Android, The Java GvrLayout owns the gvr_context.
+///     #else
+///     gvr_destroy(gvr);
+///     #endif
+///
+/// Head tracking is enabled by default, and will begin as soon as the
+/// gvr_context is created. The client should call gvr_pause_tracking() and
+/// gvr_resume_tracking() when the app is paused and resumed, respectively.
+///
+/// Note: Unless otherwise noted, the functions in this API may not be
+/// thread-safe with respect to the gvr_context, and it is up the caller to use
+/// the API in a thread-safe manner.
+///
+/// @{
+
+/// Creates a new gvr instance.
+///
+/// The instance must remain valid as long as any GVR object is in use. When
+/// the application no longer needs to use the GVR SDK, call gvr_destroy().
+///
+///
+/// On Android, the gvr_context should *almost always* be obtained from the Java
+/// GvrLayout object, rather than explicitly created here. The GvrLayout should
+/// live in the app's View hierarchy, and its use is required to ensure
+/// consistent behavior across all varieties of GVR-compatible viewers. See
+/// the Java GvrLayout and GvrApi documentation for more details.
+///
+#ifdef __ANDROID__
+/// @param env The JNIEnv associated with the current thread.
+/// @param app_context The Android application context. This must be the
+///     application context, NOT an Activity context (Note: from any Android
+///     Activity in your app, you can call getApplicationContext() to
+///     retrieve the application context).
+/// @param class_loader The class loader to use when loading Java classes.
+///     This must be your app's main class loader (usually accessible through
+///     activity.getClassLoader() on any of your Activities).
+///
+/// @return Pointer to the created gvr instance, NULL on failure.
+gvr_context* gvr_create(JNIEnv* env, jobject app_context, jobject class_loader);
+#else
+/// @return Pointer to the created gvr instance, NULL on failure.
+gvr_context* gvr_create();
+#endif  // #ifdef __ANDROID__
+
+/// Gets the current GVR runtime version.
+///
+/// Note: This runtime version may differ from the version against which the
+/// client app is compiled, as defined by the semantic version components in
+/// gvr_version.h.
+///
+/// @return The version as a gvr_version.
+gvr_version gvr_get_version();
+
+/// Gets a string representation of the current GVR runtime version. This is of
+/// the form "MAJOR.MINOR.PATCH".
+///
+/// Note: This runtime version may differ from the version against which the
+/// client app is compiled, as defined in gvr_version.h by
+/// GVR_SDK_VERSION_STRING.
+///
+/// @return The version as a static char pointer.
+const char* gvr_get_version_string();
+
+/// Gets the current GVR error code, or GVR_ERROR_NONE if there is no error.
+/// This function doesn't clear the error code; see gvr_clear_error().
+///
+/// @param gvr Pointer to the gvr instance.
+/// @return The current gvr_error code, or GVR_ERROR_NONE if no error has
+///    occurred.
+int32_t gvr_get_error(gvr_context* gvr);
+
+/// Clears the current GVR error code, and returns the error code that was
+/// cleared.
+///
+/// @param gvr Pointer to the gvr instance.
+/// @return The gvr_error code that was cleared by this function, or
+/// GVR_ERROR_NONE if no error has occurred.
+int32_t gvr_clear_error(gvr_context* gvr);
+
+/// Gets a human-readable string representing the given error code.
+///
+/// @param error_code The gvr_error code.
+/// @return A human-readable string representing the error code.
+const char* gvr_get_error_string(int32_t error_code);
+
+/// Returns an opaque struct containing information about user preferences.
+///
+/// The returned struct will remain valid as long as the context is valid.
+/// The returned struct may be updated when the user changes their preferences,
+/// so this function only needs to be called once, and calling it multiple
+/// times will return the same object each time.
+///
+/// @param gvr Pointer to the gvr instance.
+/// @return An opaque struct containing information about user preferences.
+const gvr_user_prefs* gvr_get_user_prefs(gvr_context* gvr);
+
+/// Returns the controller handedness of the given gvr_user_prefs struct.
+///
+/// @param user_prefs Pointer to the gvr_user_prefs object returned by
+///     gvr_get_user_prefs.
+/// @return Either GVR_CONTROLLER_RIGHT_HANDED or GVR_CONTROLLER_LEFT_HANDED
+///     depending on which hand the user holds the controller in.
+int32_t gvr_user_prefs_get_controller_handedness(
+    const gvr_user_prefs* user_prefs);
+
+/// Destroys a gvr_context instance.  The parameter will be nulled by this
+/// operation.  Once this function is called, the behavior of any subsequent
+/// call to a GVR SDK function that references objects created from this
+/// context is undefined.
+///
+/// @param gvr Pointer to a pointer to the gvr instance to be destroyed and
+///     nulled.
+void gvr_destroy(gvr_context** gvr);
+
+/// Initializes necessary GL-related objects and uses the current thread and
+/// GL context for rendering. Please make sure that a valid GL context is
+/// available when this function is called.  This should never be called more
+/// than once on the same GL context (doing so would cause resource leaks).
+///
+/// @param gvr Pointer to the gvr instance to be initialized.
+void gvr_initialize_gl(gvr_context* gvr);
+
+/// Gets whether asynchronous reprojection is currently enabled.
+///
+/// If enabled, frames will be collected by the rendering system and
+/// asynchronously re-projected in sync with the scanout of the display. This
+/// feature may not be available on every platform, and requires a
+/// high-priority render thread with special extensions to function properly.
+///
+/// Note: On Android, this feature can be enabled solely via the GvrLayout Java
+/// instance which (indirectly) owns this gvr_context. The corresponding
+/// method call is GvrLayout.setAsyncReprojectionEnabled().
+///
+/// Note: Because of the above requirements, asynchronous reprojection is only
+/// currently available on Daydream-ready Android devices.  This function will
+/// always return false on other devices.
+///
+/// @param gvr Pointer to the gvr instance.
+/// @return Whether async reprojection is enabled. Defaults to false.
+bool gvr_get_async_reprojection_enabled(const gvr_context* gvr);
+
+/// Gets the recommended buffer viewport configuration, populating a previously
+/// allocated gvr_buffer_viewport_list object. The updated values include the
+/// per-eye recommended viewport and field of view for the target.
+///
+/// When the recommended viewports are used for distortion rendering, this
+/// method should always be called after calling refresh_viewer_profile(). That
+/// will ensure that the populated viewports reflect the currently paired
+/// viewer.
+///
+/// @param gvr Pointer to the gvr instance from which to get the viewports.
+/// @param viewport_list Pointer to a previously allocated viewport list. This
+///     will be populated with the recommended buffer viewports and resized if
+///     necessary.
+void gvr_get_recommended_buffer_viewports(
+    const gvr_context* gvr, gvr_buffer_viewport_list* viewport_list);
+
+/// Gets the screen (non-distorted) buffer viewport configuration, populating a
+/// previously allocated gvr_buffer_viewport_list object. The updated values
+/// include the per-eye recommended viewport and field of view for the target.
+///
+/// @param gvr Pointer to the gvr instance from which to get the viewports.
+/// @param viewport_list Pointer to a previously allocated viewport list. This
+///     will be populated with the screen buffer viewports and resized if
+///     necessary.
+void gvr_get_screen_buffer_viewports(const gvr_context* gvr,
+                                     gvr_buffer_viewport_list* viewport_list);
+
+/// Returns the maximum effective size for the client's render target, given the
+/// parameters of the head mounted device selected. At this resolution, we have
+/// a 1:1 ratio between source pixels and screen pixels in the most magnified
+/// region of the screen. Applications should rarely, if ever, need to render
+/// to a larger target, as it will simply result in sampling artifacts.
+///
+/// Note that this is probably too large for most applications to use as a
+/// render target size. Applications should scale this value to be appropriate
+/// to their graphical load.
+///
+/// @param gvr Pointer to the gvr instance from which to get the size.
+///
+/// @return Maximum effective size for the target render target.
+gvr_sizei gvr_get_maximum_effective_render_target_size(const gvr_context* gvr);
+
+/// Returns a non-distorted size for the screen, given the parameters
+/// of the phone and/or the head mounted device selected.
+///
+/// @param gvr Pointer to the gvr instance from which to get the size.
+///
+/// @return Screen (non-distorted) size for the render target.
+gvr_sizei gvr_get_screen_target_size(const gvr_context* gvr);
+
+// Sets the size of the underlying render surface.
+//
+// By default, it is assumed that the display size matches the surface
+// size. If that is the case for the client app, this method need never be
+// called. However, in certain cases (e.g., hardware scaling), this will not
+// always hold, in which case the distortion pass must be informed of the
+// custom surface size.
+//
+// Note that the caller is responsible for resizing any BufferSpec objects
+// created before this function is called. Otherwise there will be rendering
+// artifacts, such as edges appearing pixelated. This function will change the
+// result of get_maximum_effective_render_target_size(), so that function can be
+// used to compute the appropriate size for buffers.
+//
+// @param gvr Pointer to the gvr_context instance.
+// @param surface_size_pixels The size in pixels of the display surface. If
+//     non-empty, this will be used in conjunction with the current display to
+//     perform properly scaled distortion. If empty, it is assumed that the
+//     rendering surface dimensions match that of the active display.
+void gvr_set_surface_size(gvr_context* gvr, gvr_sizei surface_size_pixels);
+
+/// Performs postprocessing, including lens distortion, on the contents of the
+/// passed texture and shows the result on the screen. Lens distortion is
+/// determined by the parameters of the viewer encoded in its QR code. The
+/// passed texture is not modified.
+///
+/// If the application does not call gvr_initialize_gl() before calling this
+/// function, the results are undefined.
+///
+/// @deprecated This function exists only to support legacy rendering pathways
+///     for Cardboard devices. It is incompatible with the low-latency
+///     experiences supported by async reprojection. Use the swap chain API
+///     instead.
+///
+/// @param gvr Pointer to the gvr instance which will do the distortion.
+/// @param texture_id The OpenGL ID of the texture that contains the next frame
+///     to be displayed.
+/// @param viewport_list Rendering parameters.
+/// @param head_space_from_start_space This parameter is ignored.
+/// @param target_presentation_time This parameter is ignored.
+void gvr_distort_to_screen(gvr_context* gvr, int32_t texture_id,
+                           const gvr_buffer_viewport_list* viewport_list,
+                           gvr_mat4f head_space_from_start_space,
+                           gvr_clock_time_point target_presentation_time);
+/// @}
+
+/////////////////////////////////////////////////////////////////////////////
+// Viewports and viewport lists
+/////////////////////////////////////////////////////////////////////////////
+/// @defgroup viewport Viewports and viewport lists
+/// @brief Objects to define the mapping between the application's rendering
+///     output and the user's field of view.
+/// @{
+
+/// Creates a gvr_buffer_viewport instance.
+gvr_buffer_viewport* gvr_buffer_viewport_create(gvr_context* gvr);
+
+/// Frees a gvr_buffer_viewport instance and clears the pointer.
+void gvr_buffer_viewport_destroy(gvr_buffer_viewport** viewport);
+
+/// Gets the UV coordinates specifying where the output buffer is sampled.
+///
+/// @param viewport The buffer viewport.
+/// @return UV coordinates as a rectangle.
+gvr_rectf gvr_buffer_viewport_get_source_uv(
+    const gvr_buffer_viewport* viewport);
+
+/// Sets the UV coordinates specifying where the output buffer should be
+/// sampled when compositing the final distorted image.
+///
+/// @param viewport The buffer viewport.
+/// @param uv The new UV coordinates for sampling. The coordinates must be
+///     valid, that is, left <= right and bottom <= top. Otherwise an empty
+///     source region is set, which will result in no output for this viewport.
+void gvr_buffer_viewport_set_source_uv(gvr_buffer_viewport* viewport,
+                                       gvr_rectf uv);
+
+/// Retrieves the field of view for the referenced buffer region.
+///
+/// This is a helper that converts the stored projection matrix to a field of
+/// view. Note that if the previously set projection matrix cannot be expressed
+/// as a view frustum aligned with the eye's optical axis, the result will be
+/// incorrect.
+///
+/// @param viewport The buffer viewport.
+/// @return The field of view of the rendered image, in degrees.
+gvr_rectf gvr_buffer_viewport_get_source_fov(
+    const gvr_buffer_viewport* viewport);
+
+/// Sets the field of view for the viewport contents.
+///
+/// This is a helper that sets the projection matrix in such a way that the
+/// viewport's contents fill the specified FOV around the eye's optical axis.
+///
+/// @param viewport The buffer viewport.
+/// @param fov The field of view to use when compositing the rendered image,
+///     in degrees.
+void gvr_buffer_viewport_set_source_fov(gvr_buffer_viewport* viewport,
+                                        gvr_rectf fov);
+
+/// Gets the matrix that positions the viewport in eye space.
+///
+/// @param viewport The buffer viewport.
+/// @return Matrix that transforms a quad with vertices (-1, -1, 0), (1, -1, 0),
+///     (-1, 1, 0), (1, 1, 0) representing the viewport contents to its desired
+///     eye space position for the target eye.
+gvr_mat4f gvr_buffer_viewport_get_transform(
+    const gvr_buffer_viewport* viewport);
+
+/// Sets the matrix that positions the viewport in eye space.
+///
+/// @param viewport The buffer viewport.
+/// @param transform Matrix that transforms a quad with vertices (-1, -1, 0),
+///     (1, -1, 0), (-1, 1, 0), (1, 1, 0) representing the viewport contents to
+///     its desired eye space position for the target eye.
+void gvr_buffer_viewport_set_transform(gvr_buffer_viewport* viewport,
+                                       gvr_mat4f transform);
+
+/// Gets the target logical eye for the specified viewport.
+///
+/// @param viewport The buffer viewport.
+/// @return Index of the target logical eye for this viewport.
+int32_t gvr_buffer_viewport_get_target_eye(const gvr_buffer_viewport* viewport);
+
+/// Sets the target logical eye for the specified viewport.
+///
+/// @param viewport The buffer viewport.
+/// @param index Index of the target logical eye.
+void gvr_buffer_viewport_set_target_eye(gvr_buffer_viewport* viewport,
+                                        int32_t index);
+
+/// Gets the index of the source buffer from which the viewport reads its
+/// undistorted pixels.
+///
+/// @param viewport The buffer viewport.
+/// @return Index of the source buffer. This corresponds to the index in the
+///     list of buffer specs that was passed to gvr_swap_chain_create().
+int32_t gvr_buffer_viewport_get_source_buffer_index(
+    const gvr_buffer_viewport* viewport);
+
+/// Sets the buffer from which the viewport reads its undistorted pixels.
+///
+/// To use the contents of the external surface as buffer contents, associate an
+/// external surface with the viewport by calling
+/// gvr_buffer_viewport_set_external_surface_id(), then call this function and
+/// pass GVR_BUFFER_INDEX_EXTERNAL_SURFACE.
+///
+/// @param viewport The buffer viewport.
+/// @param buffer_index The index of the source buffer. This is either an index
+///     in the list of buffer specs that was passed to
+///     gvr_swap_chain_create(), or GVR_BUFFER_INDEX_EXTERNAL_SURFACE.
+void gvr_buffer_viewport_set_source_buffer_index(
+    gvr_buffer_viewport* viewport, int32_t buffer_index);
+
+/// Gets the ID of the externally-managed Surface texture from which this
+/// viewport reads undistored pixels.
+///
+/// @param viewport The buffer viewport.
+/// @return ID of the externally-managed Surface of undistorted pixels.
+int32_t gvr_buffer_viewport_get_external_surface_id(
+    const gvr_buffer_viewport* viewport);
+
+/// Sets the ID of the externally-managed Surface texture from which this
+/// viewport reads. The ID is issued by GvrLayout.
+///
+/// @param viewport The buffer viewport.
+/// @param external_surface_id The ID of the surface to read from.
+void gvr_buffer_viewport_set_external_surface_id(
+    gvr_buffer_viewport* viewport, int32_t external_surface_id);
+
+/// Gets the type of reprojection to perform on the specified viewport.
+///
+/// @param viewport The buffer viewport.
+/// @return Type of reprojection that is applied to the viewport.
+int32_t gvr_buffer_viewport_get_reprojection(
+    const gvr_buffer_viewport* viewport);
+
+/// Sets the type of reprojection to perform on the specified viewport.
+/// Viewports that display world content should use full reprojection.
+/// Viewports that display head-locked UI should disable reprojection to avoid
+/// excessive judder. The default is to perform full reprojection.
+///
+/// @param viewport The buffer viewport.
+/// @param reprojection Type of reprojection that will be applied to the passed
+///     viewport.
+void gvr_buffer_viewport_set_reprojection(gvr_buffer_viewport* viewport,
+                                          int32_t reprojection);
+
+/// Compares two gvr_buffer_viewport instances and returns true if they specify
+/// the same view mapping.
+///
+/// @param a Instance of a buffer viewport.
+/// @param b Another instance of a buffer viewport.
+/// @return True if the passed viewports are the same.
+bool gvr_buffer_viewport_equal(const gvr_buffer_viewport* a,
+                               const gvr_buffer_viewport* b);
+
+/// Creates a new, empty list of viewports. The viewport list defines how the
+/// application's rendering output should be transformed into the stabilized,
+/// lens-distorted image that is sent to the screen.
+///
+/// The caller should populate the returned viewport using one of:
+///   - gvr_get_recommended_buffer_viewports()
+///   - gvr_get_screen_buffer_viewports()
+///   - gvr_buffer_viewport_list_set_item()
+///
+/// @param gvr Pointer the gvr instance from which to allocate the viewport
+/// list.
+/// @return Pointer to an allocated gvr_buffer_viewport_list object. The caller
+//      is responsible for calling gvr_buffer_viewport_list_destroy() on the
+///     returned object when it is no longer needed.
+gvr_buffer_viewport_list* gvr_buffer_viewport_list_create(
+    const gvr_context* gvr);
+
+/// Destroys a gvr_buffer_viewport_list instance. The parameter will be nulled
+/// by this operation.
+///
+/// @param viewport_list Pointer to a pointer to the viewport list instance to
+///     be destroyed and nulled.
+void gvr_buffer_viewport_list_destroy(gvr_buffer_viewport_list** viewport_list);
+
+/// Returns the size of the given viewport list.
+///
+/// @param viewport_list Pointer to a viewport list.
+/// @return The number of entries in the viewport list.
+size_t gvr_buffer_viewport_list_get_size(
+    const gvr_buffer_viewport_list* viewport_list);
+
+/// Retrieve a buffer viewport entry from a list.
+///
+/// @param viewport_list Pointer to the previously allocated viewport list.
+/// @param index Zero-based index of the viewport entry to query. Must be
+///    smaller than the list size.
+/// @param viewport The buffer viewport structure that will be populated with
+///    retrieved data.
+void gvr_buffer_viewport_list_get_item(
+    const gvr_buffer_viewport_list* viewport_list, size_t index,
+    gvr_buffer_viewport* viewport);
+
+/// Update an element of the viewport list or append a new one at the end.
+///
+/// @param viewport_list Pointer to a previously allocated viewport list.
+/// @param index Index of the buffer viewport entry to update. If the
+///     `viewport_list` size is equal to the index, a new viewport entry will be
+///     added. The `viewport_list` size must *not* be less than the index value.
+/// @param viewport A pointer to the buffer viewport object.
+void gvr_buffer_viewport_list_set_item(gvr_buffer_viewport_list* viewport_list,
+                                       size_t index,
+                                       const gvr_buffer_viewport* viewport);
+
+/// @}
+
+/////////////////////////////////////////////////////////////////////////////
+// Swapchains and frames
+/////////////////////////////////////////////////////////////////////////////
+/// @defgroup swap_chain Swap chains and frames
+/// @brief Functions to create a swap chain, manipulate it and submit frames
+///     for lens distortion and presentation on the screen.
+/// @{
+
+/// Creates a default buffer specification.
+gvr_buffer_spec* gvr_buffer_spec_create(gvr_context* gvr);
+
+/// Destroy the buffer specification and null the pointer.
+void gvr_buffer_spec_destroy(gvr_buffer_spec** spec);
+
+/// Gets the size of the buffer to be created.
+///
+/// @param spec Buffer specification.
+/// @return Size of the pixel buffer. The default is equal to the recommended
+///     render target size at the time when the specification was created.
+gvr_sizei gvr_buffer_spec_get_size(const gvr_buffer_spec* spec);
+
+/// Sets the size of the buffer to be created.
+///
+/// @param spec Buffer specification.
+/// @param size The size. Width and height must both be greater than zero.
+///     Otherwise, the application is aborted.
+void gvr_buffer_spec_set_size(gvr_buffer_spec* spec, gvr_sizei size);
+
+/// Gets the number of samples per pixel in the buffer to be created.
+///
+/// @param spec Buffer specification.
+/// @return Value >= 1 giving the number of samples. 1 means multisampling is
+///     disabled. Negative values and 0 are never returned.
+int32_t gvr_buffer_spec_get_samples(const gvr_buffer_spec* spec);
+
+/// Sets the number of samples per pixel in the buffer to be created.
+///
+/// @param spec Buffer specification.
+/// @param num_samples The number of samples. Negative values are an error.
+///     The values 0 and 1 are treated identically and indicate that
+//      multisampling should be disabled.
+void gvr_buffer_spec_set_samples(gvr_buffer_spec* spec, int32_t num_samples);
+
+/// Sets the color format for the buffer to be created. Default format is
+/// GVR_COLOR_FORMAT_RGBA_8888.
+///
+/// @param spec Buffer specification.
+/// @param color_format The color format for the buffer. Valid formats are in
+///     the gvr_color_format_type enum.
+void gvr_buffer_spec_set_color_format(gvr_buffer_spec* spec,
+                                      int32_t color_format);
+
+/// Sets the depth and stencil format for the buffer to be created. Currently,
+/// only packed stencil formats are supported. Default format is
+/// GVR_DEPTH_STENCIL_FORMAT_DEPTH_16.
+///
+/// @param spec Buffer specification.
+/// @param depth_stencil_format The depth and stencil format for the buffer.
+///     Valid formats are in the gvr_depth_stencil_format_type enum.
+void gvr_buffer_spec_set_depth_stencil_format(gvr_buffer_spec* spec,
+                                              int32_t depth_stencil_format);
+
+/// Creates a swap chain from the given buffer specifications.
+/// This is a potentially time-consuming operation. All frames within the
+/// swapchain will be allocated. Once rendering is stopped, call
+/// gvr_swap_chain_destroy() to free GPU resources. The passed gvr_context must
+/// not be destroyed until then.
+///
+/// Swap chains can have no buffers. This is useful when only displaying
+/// external surfaces. When `count` is zero, `buffers` must be null.
+///
+/// @param gvr GVR instance for which a swap chain will be created.
+/// @param buffers Array of pixel buffer specifications. Each frame in the
+///     swap chain will be composed of these buffers.
+/// @param count Number of buffer specifications in the array.
+/// @return Opaque handle to the newly created swap chain.
+gvr_swap_chain* gvr_swap_chain_create(gvr_context* gvr,
+                                      const gvr_buffer_spec** buffers,
+                                      int32_t count);
+
+/// Destroys the swap chain and nulls the pointer.
+void gvr_swap_chain_destroy(gvr_swap_chain** swap_chain);
+
+/// Gets the number of buffers in each frame of the swap chain.
+int32_t gvr_swap_chain_get_buffer_count(const gvr_swap_chain* swap_chain);
+
+/// Retrieves the size of the specified pixel buffer. Note that if the buffer
+/// was resized while the current frame was acquired, the return value will be
+/// different than the value obtained from the equivalent function for the
+/// current frame.
+///
+/// @param swap_chain The swap chain.
+/// @param index Index of the pixel buffer.
+/// @return Size of the specified pixel buffer in frames that will be returned
+///     from gvr_swap_chain_acquire_frame().
+gvr_sizei gvr_swap_chain_get_buffer_size(gvr_swap_chain* swap_chain,
+                                         int32_t index);
+
+/// Resizes the specified pixel buffer to the given size. The frames are resized
+/// when they are unused, so the currently acquired frame will not be resized
+/// immediately.
+///
+/// @param swap_chain The swap chain.
+/// @param index Index of the pixel buffer to resize.
+/// @param size New size for the specified pixel buffer.
+void gvr_swap_chain_resize_buffer(gvr_swap_chain* swap_chain, int32_t index,
+                                  gvr_sizei size);
+
+/// Acquires a frame from the swap chain for rendering. Buffers that are part of
+/// the frame can then be bound with gvr_frame_bind_buffer(). Once the frame
+/// is finished and all its constituent buffers are ready, call
+/// gvr_frame_submit() to display it while applying lens distortion.
+///
+/// @param swap_chain The swap chain.
+/// @return Handle to the acquired frame. NULL if the swap chain is invalid,
+///     or if acquire has already been called on this swap chain.
+gvr_frame* gvr_swap_chain_acquire_frame(gvr_swap_chain* swap_chain);
+
+/// Binds a pixel buffer that is part of the frame to the OpenGL framebuffer.
+///
+/// @param frame Frame handle acquired from the swap chain.
+/// @param index Index of the pixel buffer to bind. This corresponds to the
+///     index in the buffer spec list that was passed to
+///     gvr_swap_chain_create().
+void gvr_frame_bind_buffer(gvr_frame* frame, int32_t index);
+
+/// Unbinds any buffers bound from this frame and binds the default OpenGL
+/// framebuffer.
+void gvr_frame_unbind(gvr_frame* frame);
+
+/// Returns the dimensions of the pixel buffer with the specified index. Note
+/// that a frame that was acquired before resizing a swap chain buffer will not
+/// be resized until it is submitted to the swap chain.
+///
+/// @param frame Frame handle.
+/// @param index Index of the pixel buffer to inspect.
+/// @return Dimensions of the specified pixel buffer.
+gvr_sizei gvr_frame_get_buffer_size(const gvr_frame* frame, int32_t index);
+
+/// Gets the name (ID) of the framebuffer object associated with the specified
+/// buffer. The OpenGL state is not modified.
+///
+/// @param frame Frame handle.
+/// @param index Index of a pixel buffer.
+/// @return OpenGL object name (ID) of a framebuffer object which can be used
+///     to render into the buffer. The ID is valid only until the frame is
+///     submitted.
+int32_t gvr_frame_get_framebuffer_object(const gvr_frame* frame, int32_t index);
+
+/// Submits the frame for distortion and display on the screen. The passed
+/// pointer is nulled to prevent reuse.
+///
+/// @param frame The frame to submit.
+/// @param list Buffer view configuration to be used for this frame.
+/// @param head_space_from_start_space Transform from start space (space with
+///     head at the origin at last tracking reset) to head space (space with
+///     head at the origin and axes aligned to the view vector).
+void gvr_frame_submit(gvr_frame** frame, const gvr_buffer_viewport_list* list,
+                      gvr_mat4f head_space_from_start_space);
+
+/// Resets the OpenGL framebuffer binding to what it was at the time the
+/// passed gvr_context was created.
+void gvr_bind_default_framebuffer(gvr_context* gvr);
+
+/// @}
+
+/////////////////////////////////////////////////////////////////////////////
+// Head tracking
+/////////////////////////////////////////////////////////////////////////////
+/// @defgroup Headtracking Head tracking
+/// @brief Functions for managing head tracking.
+/// @{
+
+/// Gets the current monotonic system time.
+///
+/// @return The current monotonic system time.
+gvr_clock_time_point gvr_get_time_point_now();
+
+/// Gets the rotation from start space to head space.  The head space is a
+/// space where the head is at the origin and faces the -Z direction.
+///
+/// @param gvr Pointer to the gvr instance from which to get the pose.
+/// @param time The time at which to get the head pose. The time should be in
+///     the future. If the time is not in the future, it will be clamped to now.
+/// @return A matrix representation of the rotation from start space (the space
+///     where the head was last reset) to head space (the space with the head
+///     at the origin, and the axes aligned to the view vector).
+gvr_mat4f gvr_get_head_space_from_start_space_rotation(
+    const gvr_context* gvr, const gvr_clock_time_point time);
+
+/// Applies a simple neck model translation based on the rotation of the
+/// provided head pose.
+///
+/// Note: Neck model application may not be appropriate for all tracking
+/// scenarios, e.g., when tracking is non-biological.
+///
+/// @param gvr Pointer to the context instance from which the pose was obtained.
+/// @param head_rotation_in_start_space The head rotation as returned by
+///     gvr_get_head_space_from_start_space_rotation().
+/// @param factor A scaling factor for the neck model offset, clamped from 0 to
+///     1. This should be 1 for most scenarios, while 0 will effectively disable
+///     neck model application. This value can be animated to smoothly
+///     interpolate between alternative (client-defined) neck models.
+/// @return The new head pose with the neck model applied.
+gvr_mat4f gvr_apply_neck_model(const gvr_context* gvr,
+                               gvr_mat4f head_space_from_start_space_rotation,
+                               float factor);
+
+/// Pauses head tracking, disables all sensors (to save power).
+///
+/// @param gvr Pointer to the gvr instance for which tracking will be paused and
+///     sensors disabled.
+void gvr_pause_tracking(gvr_context* gvr);
+
+/// Resumes head tracking, re-enables all sensors.
+///
+/// @param gvr Pointer to the gvr instance for which tracking will be resumed.
+void gvr_resume_tracking(gvr_context* gvr);
+
+/// Resets head tracking.
+///
+/// This API call is deprecated. Use gvr_recenter_tracking instead, which
+/// accomplishes the same effects but avoids the undesirable side-effects of
+/// a full reset (temporary loss of tracking quality).
+///
+/// Only to be used by Cardboard apps. Daydream apps must not call this. On the
+/// Daydream platform, recentering is handled automatically and should never
+/// be triggered programatically by applications. Hybrid apps that support both
+/// Cardboard and Daydream must only call this function when in Cardboard mode
+/// (that is, when the phone is paired with a Cardboard viewer), never in
+/// Daydream mode.
+///
+/// @param gvr Pointer to the gvr instance for which tracking will be reseted.
+/// @deprecated Calls to this method can be safely replaced by calls to
+//    gvr_recenter_tracking.
+void gvr_reset_tracking(gvr_context* gvr);
+
+/// Recenters the head orientation (resets the yaw to zero, leaving pitch and
+/// roll unmodified).
+///
+/// Only to be used by Cardboard apps. Daydream apps must not call this. On the
+/// Daydream platform, recentering is handled automatically and should never
+/// be triggered programatically by applications. Hybrid apps that support both
+/// Cardboard and Daydream must only call this function when in Cardboard mode
+/// (that is, when the phone is paired with a Cardboard viewer), never in
+/// Daydream mode.
+///
+/// @param gvr Pointer to the gvr instance for which tracking will be
+///     recentered.
+void gvr_recenter_tracking(gvr_context* gvr);
+
+/// @}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Head mounted display.
+/////////////////////////////////////////////////////////////////////////////
+/// @defgroup HMD Head Mounted Display
+/// @brief Functions for managing viewer information.
+/// @{
+
+/// Sets the default viewer profile specified by viewer_profile_uri.
+/// The viewer_profile_uri that is passed in will be ignored if a valid
+/// viewer profile has already been stored on the device that the app
+/// is running on.
+///
+/// Note: This function has the potential of blocking for up to 30 seconds for
+/// each redirect if a shortened URI is passed in as argument. It will try to
+/// unroll the shortened URI for a maximum number of 5 times if the redirect
+/// continues. In that case, it is recommended to create a separate thread to
+/// call this function so that other tasks like rendering will not be blocked
+/// on this. The blocking can be avoided if a standard URI is passed in.
+///
+/// @param gvr Pointer to the gvr instance which to set the profile on.
+/// @param viewer_profile_uri A string that contains either the shortened URI or
+///     the standard URI representing the viewer profile that the app should be
+///     using. If the valid viewer profile can be found on the device, the URI
+///     that is passed in will be ignored and nothing will happen. Otherwise,
+///     gvr will look for the viewer profile specified by viewer_profile_uri,
+///     and it will be stored if found. Also, the values will be applied to gvr.
+///     A valid standard URI can be generated from this page:
+///     https://www.google.com/get/cardboard/viewerprofilegenerator/
+/// @return True if the viewer profile specified by viewer_profile_uri was
+///     successfully stored and applied, false otherwise.
+bool gvr_set_default_viewer_profile(gvr_context* gvr,
+                                    const char* viewer_profile_uri);
+
+/// Refreshes gvr_context with the viewer profile that is stored on the device.
+/// If it can not find the viewer profile, nothing will happen.
+///
+/// @param gvr Pointer to the gvr instance to refresh the profile on.
+void gvr_refresh_viewer_profile(gvr_context* gvr);
+
+/// Gets the name of the viewer vendor.
+///
+/// @param gvr Pointer to the gvr instance from which to get the vendor.
+/// @return A pointer to the vendor name. May be NULL if no viewer is paired.
+///     WARNING: This method guarantees the validity of the returned pointer
+///     only until the next use of the `gvr` context. The string should be
+///     copied immediately if persistence is required.
+const char* gvr_get_viewer_vendor(const gvr_context* gvr);
+
+/// Gets the name of the viewer model.
+///
+/// @param gvr Pointer to the gvr instance from which to get the name.
+/// @return A pointer to the model name. May be NULL if no viewer is paired.
+///     WARNING: This method guarantees the validity of the returned pointer
+///     only until the next use of the `gvr` context. The string should be
+///     copied immediately if persistence is required.
+const char* gvr_get_viewer_model(const gvr_context* gvr);
+
+/// Gets the type of the viewer, as defined by gvr_viewer_type.
+///
+/// @param gvr Pointer to the gvr instance from which to get the viewer type.
+/// @return The gvr_viewer_type of the currently paired viewer.
+int32_t gvr_get_viewer_type(const gvr_context* gvr);
+
+/// Gets the transformation matrix to convert from Head Space to Eye Space for
+/// the given eye.
+///
+/// @param gvr Pointer to the gvr instance from which to get the matrix.
+/// @param eye Selected gvr_eye type.
+/// @return Transformation matrix from Head Space to selected Eye Space.
+gvr_mat4f gvr_get_eye_from_head_matrix(const gvr_context* gvr,
+                                       const int32_t eye);
+
+/// Gets the window bounds.
+///
+/// @param gvr Pointer to the gvr instance from which to get the bounds.
+///
+/// @return Window bounds in physical pixels.
+gvr_recti gvr_get_window_bounds(const gvr_context* gvr);
+
+/// Computes the distorted point for a given point in a given eye.  The
+/// distortion inverts the optical distortion caused by the lens for the eye.
+/// Due to chromatic aberration, the distortion is different for each
+/// color channel.
+///
+/// @param gvr Pointer to the gvr instance which will do the computing.
+/// @param eye The gvr_eye type (left or right).
+/// @param uv_in A point in screen eye Viewport Space in [0,1]^2 with (0, 0)
+///     in the lower left corner of the eye's viewport and (1, 1) in the
+///     upper right corner of the eye's viewport.
+/// @param uv_out A pointer to an array of (at least) 3 elements, with each
+///     element being a Point2f representing a point in render texture eye
+///     Viewport Space in [0,1]^2 with (0, 0) in the lower left corner of the
+///     eye's viewport and (1, 1) in the upper right corner of the eye's
+///     viewport.
+///     `uv_out[0]` is the corrected position of `uv_in` for the red channel
+///     `uv_out[1]` is the corrected position of `uv_in` for the green channel
+///     `uv_out[2]` is the corrected position of `uv_in` for the blue channel
+void gvr_compute_distorted_point(const gvr_context* gvr, const int32_t eye,
+                                 const gvr_vec2f uv_in, gvr_vec2f uv_out[3]);
+
+/// @}
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+namespace gvr {
+
+/// Convenience C++ wrapper for gvr_user_prefs.
+class UserPrefs {
+ public:
+  /// Creates a C++ wrapper for a gvr_user_prefs object. Note that unlike most
+  /// of the C++ wrappers in the API, this does not take ownership, as the
+  /// gvr_user_prefs will remain valid for the lifetime of the GVR context.
+  explicit UserPrefs(const gvr_user_prefs* user_prefs)
+      : user_prefs_(user_prefs) {}
+
+  UserPrefs(UserPrefs&& other) : user_prefs_(nullptr) {
+    std::swap(user_prefs_, other.user_prefs_);
+  }
+
+  UserPrefs& operator=(UserPrefs&& other) {
+    std::swap(user_prefs_, other.user_prefs_);
+    return *this;
+  }
+
+  /// For more information, see gvr_user_prefs_get_controller_handedness().
+  ControllerHandedness GetControllerHandedness() const {
+    return static_cast<ControllerHandedness>(
+        gvr_user_prefs_get_controller_handedness(user_prefs_));
+  }
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  const gvr_user_prefs* cobj() const { return user_prefs_; }
+
+ private:
+  const gvr_user_prefs* user_prefs_;
+
+  // Disallow copy and assign.
+  UserPrefs(const UserPrefs&);
+  void operator=(const UserPrefs&);
+};
+
+/// Convenience C++ wrapper for the opaque gvr_buffer_viewport type.
+/// The constructor allocates memory, so when used in tight loops, instances
+/// should be reused.
+class BufferViewport {
+ public:
+  BufferViewport(BufferViewport&& other)
+      : viewport_(nullptr) {
+    std::swap(viewport_, other.viewport_);
+  }
+
+  BufferViewport& operator=(BufferViewport&& other) {
+    std::swap(viewport_, other.viewport_);
+    return *this;
+  }
+
+  ~BufferViewport() {
+    if (viewport_) gvr_buffer_viewport_destroy(&viewport_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_get_source_fov().
+  Rectf GetSourceFov() const {
+    return gvr_buffer_viewport_get_source_fov(viewport_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_set_source_fov().
+  void SetSourceFov(const Rectf& fov) {
+    gvr_buffer_viewport_set_source_fov(viewport_, fov);
+  }
+
+  /// For more information, see gvr_buffer_viewport_get_transform().
+  Mat4f GetTransform() const {
+    return gvr_buffer_viewport_get_transform(viewport_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_set_transform().
+  void SetTransform(const Mat4f& transform) {
+    gvr_buffer_viewport_set_transform(viewport_, transform);
+  }
+
+  /// For more information, see gvr_buffer_viewport_get_source_uv().
+  Rectf GetSourceUv() const {
+    return gvr_buffer_viewport_get_source_uv(viewport_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_set_source_uv().
+  void SetSourceUv(const Rectf& uv) {
+    gvr_buffer_viewport_set_source_uv(viewport_, uv);
+  }
+
+  /// For more information, see gvr_buffer_viewport_get_target_eye().
+  Eye GetTargetEye() const {
+    return static_cast<Eye>(gvr_buffer_viewport_get_target_eye(viewport_));
+  }
+
+  /// For more information, see gvr_buffer_viewport_set_target_eye().
+  void SetTargetEye(Eye eye) {
+    gvr_buffer_viewport_set_target_eye(viewport_, eye);
+  }
+
+  /// For more information, see gvr_buffer_viewport_get_source_buffer_index().
+  int32_t GetSourceBufferIndex() const {
+    return gvr_buffer_viewport_get_source_buffer_index(viewport_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_set_source_buffer_index().
+  void SetSourceBufferIndex(int32_t buffer_index) {
+    gvr_buffer_viewport_set_source_buffer_index(viewport_, buffer_index);
+  }
+
+  /// For more information, see gvr_buffer_viewport_get_external_surface_id().
+  int32_t GetExternalSurfaceId() const {
+    return gvr_buffer_viewport_get_external_surface_id(viewport_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_set_external_surface_id().
+  void SetExternalSurfaceId(const int32_t external_surface_id) {
+    gvr_buffer_viewport_set_external_surface_id(viewport_, external_surface_id);
+  }
+
+  /// For more information, see gvr_buffer_viewport_get_reprojection().
+  gvr_reprojection GetReprojection() const {
+    return static_cast<gvr_reprojection>(
+        gvr_buffer_viewport_get_reprojection(viewport_));
+  }
+  /// For more information, see gvr_buffer_viewport_set_reprojection().
+  void SetReprojection(gvr_reprojection reprojection) {
+    gvr_buffer_viewport_set_reprojection(viewport_, reprojection);
+  }
+
+  /// For more information, see gvr_buffer_viewport_equal().
+  bool operator==(const BufferViewport& other) const {
+    return gvr_buffer_viewport_equal(viewport_, other.viewport_) ? true : false;
+  }
+  bool operator!=(const BufferViewport& other) const {
+    return !(*this == other);
+  }
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and takes ownership.
+  explicit BufferViewport(gvr_buffer_viewport* viewport)
+      : viewport_(viewport) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_buffer_viewport* cobj() { return viewport_; }
+  const gvr_buffer_viewport* cobj() const { return viewport_; }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_buffer_viewport* release() {
+    auto result = viewport_;
+    viewport_ = nullptr;
+    return result;
+  }
+  /// @}
+
+ private:
+  friend class GvrApi;
+  friend class BufferViewportList;
+
+  explicit BufferViewport(gvr_context* gvr)
+      : viewport_(gvr_buffer_viewport_create(gvr)) {}
+
+  gvr_buffer_viewport* viewport_;
+};
+
+/// Convenience C++ wrapper for the opaque gvr_buffer_viewport_list type. This
+/// class will automatically release the wrapped gvr_buffer_viewport_list upon
+/// destruction. It can only be created via a `GvrApi` instance, and its
+/// validity is tied to the lifetime of that instance.
+class BufferViewportList {
+ public:
+  BufferViewportList(BufferViewportList&& other)
+      : context_(nullptr), viewport_list_(nullptr) {
+    std::swap(context_, other.context_);
+    std::swap(viewport_list_, other.viewport_list_);
+  }
+
+  BufferViewportList& operator=(BufferViewportList&& other) {
+    std::swap(context_, other.context_);
+    std::swap(viewport_list_, other.viewport_list_);
+    return *this;
+  }
+
+  ~BufferViewportList() {
+    if (viewport_list_) {
+      gvr_buffer_viewport_list_destroy(&viewport_list_);
+    }
+  }
+
+  /// For more information, see gvr_get_recommended_buffer_viewports().
+  void SetToRecommendedBufferViewports() {
+    gvr_get_recommended_buffer_viewports(context_, viewport_list_);
+  }
+
+  /// For more information, see gvr_get_screen_buffer_viewports().
+  void SetToScreenBufferViewports() {
+    gvr_get_screen_buffer_viewports(context_, viewport_list_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_list_set_item().
+  void SetBufferViewport(size_t index, const BufferViewport& viewport) {
+    gvr_buffer_viewport_list_set_item(viewport_list_, index,
+                                      viewport.viewport_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_list_get_item().
+  void GetBufferViewport(size_t index, BufferViewport* viewport) const {
+    gvr_buffer_viewport_list_get_item(viewport_list_, index,
+                                      viewport->viewport_);
+  }
+
+  /// For more information, see gvr_buffer_viewport_list_get_size().
+  size_t GetSize() const {
+    return gvr_buffer_viewport_list_get_size(viewport_list_);
+  }
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and takes ownership.
+  BufferViewportList(gvr_buffer_viewport_list* viewport_list,
+                     gvr_context* context)
+      : context_(context),
+        viewport_list_(viewport_list) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_buffer_viewport_list* cobj() { return viewport_list_; }
+  const gvr_buffer_viewport_list* cobj() const { return viewport_list_; }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_buffer_viewport_list* release() {
+    auto result = viewport_list_;
+    viewport_list_ = nullptr;
+    return result;
+  }
+  /// @}
+
+ private:
+  friend class Frame;
+  friend class GvrApi;
+  friend class SwapChain;
+
+  explicit BufferViewportList(gvr_context* context)
+      : context_(context),
+        viewport_list_(gvr_buffer_viewport_list_create(context)) {}
+
+  const gvr_context* context_;
+  gvr_buffer_viewport_list* viewport_list_;
+
+  // Disallow copy and assign.
+  BufferViewportList(const BufferViewportList&) = delete;
+  void operator=(const BufferViewportList&) = delete;
+};
+
+/// Convenience C++ wrapper for gvr_buffer_spec, an opaque pixel buffer
+/// specification. Frees the underlying gvr_buffer_spec on destruction.
+class BufferSpec {
+ public:
+  BufferSpec(BufferSpec&& other)
+      : spec_(nullptr) {
+    std::swap(spec_, other.spec_);
+  }
+
+  BufferSpec& operator=(BufferSpec&& other) {
+    std::swap(spec_, other.spec_);
+    return *this;
+  }
+
+  ~BufferSpec() {
+    if (spec_) gvr_buffer_spec_destroy(&spec_);
+  }
+
+  /// Gets the buffer's size. The default value is the recommended render
+  /// target size. For more information, see gvr_buffer_spec_get_size().
+  Sizei GetSize() const {
+    return gvr_buffer_spec_get_size(spec_);
+  }
+
+  /// Sets the buffer's size. For more information, see
+  /// gvr_buffer_spec_set_size().
+  void SetSize(const Sizei& size) {
+    gvr_buffer_spec_set_size(spec_, size);
+  }
+
+  /// Sets the buffer's size to the passed width and height. For more
+  /// information, see gvr_buffer_spec_set_size().
+  ///
+  /// @param width The width in pixels. Must be greater than 0.
+  /// @param height The height in pixels. Must be greater than 0.
+  void SetSize(int32_t width, int32_t height) {
+    gvr_sizei size{width, height};
+    gvr_buffer_spec_set_size(spec_, size);
+  }
+
+  /// Gets the number of samples per pixel in the buffer. For more
+  /// information, see gvr_buffer_spec_get_samples().
+  int32_t GetSamples() const { return gvr_buffer_spec_get_samples(spec_); }
+
+  /// Sets the number of samples per pixel. For more information, see
+  /// gvr_buffer_spec_set_samples().
+  void SetSamples(int32_t num_samples) {
+    gvr_buffer_spec_set_samples(spec_, num_samples);
+  }
+
+  /// Sets the color format for this buffer. For more information, see
+  /// gvr_buffer_spec_set_color_format().
+  void SetColorFormat(ColorFormat color_format) {
+    gvr_buffer_spec_set_color_format(spec_, color_format);
+  }
+
+  /// Sets the depth and stencil format for this buffer. For more
+  /// information, see gvr_buffer_spec_set_depth_stencil_format().
+  void SetDepthStencilFormat(DepthStencilFormat depth_stencil_format) {
+    gvr_buffer_spec_set_depth_stencil_format(spec_, depth_stencil_format);
+  }
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and takes ownership.
+  explicit BufferSpec(gvr_buffer_spec* spec) : spec_(spec) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_buffer_spec* cobj() { return spec_; }
+  const gvr_buffer_spec* cobj() const { return spec_; }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_buffer_spec* release() {
+    auto result = spec_;
+    spec_ = nullptr;
+    return result;
+  }
+  /// @}
+
+ private:
+  friend class GvrApi;
+  friend class SwapChain;
+
+  explicit BufferSpec(gvr_context* gvr) {
+    spec_ = gvr_buffer_spec_create(gvr);
+  }
+
+  gvr_buffer_spec* spec_;
+};
+
+/// Convenience C++ wrapper for gvr_frame, which represents a single frame
+/// acquired for rendering from the swap chain.
+class Frame {
+ public:
+  Frame(Frame&& other) : frame_(nullptr) {
+    std::swap(frame_, other.frame_);
+  }
+
+  Frame& operator=(Frame&& other) {
+    std::swap(frame_, other.frame_);
+    return *this;
+  }
+
+  ~Frame() {
+    // The swap chain owns the frame, so no clean-up is required.
+  }
+
+  /// For more information, see gvr_frame_get_buffer_size().
+  Sizei GetBufferSize(int32_t index) const {
+    return gvr_frame_get_buffer_size(frame_, index);
+  }
+
+  /// For more information, see gvr_frame_bind_buffer().
+  void BindBuffer(int32_t index) {
+    gvr_frame_bind_buffer(frame_, index);
+  }
+
+  /// For more information, see gvr_frame_unbind().
+  void Unbind() {
+    gvr_frame_unbind(frame_);
+  }
+
+  /// For more information, see gvr_frame_get_framebuffer_object().
+  int32_t GetFramebufferObject(int32_t index) {
+    return gvr_frame_get_framebuffer_object(frame_, index);
+  }
+
+  /// For more information, see gvr_frame_submit().
+  void Submit(const BufferViewportList& viewport_list,
+              const Mat4f& head_space_from_start_space) {
+    gvr_frame_submit(&frame_, viewport_list.viewport_list_,
+                     head_space_from_start_space);
+  }
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and takes ownership.
+  explicit Frame(gvr_frame* frame) : frame_(frame) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_frame* cobj() { return frame_; }
+  const gvr_frame* cobj() const { return frame_; }
+
+  /// Returns whether the wrapped gvr_frame reference is valid.
+  bool is_valid() const { return frame_ != nullptr; }
+  explicit operator bool const() { return is_valid(); }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_frame* release() {
+    auto result = frame_;
+    frame_ = nullptr;
+    return result;
+  }
+  /// @}
+
+ private:
+  friend class SwapChain;
+
+  gvr_frame* frame_;
+};
+
+/// Convenience C++ wrapper for gvr_swap_chain, which represents a queue of
+/// frames. The GvrApi object must outlive any SwapChain objects created from
+/// it.
+class SwapChain {
+ public:
+  SwapChain(SwapChain&& other)
+      : swap_chain_(nullptr) {
+    std::swap(swap_chain_, other.swap_chain_);
+  }
+
+  SwapChain& operator=(SwapChain&& other) {
+    std::swap(swap_chain_, other.swap_chain_);
+    return *this;
+  }
+
+  ~SwapChain() {
+    if (swap_chain_) gvr_swap_chain_destroy(&swap_chain_);
+  }
+
+  /// For more information, see gvr_swap_chain_get_buffer_count().
+  int32_t GetBufferCount() const {
+    return gvr_swap_chain_get_buffer_count(swap_chain_);
+  }
+
+  /// For more information, see gvr_swap_chain_get_buffer_size().
+  Sizei GetBufferSize(int32_t index) const {
+    return gvr_swap_chain_get_buffer_size(swap_chain_, index);
+  }
+
+  /// For more information, see gvr_swap_chain_resize_buffer().
+  void ResizeBuffer(int32_t index, Sizei size) {
+    gvr_swap_chain_resize_buffer(swap_chain_, index, size);
+  }
+
+  /// For more information, see gvr_swap_chain_acquire_frame().
+  /// Note that if Frame acquisition fails, the returned Frame may not be valid.
+  /// The caller should inspect the returned Frame's validity before using,
+  /// and reschedule frame acquisition upon failure.
+  Frame AcquireFrame() {
+    Frame result(gvr_swap_chain_acquire_frame(swap_chain_));
+    return result;
+  }
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and takes ownership.
+  explicit SwapChain(gvr_swap_chain* swap_chain) : swap_chain_(swap_chain) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_swap_chain* cobj() { return swap_chain_; }
+  const gvr_swap_chain* cobj() const { return swap_chain_; }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_swap_chain* release() {
+    auto result = swap_chain_;
+    swap_chain_ = nullptr;
+    return result;
+  }
+  /// @}
+
+ private:
+  friend class GvrApi;
+
+  SwapChain(gvr_context* gvr, const std::vector<BufferSpec>& specs) {
+    std::vector<const gvr_buffer_spec*> c_specs;
+    for (const auto& spec : specs)
+      c_specs.push_back(spec.spec_);
+    swap_chain_ = gvr_swap_chain_create(gvr, c_specs.data(),
+                                        static_cast<int32_t>(c_specs.size()));
+  }
+
+  gvr_swap_chain* swap_chain_;
+
+  // Disallow copy and assign.
+  SwapChain(const SwapChain&);
+  void operator=(const SwapChain&);
+};
+
+/// This is a convenience C++ wrapper for the Google VR C API.
+///
+/// This wrapper strategy prevents ABI compatibility issues between compilers
+/// by ensuring that the interface between client code and the implementation
+/// code in libgvr.so is a pure C interface. The translation from C++ calls
+/// to C calls provided by this wrapper runs entirely in the client's binary
+/// and is compiled by the client's compiler.
+///
+/// Methods in this class are only documented insofar as the C++ wrapping logic
+/// is concerned; for information about the method itself, please refer to the
+/// corresponding function in the C API.
+///
+/// Example API usage:
+///
+///     // Functionality supplied by the application in the example below has
+///     // the "app-" prefix.
+///     #ifdef __ANDROID__
+///     // On Android, the gvr_context should almost always be obtained from the
+///     // Java GvrLayout object via
+///     // GvrLayout.getGvrApi().getNativeGvrContext().
+///     std::unique_ptr<GvrApi> gvr = GvrApi::WrapNonOwned(gvr_context);
+///     #else
+///     std::unique_ptr<GvrApi> gvr = GvrApi::Create();
+///     #endif
+///
+///     gvr->InitializeGl();
+///
+///     gvr::BufferViewportList viewport_list =
+///         gvr->CreateEmptyBufferViewportList();
+///     gvr->GetRecommendedBufferViewports(&viewport_list);
+///     gvr::BufferViewport left_eye_viewport = gvr->CreateBufferViewport();
+///     gvr::BufferViewport right_eye_viewport = gvr->CreateBufferViewport();
+///     viewport_list.Get(0, &left_eye_view);
+///     viewport_list.Get(1, &right_eye_view);
+///
+///     std::vector<gvr::BufferSpec> specs;
+///     specs.push_back(gvr->CreateBufferSpec());
+///     specs[0].SetSamples(app_samples);
+///     gvr::SwapChain swap_chain = gvr->CreateSwapChain(specs);
+///
+///     while (client_app_should_render) {
+///       // A client app should be ready for the render target size to change
+///       // whenever a new QR code is scanned, or a new viewer is paired.
+///       gvr::Sizei render_target_size =
+///           gvr->GetRecommendedRenderTargetSize();
+///       swap_chain.ResizeBuffer(0, render_target_size);
+///       gvr::Frame frame = swap_chain.AcquireFrame();
+///       while (!frame) {
+///         std::this_thread::sleep_for(2ms);
+///         frame = swap_chain.AcquireFrame();
+///       }
+///
+///       // This function will depend on your render loop's implementation.
+///       gvr::ClockTimePoint next_vsync = AppGetNextVsyncTime();
+///
+///       const gvr::Mat4f head_view =
+///           gvr->GetHeadSpaceFromStartSpaceRotation(next_vsync);
+///       const gvr::Mat4f left_eye_view = MatrixMultiply(
+///           gvr->GetEyeFromHeadMatrix(kLeftEye), head_view);
+///       const gvr::Mat4f right_eye_view = MatrixMultiply(
+///           gvr->GetEyeFromHeadMatrix(kRightEye), head_view);
+///
+///       frame.BindBuffer(0);
+///       // App does its rendering to the current framebuffer here.
+///       AppDoSomeRenderingForEye(
+///           left_eye_viewport.GetSourceUv(), left_eye_view);
+///       AppDoSomeRenderingForEye(
+///           right_eye_viewport.GetSourceUv(), right_eye_view);
+///       frame.Unbind();
+///       frame.Submit(viewport_list, head_matrix);
+///     }
+///
+class GvrApi {
+ public:
+#ifdef __ANDROID__
+  /// Instantiates and returns a GvrApi instance that owns a gvr_context.
+  ///
+  /// @param env The JNIEnv associated with the current thread.
+  /// @param app_context The Android application context. This must be the
+  ///     application context, NOT an Activity context (Note: from any Android
+  ///     Activity in your app, you can call getApplicationContext() to
+  ///     retrieve the application context).
+  /// @param class_loader The class loader to use when loading Java classes.
+  ///     This must be your app's main class loader (usually accessible through
+  ///     activity.getClassLoader() on any of your Activities).
+  /// @return unique_ptr to the created GvrApi instance, nullptr on failure.
+  static std::unique_ptr<GvrApi> Create(JNIEnv* env, jobject app_context,
+                                        jobject class_loader) {
+    gvr_context* context = gvr_create(env, app_context, class_loader);
+    if (!context) {
+      return nullptr;
+    }
+    return std::unique_ptr<GvrApi>(new GvrApi(context, true /* owned */));
+  }
+#else
+  /// Instantiates and returns a GvrApi instance that owns a gvr_context.
+  ///
+  /// @return unique_ptr to the created GvrApi instance, nullptr on failure.
+  static std::unique_ptr<GvrApi> Create() {
+    gvr_context* context = gvr_create();
+    if (!context) {
+      return nullptr;
+    }
+    return std::unique_ptr<GvrApi>(new GvrApi(context, true /* owned */));
+  }
+#endif  // #ifdef __ANDROID__
+
+  ~GvrApi() {
+    if (context_ && owned_) {
+      gvr_destroy(&context_);
+    }
+  }
+
+  /// @name Error handling
+  /// @{
+
+  /// For more information, see gvr_get_error().
+  Error GetError() { return static_cast<Error>(gvr_get_error(context_)); }
+
+  /// For more information, see gvr_clear_error().
+  Error ClearError() { return static_cast<Error>(gvr_clear_error(context_)); }
+
+  /// For more information, see gvr_get_error_string().
+  static const char* GetErrorString(Error error_code) {
+    return gvr_get_error_string(error_code);
+  }
+
+  /// For more information, see gvr_get_user_prefs().
+  UserPrefs GetUserPrefs() { return UserPrefs(gvr_get_user_prefs(context_)); }
+
+  /// @}
+
+  /// @name Rendering
+  /// @{
+
+  /// For more information, see gvr_initialize_gl().
+  void InitializeGl() { gvr_initialize_gl(context_); }
+
+  /// For more information, see gvr_get_async_reprojection_enabled().
+  bool GetAsyncReprojectionEnabled() const {
+    return gvr_get_async_reprojection_enabled(context_);
+  }
+
+  /// Constructs a C++ wrapper for a gvr_buffer_viewport object.  For more
+  /// information, see gvr_buffer_viewport_create().
+  ///
+  /// @return A new BufferViewport instance with memory allocated for an
+  ///     underlying gvr_buffer_viewport.
+  BufferViewport CreateBufferViewport() const {
+    return BufferViewport(context_);
+  }
+
+  /// Constructs a C++ wrapper for a gvr_buffer_viewport_list object.
+  /// For more information, see gvr_buffer_viewport_list_create().
+  ///
+  /// @return A new, empty BufferViewportList instance.
+  ///     Note: The validity of the returned object is closely tied to the
+  ///     lifetime of the member gvr_context. The caller is responsible for
+  ///     ensuring correct usage accordingly.
+  BufferViewportList CreateEmptyBufferViewportList() const {
+    return BufferViewportList(context_);
+  }
+
+  /// For more information, see gvr_get_maximum_effective_render_target_size().
+  Sizei GetMaximumEffectiveRenderTargetSize() const {
+    return gvr_get_maximum_effective_render_target_size(context_);
+  }
+
+  /// For more information, see gvr_get_screen_target_size().
+  Sizei GetScreenTargetSize() const {
+    return gvr_get_screen_target_size(context_);
+  }
+
+  /// For more information, see gvr_set_surface_size().
+  void SetSurfaceSize(Sizei surface_size_pixels) {
+    gvr_set_surface_size(context_, surface_size_pixels);
+  }
+
+  /// For more information, see gvr_distort_to_screen().
+  void DistortToScreen(int32_t texture_id,
+                       const BufferViewportList& viewport_list,
+                       const Mat4f& rendered_head_pose_in_start_space_matrix,
+                       const ClockTimePoint& texture_presentation_time) {
+    gvr_distort_to_screen(context_, texture_id, viewport_list.viewport_list_,
+                          rendered_head_pose_in_start_space_matrix,
+                          texture_presentation_time);
+  }
+
+  /// For more information, see gvr_buffer_spec_create().
+  BufferSpec CreateBufferSpec() {
+    return BufferSpec(context_);
+  }
+
+  /// For more information, see gvr_swap_chain_create().
+  SwapChain CreateSwapChain(const std::vector<BufferSpec>& specs) {
+    return SwapChain(context_, specs);
+  }
+
+  /// For more information, see gvr_bind_default_framebuffer().
+  void BindDefaultFramebuffer() {
+    gvr_bind_default_framebuffer(context_);
+  }
+  /// @}
+
+  /// @name Head tracking
+  /// @{
+
+  /// For more information see gvr_get_head_space_from_start_space_rotation.
+  ///
+  /// @param time_point The time at which to calculate the head pose in start
+  ///     space.
+  /// @return The matrix representation of the rotation from start space
+  ///     (the space with the head pose at the last tracking reset at origin) to
+  ///     head space (the space with the head at origin and axes aligned to the
+  ///     view vector).
+  Mat4f GetHeadSpaceFromStartSpaceRotation(const ClockTimePoint& time_point) {
+    return gvr_get_head_space_from_start_space_rotation(context_, time_point);
+  }
+
+  /// For more information, see gvr_apply_neck_model().
+  Mat4f ApplyNeckModel(const Mat4f& head_pose_in_start_space, float factor) {
+    return gvr_apply_neck_model(context_, head_pose_in_start_space, factor);
+  }
+
+  /// For more information, see gvr_pause_tracking().
+  void PauseTracking() { gvr_pause_tracking(context_); }
+
+  /// For more information, see gvr_resume_tracking().
+  void ResumeTracking() { gvr_resume_tracking(context_); }
+
+  /// For more information, see gvr_reset_tracking().
+  void ResetTracking() { gvr_reset_tracking(context_); }
+
+  // For more information, see gvr_recenter_tracking().
+  void RecenterTracking() { gvr_recenter_tracking(context_); }
+
+  /// For more information, see gvr_get_time_point_now().
+  static ClockTimePoint GetTimePointNow() { return gvr_get_time_point_now(); }
+  /// @}
+
+  /// @name Viewer parameters
+  /// @{
+
+  /// For more information, see gvr_set_default_viewer_profile().
+  bool SetDefaultViewerProfile(const char* viewer_profile_uri) {
+    return gvr_set_default_viewer_profile(context_, viewer_profile_uri);
+  }
+
+  /// For more information, see gvr_refresh_viewer_profile().
+  void RefreshViewerProfile() { gvr_refresh_viewer_profile(context_); }
+
+  /// For more information, see gvr_get_viewer_vendor().
+  const char* GetViewerVendor() const {
+    return gvr_get_viewer_vendor(context_);
+  }
+
+  /// For more information, see gvr_get_viewer_model().
+  const char* GetViewerModel() const { return gvr_get_viewer_model(context_); }
+
+  /// For more information, see gvr_get_viewer_type().
+  ViewerType GetViewerType() const {
+    return static_cast<ViewerType>(gvr_get_viewer_type(context_));
+  }
+
+  /// For more information, see gvr_get_eye_from_head_matrix().
+  Mat4f GetEyeFromHeadMatrix(Eye eye) const {
+    return gvr_get_eye_from_head_matrix(context_, eye);
+  }
+
+  /// For more information, see gvr_get_window_bounds().
+  Recti GetWindowBounds() const { return gvr_get_window_bounds(context_); }
+
+  /// For more information, see gvr_compute_distorted_point().
+  std::array<Vec2f, 3> ComputeDistortedPoint(Eye eye, const Vec2f& uv_in) {
+    std::array<Vec2f, 3> uv_out = {{{}}};
+    gvr_compute_distorted_point(context_, eye, uv_in, uv_out.data());
+    return uv_out;
+  }
+  /// @}
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and optionally takes ownership.
+  ///
+  /// @param context C object to wrap.
+  /// @param owned Whether the wrapper will own the underlying C object.
+  explicit GvrApi(gvr_context* context, bool owned = true)
+      : context_(context), owned_(owned) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_context* cobj() { return context_; }
+  const gvr_context* cobj() const { return context_; }
+
+  /// @deprecated Use cobj() instead.
+  gvr_context* GetContext() { return context_; }
+  /// @deprecated Use cobj() instead.
+  const gvr_context* GetContext() const { return context_; }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_context* release() {
+    auto result = context_;
+    context_ = nullptr;
+    return result;
+  }
+
+  /// Instantiates a GvrApi instance that wraps a *non-owned* gvr_context.
+  ///
+  /// Ownership of the provided `context` remains with the caller, and they
+  /// are responsible for ensuring proper disposal of the context.
+  ///
+  /// @param context Pointer to a non-null, non-owned gvr_context instance.
+  /// @return unique_ptr to the created GvrApi instance. Never null.
+  static std::unique_ptr<GvrApi> WrapNonOwned(gvr_context* context) {
+    return std::unique_ptr<GvrApi>(new GvrApi(context, false /* owned */));
+  }
+  /// @}
+
+ private:
+  gvr_context* context_;
+
+  // Whether context_ is owned by the GvrApi instance. If owned, the context
+  // will be released upon destruction.
+  const bool owned_;
+
+  // Disallow copy and assign.
+  GvrApi(const GvrApi&);
+  void operator=(const GvrApi&);
+};
+
+}  // namespace gvr
+#endif  // #if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+
+#endif  // VR_GVR_CAPI_INCLUDE_GVR_H_
diff --git a/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_audio.h b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_audio.h
new file mode 100644
index 0000000..eee3d0c
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_audio.h
@@ -0,0 +1,854 @@
+/* Copyright 2016 Google Inc. All rights reserved.
+ *
+ * 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 VR_GVR_CAPI_INCLUDE_GVR_AUDIO_H_
+#define VR_GVR_CAPI_INCLUDE_GVR_AUDIO_H_
+
+#if __ANDROID__
+#include <jni.h>
+#endif  // __ANDROID__
+
+#include <stdint.h>
+
+#include "vr/gvr/capi/include/gvr.h"
+#include "vr/gvr/capi/include/gvr_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/// @defgroup Audio Spatial Audio API
+/// @brief This is the GVR Audio C API, a spatial audio rendering engine,
+/// optimized for mobile VR.
+///
+/// It allows the user to spatialize sound sources in 3D space, including
+/// distance and elevation cues. Specifically, the API is capable of playing
+/// back spatial sound in three ways:
+///
+/// - **Sound object rendering**: This allows the user to create a virtual sound
+///   source in 3D space. These sources, while spatialized, are fed with mono
+///   audio data.
+///
+/// - **Ambisonic soundfields**: Ambisonic recordings are multi-channel audio
+///   files which are spatialized all around the listener in 360 degrees. These
+///   can be thought of as recorded or pre-baked soundfields. They can be of
+///   great use for background effects which sound perfectly spatial.  Examples
+///   include rain noise, crowd noise or even the sound of the ocean off to one
+///   side.
+///
+/// - **Stereo Sounds**: This allows the user to directly play back
+///   non-spatialized mono or stereo audio files. This is useful for music and
+///   other such audio.
+///
+/// **Initialization**
+///
+///     gvr_audio_context* gvr_audio_create(int32_t rendering_mode);
+///
+/// The rendering_mode argument corresponds to a `gvr_audio_rendering_mode` enum
+/// value, which specifies a rendering configuration setting:
+///
+/// - `GVR_AUDIO_RENDERING_STEREO_PANNING`:
+///   Stereo panning of all sound objects. This disables HRTF-based rendering.
+/// - `GVR_AUDIO_RENDERING_BINAURAL_LOW_QUALITY`:
+///   This renders sound objects over a virtual array of 8 loudspeakers arranged
+///   in a cube configuration around the listener’s head. HRTF-based rendering
+///   is enabled.
+/// - `GVR_AUDIO_RENDERING_BINAURAL_HIGH_QUALITY`:
+///   This renders sound objects over a virtual array of 16 loudspeakers
+///   arranged in an approximate equidistribution about the listener’s
+///   head. HRTF-based rendering is enabled.
+///
+/// For most modern phones, the high quality mode offers a good balance between
+/// performance and audio quality. To optimize the rendering performance for
+/// headphones *and* speaker playback, the stereo speaker mode can be enabled
+/// which automatically switches to stereo panning when no headphone is plugin.
+/// Note that this can lead to varying CPU usage based on headphone and speaker
+/// playback.
+///
+/// **Sound engine control**
+///
+/// Audio playback on the default audio device can be started and stopped by
+/// calling the following two methods:
+///
+///     void gvr_audio_pause(gvr_audio_context* api);
+///     void gvr_audio_resume(gvr_audio_context* api);
+///
+/// Note that:
+///
+///     void gvr_audio_update(gvr_audio_context* api);
+///
+/// must be called from the main thread at a regular rate. It is used to execute
+/// background operations outside of the audio thread.
+///
+/// **Listener position and rotation**
+///
+/// To ensure that the audio in your application reacts to listener head
+/// movement, it is important to update the listener's head orientation in the
+/// graphics callback using the head orientation matrix.
+///
+/// The following methods can be used to control the listener’s head position
+/// and orientation with the audio engine:
+///
+///     void gvr_audio_set_head_position(gvr_audio_context* api, float x,
+///                                      float y, float z);
+/// or
+///
+///     void gvr_audio_set_head_position_gvr(gvr_audio_context* api,
+///                                          const gvr_vec3f& position);
+///
+/// and
+///
+///     void gvr_audio_set_head_rotation(gvr_audio_context* api,
+///                                      float x, float y, float z, float w);
+/// or
+///
+///     void gvr_audio_set_head_rotation_gvr(gvr_audio_context* api,
+///                                          const gvr_quatf& rotation);
+///
+/// **Preloading Sounds**
+///
+/// Both mono sound files for use with Sound Objects and multi-channel Ambisonic
+/// soundfield files can be preloaded into memory before playback or
+/// alternatively streamed during playback. Preloading can be useful to reduce
+/// CPU usage especially if the same audio clip is likely to be played back many
+/// times. In this case playback latency is also reduced.
+///
+/// Sound files can be preloaded into memory by calling:
+///
+///     bool gvr_audio_preload_soundfile(gvr_audio_context* api,
+///                                      const char* filename);
+///
+/// Unused sound files can be unloaded with a call to:
+///
+///     void gvr_audio_unload_soundfile(gvr_audio_context* api,
+///                                     const char* filename);
+///
+/// NOTE: If a sound object, soundfield or stereo sound is created with a file
+/// that has not been preloaded, that audio will be streamed.
+///
+/// **Spatializtion of sound objects**
+///
+/// The GVR Audio System allows the user to create virtual sound objects which
+/// can be placed anywhere in space around the listener.
+///
+/// To create a new sound object, call:
+///
+///     gvr_audio_source_id
+///     gvr_audio_create_sound_object(gvr_audio_context* api,
+///                                   const char* filename);
+///
+/// This returns a handle that can be used to set properties such as the
+/// position and the volume of the sound object via calls to the following two
+/// functions:
+///
+///     void
+///     gvr_audio_set_sound_object_position(gvr_audio_context* api,
+///                                         gvr_audio_source_id sound_object_id,
+///                                         float x, float y, float z);
+///
+///     void
+///     gvr_audio_set_sound_volume(gvr_audio_context* api,
+///                                gvr_audio_source_id source_id, float volume);
+///
+/// The behavior of Sound Objects with respect to their distance from the
+/// listener can be controlled via calls to the following method:
+///
+///     void gvr_audio_set_sound_object_distance_rolloff_model(
+///         gvr_audio_context* api, gvr_audio_source_id sound_object_id,
+///         int32_t rolloff_model, float min_distance, float max_distance);
+///
+/// This enables a user to choose between logarithmic and linear distance
+/// rolloff methods, or to completely disable distance rolloff effects.
+///
+///
+/// The spatialized playback of a sound object can be triggered with a call to:
+///
+///     void gvr_audio_play_sound(gvr_audio_context* api,
+///                               gvr_audio_source_id source_id,
+///                               bool looping_enabled);
+///
+/// and stopped with a call to:
+///
+///     void gvr_audio_stop_sound(gvr_audio_context* api,
+///                               gvr_audio_source_id source_id);
+///
+/// Note that the sound object handle destroys itself at the moment the sound
+/// playback has stopped. This way, no clean up of sound object handles is
+/// needed. On subsequent calls to this function the corresponding
+/// gvr_audio_source_id no longer refers to a valid sound object.
+///
+/// The following function can be used to check if a sound object is currently
+/// active:
+///
+///     bool gvr_audio_is_sound_playing(const gvr_audio_context* api,
+///                                     gvr_audio_source_id source_id);
+///
+/// **Rendering of ambisonic soundfields**
+///
+/// The GVR Audio System also provides the user with the ability to play back
+/// ambisonic soundfields. Ambisonic soundfields are captured or pre-rendered
+/// 360 degree recordings. It is best to think of them as equivalent to 360
+/// degree video. While they envelop and surround the listener, they only react
+/// to the listener's rotational movement. That is, one cannot walk towards
+/// features in the soundfield. Soundfields are ideal for accompanying 360
+/// degree video playback, for introducing background and environmental effects
+/// such as rain or crowd noise, or even for pre baking 3D audio to reduce
+/// rendering costs.  The GVR Audio System supports full 3D First Order
+/// Ambisonic recordings using ACN channel ordering and SN3D normalization. For
+/// more information please see our Spatial Audio specification at:
+/// https://github.com/google/spatial-media/blob/master/docs/spatial-audio-rfc.md#semantics
+///
+/// Note that Soundfield playback is directly streamed from the sound file and
+/// no sound file preloading is needed.
+///
+/// To obtain a soundfield handler, call:
+///
+///     gvr_audio_source_id gvr_audio_create_soundfield(gvr_audio_context* api,
+///                                                    const char* filename);
+///
+/// This returns a gvr_audio_source_id handle that allows the user to begin
+/// playback of the soundfield, to alter the soundfield’s volume or to stop
+/// soundfield playback and as such destroy the object. These actions can be
+/// achieved with calls to the following functions:
+///
+///     void gvr_audio_play_sound(gvr_audio_context* api,
+///                               gvr_audio_source_id source_id,
+///                               bool looping_enabled);
+///
+///     void gvr_audio_set_sound_volume(gvr_audio_context* api,
+///                                     gvr_audio_source_id source_id,
+///                                     float volume);
+///
+///     void gvr_audio_stop_sound(gvr_audio_context* api,
+///                               gvr_audio_source_id source_id);
+///
+/// Ambisonic soundfields can also be rotated about the listener's head in order
+/// to align the components of the soundfield with the visuals of the game/app.
+///
+/// void gvr_audio_set_soundfield_rotation(gvr_audio_context* api,
+///                                        gvr_audio_source_id soundfield_id,
+///                                        const gvr_quatf&
+///                                        soundfield_rotation);
+///
+/// **Direct Playback of Stereo or Mono Sounds**
+///
+/// The GVR Audio System allows the direct non-spatialized playback of both
+/// stereo and mono audio. Such audio is often used for music or sound effects
+/// that should not be spatialized.
+///
+/// A stereo sound can be created with a call to:
+///
+/// gvr_audio_source_id gvr_audio_create_stereo_sound(gvr_audio_context* api,
+///                                                  const char* filename);
+///
+/// **Paused Sounds and Stopped Sounds**
+///
+/// When using sound sources of any of the above types, the user can ensure that
+/// the given source is currently playing before calling.
+///
+/// bool gvr_audio_is_sound_playing(gvr_audio_source_id source_id);
+///
+/// This method will return false if the source has been either paused or
+/// stopped, and true if the source is currently playing.
+///
+/// Once one is finished with a Sound Object and wish to remove it, a call can
+/// be placed to:
+///
+/// void gvr_audio_stop_sound(gvr_audio_source_id source_id);
+///
+/// Once a source has been stopped it is destroyed and the corresponding
+/// gvr_audio_source_id will be invalid. Sources which have been played with the
+/// |looping_enabled| parameter disabled will also be destroyed once playback
+/// of the full audio clip has completed.
+///
+/// To check whether a given gvr_audio_source_id corresponds to a valid source
+/// which exists and is in a playable state, a call can be made to:
+///
+/// bool gvr_audio_is_source_id_valid(gvr_audio_source_id source_id);
+///
+/// By using this pair of methods a user can differentiate between sources which
+/// have been paused and those which have ceased.
+///
+/// **Room effects**
+///
+/// The GVR Audio System provides a powerful reverb engine which can be used to
+/// create customized room effects by specifying the size of a room and a
+/// material for each surface of the room from the gvr_audio_material_name enum.
+/// Each of these surface materials has unique absorption properties which
+/// differ with frequency. The room created will be centered around the
+/// listener. Note that the Google VR Audio System uses meters as the unit of
+/// distance throughout.
+///
+/// The following methods are used to control room effects:
+///
+///     void gvr_audio_enable_room(gvr_audio_context* api, bool enable);
+///
+/// enables or disables room effects with smooth transitions.
+///
+/// and
+///
+///     void
+///     gvr_audio_set_room_properties(gvr_audio_context* api, float size_x,
+///                                   float size_y, float size_z,
+///                                   gvr_audio_material_name wall_material,
+///                                   gvr_audio_material_name ceiling_material,
+///                                   gvr_audio_material_name floor_material);
+///
+/// allows the user to describe the room based on its dimensions and its surface
+/// properties. For example, one can expect very large rooms to be more
+/// reverberant than smaller rooms, and a room with with hard surface materials
+/// such as brick to be more reverberant than one with soft absorbent materials
+/// such as heavy curtains on every surface.
+///
+/// Note that when a sound source is located outside of the listener's room,
+/// it will sound different from sources located within the room due to
+/// attenuation of both the direct sound and the reverb on that source. Sources
+/// located far outside of the listener's room will not be audible to the
+/// listener.
+///
+/// The following method can be used to subtly adjust the reverb in a room by
+/// changing the gain/attenuation on the reverb, setting a multiplier on the
+/// reverberation time to control the reverb's length, or adjusting the balance
+/// between the low and high frequency components of the reverb.
+///
+/// void gvr_audio_set_room_reverb_adjustments(gvr_audio_context* api,
+///                                            float gain,
+///                                            float time_adjust,
+///                                            float brightness_adjust);
+///
+/// If you are writing C++ code, you might prefer to use the C++ wrapper
+/// rather than implement this C API directly.
+///
+/// **Example usage (C++ API)**
+///
+/// Construction:
+///
+///     std::unique_ptr<gvr::AudioApi> gvr_audio_api(new gvr::AudioApi);
+///     gvr_audio_api->Init(GVR_AUDIO_RENDERING_BINAURAL_HIGH_QUALITY);
+///
+/// Update head rotation in DrawFrame():
+///
+///     head_pose_ = gvr_api_->GetHeadSpaceFromStartSpaceRotation(target_time);
+///     gvr_audio_api_->SetHeadPose(head_pose_);
+///     gvr_audio_api_->Update();
+///
+/// Preload sound file, create sound handle and start playback:
+///
+///     gvr_audio_api->PreloadSoundfile(kSoundFile);
+///     AudioSourceId source_id =
+///                   gvr_audio_api_->CreateSoundObject("sound.wav");
+///     gvr_audio_api->SetSoundObjectPosition(source_id,
+///                                           position_x,
+///                                           position_y,
+///                                           position_z);
+///     gvr_audio_api->PlaySound(source_id, true /* looped playback */);
+///
+
+/// @{
+
+typedef struct gvr_audio_context_ gvr_audio_context;
+
+/// Creates and initializes a gvr_audio_context. This call also initializes
+/// the audio interface and starts the audio engine. Note that the returned
+/// instance must be deleted with gvr_audio_destroy.
+///
+#ifdef __ANDROID__
+/// @param env The JNI Env associated with the current thread.
+/// @param android_context The Android application context. This must be the
+///     application context, NOT an Activity context (Note: from any Android
+///     Activity in your app, you can call getApplicationContext() to
+///     retrieve the application context).
+/// @param class_loader The class loader to use when loading Java
+///     classes. This must be your app's main class loader (usually
+///     accessible through activity.getClassLoader() on any of your Activities).
+/// @param rendering_mode The gvr_audio_rendering_mode value which determines
+///     the rendering configuration preset. This is passed as an int32_t to
+///     ensure API compatibility.
+/// @return gvr_audio_context instance.
+gvr_audio_context* gvr_audio_create(JNIEnv* env, jobject android_context,
+                                    jobject class_loader,
+                                    int32_t rendering_mode);
+#else
+/// @param rendering_mode The gvr_audio_rendering_mode value which determines
+///     the rendering configuration preset. This is passed as an int32_t to
+///     ensure API compatibility.
+/// @return gvr_audio_context instance.
+gvr_audio_context* gvr_audio_create(int32_t rendering_mode);
+#endif  // #ifdef __ANDROID__
+
+/// Destroys a gvr_audio_context that was previously created with
+/// gvr_audio_create or gvr_audio_create_android.
+///
+/// @param api Pointer to a pointer to a gvr_audio_context. The pointer
+///     will be set to NULL after destruction.
+void gvr_audio_destroy(gvr_audio_context** api);
+
+/// Resumes the VR Audio system.
+/// Call this when your app/game loses focus.
+/// Calling this when not paused is a no-op.
+/// Thread-safe (call from any thread).
+///
+/// @param api Pointer to a gvr_audio_context.
+void gvr_audio_resume(gvr_audio_context* api);
+
+/// Pauses the VR Audio system.
+/// Calling this when already paused is a no-op.
+/// Thread-safe (call from any thread).
+///
+/// @param api Pointer to a gvr_audio_context.
+void gvr_audio_pause(gvr_audio_context* api);
+
+/// This method must be called from the main thread at a regular rate. It is
+/// used to execute background operations outside of the audio thread.
+///
+/// @param api Pointer to a gvr_audio_context.
+void gvr_audio_update(gvr_audio_context* api);
+
+/// Preloads a local sound file. Note that the local file access method
+/// depends on the target platform.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param filename Name of the file, used as identifier.
+/// @return True on success or if file has already been preloaded.
+bool gvr_audio_preload_soundfile(gvr_audio_context* api, const char* filename);
+
+/// Unloads a previously preloaded sample from memory. Note that if the sample
+/// is currently used, the memory is freed at the moment playback stops.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param filename Name of the file, used as identifier.
+void gvr_audio_unload_soundfile(gvr_audio_context* api, const char* filename);
+
+/// Returns a new sound object. Note that the sample should only contain a
+/// single audio channel (stereo sources are automatically downmixed to mono).
+/// The handle automatically destroys itself at the moment the sound playback
+/// has stopped.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param filename The path/name of the file to be played.
+/// @return Id of new sound object. Returns kInvalidId if the sound file has not
+///     been preloaded or if the number of input channels is > 1.
+gvr_audio_source_id gvr_audio_create_sound_object(gvr_audio_context* api,
+                                                 const char* filename);
+
+/// Returns a new ambisonic sound field. Note that the sample needs to be
+/// preloaded and must have 4 separate audio channels. The handle automatically
+/// destroys itself at the moment the sound playback has stopped.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param filename The path/name of the file to be played.
+/// @return Id of new soundfield. Returns kInvalidId if the sound file has not
+///     been preloaded or if the number of input channels does not match that
+///     required.
+gvr_audio_source_id gvr_audio_create_soundfield(gvr_audio_context* api,
+                                               const char* filename);
+
+/// Returns a new stereo non-spatialized source, which directly plays back mono
+/// or stereo audio. Note the sample needs to be preloaded and may contain only
+/// one (mono) or two (stereo) audio channels.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param filename The path/name of the file to be played..
+/// @return Id of new stereo non-spatialized source. Returns kInvalidId if the
+///     sound file has not been preloaded or if the number of input channels is
+///     > 2;
+gvr_audio_source_id gvr_audio_create_stereo_sound(gvr_audio_context* api,
+                                                 const char* filename);
+
+/// Starts the playback of a sound.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param source_id Id of the audio source to be stopped.
+/// @param looping_enabled Enables looped audio playback.
+void gvr_audio_play_sound(gvr_audio_context* api, gvr_audio_source_id source_id,
+                          bool looping_enabled);
+
+/// Pauses the playback of a sound.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param source_id Id of the audio source to be paused.
+void gvr_audio_pause_sound(gvr_audio_context* api,
+                           gvr_audio_source_id source_id);
+
+/// Resumes the playback of a sound.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param source_id Id of the audio source to be resumed.
+void gvr_audio_resume_sound(gvr_audio_context* api,
+                            gvr_audio_source_id source_id);
+
+/// Stops the playback of a sound and destroys the corresponding sound object
+/// or Soundfield.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param source_id Id of the audio source to be stopped.
+void gvr_audio_stop_sound(gvr_audio_context* api,
+                          gvr_audio_source_id source_id);
+
+/// Checks if a sound is playing.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param source_id Id of the audio source to be checked.
+/// @return True if the sound is being played.
+bool gvr_audio_is_sound_playing(const gvr_audio_context* api,
+                                gvr_audio_source_id source_id);
+
+/// Checks if a |source_id| is valid, and that the corresponding source is in a
+/// playable state. Sources that have been stopped will be reported as invalid.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param source_id Id of the audio source to be checked.
+/// @return True if the source exists and is in a playable state.
+bool gvr_audio_is_source_id_valid(const gvr_audio_context* api,
+                                  gvr_audio_source_id source_id);
+
+/// Repositions an existing sound object.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param sound_object_id Id of the sound object to be moved.
+/// @param x X coordinate the sound will be placed at.
+/// @param y Y coordinate the sound will be placed at.
+/// @param z Z coordinate the sound will be placed at.
+void gvr_audio_set_sound_object_position(gvr_audio_context* api,
+                                         gvr_audio_source_id sound_object_id,
+                                         float x, float y, float z);
+
+/// Sets the given ambisonic soundfields's rotation.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param soundfield_id Id of the soundfield source to be rotated.
+/// @param soundfield_rotation Quaternion representing the soundfield rotation.
+void gvr_audio_set_soundfield_rotation(gvr_audio_context* api,
+                                       gvr_audio_source_id soundfield_id,
+                                       const gvr_quatf& soundfield_rotation);
+
+/// Sets the given sound object source's distance attenuation method with
+/// minimum and maximum distances. Maximum distance must be greater than the
+/// minimum distance for the method to be set.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param sound_object_id Id of sound object source.
+/// @param rolloff_model Linear or logarithmic distance rolloff models. Note
+///     setting the rolloff model to |GVR_AUDIO_ROLLOFF_NONE| will allow
+///     distance attenuation values to be set manually.
+/// @param min_distance Minimum distance to apply distance attenuation method.
+/// @param max_distance Maximum distance to apply distance attenuation method.
+void gvr_audio_set_sound_object_distance_rolloff_model(
+    gvr_audio_context* api, gvr_audio_source_id sound_object_id,
+    int32_t rolloff_model, float min_distance, float max_distance);
+
+
+/// Changes the master volume.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param volume Volume value. Should range from 0 (mute) to 1 (max).
+void gvr_audio_set_master_volume(gvr_audio_context* api, float volume);
+
+/// Changes the volume of an existing sound.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param source_id Id of the audio source to be modified.
+/// @param volume Volume value. Should range from 0 (mute) to 1 (max).
+void gvr_audio_set_sound_volume(gvr_audio_context* api,
+                                gvr_audio_source_id source_id, float volume);
+
+/// Sets the head pose from a matrix representation of the same.
+///
+/// @param api Pointer to a gvr_audio_context on which to set the pose.
+/// @param head_pose_matrix Matrix representing the head transform to be set.
+void gvr_audio_set_head_pose(gvr_audio_context* api,
+                             const gvr_mat4f& head_pose_matrix);
+
+/// Turns on/off the room reverberation effect.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param enable True to enable room effect.
+void gvr_audio_enable_room(gvr_audio_context* api, bool enable);
+
+/// Sets the room properties describing the dimensions and surface materials of
+/// a given room.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param size_x Dimension along X axis.
+/// @param size_y Dimension along Y axis.
+/// @param size_z Dimension along Z axis.
+/// @param wall_material Surface gvr_audio_material_type for the four walls.
+/// @param ceiling_material Surface gvr_audio_material_type for the ceiling.
+/// @param floor_material Surface gvr_audio_material_type for the floor.
+void gvr_audio_set_room_properties(gvr_audio_context* api, float size_x,
+                                   float size_y, float size_z,
+                                   int32_t wall_material,
+                                   int32_t ceiling_material,
+                                   int32_t floor_material);
+
+/// Adjusts the properties of the current reverb, allowing changes to the
+/// reverb's gain, duration and low/high frequency balance.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param gain Reverb volume (linear) adjustment in range [0, 1] for
+///     attenuation, range [1, inf) for gain boost.
+/// @param time_adjust Reverb time adjustment multiplier to scale the
+///     reverberation tail length. This value should be >= 0.
+/// @param brightness_adjust Reverb brightness adjustment that controls the
+///     reverberation ratio across low and high frequency bands.
+void gvr_audio_set_room_reverb_adjustments(gvr_audio_context* api, float gain,
+                                           float time_adjust,
+                                           float brightness_adjust);
+
+/// Enables the stereo speaker mode. It enforces stereo-panning when headphones
+/// are *not* plugged into the phone. This helps to avoid HRTF-based coloring
+/// effects and reduces computational complexity when speaker playback is
+/// active. By default the stereo speaker mode optimization is disabled.
+///
+/// @param api Pointer to a gvr_audio_context.
+/// @param enable True to enable the stereo speaker mode.
+void gvr_audio_enable_stereo_speaker_mode(gvr_audio_context* api, bool enable);
+
+/// @}
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+// Convenience C++ wrapper.
+#if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+
+#include <memory>
+#include <string>
+
+namespace gvr {
+/// This is a convenience C++ wrapper for the Audio C API.
+///
+/// This wrapper strategy prevents ABI compatibility issues between compilers
+/// by ensuring that the interface between client code and the implementation
+/// code in libgvr.so is a pure C interface. The translation from C++ calls
+/// to C calls provided by this wrapper runs entirely in the client's binary
+/// and is compiled by the client's compiler.
+///
+/// Methods in this class are only documented insofar as the C++ wrapping logic
+/// is concerned; for information about the method itself, please refer to the
+/// corresponding function in the C API.
+///
+///
+/// THREADING: this class is thread-safe and reentrant after initialized
+/// with Init().
+class AudioApi {
+ public:
+  /// Creates an (uninitialized) ControllerApi object. You must initialize
+  /// it by calling Init() before interacting with it.
+  AudioApi() : context_(nullptr) {}
+
+  ~AudioApi() {
+    if (context_) {
+      gvr_audio_destroy(&context_);
+    }
+  }
+
+/// Creates and initializes a gvr_audio_context.
+/// For more information, see gvr_audio_create().
+#ifdef __ANDROID__
+  bool Init(JNIEnv* env, jobject android_context, jobject class_loader,
+            AudioRenderingMode rendering_mode) {
+    context_ =
+        gvr_audio_create(env, android_context, class_loader, rendering_mode);
+    return context_ != nullptr;
+  }
+#else
+  bool Init(AudioRenderingMode rendering_mode) {
+    context_ = gvr_audio_create(rendering_mode);
+    return context_ != nullptr;
+  }
+#endif  // #ifdef __ANDROID__
+
+  /// Pauses the audio engine.
+  /// For more information, see gvr_audio_pause().
+  void Pause() { gvr_audio_pause(context_); }
+
+  /// Resumes the audio engine.
+  /// For more information, see gvr_audio_resume().
+  void Resume() { gvr_audio_resume(context_); }
+
+  /// For more information, see gvr_audio_update().
+  void Update() { gvr_audio_update(context_); }
+
+  /// Preloads a local sound file.
+  /// For more information, see gvr_audio_preload_soundfile().
+  bool PreloadSoundfile(const std::string& filename) {
+    return gvr_audio_preload_soundfile(context_, filename.c_str());
+  }
+
+  /// Unloads a previously preloaded sample from memory.
+  /// For more information, see gvr_audio_preload_soundfile().
+  void UnloadSoundfile(const std::string& filename) {
+    gvr_audio_unload_soundfile(context_, filename.c_str());
+  }
+
+  /// Returns a new sound object.
+  /// For more information, see gvr_audio_create_sound_object().
+  AudioSourceId CreateSoundObject(const std::string& filename) {
+    return gvr_audio_create_sound_object(context_, filename.c_str());
+  }
+
+  /// Returns a new sound field.
+  /// For more information, see gvr_audio_create_soundfield().
+  AudioSourceId CreateSoundfield(const std::string& filename) {
+    return gvr_audio_create_soundfield(context_, filename.c_str());
+  }
+
+  /// Returns a new stereo soound.
+  /// For more information, see gvr_audio_create_stereo_sound().
+  AudioSourceId CreateStereoSound(const std::string& filename) {
+    return gvr_audio_create_stereo_sound(context_, filename.c_str());
+  }
+
+  /// Starts the playback of a sound.
+  /// For more information, see gvr_audio_play_sound().
+  void PlaySound(AudioSourceId source_id, bool looping_enabled) {
+    gvr_audio_play_sound(context_, source_id, looping_enabled);
+  }
+
+  /// Pauses the playback of a sound.
+  /// For more information, see gvr_audio_pause_sound().
+  void PauseSound(AudioSourceId source_id) {
+    gvr_audio_pause_sound(context_, source_id);
+  }
+
+  /// Resumes the playback of a sound.
+  /// For more information, see gvr_audio_resume_sound().
+  void ResumeSound(AudioSourceId source_id) {
+    gvr_audio_resume_sound(context_, source_id);
+  }
+
+  /// Stops the playback of a sound.
+  /// For more information, see gvr_audio_stop_sound().
+  void StopSound(AudioSourceId source_id) {
+    gvr_audio_stop_sound(context_, source_id);
+  }
+
+  /// Checks if a sound is playing.
+  /// For more information, see gvr_audio_is_sound_playing().
+  bool IsSoundPlaying(AudioSourceId source_id) const {
+    return gvr_audio_is_sound_playing(context_, source_id);
+  }
+
+  /// Checks if a source is in a valid playable state.
+  /// For more information, see gvr_audio_is_source_id_valid().
+  bool IsSourceIdValid(AudioSourceId source_id) {
+    return gvr_audio_is_source_id_valid(context_, source_id);
+  }
+
+  /// Repositions an existing sound object.
+  /// For more information, see gvr_audio_set_sound_object_position().
+  void SetSoundObjectPosition(AudioSourceId sound_object_id, float x, float y,
+                              float z) {
+    gvr_audio_set_sound_object_position(context_, sound_object_id, x, y, z);
+  }
+
+  void SetSoundObjectDistanceRolloffModel(
+      AudioSourceId sound_object_id,
+      gvr_audio_distance_rolloff_type rolloff_model, float min_distance,
+      float max_distance) {
+    gvr_audio_set_sound_object_distance_rolloff_model(
+        context_, sound_object_id, rolloff_model, min_distance, max_distance);
+  }
+
+  /// Rotates an existing soundfield.
+  /// For more information, see gvr_audio_set_soundfield_rotation().
+  void SetSoundfieldRotation(AudioSourceId soundfield_id,
+                             const Quatf& soundfield_rotation) {
+    gvr_audio_set_soundfield_rotation(context_, soundfield_id,
+                                      soundfield_rotation);
+  }
+
+  /// Changes the master volume.
+  /// For more information, see gvr_audio_set_master_volume().
+  void SetMasterVolume(float volume) {
+    gvr_audio_set_master_volume(context_, volume);
+  }
+
+  /// Changes the volume of an existing sound.
+  /// For more information, see gvr_audio_set_sound_volume().
+  void SetSoundVolume(AudioSourceId source_id, float volume) {
+    gvr_audio_set_sound_volume(context_, source_id, volume);
+  }
+
+  /// Sets the head position from a matrix representation.
+  /// For more information, see gvr_audio_set_head_pose().
+  void SetHeadPose(const Mat4f& head_pose_matrix) {
+    gvr_audio_set_head_pose(context_, head_pose_matrix);
+  }
+
+  /// Turns on/off the room reverberation effect.
+  /// For more information, see gvr_audio_enable_room().
+  void EnableRoom(bool enable) { gvr_audio_enable_room(context_, enable); }
+
+  /// Sets the room properties describing the dimensions and surface materials
+  /// of a given room. For more information, see
+  /// gvr_audio_set_room_properties().
+  void SetRoomProperties(float size_x, float size_y, float size_z,
+                         gvr_audio_material_type wall_material,
+                         gvr_audio_material_type ceiling_material,
+                         gvr_audio_material_type floor_material) {
+    gvr_audio_set_room_properties(context_, size_x, size_y, size_z,
+                                  wall_material, ceiling_material,
+                                  floor_material);
+  }
+
+  /// Adjusts the properties of the current reverb, allowing changes to the
+  /// reverb's gain, duration and low/high frequency balance. For more
+  /// information see gvr_audio_set_room_reverb_adjustments().
+  void SetRoomReverbAdjustments(float gain, float time_adjust,
+                                float brightness_adjust) {
+    gvr_audio_set_room_reverb_adjustments(context_, gain, time_adjust,
+                                          brightness_adjust);
+  }
+
+  /// Enables the stereo speaker mode. For more information see
+  /// gvr_audio_enable_stereo_speaker_mode().
+  void EnableStereoSpeakerMode(bool enable) {
+    gvr_audio_enable_stereo_speaker_mode(context_, enable);
+  }
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and takes ownership.
+  explicit AudioApi(gvr_audio_context* context)
+      : context_(context) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_audio_context* cobj() { return context_; }
+  const gvr_audio_context* cobj() const { return context_; }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_audio_context* Release() {
+    auto result = context_;
+    context_ = nullptr;
+    return result;
+  }
+  /// @}
+
+ private:
+  gvr_audio_context* context_;
+
+  // Disallow copy and assign:
+  AudioApi(const AudioApi&);
+  void operator=(const AudioApi&);
+};
+
+}  // namespace gvr
+#endif  // #if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+
+#endif  // VR_GVR_CAPI_INCLUDE_GVR_AUDIO_H_
diff --git a/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_controller.h b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_controller.h
new file mode 100644
index 0000000..a67502f
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_controller.h
@@ -0,0 +1,752 @@
+/* Copyright 2016 Google Inc. All rights reserved.
+ *
+ * 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 VR_GVR_CAPI_INCLUDE_GVR_CONTROLLER_H_
+#define VR_GVR_CAPI_INCLUDE_GVR_CONTROLLER_H_
+
+#ifdef __ANDROID__
+#include <jni.h>
+#endif
+
+#include <stdint.h>
+
+#include "vr/gvr/capi/include/gvr.h"
+#include "vr/gvr/capi/include/gvr_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @defgroup Controller Controller API
+/// @brief This is the Controller C API, which allows access to a VR controller.
+///
+/// If you are writing C++ code, you might prefer to use the C++ wrapper rather
+/// than implement this C API directly.
+///
+/// Typical initialization example:
+///
+///     // Get your gvr_context* pointer from GvrLayout:
+///     gvr_context* gvr = ......;  // (get from GvrLayout in Java)
+///
+///     // Set up the API features:
+///     int32_t options = gvr_controller_get_default_options();
+///
+///     // Enable non-default options, if needed:
+///     options |= GVR_CONTROLLER_ENABLE_GYRO | GVR_CONTROLLER_ENABLE_ACCEL;
+///
+///     // Create and init:
+///     gvr_controller_context* context =
+///         gvr_controller_create_and_init(options, gvr);
+///
+///     // Check if init was successful.
+///     if (!context) {
+///       // Handle error.
+///       return;
+///     }
+///
+///     gvr_controller_state* state = gvr_controller_state_create();
+///
+///     // Resume:
+///     gvr_controller_resume(api);
+///
+/// Usage:
+///
+///     void DrawFrame() {
+///       gvr_controller_state_update(context, 0, state);
+///       // ... process controller state ...
+///     }
+///
+///     // When your application gets paused:
+///     void OnPause() {
+///       gvr_controller_pause(context);
+///     }
+///
+///     // When your application gets resumed:
+///     void OnResume() {
+///       gvr_controller_resume(context);
+///     }
+///
+/// To conserve battery, be sure to call gvr_controller_pause and
+/// gvr_controller_resume when your app gets paused and resumed, respectively.
+///
+/// THREADING: unless otherwise noted, all functions are thread-safe, so
+/// you can operate on the same gvr_controller_context object from multiple
+/// threads.
+/// @{
+
+/// Represents a Daydream Controller API object, used to invoke the
+/// Daydream Controller API.
+typedef struct gvr_controller_context_ gvr_controller_context;
+
+/// Returns the default features for the controller API.
+///
+/// @return The set of default features, as bit flags (an OR'ed combination of
+///     the GVR_CONTROLLER_ENABLE_* feature flags).
+int32_t gvr_controller_get_default_options();
+
+/// Creates and initializes a gvr_controller_context instance which can be used
+/// to invoke the Daydream Controller API functions. Important: after creation
+/// the API will be in the paused state (the controller will be inactive).
+/// You must call gvr_controller_resume() explicitly (typically, in your Android
+/// app's onResume() callback).
+///
+/// @param options The API options. To get the defaults, use
+///     gvr_controller_get_default_options().
+/// @param context The GVR Context object to sync with (optional).
+///     This can be nullptr. If provided, the context's state will
+///     be synchronized with the controller's state where possible. For
+///     example, when the user recenters the controller, this will
+///     automatically recenter head tracking as well.
+///     WARNING: the caller is responsible for making sure the pointer
+///     remains valid for the lifetime of this object.
+/// @return A pointer to the initialized API, or NULL if an error occurs.
+gvr_controller_context* gvr_controller_create_and_init(
+    int32_t options, gvr_context* context);
+
+#ifdef __ANDROID__
+/// Creates and initializes a gvr_controller_context instance with an explicit
+/// Android context and class loader.
+///
+/// @param env The JNI Env associated with the current thread.
+/// @param android_context The Android application context. This must be the
+///     application context, NOT an Activity context (Note: from any Android
+///     Activity in your app, you can call getApplicationContext() to
+///     retrieve the application context).
+/// @param class_loader The class loader to use when loading Java
+///     classes. This must be your app's main class loader (usually
+///     accessible through activity.getClassLoader() on any of your Activities).
+/// @param options The API options. To get the defaults, use
+///     gvr_controller_get_default_options().
+/// @param context The GVR Context object to sync with (optional).
+///     This can be nullptr. If provided, the context's state will
+///     be synchronized with the controller's state where possible. For
+///     example, when the user recenters the controller, this will
+///     automatically recenter head tracking as well.
+///     WARNING: the caller is responsible for making sure the pointer
+///     remains valid for the lifetime of this object.
+/// @return A pointer to the initialized API, or NULL if an error occurs.
+gvr_controller_context* gvr_controller_create_and_init_android(
+    JNIEnv *env, jobject android_context, jobject class_loader,
+    int32_t options, gvr_context* context);
+#endif  // #ifdef __ANDROID__
+
+/// Destroys a gvr_controller_context that was previously created with
+/// gvr_controller_init.
+///
+/// @param api Pointer to a pointer to a gvr_controller_context. The pointer
+///     will be set to NULL after destruction.
+void gvr_controller_destroy(gvr_controller_context** api);
+
+/// Pauses the controller, possibly releasing resources.
+/// Call this when your app/game loses focus.
+/// Calling this when already paused is a no-op.
+/// Thread-safe (call from any thread).
+///
+/// @param api Pointer to a pointer to a gvr_controller_context.
+void gvr_controller_pause(gvr_controller_context* api);
+
+/// Resumes the controller. Call this when your app/game regains focus.
+/// Calling this when already resumed is a no-op.
+/// Thread-safe (call from any thread).
+///
+/// @param api Pointer to a pointer to a gvr_controller_context.
+void gvr_controller_resume(gvr_controller_context* api);
+
+/// Convenience to convert an API status code to string. The returned pointer
+/// is static and valid throughout the lifetime of the application.
+///
+/// @param status The gvr_controller_api_status to convert to string.
+/// @return A pointer to a string that describes the value.
+const char* gvr_controller_api_status_to_string(int32_t status);
+
+/// Convenience to convert an connection state to string. The returned pointer
+/// is static and valid throughout the lifetime of the application.
+///
+/// @param state The state to convert to string.
+/// @return A pointer to a string that describes the value.
+const char* gvr_controller_connection_state_to_string(int32_t state);
+
+/// Convenience to convert an connection state to string. The returned pointer
+/// is static and valid throughout the lifetime of the application.
+///
+/// @param button The gvr_controller_button to convert to string.
+/// @return A pointer to a string that describes the value.
+const char* gvr_controller_button_to_string(int32_t button);
+
+/// Creates a gvr_controller_state.
+gvr_controller_state* gvr_controller_state_create();
+
+/// Destroys a a gvr_controller_state that was previously created with
+/// gvr_controller_state_create.
+void gvr_controller_state_destroy(gvr_controller_state** state);
+
+/// Updates the controller state. Reading the controller state is not a
+/// const getter: it has side-effects. In particular, some of the
+/// gvr_controller_state fields (the ones documented as "transient") represent
+/// one-time events and will be true for only one read operation, and false
+/// in subsequente reads.
+///
+/// @param api Pointer to a pointer to a gvr_controller_context.
+/// @param flags Optional flags reserved for future use. A value of 0 should be
+///     used until corresponding flag attributes are defined and documented.
+/// @param out_state A pointer where the controller's state
+///     is to be written. This must have been allocated with
+///     gvr_controller_state_create().
+void gvr_controller_state_update(gvr_controller_context* api, int32_t flags,
+                                 gvr_controller_state* out_state);
+
+/// Gets the API status of the controller state. Returns one of the
+/// gvr_controller_api_status variants, but returned as an int32_t for ABI
+/// compatibility.
+int32_t gvr_controller_state_get_api_status(const gvr_controller_state* state);
+
+/// Gets the connection state of the controller. Returns one of the
+/// gvr_controller_connection_state variants, but returned as an int32_t for ABI
+/// compatibility.
+int32_t gvr_controller_state_get_connection_state(
+    const gvr_controller_state* state);
+
+/// Returns the current controller orientation, in Start Space. The Start Space
+/// is the same space as the headset space and has these three axes
+/// (right-handed):
+///
+/// * The positive X axis points to the right.
+/// * The positive Y axis points upwards.
+/// * The positive Z axis points backwards.
+///
+/// The definition of "backwards" and "to the right" are based on the position
+/// of the controller when tracking started. For Daydream, this is when the
+/// controller was first connected in the "Connect your Controller" screen
+/// which is shown when the user enters VR.
+///
+/// The definition of "upwards" is given by gravity (away from the pull of
+/// gravity). This API may not work in environments without gravity, such
+/// as space stations or near the center of the Earth.
+///
+/// Since the coordinate system is right-handed, rotations are given by the
+/// right-hand rule. For example, rotating the controller counter-clockwise
+/// on a table top as seen from above means a positive rotation about the
+/// Y axis, while clockwise would mean negative.
+///
+/// Note that this is the Start Space for the *controller*, which initially
+/// coincides with the Start Space for the headset, but they may diverge over
+/// time due to controller/headset drift. A recentering operation will bring
+/// the two spaces back into sync.
+///
+/// Remember that a quaternion expresses a rotation. Given a rotation of theta
+/// radians about the (x, y, z) axis, the corresponding quaternion (in
+/// xyzw order) is:
+///
+///     (x * sin(theta/2), y * sin(theta/2), z * sin(theta/2), cos(theta/2))
+///
+/// Here are some examples of orientations of the controller and their
+/// corresponding quaternions, all given in xyzw order:
+///
+///   * Initial pose, pointing forward and lying flat on a surface: identity
+///     quaternion (0, 0, 0, 1). Corresponds to "no rotation".
+///
+///   * Flat on table, rotated 90 degrees counter-clockwise: (0, 0.7071, 0,
+///     0.7071). Corresponds to a +90 degree rotation about the Y axis.
+///
+///   * Flat on table, rotated 90 degrees clockwise: (0, -0.7071, 0, 0.7071).
+///     Corresponds to a -90 degree rotation about the Y axis.
+///
+///   * Flat on table, rotated 180 degrees (pointing backwards): (0, 1, 0, 0).
+///     Corresponds to a 180 degree rotation about the Y axis.
+///
+///   * Pointing straight up towards the sky: (0.7071, 0, 0, 0.7071).
+///     Corresponds to a +90 degree rotation about the X axis.
+///
+///   * Pointing straight down towards the ground: (-0.7071, 0, 0, 0.7071).
+///     Corresponds to a -90 degree rotation about the X axis.
+///
+///   * Banked 90 degrees to the left: (0, 0, 0.7071, 0.7071). Corresponds
+///     to a +90 degree rotation about the Z axis.
+///
+///   * Banked 90 degrees to the right: (0, 0, -0.7071, 0.7071). Corresponds
+///     to a -90 degree rotation about the Z axis.
+gvr_quatf gvr_controller_state_get_orientation(
+    const gvr_controller_state* state);
+
+/// Returns the current controller gyro reading, in Start Space.
+///
+/// The gyro measures the controller's angular speed in radians per second.
+/// Note that this is an angular *speed*, so it reflects how fast the
+/// controller's orientation is changing with time.
+/// In particular, if the controller is not being rotated, the angular speed
+/// will be zero on all axes, regardless of the current pose.
+///
+/// The axes are in the controller's device space. Specifically:
+///
+///    * The X axis points to the right of the controller.
+///    * The Y axis points upwards perpendicular to the top surface of the
+///      controller.
+///    * The Z axis points backwards along the body of the controller,
+///      towards its rear, where the charging port is.
+///
+/// As usual in a right-handed coordinate system, the sign of the angular
+/// velocity is given by the right-hand rule. So, for example:
+///
+///    * If the controller is flat on a table top spinning counter-clockwise
+///      as seen from above, you will read a positive angular velocity
+///      about the Y axis. Clockwise would be negative.
+///    * If the controller is initially pointing forward and lying flat and
+///      is then gradually angled up so that its tip points towards the sky,
+///      it will report a positive angular velocity about the X axis during
+///      that motion. Likewise, angling it down will report a negative angular
+///      velocity about the X axis.
+///    * If the controller is banked (rolled) to the right, this will
+///      report a negative angular velocity about the Z axis during the
+///      motion (remember the Z axis points backwards along the controller).
+///      Banking to the left will report a positive angular velocity about
+///      the Z axis.
+gvr_vec3f gvr_controller_state_get_gyro(const gvr_controller_state* state);
+
+/// Current (latest) controller accelerometer reading, in Start Space.
+///
+/// The accelerometer indicates the direction in which the controller feels
+/// an acceleration, including gravity. The reading is given in meters
+/// per second squared (m/s^2). The axes are the same as for the gyro.
+/// To have an intuition for the signs used in the accelerometer, it is useful
+/// to imagine that, when at rest, the controller is being "pushed" by a
+/// force opposite to gravity. It is as if, by the equivalency princle, it were
+/// on a frame of reference that is accelerating in the opposite direction to
+/// gravity. For example:
+///
+///   * If the controller is lying flat on a table top, it will read a positive
+///     acceleration of about 9.8 m/s^2 along the Y axis, corresponding to
+///     the acceleration of gravity (as if the table were pushing the controller
+///     upwards at 9.8 m/s^2 to counteract gravity).
+///   * If, in that situation, the controller is now accelerated upwards at
+///     3.0 m/s^2, then the reading will be 12.8 m/s^2 along the Y axis,
+///     since the controller will now feel a stronger acceleration corresponding
+///     to the 9.8 m/s^2 plus the upwards push of 3.0 m/s^2.
+///   * If, the controller is accelerated downwards at 5.0 m/s^2, then the
+///     reading will now be 4.8 m/s^2 along the Y axis, since the controller
+///     will now feel a weaker acceleration (as the acceleration is giving in
+///     to gravity).
+///   * If you were to give in to gravity completely, letting the controller
+///     free fall towards the ground, it will read 0 on all axes, as there
+///     will be no force acting on the controller. (Please do not put your
+///     controller in a free-fall situation. This is just a theoretical
+///     example.)
+gvr_vec3f gvr_controller_state_get_accel(const gvr_controller_state* state);
+
+/// Returns whether the user is touching the touchpad.
+bool gvr_controller_state_is_touching(const gvr_controller_state* state);
+
+/// If the user is touching the touchpad, this returns the touch position in
+/// normalized coordinates, where (0,0) is the top-left of the touchpad
+/// and (1,1) is the bottom right. If the user is not touching the touchpad,
+/// then this is the position of the last touch.
+gvr_vec2f gvr_controller_state_get_touch_pos(const gvr_controller_state* state);
+
+/// Returns true if user just started touching touchpad (this is a transient
+/// event:
+/// it is true for only one frame after the event).
+bool gvr_controller_state_get_touch_down(const gvr_controller_state* state);
+
+/// Returns true if user just stopped touching touchpad (this is a transient
+/// event:
+/// it is true for only one frame after the event).
+bool gvr_controller_state_get_touch_up(const gvr_controller_state* state);
+
+/// Returns true if a recenter operation just ended (this is a transient event:
+/// it is true only for one frame after the recenter ended). If this is
+/// true then the `orientation` field is already relative to the new center.
+bool gvr_controller_state_get_recentered(const gvr_controller_state* state);
+
+/// Returns whether the recenter flow is currently in progress.
+///
+/// @deprecated Use gvr_controller_state_get_recentered instead.
+bool gvr_controller_state_get_recentering(const gvr_controller_state* state);
+
+/// Returns whether the given button is currently pressed.
+bool gvr_controller_state_get_button_state(const gvr_controller_state* state,
+
+                                           int32_t button);
+
+/// Returns whether the given button was just pressed (transient).
+bool gvr_controller_state_get_button_down(const gvr_controller_state* state,
+                                          int32_t button);
+
+/// Returns whether the given button was just released (transient).
+bool gvr_controller_state_get_button_up(const gvr_controller_state* state,
+                                        int32_t button);
+
+/// Returns the timestamp (nanos) when the last orientation event was received.
+int64_t gvr_controller_state_get_last_orientation_timestamp(
+    const gvr_controller_state* state);
+
+/// Returns the timestamp (nanos) when the last gyro event was received.
+int64_t gvr_controller_state_get_last_gyro_timestamp(
+    const gvr_controller_state* state);
+
+/// Returns the timestamp (nanos) when the last accelerometer event was
+/// received.
+int64_t gvr_controller_state_get_last_accel_timestamp(
+    const gvr_controller_state* state);
+
+/// Returns the timestamp (nanos) when the last touch event was received.
+int64_t gvr_controller_state_get_last_touch_timestamp(
+    const gvr_controller_state* state);
+
+/// Returns the timestamp (nanos) when the last button event was received.
+int64_t gvr_controller_state_get_last_button_timestamp(
+    const gvr_controller_state* state);
+
+// Current (latest) controller simulated position for use with an elbow model.
+gvr_vec3f gvr_controller_state_get_position(const gvr_controller_state* state);
+
+// Returns the timestamp (nanos) when the last position event was received.
+int64_t gvr_controller_state_get_last_position_timestamp(
+    const gvr_controller_state* state);
+
+/// @}
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+
+// Convenience C++ wrapper.
+#if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+
+#include <memory>
+
+namespace gvr {
+/// This is a convenience C++ wrapper for the Controller C API.
+///
+/// This wrapper strategy prevents ABI compatibility issues between compilers
+/// by ensuring that the interface between client code and the implementation
+/// code in libgvr.so is a pure C interface. The translation from C++ calls
+/// to C calls provided by this wrapper runs entirely in the client's binary
+/// and is compiled by the client's compiler.
+///
+/// Methods in this class are only documented insofar as the C++ wrapping logic
+/// is concerned; for information about the method itself, please refer to the
+/// corresponding function in the C API.
+///
+/// Typical C++ initialization example:
+///
+///     std::unique_ptr<ControllerApi> controller_api(new ControllerApi);
+///
+///     // Your GVR context pointer (which can be obtained from GvrLayout)
+///     gvr_context* context = .....;  // (get it from GvrLayout)
+///
+///     // Set up the options:
+///     int32_t options = ControllerApi::DefaultOptions();
+///
+///     // Enable non-default options, if you need them:
+///     options |= GVR_CONTROLLER_ENABLE_GYRO;
+///
+///     // Init the ControllerApi object:
+///     bool success = controller_api->Init(options, context);
+///     if (!success) {
+///       // Handle failure.
+///       // Do NOT call other methods (like Resume, etc) if init failed.
+///       return;
+///     }
+///
+///     // Resume the ControllerApi (if your app is on the foreground).
+///     controller_api->Resume();
+///
+///     ControllerState state;
+///
+/// Usage example:
+///
+///     void DrawFrame() {
+///       state.Update(*controller_api);
+///       // ... process controller state ...
+///     }
+///
+///     // When your application gets paused:
+///     void OnPause() {
+///       controller_api->Pause();
+///     }
+///
+///     // When your application gets resumed:
+///     void OnResume() {
+///       controller_api->Resume();
+///     }
+///
+/// To conserve battery, be sure to call Pause() and Resume() when your app
+/// gets paused and resumed, respectively. This will allow the underlying
+/// logic to unbind from the VR service and let the controller sleep when
+/// no apps are using it.
+///
+/// THREADING: this class is thread-safe and reentrant after initialized
+/// with Init().
+class ControllerApi {
+ public:
+  /// Creates an (uninitialized) ControllerApi object. You must initialize
+  /// it by calling Init() before interacting with it.
+  ControllerApi() : context_(nullptr) {}
+
+  /// Returns the default controller options.
+  static int32_t DefaultOptions() {
+    return gvr_controller_get_default_options();
+  }
+
+  // Deprecated factory-style create method.
+  // TODO(btco): remove this once no one is using it.
+  static std::unique_ptr<ControllerApi> Create() {
+    return std::unique_ptr<ControllerApi>(new ControllerApi);
+  }
+
+  /// Initializes the controller API.
+  ///
+  /// This method must be called exactly once in the lifetime of this object.
+  /// Other methods in this class may only be called after Init() returns true.
+  /// Note: this does not cause the ControllerApi to be resumed. You must call
+  /// Resume() explicitly in order to start using the controller.
+  ///
+  /// For more information see gvr_controller_create_and_init().
+  ///
+  /// @return True if initialization was successful, false if it failed.
+  ///     Initialization may fail, for example, because invalid options were
+  ///     supplied.
+  bool Init(int32_t options, gvr_context* context) {
+    context_ = gvr_controller_create_and_init(options, context);
+    return context_ != nullptr;
+  }
+
+#ifdef __ANDROID__
+  /// Overload of Init() with explicit Android context and class loader
+  /// (for Android only). For more information, see:
+  /// gvr_controller_create_and_init_android().
+  bool Init(JNIEnv *env, jobject android_context, jobject class_loader,
+            int32_t options, gvr_context* context) {
+    context_ = gvr_controller_create_and_init_android(
+        env, android_context, class_loader, options, context);
+    return context_ != nullptr;
+  }
+#endif  // #ifdef __ANDROID__
+
+  /// Convenience overload that calls Init without a gvr_context.
+  // TODO(btco): remove once it is no longer being used.
+  bool Init(int32_t options) {
+    return Init(options, nullptr);
+  }
+
+  /// Pauses the controller.
+  /// For more information, see gvr_controller_pause().
+  void Pause() {
+    gvr_controller_pause(context_);
+  }
+
+  /// Resumes the controller.
+  /// For more information, see gvr_controller_resume().
+  void Resume() {
+    gvr_controller_resume(context_);
+  }
+
+  /// Destroys this ControllerApi instance.
+  ~ControllerApi() {
+    if (context_) gvr_controller_destroy(&context_);
+  }
+
+  /// Convenience functions to convert enums to strings.
+  /// For more information, see the corresponding functions in the C API.
+  static const char* ToString(ControllerApiStatus status) {
+    return gvr_controller_api_status_to_string(status);
+  }
+
+  static const char* ToString(ControllerConnectionState state) {
+    return gvr_controller_connection_state_to_string(state);
+  }
+
+  static const char* ToString(ControllerButton button) {
+    return gvr_controller_button_to_string(button);
+  }
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and takes ownership.
+  explicit ControllerApi(gvr_controller_context* context)
+      : context_(context) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_controller_context* cobj() { return context_; }
+  const gvr_controller_context* cobj() const { return context_; }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_controller_context* release() {
+    auto result = context_;
+    context_ = nullptr;
+    return result;
+  }
+  /// @}
+
+ protected:
+  gvr_controller_context* context_;
+
+ private:
+  friend class ControllerState;
+
+  // Disallow copy and assign:
+  ControllerApi(const ControllerApi&);
+  void operator=(const ControllerApi&);
+};
+
+/// Convenience C++ wrapper for the opaque gvr_controller_state type. See the
+/// gvr_controller_state functions for more information.
+class ControllerState {
+ public:
+  ControllerState() : state_(gvr_controller_state_create()) {}
+
+  ~ControllerState() {
+    if (state_) gvr_controller_state_destroy(&state_);
+  }
+
+  /// For more information, see gvr_controller_state_update().
+  void Update(const ControllerApi& api) {
+    gvr_controller_state_update(api.context_, 0, state_);
+  }
+
+  /// For more information, see gvr_controller_state_update().
+  void Update(const ControllerApi& api, int32_t flags) {
+    gvr_controller_state_update(api.context_, flags, state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_api_status().
+  ControllerApiStatus GetApiStatus() const {
+    return static_cast<ControllerApiStatus>(
+        gvr_controller_state_get_api_status(state_));
+  }
+
+  /// For more information, see gvr_controller_state_get_connection_state().
+  ControllerConnectionState GetConnectionState() const {
+    return static_cast<ControllerConnectionState>(
+        gvr_controller_state_get_connection_state(state_));
+  }
+
+  /// For more information, see gvr_controller_state_get_orientation().
+  gvr_quatf GetOrientation() const {
+    return gvr_controller_state_get_orientation(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_gyro().
+  gvr_vec3f GetGyro() const { return gvr_controller_state_get_gyro(state_); }
+
+  /// For more information, see gvr_controller_state_get_accel().
+  gvr_vec3f GetAccel() const { return gvr_controller_state_get_accel(state_); }
+
+  /// For more information, see gvr_controller_state_is_touching().
+  bool IsTouching() const { return gvr_controller_state_is_touching(state_); }
+
+  /// For more information, see gvr_controller_state_get_touch_pos().
+  gvr_vec2f GetTouchPos() const {
+    return gvr_controller_state_get_touch_pos(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_touch_down().
+  bool GetTouchDown() const {
+    return gvr_controller_state_get_touch_down(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_touch_up().
+  bool GetTouchUp() const { return gvr_controller_state_get_touch_up(state_); }
+
+  /// For more information, see gvr_controller_state_get_recentered().
+  bool GetRecentered() const {
+    return gvr_controller_state_get_recentered(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_recentering().
+  bool GetRecentering() const {
+    return gvr_controller_state_get_recentering(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_button_state().
+  bool GetButtonState(ControllerButton button) const {
+    return gvr_controller_state_get_button_state(state_, button);
+  }
+
+  /// For more information, see gvr_controller_state_get_button_down().
+  bool GetButtonDown(ControllerButton button) const {
+    return gvr_controller_state_get_button_down(state_, button);
+  }
+
+  /// For more information, see gvr_controller_state_get_button_up().
+  bool GetButtonUp(ControllerButton button) const {
+    return gvr_controller_state_get_button_up(state_, button);
+  }
+
+  /// For more information, see
+  /// gvr_controller_state_get_last_orientation_timestamp().
+  int64_t GetLastOrientationTimestamp() const {
+    return gvr_controller_state_get_last_orientation_timestamp(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_last_gyro_timestamp().
+  int64_t GetLastGyroTimestamp() const {
+    return gvr_controller_state_get_last_gyro_timestamp(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_last_accel_timestamp().
+  int64_t GetLastAccelTimestamp() const {
+    return gvr_controller_state_get_last_accel_timestamp(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_last_touch_timestamp().
+  int64_t GetLastTouchTimestamp() const {
+    return gvr_controller_state_get_last_touch_timestamp(state_);
+  }
+
+  /// For more information, see
+  /// gvr_controller_state_get_last_button_timestamp().
+  int64_t GetLastButtonTimestamp() const {
+    return gvr_controller_state_get_last_button_timestamp(state_);
+  }
+
+  /// For more information, see gvr_controller_state_get_position().
+  gvr_vec3f GetPosition() const {
+    return gvr_controller_state_get_position(state_);
+  }
+
+  /// For more information, see
+  /// gvr_controller_state_get_last_position_timestamp().
+  int64_t GetLastPositionTimestamp() const {
+    return gvr_controller_state_get_last_position_timestamp(state_);
+  }
+
+  /// @name Wrapper manipulation
+  /// @{
+  /// Creates a C++ wrapper for a C object and takes ownership.
+  explicit ControllerState(gvr_controller_state* state) : state_(state) {}
+
+  /// Returns the wrapped C object. Does not affect ownership.
+  gvr_controller_state* cobj() { return state_; }
+  const gvr_controller_state* cobj() const { return state_; }
+
+  /// Returns the wrapped C object and transfers its ownership to the caller.
+  /// The wrapper becomes invalid and should not be used.
+  gvr_controller_state* release() {
+    auto result = state_;
+    state_ = nullptr;
+    return result;
+  }
+  /// @}
+
+ private:
+  gvr_controller_state* state_;
+};
+
+}  // namespace gvr
+#endif  // #if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+
+#endif  // VR_GVR_CAPI_INCLUDE_GVR_CONTROLLER_H_
diff --git a/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_types.h b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_types.h
new file mode 100644
index 0000000..ff00863
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/include/gvr_types.h
@@ -0,0 +1,575 @@
+/* Copyright 2016 Google Inc. All rights reserved.
+ *
+ * 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 VR_GVR_CAPI_INCLUDE_GVR_TYPES_H_
+#define VR_GVR_CAPI_INCLUDE_GVR_TYPES_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @defgroup types Google VR Types
+/// @brief Various types used in the Google VR NDK.
+/// @{
+
+/// Primary context for invoking Google VR APIs.
+typedef struct gvr_context_ gvr_context;
+
+/// An enum for the left and right eye.
+typedef enum {
+  GVR_LEFT_EYE = 0,
+  GVR_RIGHT_EYE,
+  GVR_NUM_EYES
+} gvr_eye;
+
+/// The type of VR viewer.
+typedef enum {
+  /// A Cardboard-compatible viewer. A typical Cardboard viewer supports a
+  /// simple touchscreen-based trigger input mechanism. On most platforms, this
+  // is the default viewer type if no viewer has been explicitly paired.
+  GVR_VIEWER_TYPE_CARDBOARD = 0,
+  /// A Daydream-compatible viewer. A typical Daydream viewer supports 3DOF
+  /// controller input (as defined in gvr_controller.h), and is intended only
+  /// for Daydream-ready platforms. It does *not* support touchscreen-based
+  /// input unless Cardboard emulation is explicitly enabled.
+  GVR_VIEWER_TYPE_DAYDREAM = 1,
+} gvr_viewer_type;
+
+/// @}
+
+/// Version information for the Google VR API.
+typedef struct gvr_version_ {
+  int32_t major;
+  int32_t minor;
+  int32_t patch;
+} gvr_version;
+
+/// An integral 2D size. Used for render target sizes.
+typedef struct gvr_sizei {
+  int32_t width;
+  int32_t height;
+} gvr_sizei;
+
+/// An integral 2D rect. Used for window bounds in pixels.
+typedef struct gvr_recti {
+  int32_t left;
+  int32_t right;
+  int32_t bottom;
+  int32_t top;
+} gvr_recti;
+
+/// A floating point 2D rect. Used for field of view, and also for ranges
+/// in texture space. When used for a field of view, all angles are in positive
+/// degrees from the optical axis.
+typedef struct gvr_rectf {
+  float left;
+  float right;
+  float bottom;
+  float top;
+} gvr_rectf;
+
+/// A floating point 2D vector.
+typedef struct gvr_vec2f {
+  float x;
+  float y;
+} gvr_vec2f;
+
+/// A floating point 3D vector.
+typedef struct gvr_vec3f {
+  float x;
+  float y;
+  float z;
+} gvr_vec3f;
+
+/// A floating point 4x4 matrix.
+typedef struct gvr_mat4f { float m[4][4]; } gvr_mat4f;
+
+/// A floating point quaternion, in JPL format.
+/// We use this simple struct in order not to impose a dependency on a
+/// particular math library. The user of this API is free to encapsulate this
+/// into any math library they want.
+typedef struct gvr_quatf {
+  /// qx, qy, qz are the vector component.
+  float qx;
+  float qy;
+  float qz;
+  /// qw is the linelar component.
+  float qw;
+} gvr_quatf;
+
+/// A *monotonic system time* representation. On Android, this is equivalent to
+/// System.nanoTime(), or clock_gettime(CLOCK_MONOTONIC). If there is any doubt
+/// about how to get the current time for the current platform, simply use
+/// gvr_get_time_point_now().
+typedef struct gvr_clock_time_point {
+  int64_t monotonic_system_time_nanos;
+} gvr_clock_time_point;
+
+/// A structure that ties together a region of a buffer, the field of view
+/// rendered into that region and a target eye index to define part of the
+/// user's field of view. The SDK implementation uses this information to
+/// transform the images generated by the app output into the final display that
+/// is sent to the screen.
+///
+/// A set of these structures will most often be generated by the API, via
+/// gvr_get_recommended_buffer_viewports() or
+/// gvr_get_screen_buffer_viewports(). However, the client may also customize
+/// these values via gvr_buffer_viewport_list_set(), constructing a custom
+/// gvr_buffer_viewport_list for use in the distortion pass.
+typedef struct gvr_buffer_viewport_ gvr_buffer_viewport;
+
+/// List of buffer viewports that completely specifies how to transform the
+/// frame's buffers into the image displayed on the screen.
+typedef struct gvr_buffer_viewport_list_ gvr_buffer_viewport_list;
+
+/// Specification of a pixel buffer. A pixel buffer can have color, depth and
+/// stencil attachments and mostly corresponds to the OpenGL concept of a
+/// framebuffer object. However, since there can be multiple such objects for
+/// each frame, we avoid calling them "framebuffers". Pixel buffers which are
+/// part of the currently acquired frame are immutable, i.e., they cannot be
+/// resized or otherwise reconfigured.
+typedef struct gvr_buffer_spec_ gvr_buffer_spec;
+
+/// Swap chain that contains some number of frames. Frames in the swap chain
+/// can be unused, in the process of being distorted and presented on the
+/// screen, or acquired and being rendered to by the application. The swap chain
+/// ensures that the most recent available frame is always shown and that the
+/// application never has to wait to render the next frame.
+typedef struct gvr_swap_chain_ gvr_swap_chain;
+
+/// A single frame acquired from the swap chain. Each frame is composed of one
+/// or more buffers, which are then lens distorted and composited into the final
+/// output. Buffers are identified by indices that correspond to the position
+/// of their gvr_buffer_spec in the list passed when constructing the swap
+/// chain.
+typedef struct gvr_frame_ gvr_frame;
+
+/// @addtogroup types
+/// @{
+
+/// Constants that represent GVR error codes.
+typedef enum {
+  GVR_ERROR_NONE = 0,
+  GVR_ERROR_CONTROLLER_CREATE_FAILED = 2,
+  GVR_ERROR_NO_FRAME_AVAILABLE = 3,
+} gvr_error;
+
+/// Controller API options (bit flags).
+enum {
+  /// Indicates that controller orientation data should be reported.
+  GVR_CONTROLLER_ENABLE_ORIENTATION = 1 << 0,
+  /// Indicates that controller touchpad data should be reported.
+  GVR_CONTROLLER_ENABLE_TOUCH = 1 << 1,
+  /// Indicates that controller gyroscope data should be reported.
+  GVR_CONTROLLER_ENABLE_GYRO = 1 << 2,
+  /// Indicates that controller accelerometer data should be reported.
+  GVR_CONTROLLER_ENABLE_ACCEL = 1 << 3,
+  /// Indicates that controller gestures should be reported.
+  GVR_CONTROLLER_ENABLE_GESTURES = 1 << 4,
+  /// Indicates that controller pose prediction should be enabled.
+  GVR_CONTROLLER_ENABLE_POSE_PREDICTION = 1 << 5,
+  /// Indicates that controller position data should be reported.
+  GVR_CONTROLLER_ENABLE_POSITION = 1 << 6,
+};
+
+/// Constants that represent the status of the controller API.
+typedef enum {
+  /// API is happy and healthy. This doesn't mean the controller itself
+  /// is connected, it just means that the underlying service is working
+  /// properly.
+  GVR_CONTROLLER_API_OK = 0,
+
+  /// Any other status represents a permanent failure that requires
+  /// external action to fix:
+
+  /// API failed because this device does not support controllers (API is too
+  /// low, or other required feature not present).
+  GVR_CONTROLLER_API_UNSUPPORTED = 1,
+  /// This app was not authorized to use the service (e.g., missing permissions,
+  /// the app is blacklisted by the underlying service, etc).
+  GVR_CONTROLLER_API_NOT_AUTHORIZED = 2,
+  /// The underlying VR service is not present.
+  GVR_CONTROLLER_API_UNAVAILABLE = 3,
+  /// The underlying VR service is too old, needs upgrade.
+  GVR_CONTROLLER_API_SERVICE_OBSOLETE = 4,
+  /// The underlying VR service is too new, is incompatible with current client.
+  GVR_CONTROLLER_API_CLIENT_OBSOLETE = 5,
+  /// The underlying VR service is malfunctioning. Try again later.
+  GVR_CONTROLLER_API_MALFUNCTION = 6,
+} gvr_controller_api_status;
+
+/// Constants that represent the state of the controller.
+typedef enum {
+  /// Controller is disconnected.
+  GVR_CONTROLLER_DISCONNECTED = 0,
+  /// Controller is scanning.
+  GVR_CONTROLLER_SCANNING = 1,
+  /// Controller is connecting.
+  GVR_CONTROLLER_CONNECTING = 2,
+  /// Controller is connected.
+  GVR_CONTROLLER_CONNECTED = 3,
+} gvr_controller_connection_state;
+
+/// Controller buttons.
+typedef enum {
+  GVR_CONTROLLER_BUTTON_NONE = 0,
+  GVR_CONTROLLER_BUTTON_CLICK = 1,  ///< Touchpad Click.
+  GVR_CONTROLLER_BUTTON_HOME = 2,
+  GVR_CONTROLLER_BUTTON_APP = 3,
+  GVR_CONTROLLER_BUTTON_VOLUME_UP = 4,
+  GVR_CONTROLLER_BUTTON_VOLUME_DOWN = 5,
+
+  /// Note: there are 5 buttons on the controller, but the state arrays have
+  /// this many elements due to the inclusion of a dummy "none" button.
+  GVR_CONTROLLER_BUTTON_COUNT = 6,
+} gvr_controller_button;
+
+/// @}
+
+/// Opaque handle to controller state.
+typedef struct gvr_controller_state_ gvr_controller_state;
+
+/// @addtogroup types
+/// @{
+
+/// Rendering modes define CPU load / rendering quality balances.
+typedef enum {
+  /// Stereo panning of all Sound Objects. This disables HRTF-based rendering.
+  GVR_AUDIO_RENDERING_STEREO_PANNING = 0,
+  /// HRTF-based rendering over a virtual array of 8 loudspeakers arranged in
+  /// a cube configuration around the listener’s head.
+  GVR_AUDIO_RENDERING_BINAURAL_LOW_QUALITY = 1,
+  /// HRTF-based rendering over a virtual array of 16 loudspeakers arranged in
+  /// an approximate equidistribution about the around the listener’s head.
+  GVR_AUDIO_RENDERING_BINAURAL_HIGH_QUALITY = 2,
+} gvr_audio_rendering_mode;
+
+/// Room surface material names, used to set room properties.
+typedef enum {
+  /// Acoustically transparent material, reflects no sound.
+  GVR_AUDIO_MATERIAL_TRANSPARENT = 0,
+  /// Acoustic ceiling tiles, absorbs most frequencies.
+  GVR_AUDIO_MATERIAL_ACOUSTIC_CEILING_TILES = 1,
+  /// Bare brick, relatively reflective.
+  GVR_AUDIO_MATERIAL_BRICK_BARE = 2,
+  /// Painted brick
+  GVR_AUDIO_MATERIAL_BRICK_PAINTED = 3,
+  /// Coarse surface concrete block.
+  GVR_AUDIO_MATERIAL_CONCRETE_BLOCK_COARSE = 4,
+  /// Painted concrete block.
+  GVR_AUDIO_MATERIAL_CONCRETE_BLOCK_PAINTED = 5,
+  /// Heavy curtains.
+  GVR_AUDIO_MATERIAL_CURTAIN_HEAVY = 6,
+  /// Fiber glass insulation.
+  GVR_AUDIO_MATERIAL_FIBER_GLASS_INSULATION = 7,
+  /// Thin glass.
+  GVR_AUDIO_MATERIAL_GLASS_THIN = 8,
+  /// Thick glass.
+  GVR_AUDIO_MATERIAL_GLASS_THICK = 9,
+  /// Grass.
+  GVR_AUDIO_MATERIAL_GRASS = 10,
+  /// Linoleum on concrete.
+  GVR_AUDIO_MATERIAL_LINOLEUM_ON_CONCRETE = 11,
+  /// Marble.
+  GVR_AUDIO_MATERIAL_MARBLE = 12,
+  /// Galvanized sheet metal.
+  GVR_AUDIO_MATERIAL_METAL = 13,
+  /// Wooden parquet on concrete.
+  GVR_AUDIO_MATERIAL_PARQUET_ON_CONCRETE = 14,
+  /// Rough plaster surface.
+  GVR_AUDIO_MATERIAL_PLASTER_ROUGH = 15,
+  /// Smooth plaster surface.
+  GVR_AUDIO_MATERIAL_PLASTER_SMOOTH = 16,
+  /// Plywood panel.
+  GVR_AUDIO_MATERIAL_PLYWOOD_PANEL = 17,
+  /// Polished concrete OR tile surface.
+  GVR_AUDIO_MATERIAL_POLISHED_CONCRETE_OR_TILE = 18,
+  /// Sheet rock.
+  GVR_AUDIO_MATERIAL_SHEET_ROCK = 19,
+  /// Surface of water or ice.
+  GVR_AUDIO_MATERIAL_WATER_OR_ICE_SURFACE = 20,
+  /// Wooden ceiling.
+  GVR_AUDIO_MATERIAL_WOOD_CEILING = 21,
+  /// Wood paneling.
+  GVR_AUDIO_MATERIAL_WOOD_PANEL = 22,
+} gvr_audio_material_type;
+
+/// Distance rolloff models used for distance attenuation.
+typedef enum {
+  /// Logarithmic distance rolloff model.
+  GVR_AUDIO_ROLLOFF_LOGARITHMIC = 0,
+  /// Linear distance rolloff model.
+  GVR_AUDIO_ROLLOFF_LINEAR = 1,
+  /// No distance attenuation will be applied.
+  GVR_AUDIO_ROLLOFF_NONE = 2,
+} gvr_audio_distance_rolloff_type;
+
+/// Sound object and sound field identifier.
+typedef int32_t gvr_audio_source_id;
+
+/// Valid color formats for swap chain buffers.
+typedef enum {
+  /// Equivalent to GL_RGBA8
+  GVR_COLOR_FORMAT_RGBA_8888 = 0,
+  /// Equivalent to GL_RGB565
+  GVR_COLOR_FORMAT_RGB_565 = 1,
+} gvr_color_format_type;
+
+typedef enum {
+  /// No depth or stencil buffer.
+  GVR_DEPTH_STENCIL_FORMAT_NONE = 255,
+  /// Equivalent to GL_DEPTH_COMPONENT16.
+  GVR_DEPTH_STENCIL_FORMAT_DEPTH_16 = 0,
+  /// Equivalent to GL_DEPTH_COMPONENT24.
+  GVR_DEPTH_STENCIL_FORMAT_DEPTH_24 = 1,
+  /// Equivlaent to GL_DEPTH24_STENCIL8.
+  GVR_DEPTH_STENCIL_FORMAT_DEPTH_24_STENCIL_8 = 2,
+  /// Equivalent to GL_DEPTH_COMPONENT32F.
+  GVR_DEPTH_STENCIL_FORMAT_DEPTH_32_F = 3,
+  /// Equivalent to GL_DEPTH_32F_STENCIL8.
+  GVR_DEPTH_STENCIL_FORMAT_DEPTH_32_F_STENCIL_8 = 4,
+  /// Equivalent to GL_STENCIL8.
+  GVR_DEPTH_STENCIL_FORMAT_STENCIL_8 = 5,
+} gvr_depth_stencil_format_type;
+
+/// Types of asynchronous reprojection.
+typedef enum {
+  /// Do not reproject.
+  GVR_REPROJECTION_NONE = 0,
+  /// Reproject in all dimensions.
+  GVR_REPROJECTION_FULL = 1,
+} gvr_reprojection;
+
+typedef enum {
+  GVR_CONTROLLER_RIGHT_HANDED = 0,
+  GVR_CONTROLLER_LEFT_HANDED = 1,
+} gvr_controller_handedness;
+
+typedef struct gvr_user_prefs_ gvr_user_prefs;
+
+// Anonymous enum for miscellaneous integer constants.
+enum {
+  /// Constant that represents a nonexistent external surface. Pass to
+  /// gvr_buffer_viewport_set_external_surface_id() to disable sampling from
+  /// an external surface.
+  GVR_EXTERNAL_SURFACE_ID_NONE = -1,
+  /// Special index for a source buffer that has the same contents as the
+  /// external surface attached to the given viewport. Pass this to
+  /// gvr_buffer_viewport_set_source_buffer_index() to use the external surface
+  /// as the buffer contents.
+  GVR_BUFFER_INDEX_EXTERNAL_SURFACE = -1,
+};
+
+/// @}
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+// These typedefs convert the C-style names to C++-style names.
+
+namespace gvr {
+
+const int32_t kControllerEnableOrientation =
+    static_cast<int32_t>(GVR_CONTROLLER_ENABLE_ORIENTATION);
+const int32_t kControllerEnableTouch =
+    static_cast<int32_t>(GVR_CONTROLLER_ENABLE_TOUCH);
+const int32_t kControllerEnableGyro =
+    static_cast<int32_t>(GVR_CONTROLLER_ENABLE_GYRO);
+const int32_t kControllerEnableAccel =
+    static_cast<int32_t>(GVR_CONTROLLER_ENABLE_ACCEL);
+const int32_t kControllerEnableGestures =
+    static_cast<int32_t>(GVR_CONTROLLER_ENABLE_GESTURES);
+const int32_t kControllerEnablePosePrediction =
+    static_cast<int32_t>(GVR_CONTROLLER_ENABLE_POSE_PREDICTION);
+const int32_t kControllerEnablePosition =
+    static_cast<int32_t>(GVR_CONTROLLER_ENABLE_POSITION);
+
+typedef gvr_controller_api_status ControllerApiStatus;
+const ControllerApiStatus kControllerApiOk =
+    static_cast<ControllerApiStatus>(GVR_CONTROLLER_API_OK);
+const ControllerApiStatus kControllerApiUnsupported =
+    static_cast<ControllerApiStatus>(GVR_CONTROLLER_API_UNSUPPORTED);
+const ControllerApiStatus kControllerApiNotAuthorized =
+    static_cast<ControllerApiStatus>(GVR_CONTROLLER_API_NOT_AUTHORIZED);
+const ControllerApiStatus kControllerApiUnavailable =
+    static_cast<ControllerApiStatus>(GVR_CONTROLLER_API_UNAVAILABLE);
+const ControllerApiStatus kControllerApiServiceObsolete =
+    static_cast<ControllerApiStatus>(GVR_CONTROLLER_API_SERVICE_OBSOLETE);
+const ControllerApiStatus kControllerApiClientObsolete =
+    static_cast<ControllerApiStatus>(GVR_CONTROLLER_API_CLIENT_OBSOLETE);
+const ControllerApiStatus kControllerApiMalfunction =
+    static_cast<ControllerApiStatus>(GVR_CONTROLLER_API_MALFUNCTION);
+
+typedef gvr_controller_connection_state ControllerConnectionState;
+const ControllerConnectionState kControllerDisconnected =
+    static_cast<ControllerConnectionState>(GVR_CONTROLLER_DISCONNECTED);
+const ControllerConnectionState kControllerScanning =
+    static_cast<ControllerConnectionState>(GVR_CONTROLLER_SCANNING);
+const ControllerConnectionState kControllerConnecting =
+    static_cast<ControllerConnectionState>(GVR_CONTROLLER_CONNECTING);
+const ControllerConnectionState kControllerConnected =
+    static_cast<ControllerConnectionState>(GVR_CONTROLLER_CONNECTED);
+
+typedef gvr_controller_button ControllerButton;
+const ControllerButton kControllerButtonNone =
+    static_cast<ControllerButton>(GVR_CONTROLLER_BUTTON_NONE);
+const ControllerButton kControllerButtonClick =
+    static_cast<ControllerButton>(GVR_CONTROLLER_BUTTON_CLICK);
+const ControllerButton kControllerButtonHome =
+    static_cast<ControllerButton>(GVR_CONTROLLER_BUTTON_HOME);
+const ControllerButton kControllerButtonApp =
+    static_cast<ControllerButton>(GVR_CONTROLLER_BUTTON_APP);
+const ControllerButton kControllerButtonVolumeUp =
+    static_cast<ControllerButton>(GVR_CONTROLLER_BUTTON_VOLUME_UP);
+const ControllerButton kControllerButtonVolumeDown =
+    static_cast<ControllerButton>(GVR_CONTROLLER_BUTTON_VOLUME_DOWN);
+const ControllerButton kControllerButtonCount =
+    static_cast<ControllerButton>(GVR_CONTROLLER_BUTTON_COUNT);
+
+/// An uninitialized external surface ID.
+const int32_t kUninitializedExternalSurface = GVR_BUFFER_INDEX_EXTERNAL_SURFACE;
+/// The default source buffer index for viewports.
+const int32_t kDefaultBufferIndex = 0;
+
+typedef gvr_eye Eye;
+
+typedef gvr_viewer_type ViewerType;
+const ViewerType kViewerTypeCardboard =
+    static_cast<ViewerType>(GVR_VIEWER_TYPE_CARDBOARD);
+const ViewerType kViewerTypeDaydream =
+    static_cast<ViewerType>(GVR_VIEWER_TYPE_DAYDREAM);
+
+typedef gvr_version Version;
+typedef gvr_sizei Sizei;
+typedef gvr_recti Recti;
+typedef gvr_rectf Rectf;
+typedef gvr_vec2f Vec2f;
+typedef gvr_vec3f Vec3f;
+typedef gvr_mat4f Mat4f;
+typedef gvr_quatf Quatf;
+typedef gvr_clock_time_point ClockTimePoint;
+
+typedef gvr_vec2f ControllerVec2;
+typedef gvr_vec3f ControllerVec3;
+typedef gvr_quatf ControllerQuat;
+
+typedef gvr_audio_rendering_mode AudioRenderingMode;
+typedef gvr_audio_material_type AudioMaterialName;
+typedef gvr_audio_distance_rolloff_type AudioRolloffMethod;
+typedef gvr_audio_source_id AudioSourceId;
+
+typedef gvr_color_format_type ColorFormat;
+const ColorFormat kColorFormatRgba8888 =
+    static_cast<ColorFormat>(GVR_COLOR_FORMAT_RGBA_8888);
+const ColorFormat kColorFormatRgb565 =
+    static_cast<ColorFormat>(GVR_COLOR_FORMAT_RGB_565);
+
+typedef gvr_depth_stencil_format_type DepthStencilFormat;
+const DepthStencilFormat kDepthStencilFormatNone =
+    static_cast<DepthStencilFormat>(GVR_DEPTH_STENCIL_FORMAT_NONE);
+const DepthStencilFormat kDepthStencilFormatDepth16 =
+    static_cast<DepthStencilFormat>(GVR_DEPTH_STENCIL_FORMAT_DEPTH_16);
+const DepthStencilFormat kDepthStencilFormatDepth24 =
+    static_cast<DepthStencilFormat>(GVR_DEPTH_STENCIL_FORMAT_DEPTH_24);
+const DepthStencilFormat kDepthStencilFormatDepth24Stencil8 =
+    static_cast<DepthStencilFormat>(
+        GVR_DEPTH_STENCIL_FORMAT_DEPTH_24_STENCIL_8);
+const DepthStencilFormat kDepthStencilFormatDepth32f =
+    static_cast<DepthStencilFormat>(GVR_DEPTH_STENCIL_FORMAT_DEPTH_32_F);
+const DepthStencilFormat kDepthStencilFormatDepth32fStencil8 =
+    static_cast<DepthStencilFormat>(
+        GVR_DEPTH_STENCIL_FORMAT_DEPTH_32_F_STENCIL_8);
+const DepthStencilFormat kDepthStencilFormatStencil8 =
+    static_cast<DepthStencilFormat>(GVR_DEPTH_STENCIL_FORMAT_STENCIL_8);
+
+typedef gvr_controller_handedness ControllerHandedness;
+const ControllerHandedness kControllerRightHanded =
+    static_cast<ControllerHandedness>(GVR_CONTROLLER_RIGHT_HANDED);
+const ControllerHandedness kControllerLeftHanded =
+    static_cast<ControllerHandedness>(GVR_CONTROLLER_LEFT_HANDED);
+
+typedef gvr_error Error;
+const Error kErrorNone = static_cast<Error>(GVR_ERROR_NONE);
+const Error kErrorControllerCreateFailed =
+    static_cast<Error>(GVR_ERROR_CONTROLLER_CREATE_FAILED);
+const Error kErrorNoFrameAvailable =
+    static_cast<Error>(GVR_ERROR_NO_FRAME_AVAILABLE);
+
+class AudioApi;
+class BufferSpec;
+class ControllerApi;
+class ControllerState;
+class Frame;
+class GvrApi;
+class BufferViewport;
+class BufferViewportList;
+class SwapChain;
+class UserPrefs;
+
+}  // namespace gvr
+
+// Non-member equality operators for convenience. Since typedefs do not trigger
+// argument-dependent lookup, these operators have to be defined for the
+// underlying types.
+inline bool operator==(const gvr_vec2f& lhs, const gvr_vec2f& rhs) {
+  return lhs.x == rhs.x && lhs.y == rhs.y;
+}
+
+inline bool operator!=(const gvr_vec2f& lhs, const gvr_vec2f& rhs) {
+  return !(lhs == rhs);
+}
+
+inline bool operator==(const gvr_vec3f& lhs, const gvr_vec3f& rhs) {
+  return lhs.x == rhs.x && lhs.y == rhs.y;
+}
+
+inline bool operator!=(const gvr_vec3f& lhs, const gvr_vec3f& rhs) {
+  return !(lhs == rhs);
+}
+
+inline bool operator==(const gvr_recti& lhs, const gvr_recti& rhs) {
+  return lhs.left == rhs.left && lhs.right == rhs.right &&
+         lhs.bottom == rhs.bottom && lhs.top == rhs.top;
+}
+
+inline bool operator!=(const gvr_recti& lhs, const gvr_recti& rhs) {
+  return !(lhs == rhs);
+}
+
+inline bool operator==(const gvr_rectf& lhs, const gvr_rectf& rhs) {
+  return lhs.left == rhs.left && lhs.right == rhs.right &&
+         lhs.bottom == rhs.bottom && lhs.top == rhs.top;
+}
+
+inline bool operator!=(const gvr_rectf& lhs, const gvr_rectf& rhs) {
+  return !(lhs == rhs);
+}
+
+inline bool operator==(const gvr_sizei& lhs, const gvr_sizei& rhs) {
+  return lhs.width == rhs.width && lhs.height == rhs.height;
+}
+
+inline bool operator!=(const gvr_sizei& lhs, const gvr_sizei& rhs) {
+  return !(lhs == rhs);
+}
+
+#endif  // #if defined(__cplusplus) && !defined(GVR_NO_CPP_WRAPPER)
+
+#endif  // VR_GVR_CAPI_INCLUDE_GVR_TYPES_H_
diff --git a/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_experimental.h b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_experimental.h
new file mode 100644
index 0000000..6bc2b4e
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_experimental.h
@@ -0,0 +1,240 @@
+#ifndef VR_GVR_CAPI_SRC_GVR_EXPERIMENTAL_H_
+#define VR_GVR_CAPI_SRC_GVR_EXPERIMENTAL_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "vr/gvr/capi/include/gvr_types.h"
+#include "vr/gvr/capi/src/gvr_types_experimental.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// NOTE: APIs added to this file are *not* part of the core GVR library, and
+// should only be used for prototyping or experimental projects. The idea is
+// that APIs added here can be used for testing and development, graduating to
+// the core API (gvr.h or gvr_private.h) after we're ready to commit to them
+// indefinitely.
+
+// ************************************************************************** //
+// *                     DaydreamOS experimental APIs                       * //
+// ************************************************************************** //
+
+/// Gets the position and rotation from start space to head space.  The head
+/// space is a space where the head is at the origin and faces the -Z direction.
+///
+/// @param gvr Pointer to the gvr instance from which to get the pose.
+/// @param time The time at which to get the head pose. The time should be in
+///     the future. If the time is not in the future, it will be clamped to now.
+/// @return A matrix representation of the position and rotation from start
+//      space (the space where the head was last reset) to head space (the space
+///     with the head at the origin, and the axes aligned to the view vector).
+gvr_mat4f gvr_get_head_space_from_start_space_pose(
+    gvr_context* gvr, const gvr_clock_time_point time);
+
+/// Sets the compositor z-order of the swap chain.
+///
+/// @param swap_chain the swap chain to change.
+/// @param z_order Z order, higher values are displayed on top of lower ones,
+///     the default value is 0.
+void gvr_swap_chain_set_z_order(const gvr_swap_chain* swap_chain, int z_order);
+
+/// Creates a gvr_external_surface instance.
+/// An external surface is mainly used to pass external content (such as video
+/// frames, pre-rendered 2D Android UI) into distortion pass for compositing.
+/// The method gvr_external_surface_get_surface can be used to bridge Android
+/// components with GVR distortion pass via a traditional Android Surface
+/// instance.
+///
+/// @param gvr Pointer to the gvr instance from which to create the external
+///     surface.
+/// @return Pointer to an allocated gvr_external_surface object. The caller
+//      is responsible for calling gvr_external_surface_destroy() on the
+///     returned object when it is no longer needed.
+gvr_external_surface* gvr_external_surface_create(gvr_context* gvr);
+
+/// Frees a gvr_external_surface instance and clears the pointer.
+/// Note that once a gvr_external_surface is destroyed, the Java Surface object
+/// returned from gvr_external_surface_get_surface remains to be accessible and
+/// functioning. It's up to Java's garbage collection to release all resources
+/// behind the Java Surface object.
+///
+/// @param surface Pointer to a pointer to the gvr_external_surface instance to
+///     be destroyed and nulled.
+void gvr_external_surface_destroy(gvr_external_surface** surface);
+
+/// Get an Android Surface as a Java object from the gvr_external_surface. This
+/// API is mainly used by standalone display service (aka when
+/// gvr_using_vr_display_service returns true) to access an Android Surface.
+///
+/// @param surface The gvr_external_surface associated with the Android Surface.
+///     Note that this API has to be called within a JNIEnv and is using the
+///     JNIEnv passed in during gvr_create.
+/// @return A jobject that is an instance of the 'android/view/Surface' Java
+///     class, NULL on failure. Note that the return value is really an opaque
+///     handle to a Java object and the life cycle of that object is maintained
+///     by Java (i.e. it will get garbage collected eventually). Thus, there is
+///     no need for an explicit destroy call.
+void* gvr_external_surface_get_surface(const gvr_external_surface* surface);
+
+/// Get the Surface ID associated with the gvr_external_surface. Note that the
+/// returned ID is used for internal bookkeeping only and should not be used
+/// by the app itself for lookups.
+/// @param surface The gvr_external_surface to query the ID for.
+/// @return The external surface ID associated with the gvr_external_surface.
+int32_t gvr_external_surface_get_surface_id(
+    const gvr_external_surface* surface);
+
+/// Queries whether a particular GVR feature is supported by the underlying
+/// platform.
+///
+/// @param gvr The context to query against.
+/// @param feature The gvr_feature type being queried.
+/// @return true if feature is supported, false otherwise.
+bool gvr_experimental_is_feature_supported(const gvr_context* gvr,
+                                           int32_t feature);
+
+/// Sets the z order of the layer to be created.
+/// Note that this API is a short-term workaround for SysUI work and is never
+/// meant to graduate as is to either gvr.h or gvr_private.h. The proper
+/// solution is tracked in b/33946428 and probably involves setting the
+/// attribute on some data structure that represents a layer.
+///
+/// @param spec Buffer specification.
+/// @param z_order Z order, higher values are displayed on top of lower ones,
+///     the default value is 0.
+void gvr_buffer_spec_set_z_order(gvr_buffer_spec* spec, int z_order);
+
+/// Sets the initial visibility of the layer to be created.
+/// Note that this API is a short-term workaround for SysUI work and is never
+/// meant to graduate as is to either gvr.h or gvr_private.h. The proper
+/// solution is tracked in b/33946428 and probably involves setting the
+/// attribute on some data structure that represents a layer.
+///
+/// @param spec Buffer specification.
+/// @param visibility Initial visibility of the layer, defaults to GVR_VISIBLE.
+///     See enum gvr_visibility for possible values.
+void gvr_buffer_spec_set_visibility(gvr_buffer_spec* spec,
+                                    int32_t visibility);
+
+/// Sets whether to blur layers below the layer to be created.
+/// Blurring is applied only to visible layers and only when the layer is
+/// visible.
+/// Note that this API currently is only implemented by the DreamOS
+/// implementation of GVR and is a no-op in other implementations.
+/// TODO(b/33946428): investigate the proper way to surface this feature
+/// to SysUI.
+///
+/// @param spec Buffer specification.
+/// @param blur_behind whether to blur layers behind, defaults to
+///     GVR_BLUR_BEHIND_TRUE. See enum gvr_blur_behind for possible values.
+void gvr_buffer_spec_set_blur_behind(gvr_buffer_spec* spec,
+                                     int32_t blur_behind);
+
+// ************************************************************************** //
+// *                     Daydream PlexEng experimental APIs                 * //
+// ************************************************************************** //
+
+// Registers a new performance event listener that will be invoked on points
+// of interest. By default no event listener is attached.  If multiple event
+// listeners are attached they will all be invoked.  Failures can be checked
+// with gvr_get_error().
+// @param out_handle The pointer to memory where a successfully created handle
+//     will be written.
+// @param gvr The context to register callbacks for.
+// @param user_data The pointer that will be passed back on callbacks for
+//     user_data.
+// @param event_callback The callback to be invoked when an event is observed.
+//     On performance events callback will be invoked with the
+//     user_data passed here, the gvr_perf_event_callback_type, and a float
+//     value (if applicable) or -1.f.
+// @return Returns GVR_EXPERIMENTAL_ERROR_NONE if a handle was created,
+//     GVR_EXPERIMENTAL_ERROR_UNIMPLEMENTED if this feature is disabled,
+//     or GVR_EXPERIMENTAL_ERROR_INVALID_ARGUMENT if a null pointer was passed.
+bool gvr_experimental_register_perf_event_callback(
+    gvr_context* gvr, int* out_handle, void* user_data,
+    void (*event_callback)(void*, int, float));
+
+// Unregisters a previously registered callback by its handle. Failures can be
+// checked with gvr_get_error().
+// @param handle The handle which was returned when registering the callback.
+// @return Returns GVR_EXPERIMENTAL_ERROR_NONE if callback was unregistered,
+//     GVR_EXPERIMENTAL_ERROR_INVALID_ARGUMENT if the if the handle wasn't
+//     previously
+//     registered.  If this feature is not enabled it will return
+//     GVR_EXPERIMENTAL_ERROR_UNIMPLEMENTED.
+bool gvr_experimental_unregister_perf_event_callback(gvr_context* gvr,
+                                                     int handle);
+
+// ************************************************************************** //
+// *                    GVR Analytics experimental APIs                     * //
+// ************************************************************************** //
+// TODO(b/31634289): These functions are experimental because their main client
+// case is the performance monitoring HUD, whose form and function is still
+// under development. Consequently, the analytics API may change as the HUD's
+// needs become clearer.
+//
+// These functions will be moved into the main API (probably gvr_private.h) once
+// the HUD is ready to be shipped as part of the SDK.
+//
+// Contacts: georgelu@
+
+/// Returns whether the "Performance Monitoring" developer option is enabled.
+///
+/// @param user_prefs Pointer to the gvr_user_prefs object returned by
+///     gvr_get_user_prefs.
+/// @return True if the "Performance Monitoring" developer option is enabled.
+bool gvr_user_prefs_get_performance_monitoring_enabled(
+    const gvr_user_prefs* user_prefs);
+
+// Opaque struct returned by gvr_get_analytics that can be queried through
+// gvr_analytics_get* functions.
+//
+// Note: The struct is never actually defined since gvr_analytics is actually
+// just a static_cast of a gvr_context, similar to how gvr_user_prefs works.
+typedef struct gvr_analytics_ gvr_analytics;
+
+// Returns an opaque struct that can be queried for analytics data. The returned
+// struct remains valid as long as the context is valid.
+//
+// @param gvr Pointer to the current gvr_context instance.
+// @return An opaque struct that can be queried through gvr_analytics_*
+//     functions.
+const gvr_analytics* gvr_get_analytics(gvr_context* gvr);
+
+// If the "Performance Monitoring" developer option in VR settings is enabled,
+// returns a gvr_analytics_sample* containing analytics data. Caller is
+// responsible for calling gvr_analytics_destroy_sample on the returned object.
+//
+// @param analytics gvr_analytics* returned by gvr_get_analytics.
+// @return gvr_analytics_sample* containing analytics data.
+const gvr_analytics_sample* gvr_analytics_create_sample(
+    const gvr_analytics* analytics);
+
+// Returns pointer to a buffer containing a serialized AnalyticsSample proto.
+// The buffer is valid only for the lifetime of the gvr_analytics_sample.
+//
+// @param Pointer to a gvr_analytics_sample object.
+// @return Pointer to buffer.
+const char* gvr_analytics_sample_get_buffer(const gvr_analytics_sample* sample);
+
+// Returns the length of the buffer returned by gvr_analytics_sample_get_buffer.
+//
+// @param Pointer to a gvr_analytics_sample object.
+// @return Length of buffer.
+size_t gvr_analytics_sample_get_buffer_length(
+    const gvr_analytics_sample* sample);
+
+// Destroys a gvr_analytics_sample* previously created through
+// gvr_analytics_create_sample.
+//
+// @param sample Pointer to pointer that will be set to null and whose
+//     underlying gvr_analytics_sample will be destroyed.
+void gvr_analytics_destroy_sample(const gvr_analytics_sample** sample);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // VR_GVR_CAPI_SRC_GVR_EXPERIMENTAL_H_
diff --git a/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_private.h b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_private.h
new file mode 100644
index 0000000..0a9d30e
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_private.h
@@ -0,0 +1,378 @@
+#ifndef VR_GVR_CAPI_SRC_GVR_PRIVATE_H_
+#define VR_GVR_CAPI_SRC_GVR_PRIVATE_H_
+
+#include <stddef.h>
+
+#include "vr/gvr/capi/include/gvr_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Opaque handle to gvr_tracker_state object containing serialized state.
+typedef struct gvr_tracker_state_ gvr_tracker_state;
+
+// Opaque handle to gvr_display_synchronizer object for display synchronization.
+typedef struct gvr_display_synchronizer_ gvr_display_synchronizer;
+
+// Internal Google VR C API methods. These methods are exposed only to internal
+// targets, but should follow all the same backwards-compatible restrictions
+// as the public C API.
+
+/// Sets whether asynchronous reprojection is currently enabled.
+///
+/// If enabled, frames will be collected by the rendering system and
+/// asynchronously re-projected in sync with the scanout of the display. This
+/// feature may not be available on every platform, and requires a
+/// high-priority render thread with special extensions to function properly.
+///
+/// Note: On Android, this feature can be enabled solely via the GvrLayout Java
+/// instance which (indirectly) owns this gvr_context. The corresponding
+/// method call is GvrLayout.setAsyncReprojectionEnabled().
+///
+/// @param gvr Pointer to the gvr instance.
+/// @Param enabled Whether to enable async reprojection.
+/// @return Whether the setting was succesfully applied.
+bool gvr_set_async_reprojection_enabled(gvr_context* gvr, bool enabled);
+
+// Initializes necessary GL-related objects and uses the current thread and
+// GL context for racing the scanline. This function should only be called
+// by the SDK itself, not by any application, unless that application is
+// providing a high-priority thread and GL context for async reprojection.
+//
+// Note: This method is private as it is intended for use solely by the
+// hidden async reprojection implementation in ScanlineRacingRenderer.java,
+// called in onSurfaceCreated().
+//
+// @param gvr Pointer to the gvr_context instance.
+void gvr_on_surface_created_reprojection_thread(gvr_context* gvr);
+
+// Renders the scanline layer. This function should only be called
+// in the same thread that gvr_initialize_gl_projection_thread was called in.
+// This function should only be called by the SDK itself, not by any
+// application, unless that application is providing a high-priority
+// thread and GL context for async reprojection.
+//
+// Note: This method is private as it is intended for use solely by the
+// hidden async reprojection implementation in ScanlineRacingRenderer.java.
+//
+// @param gvr Pointer to the gvr_context instance.
+void gvr_render_reprojection_thread(gvr_context* gvr);
+
+// Signals to the reprojection thread that it is paused. This is necessary
+// in case the application render thread is blocked on pending work by the
+// reprojection thread. This function will abort any blocking.
+//
+// @param gvr Pointer to the gvr_context instance.
+void gvr_on_pause_reprojection_thread(gvr_context* gvr);
+
+// Sets the parameters for the external surface managed by the reprojection
+// thread.
+//
+// @param gvr Pointer to the gvr_context instance.
+// @param surface_id The ID of the external Surface managed by the reprojection
+//     thread. The ID is issued by the SurfaceTextureManager.
+// @param texture_id The GL texture ID associated with the external Surface.
+// @param timestamp The timestamp of the most recent frame the Surface holds.
+// @param surface_transfrom Matrix that transforms homogeneous texture coords to
+//     the external surface texture space.
+void gvr_update_surface_reprojection_thread(gvr_context* gvr,
+      int32_t surface_id, int32_t texture_id, gvr_clock_time_point timestamp,
+      gvr_mat4f surface_transform);
+
+// Removes all external surfaces managed by the reprojection thread. This does
+// not destoy the surfaces: it removes tracking by the reprojection thread.
+//
+// @param gvr Pointer to the gvr_context instance.
+void gvr_remove_all_surfaces_reprojection_thread(gvr_context* gvr);
+
+// Reconnects the sensors when the sensor producers are created internally.
+//
+// Note: This function is not thread-safe, and should be called only on the
+// rendering thread. It is intended to be used internally by GvrLayout when
+// the target presentation display changes.
+//
+// @param gvr Pointer to the gvr_context instance.
+void gvr_reconnect_sensors(gvr_context* gvr);
+
+// Sets VR viewer params for the current context.
+//
+// Note: This function does not update the viewer proto in the common storage
+// location. Rather, it overrides the viewer params solely for the provided
+// gvr_context.
+//
+// @param gvr Pointer to the gvr_context instance.
+// @param serialized_viewer_params A pointer to the payload containing the
+//     serialized viewer params proto.
+// @param serialized_viewer_params_size_bytes The length in bytes of the
+//     serialized viewer params payload.
+// @return Whether the serialized viewer params proto was successfully applied.
+bool gvr_set_viewer_params(gvr_context* gvr,
+                           const void* serialized_viewer_params,
+                           size_t serialized_viewer_params_size_bytes);
+
+// Sets the lens offset.
+//
+// @param offset The offset of the lens center from the expected location in
+// screen space.
+void gvr_set_lens_offset(gvr_context* gvr, gvr_vec2f offset);
+
+// Sets display metrics for the current context.
+//
+// Note: This function does not update the phone proto in the commom storage
+// location. Rather, it overrides the internal metrics solely for the provided
+// |gvr| context.
+//
+// @param gvr Pointer to the gvr_context instance.
+// @param size_pixels The dimensions in pixels of the active display.
+// @param meters_per_pixel The density of the current display in meters/pixel.
+// @param border_size_meters The size of the border around the display
+//     in meters.  When the device sits on a surface in the proper
+//     orientation this is the distance from the surface to the edge
+//     of the display.
+void gvr_set_display_metrics(gvr_context* gvr, gvr_sizei size_pixels,
+                             gvr_vec2f meters_per_pixel,
+                             float border_size_meters);
+
+// Sets the display rotation offset that is applied at distortion correction
+// time to take into account the device's display orientation.
+//
+// For instance, calling this with display_output_rotation set to 1 allows
+// clients to lock their phone orientation to portrait on an Android phone and
+// still get a correctly rendered VR mode with the two eyes stacked up along the
+// longer phone dimension.
+//
+// @param gvr Pointer to the gvr_context instance.
+// @param display_output_rotation Value encoding the rotation used when
+//     performing distortion correction. Supported values are:
+//     0 - Default mode. Eye viewports are positioned side-by-side along the
+//         "width" dimension, with left eye in the x < 0.5 half.
+//     1 - Applies a clock-wise rotation of 90 degrees on the display when
+//         doing distortion correction. Eye viewports are positioned
+//         side-by-side along the "height" dimension, with left eye in the
+//         y > 0.5 half.
+// Rotation modes used when performing distortion correction.
+enum {
+  GVR_PRIVATE_DISPLAY_OUTPUT_ROTATION_0 = 0,
+  GVR_PRIVATE_DISPLAY_OUTPUT_ROTATION_90 = 1,
+};
+void gvr_set_display_output_rotation(gvr_context* gvr,
+                                     int32_t display_output_rotation);
+
+// Gets the size of the border around the display used by the given gvr_context.
+//
+// @param gvr Pointer to the gvr_context instance.
+float gvr_get_border_size_meters(const gvr_context* gvr);
+
+// Returns whether the surface size was changed since the last call to this
+// function (it's changed with gvr_set_surface_size()).
+//
+// @param gvr Pointer to the gvr_context instance.
+// @return Whether the surface size was changed.
+bool gvr_check_surface_size_changed(gvr_context* gvr);
+
+// Returns the current surface size in pixels, or (0, 0) if the surface size
+// matches that of the active display (which is the default).
+//
+// @param gvr Pointer to the gvr_context instance.
+// @return The current surface size in pixels.
+gvr_sizei gvr_get_surface_size(const gvr_context* gvr);
+
+// Sets a handler that is called back when the back gesture is detected,
+// which is when the phone changes from landscape to portrait orientation
+// within a few seconds.
+//
+// @param gvr Pointer to the gvr_context instance.
+// @param handler The event_handler callback. May be null to clear the
+//     registered event_handler.
+// @param user_data An opaque pointer to user_data which will be supplied
+//     as the callback argument. The caller is responsible for ensuring the
+//     validity of this data for the duration of the handler registration.
+typedef void (*event_handler)(void* user_data);
+void gvr_set_back_gesture_event_handler(gvr_context* gvr, event_handler handler,
+                                        void* user_data);
+
+// Internal method to pause head tracking used by GvrLayout. Disables all
+// sensors (to save power) and gets the serialized tracker state.
+//
+// @param gvr Pointer to the gvr instance for which tracking will be paused and
+//     sensors disabled.
+//
+// @return Pointer to a tracker_state object containing the serialized tracker
+//     state. The caller is responsible for calling destroy on the returned
+//     handle.
+gvr_tracker_state* gvr_pause_tracking_get_state(gvr_context* gvr);
+
+// Internal method to resume head tracking used by GvrLayout. Re-enables all
+// sensors and sets the tracker state.
+//
+// @param gvr Pointer to the gvr instance for which tracking will be resumed.
+//     serialized tracker state object.
+// @param tracker_state Pointer to a tracker_state object containing the
+//     serialized tracker state object.
+void gvr_resume_tracking_set_state(
+    gvr_context* gvr, gvr_tracker_state* tracker_state);
+
+// Sets the internal flag that ignores calls to the public API's
+// gvr_pause_tracking and gvr_resume_tracking.
+// When true, the tracker is handled through GvrLayout
+// gvr_pause_tracking_private / gvr_resume_tracking_private direct calls through
+// the GvrApi instance are ignored. This is workaround to temporarily support
+// clients using GvrLayout that manually call pause/ resume tracking.
+// TODO(b/30404822) : clean this up once all existing clients move away from the
+// obsolete behavior.
+//
+// @param gvr Pointer to the gvr instance.
+// @param should_ignore Whether manual pause / resume tracker should be ignored.
+void gvr_set_ignore_manual_tracker_pause_resume(gvr_context* gvr,
+                                                bool should_ignore);
+
+// Creates a new tracker state object from the serialized tracker state buffer.
+//
+// @param tracker_state_buffer Pointer to buffer containing the serialized
+// tracker state.
+// @param buf_size Size of the tracker state buffer.
+//
+// @return Pointer to a tracker_state object containing the serialized tracker
+// state string. The caller is responsible for calling destroy on the returned
+// handle.
+gvr_tracker_state* gvr_tracker_state_create(const char* tracker_state_buffer,
+                                            size_t buf_size);
+
+// Gets the size of the buffer that is required to hold the serialized
+// gvr_tracker_state.
+//
+// @param Pointer to a gvr_tracker_state object containing the serialized
+// tracker state.
+//
+// @return Size of the buffer,
+size_t gvr_tracker_state_get_buffer_size(gvr_tracker_state* tracker_state);
+
+// Gets the buffer that holds the serialized gvr_tracker_state.
+//
+// @param Pointer to a tracker_state object containing the serialized tracker
+// state.
+//
+// @return Pointer to the buffer.
+const char* gvr_tracker_state_get_buffer(gvr_tracker_state* tracker_state);
+
+// Destroys a gvr_tracker_state instance.
+//
+// @param tracker_state Pointer to a pointer of the gvr_tracker_state instance
+// to be destroyed and nulled.
+void gvr_tracker_state_destroy(gvr_tracker_state** tracker_state);
+
+// Creates a new synchronizer instance.
+//
+// @return synchronizer Pointer to the new gvr_display_synchronizer instance.
+gvr_display_synchronizer* gvr_display_synchronizer_create();
+
+// Destroy the synchonronizer instance and null the pointer.
+//
+// @param synchronizer Pointer to a pointer to the gvr_display_synchronizer
+//     instance.
+void gvr_display_synchronizer_destroy(gvr_display_synchronizer** synchronizer);
+
+// Resets the synchronizer with updated vsync timing data.
+//
+// @param synchronizer Pointer to the new gvr_display_synchronizer instance.
+// @param expected_interval_nanos The expected average time between
+//     synchronization times, in nanoseconds, or 0 if unknown.
+// @param vsync_offset_nanos The duration, in nanos, such that the current sync
+//     time minus the display vsync offset is the time when the physical
+//     scan-out hardware begins to read data from the frame buffer.
+void gvr_display_synchronizer_reset(gvr_display_synchronizer* synchronizer,
+                                    int64_t expected_interval_nanos,
+                                    int64_t vsync_offset_nanos);
+
+// Updates the synchronizer with dispplay data for a new frame.
+//
+// @param vsync_time The new frame's vsync time.
+// @param rotation_degrees The screen rotation from sensor space to display
+//     space in degrees.
+void gvr_display_synchronizer_update(gvr_display_synchronizer* synchronizer,
+                                     gvr_clock_time_point vsync_time,
+                                     int32_t rotation);
+
+// Installs the display synchronizer into a GVR context.
+//
+// @param gvr Pointer to the current gvr_context instance.
+// @param synchronizer Pointer to the gvr_display_synchronizer instance, to be
+//     used by the context implementation during rendering.
+void gvr_set_display_synchronizer(gvr_context* gvr,
+                                  gvr_display_synchronizer* synchronizer);
+
+// Sets the current error code. Overwrites any existing error code.
+//
+// @param gvr Pointer to the current gvr_context instance.
+// @param error_code The error code to set.
+void gvr_set_error(gvr_context* gvr, int32_t error_code);
+
+// Called by the platform layer to to indicate the application is paused. (e.g.
+// On Android, this function is called by GvrLayout.OnPause().)
+//
+// @param gvr Pointer to the current gvr_context instance.
+void gvr_pause(gvr_context* gvr);
+
+// Called by the platform layer to to indicate the application has resumed.
+// (e.g. On Android, this function is called by GvrLayout.OnResume().)
+//
+// @param gvr Pointer to the current gvr_context instance.
+void gvr_resume(gvr_context* gvr);
+
+// Dumps additional data to logcat or disk to be included in bug reports.
+//
+// @param gvr Pointer to the current gvr_context instance.
+void gvr_dump_debug_data(gvr_context* gvr);
+
+// Returns true if the libgvr implementation is using the dedicated VR display
+// service, false otherwise.
+//
+// @param gvr Pointer to the current gvr_context instance.
+bool gvr_using_vr_display_service(gvr_context* gvr);
+
+// Creates a new gvr_context using the supplied tracker, only for testing.
+//
+// Note: The pose returned is *in start space*. This is *not* the same space as
+// the pose normally returned by |gvr_get_head_space_from_start_space_rotation|.
+//
+// @param tracker The test pose tracker to use.
+// @param user_data An opaque pointer to user_data which will be supplied
+//     as the callback argument. The caller is responsible for ensuring the
+//     validity of this data for the duration of the handler registration.
+typedef gvr_mat4f (*gvr_test_pose_tracker)(void*, gvr_clock_time_point);
+gvr_context* gvr_create_with_tracker_for_testing(gvr_test_pose_tracker tracker,
+                                                 void* user_data);
+
+// Request resource sharing between the application's OpenGL context and the
+// scanline racing context.  This must be called before gvr_initialize_gl.
+// <p>
+// This is a best effort request rather than an explicit toggle; it is a no-op
+// if the client does not enable async reprojection, or if the platform does not
+// support resource sharing.
+// <p>
+// The only OpenGL resource that we need sharing for is the framebuffer texture
+// that the app renders to, and that distortion samples from.  If resource
+// sharing is disabled, then we use an EGLImage so that it can be accessed from
+// both contexts.
+// <p>
+// Also sets a callback function that is called at the end of gvr_initialize_gl,
+// while the application's OpenGL context is still active on the current thread.
+// This is used internally to notify the scanline racing renderer that the
+// application's OpenGL context has been created.
+//
+// @param gvr Pointer to the current gvr_context instance.
+// @param handler Callback that gets called when the app context becomes ready.
+// @param user_data An opaque pointer to user data which will be supplied
+//     as the callback argument. The caller is responsible for ensuring the
+//     validity of this data for the duration of the handler registration.
+typedef void (*gvr_egl_context_listener)(void*);
+void gvr_request_context_sharing(gvr_context* gvr,
+                                 gvr_egl_context_listener handler,
+                                 void* user_data);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // VR_GVR_CAPI_SRC_GVR_PRIVATE_H_
diff --git a/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_types_experimental.h b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_types_experimental.h
new file mode 100644
index 0000000..1df2443
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/include/vr/gvr/capi/src/gvr_types_experimental.h
@@ -0,0 +1,68 @@
+#ifndef VR_GVR_CAPI_SRC_GVR_TYPES_EXPERIMENTAL_H_
+#define VR_GVR_CAPI_SRC_GVR_TYPES_EXPERIMENTAL_H_
+
+#include <string>
+
+// ************************************************************************** //
+// *                     DaydreamOS experimental Types                * //
+// ************************************************************************** //
+
+// Visibility of a layer.
+typedef enum {
+  GVR_INVISIBLE = 0,
+  GVR_VISIBLE = 1,
+} gvr_visibility;
+
+// Whether to blur layers behind a layer.
+typedef enum {
+  GVR_BLUR_BEHIND_FALSE = 0,
+  GVR_BLUR_BEHIND_TRUE = 1,
+} gvr_blur_behind;
+
+// GVR external surface
+typedef struct gvr_external_surface_ gvr_external_surface;
+
+// ************************************************************************** //
+// *                     Daydream PlexEng experimental Types                * //
+// ************************************************************************** //
+
+// Types of events that can have callbacks registered.
+// If documented, type will return a payload value when called, or will
+// otherwise be invoked with -1.f.
+// This enum has to be duplicated because there is no way to include from
+// /vr/gvr/render/performance_registry.h.  Duplicate changes made here there.
+typedef enum {
+  // Will be invoked with value -1.f.
+  GVR_ON_ASYNC_REPROJECTION_FRAME_START = 0,
+  // Will be invoked with value -1.f.
+  GVR_ON_ASYNC_REPROJECTION_FRAME_STOP = 1,
+  // When invoked will be called with how late in microseconds the frame was.
+  GVR_ON_ASYNC_REPROJECTION_FRAME_DROP = 2,
+  // The number of types of performance events you can have.
+  // Also note that this value is considered invalid.
+  GVR_NUM_PERF_EVENT_CALLBACK_TYPES = 3,
+} gvr_perf_event_callback_type;
+
+// Types of VR-specific features which may or may not be supported on the
+// underlying platform.
+typedef enum {
+  // Asynchronous reprojection warps the app's rendered frame using the most
+  // recent head pose just before pushing the frame to the display.
+  GVR_ASYNC_REPROJECTION = 0,
+  // Head tracking with 6 degrees of freedom (position & rotation)
+  GVR_6DOF_HEAD_POSE = 1,
+} gvr_feature;
+
+// ************************************************************************** //
+// *                    GVR Analytics experimental APIs                     * //
+// ************************************************************************** //
+
+// Opaque struct returned by gvr_analytics_create_sample, used to transmit an
+// AnaylticsSample proto across the native layer.
+typedef struct gvr_analytics_sample_ {
+  // Serialized AnalyticsSample proto. Note that this is not a C string, meaning
+  // it is not null-terminated and may contain non-terminating nulls.
+  std::string serialized_proto;
+} gvr_analytics_sample;
+
+#endif  // VR_GVR_CAPI_SRC_GVR_TYPES_EXPERIMENTAL_H_
diff --git a/libs/vr/libgvr/prebuilt/lib/android_arm/libgvr.so b/libs/vr/libgvr/prebuilt/lib/android_arm/libgvr.so
new file mode 100644
index 0000000..1d0ba50
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/lib/android_arm/libgvr.so
Binary files differ
diff --git a/libs/vr/libgvr/prebuilt/lib/android_arm/libgvr_audio.so b/libs/vr/libgvr/prebuilt/lib/android_arm/libgvr_audio.so
new file mode 100644
index 0000000..905ca64
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/lib/android_arm/libgvr_audio.so
Binary files differ
diff --git a/libs/vr/libgvr/prebuilt/lib/android_arm64/libgvr.so b/libs/vr/libgvr/prebuilt/lib/android_arm64/libgvr.so
new file mode 100644
index 0000000..d62f7ca
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/lib/android_arm64/libgvr.so
Binary files differ
diff --git a/libs/vr/libgvr/prebuilt/lib/android_arm64/libgvr_audio.so b/libs/vr/libgvr/prebuilt/lib/android_arm64/libgvr_audio.so
new file mode 100644
index 0000000..e342f6a
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/lib/android_arm64/libgvr_audio.so
Binary files differ
diff --git a/libs/vr/libgvr/prebuilt/lib/common_library.aar b/libs/vr/libgvr/prebuilt/lib/common_library.aar
new file mode 100644
index 0000000..13147fe
--- /dev/null
+++ b/libs/vr/libgvr/prebuilt/lib/common_library.aar
Binary files differ
diff --git a/libs/vr/libgvr/shim_gvr.cpp b/libs/vr/libgvr/shim_gvr.cpp
new file mode 100644
index 0000000..4b074e7
--- /dev/null
+++ b/libs/vr/libgvr/shim_gvr.cpp
@@ -0,0 +1,1358 @@
+#define LOG_TAG "libgvr_shim"
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl31.h>
+#include <GLES3/gl3ext.h>
+#include <algorithm>
+#include <cmath>
+
+#ifdef __ARM_NEON
+#include <arm_neon.h>
+#else
+#ifndef __FLOAT32X4T_86
+#define __FLOAT32X4T_86
+typedef float float32x4_t __attribute__ ((__vector_size__ (16)));
+typedef struct float32x4x4_t { float32x4_t val[4]; };
+#endif
+#endif
+
+#include <cutils/log.h>
+#include <dvr/graphics.h>
+#include <dvr/performance_client_api.h>
+#include <dvr/pose_client.h>
+#include <private/dvr/buffer_hub_queue_core.h>
+#include <private/dvr/buffer_hub_queue_producer.h>
+#include <private/dvr/clock_ns.h>
+#include <private/dvr/display_client.h>
+#include <private/dvr/graphics_private.h>
+#include <private/dvr/internal_types.h>
+#include <private/dvr/numeric.h>
+#include <private/dvr/types.h>
+#include <private/dvr/video_mesh_surface_client.h>
+#include <sys/system_properties.h>
+#include <vr/gvr/capi/include/gvr.h>
+#include <vr/gvr/capi/include/gvr_ext.h>
+#include <vr/gvr/capi/include/gvr_util.h>
+#include <vr/gvr/capi/src/gvr_experimental.h>
+#include <vr/gvr/capi/src/gvr_private.h>
+
+#include <android_runtime/android_view_Surface.h>
+#include <gui/Surface.h>
+
+using android::dvr::DisplayClient;
+using android::dvr::EigenToGvrMatrix;
+using android::dvr::FieldOfView;
+using android::dvr::FovRadiansToDegrees;
+using android::dvr::GetSystemClockNs;
+using android::dvr::GvrIdentityMatrix;
+using android::dvr::GvrMatrixToPosef;
+using android::dvr::GvrToDvrFov;
+using android::dvr::GvrToEigenMatrix;
+using android::dvr::GvrToEigenRotation;
+using android::dvr::GvrTranslationMatrix;
+using android::dvr::IsEqual;
+using android::dvr::PosefToGvrMatrix;
+using android::dvr::mat3;
+using android::dvr::mat4;
+using android::dvr::Posef;
+using android::dvr::quat;
+using android::dvr::vec3;
+
+namespace {
+
+constexpr static int32_t GVR_SDK_MAJOR_VERSION = 2;
+constexpr static int32_t GVR_SDK_MINOR_VERSION = 0;
+constexpr static int32_t GVR_SDK_PATCH_VERSION = 0;
+
+// The "DaydreamOS" part has been appended to make easier to see when VrCore
+// dynamic GVR API loading is effectively working.
+static const char* kVersionString = "2.0.0 DaydreamOS";
+static const char* kViewerVendor = "Google";
+static const char* kViewerModel = "Lucid";
+
+// Experimental system property used to provide 6DoF information on 3DoF APIs.
+static const char* kForce6DofProp = "experimental.force_6dof";
+
+static constexpr int kControllerCount = 2;
+
+gvr_frame* GetFrameFromSwapChain(gvr_swap_chain* swap_chain) {
+  return reinterpret_cast<gvr_frame*>(swap_chain);
+}
+
+gvr_swap_chain* GetSwapChainForFrame(gvr_frame* frame) {
+  return reinterpret_cast<gvr_swap_chain*>(frame);
+}
+
+const gvr_swap_chain* GetSwapChainForFrame(const gvr_frame* frame) {
+  return reinterpret_cast<const gvr_swap_chain*>(frame);
+}
+
+// Returns the world to head transform as a Posef.
+Posef ToPosef(const DvrPoseAsync& pose) {
+  return Posef(
+      quat(pose.orientation[3], pose.orientation[0], pose.orientation[1],
+           pose.orientation[2]),
+      vec3(pose.translation[0], pose.translation[1], pose.translation[2]));
+}
+
+// Returns the world to head transform, with 0 position, as a gvr matrix
+gvr_mat4f Gvr6dofTo3dof(const gvr_mat4f& pose) {
+  gvr_mat4f ret = pose;
+  ret.m[0][3] = 0;
+  ret.m[1][3] = 0;
+  ret.m[2][3] = 0;
+  return ret;
+}
+
+void GvrToDvrPose(gvr_mat4f world_to_head_transform,
+                  /*out*/ float32x4_t* orientation,
+                  /*out */ float32x4_t* translation) {
+  Posef pose = GvrMatrixToPosef(world_to_head_transform);
+  (*orientation)[0] = pose.GetRotation().x();
+  (*orientation)[1] = pose.GetRotation().y();
+  (*orientation)[2] = pose.GetRotation().z();
+  (*orientation)[3] = pose.GetRotation().w();
+  (*translation)[0] = pose.GetPosition().x();
+  (*translation)[1] = pose.GetPosition().y();
+  (*translation)[2] = pose.GetPosition().z();
+  (*translation)[3] = 0;
+}
+
+bool MatricesAlmostEqual(const gvr_mat4f& m1, const gvr_mat4f& m2,
+                         float tolerance) {
+  for (int row = 0; row < 4; ++row) {
+    for (int col = 0; col < 4; ++col) {
+      if (!IsEqual(m1.m[row][col], m2.m[row][col], tolerance))
+        return false;
+    }
+  }
+  return true;
+}
+
+gvr_mat4f FovToViewportTransform(const gvr_rectf& fov) {
+  // Depth range (1 1000) is chosen to match gvr impl in google3, which is
+  // chosen to match Unity integration.
+  return EigenToGvrMatrix(
+      GvrToDvrFov(fov).GetProjectionMatrix(1.f, 1000.f).inverse());
+}
+
+gvr_rectf ViewportTransformToFov(const gvr_mat4f& transform) {
+  return DvrToGvrFov(
+      FieldOfView::FromProjectionMatrix(GvrToEigenMatrix(transform).inverse()));
+}
+
+bool GetGlColorFormat(int32_t gvr_color_format,
+                      /*out*/ GLenum* gl_color_format) {
+  switch (gvr_color_format) {
+    case GVR_COLOR_FORMAT_RGBA_8888:
+      *gl_color_format = GL_RGBA8;
+      break;
+    case GVR_COLOR_FORMAT_RGB_565:
+      *gl_color_format = GL_RGB565;
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+
+bool GetGlDepthFormat(int32_t gvr_depth_format,
+                      /*out*/ GLenum* gl_depth_format) {
+  switch (gvr_depth_format) {
+    case GVR_DEPTH_STENCIL_FORMAT_DEPTH_16:
+      *gl_depth_format = GL_DEPTH_COMPONENT16;
+      break;
+    case GVR_DEPTH_STENCIL_FORMAT_DEPTH_24:
+      *gl_depth_format = GL_DEPTH_COMPONENT24;
+      break;
+    case GVR_DEPTH_STENCIL_FORMAT_DEPTH_24_STENCIL_8:
+      *gl_depth_format = GL_DEPTH24_STENCIL8;
+      break;
+    case GVR_DEPTH_STENCIL_FORMAT_DEPTH_32_F:
+      *gl_depth_format = GL_DEPTH_COMPONENT32F;
+      break;
+    case GVR_DEPTH_STENCIL_FORMAT_DEPTH_32_F_STENCIL_8:
+      *gl_depth_format = GL_DEPTH32F_STENCIL8;
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+
+// Returns true on success, false on failure. If the swap_chain already has a
+// DvrGraphicsContext and gvr buffer, they'll be freed first. If creation fails,
+// the DvrGraphicsContext in the swap_chain will be set to null and the
+// corresponding gvr buffer will be freed.
+bool CreateDvrGraphicsContextAndGvrBuffer(gvr_swap_chain* swap_chain) {
+  if (swap_chain->buffers_.empty()) {
+    ALOGE("Can't create a graphics context for an empty swap chain");
+    return false;
+  }
+
+  // We currently only render the first gvr buffer. Create a DvrGraphicsContext
+  // for the first buffer only.
+  gvr_buffer& buf = swap_chain->buffers_[0];
+  buf.FreeGl();
+
+  bool visible;
+  int z_order;
+  if (swap_chain->graphics_context_ != nullptr) {
+    visible = dvrGraphicsSurfaceGetVisible(swap_chain->graphics_context_);
+    z_order = dvrGraphicsSurfaceGetZOrder(swap_chain->graphics_context_);
+    dvrGraphicsContextDestroy(swap_chain->graphics_context_);
+    swap_chain->graphics_context_ = nullptr;
+  } else {
+    visible = buf.spec.initially_visible;
+    z_order = buf.spec.z_order;
+  }
+
+  int width = 0, height = 0;
+  GLuint texture_id = 0;
+  GLenum texture_target = 0;
+  DvrSurfaceParameter surface_params[] = {
+      DVR_SURFACE_PARAMETER_IN(DISABLE_DISTORTION, false),
+      DVR_SURFACE_PARAMETER_IN(CREATE_GL_CONTEXT, 0),
+      DVR_SURFACE_PARAMETER_IN(WIDTH, buf.spec.size.width),
+      DVR_SURFACE_PARAMETER_IN(HEIGHT, buf.spec.size.height),
+      DVR_SURFACE_PARAMETER_IN(BLUR_BEHIND, buf.spec.blur_behind),
+      DVR_SURFACE_PARAMETER_IN(VISIBLE, visible),
+      DVR_SURFACE_PARAMETER_IN(Z_ORDER, z_order),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_WIDTH, &width),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_HEIGHT, &height),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_TEXTURE_TARGET_TYPE, &texture_target),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_TEXTURE_TARGET_ID, &texture_id),
+      DVR_SURFACE_PARAMETER_LIST_END,
+  };
+
+  DvrGraphicsContext* graphics_context;
+  int ret = dvrGraphicsContextCreate(surface_params, &graphics_context);
+  if (ret < 0) {
+    ALOGE("dvrGraphicsContextCreate failed: %d (%s)", ret, strerror(-ret));
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    return false;
+  }
+
+  // Sanity check that the size of the buffer we allocated from the system is
+  // what we expect
+  if (buf.spec.size != gvr_sizei{width, height}) {
+    ALOGE(
+        "The created surface is the wrong size."
+        " Should be %dx%d, instead got %dx%d.",
+        buf.spec.size.width, buf.spec.size.height, width, height);
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    dvrGraphicsContextDestroy(graphics_context);
+    return false;
+  }
+
+  buf = gvr_buffer(swap_chain->context, buf.spec, texture_id, texture_target);
+  if (buf.frame_buffer == 0) {
+    dvrGraphicsContextDestroy(graphics_context);
+    return false;
+  }
+
+  swap_chain->graphics_context_ = graphics_context;
+  return true;
+}
+
+bool SwapChainResizeBuffer(gvr_swap_chain* swap_chain, int buffer_index) {
+  gvr_buffer& buf = swap_chain->buffers_[buffer_index];
+  buf.FreeGl();
+  gvr_sizei orig_size = buf.spec.size;
+  buf.spec.size = buf.requested_size;
+  bool resize_successful = false;
+  if (buffer_index == 0) {
+    resize_successful = CreateDvrGraphicsContextAndGvrBuffer(swap_chain);
+  } else {
+    buf = gvr_buffer(swap_chain->context, buf.spec, 0, GL_TEXTURE_2D);
+    resize_successful = buf.frame_buffer != 0;
+  }
+
+  if (resize_successful) {
+    // The resize was successful, so clear the resize request
+    buf.requested_size = {-1, -1};
+  } else {
+    ALOGE("Failed to resize buffer. orig_size=%dx%d requested_size=%dx%d.",
+          orig_size.width, orig_size.height, buf.requested_size.width,
+          buf.requested_size.height);
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    buf.spec.size = orig_size;
+  }
+
+  return resize_successful;
+}
+
+void WaitNextFrame(gvr_swap_chain* swap_chain, int64_t start_delay_nanos,
+                   gvr_frame_schedule* out_next_frame_schedule,
+                   bool called_by_app) {
+  if (called_by_app)
+    swap_chain->wait_next_frame_called_by_app_ = true;
+
+  DvrFrameSchedule dvr_schedule;
+  int ret = dvrGraphicsWaitNextFrame(swap_chain->graphics_context_,
+                                     start_delay_nanos, &dvr_schedule);
+  if (ret < 0) {
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    return;
+  }
+  if (out_next_frame_schedule) {
+    out_next_frame_schedule->vsync_count = dvr_schedule.vsync_count;
+    out_next_frame_schedule->scheduled_finish.monotonic_system_time_nanos =
+        dvr_schedule.scheduled_frame_finish_ns;
+  }
+
+  DvrPoseAsync pose;
+  ret = dvrPoseGet(swap_chain->context->pose_client_, dvr_schedule.vsync_count,
+                   &pose);
+  if (ret < 0) {
+    ALOGW("dvrPoseGet failed: %d", ret);
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    return;
+  }
+
+  swap_chain->context->next_frame_6dof_pose_ = PosefToGvrMatrix(ToPosef(pose));
+
+  for (int i = 0; i < kControllerCount; ++i) {
+    ret = dvrPoseGetController(swap_chain->context->pose_client_, i,
+                               dvr_schedule.vsync_count, &pose);
+    if (ret == 0) {
+      // Silently fail when there are no controllers.
+      swap_chain->context->next_frame_controller_pose_[i] =
+          PosefToGvrMatrix(ToPosef(pose).Inverse());
+    }
+  }
+}
+
+bool VerifyBufferIndex(const std::string& function_name,
+                       const gvr_swap_chain* swap_chain, int index) {
+  if (index > static_cast<int32_t>(swap_chain->buffers_.size())) {
+    ALOGE("%s out of range buffer index. index=%d num_buffers=%zu.",
+          function_name.c_str(), index, swap_chain->buffers_.size());
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    return false;
+  }
+  return true;
+}
+
+}  // anonymous namespace
+
+gvr_context* gvr_create(JNIEnv* env, jobject /* app_context */,
+                        jobject /* class_loader */) {
+  std::unique_ptr<gvr_context> context(new gvr_context);
+
+  // Set cpu set to avoid default scheduling randomness.
+  dvrSetCpuPartition(0, "/application/performance");
+
+  context->jni_env_ = env;
+  context->pose_client_ = dvrPoseCreate();
+  if (!context->pose_client_) {
+    ALOGE("Failed to create pose client");
+    return nullptr;
+  }
+
+  context->display_client_ = DisplayClient::Create();
+  if (!context->display_client_) {
+    ALOGE("Failed to create display client");
+    return nullptr;
+  }
+
+  int ret =
+      context->display_client_->GetDisplayMetrics(&context->display_metrics_);
+  if (ret < 0) {
+    ALOGE("Failed to get display metrics: %d (%s)", ret, strerror(-ret));
+    return nullptr;
+  }
+
+  const float* left_fov = context->display_metrics_.left_fov_lrbt.data();
+  context->left_eye_viewport_transform_ =
+      FovToViewportTransform(FovRadiansToDegrees(
+          gvr_rectf{left_fov[0], left_fov[1], left_fov[2], left_fov[3]}));
+
+  const float* right_fov = context->display_metrics_.right_fov_lrbt.data();
+  context->right_eye_viewport_transform_ =
+      FovToViewportTransform(FovRadiansToDegrees(
+          gvr_rectf{right_fov[0], right_fov[1], right_fov[2], right_fov[3]}));
+
+  context->next_frame_6dof_pose_ = GvrIdentityMatrix();
+
+  for (int i = 0; i < kControllerCount; ++i) {
+    context->next_frame_controller_pose_[i] = GvrIdentityMatrix();
+  }
+
+  // Check the system property to force 6DoF when requested 3DoF.
+  char prop_buffer[PROP_VALUE_MAX];
+  if (__system_property_get(kForce6DofProp, prop_buffer) &&
+      (!strncasecmp("1", prop_buffer, PROP_VALUE_MAX) ||
+       !strncasecmp("true", prop_buffer, PROP_VALUE_MAX))) {
+    context->force_6dof_ = true;
+  }
+
+  return context.release();
+}
+
+gvr_version gvr_get_version() {
+  gvr_version version = {};
+  version.major = GVR_SDK_MAJOR_VERSION;
+  version.minor = GVR_SDK_MINOR_VERSION;
+  version.patch = GVR_SDK_PATCH_VERSION;
+  return version;
+}
+
+const char* gvr_get_version_string() { return kVersionString; }
+
+int32_t gvr_get_error(gvr_context* gvr) { return gvr->last_error_; }
+
+int32_t gvr_clear_error(gvr_context* gvr) {
+  int32_t last_error = gvr->last_error_;
+  gvr->last_error_ = GVR_ERROR_NONE;
+  return last_error;
+}
+
+const char* gvr_get_error_string(int32_t error_code) {
+  switch (error_code) {
+    case GVR_ERROR_NONE:
+      return "No error";
+    case GVR_ERROR_CONTROLLER_CREATE_FAILED:
+      return "Creation of GVR controller context failed";
+    case GVR_ERROR_NO_FRAME_AVAILABLE:
+      return "No frame available in swap chain";
+    case GVR_ERROR_INTERNAL:
+      return "Internal error";
+    default:
+      return "(Internal error: unknown error code)";
+  }
+}
+
+const gvr_user_prefs* gvr_get_user_prefs(gvr_context* gvr) {
+  return &gvr->user_prefs_;
+}
+
+int32_t gvr_user_prefs_get_controller_handedness(
+    const gvr_user_prefs* /* user_prefs */) {
+  return GVR_CONTROLLER_RIGHT_HANDED;
+}
+
+gvr_context_::~gvr_context_() {
+  for (gvr_swap_chain* swap_chain : swap_chains_)
+    swap_chain->context = nullptr;
+  if (pose_client_)
+    dvrPoseDestroy(pose_client_);
+}
+
+void gvr_destroy(gvr_context** gvr) {
+  if (!gvr || !(*gvr)) {
+    ALOGW("gvr_destroy: Invalid gvr_context pointer.");
+    return;
+  }
+  delete *gvr;
+  *gvr = nullptr;
+}
+
+void gvr_initialize_gl(gvr_context* /* gvr */) {}
+
+bool gvr_get_async_reprojection_enabled(const gvr_context* /* gvr */) {
+  return true;
+}
+
+void gvr_get_recommended_buffer_viewports(
+    const gvr_context* gvr, gvr_buffer_viewport_list* viewport_list) {
+  gvr_buffer_viewport left(
+      /*buffer_index*/ 0,
+      /*uv*/ {0, .5f, 0, 1}, gvr->left_eye_viewport_transform_, GVR_LEFT_EYE,
+      GVR_EXTERNAL_SURFACE_ID_NONE, GVR_REPROJECTION_FULL);
+
+  gvr_buffer_viewport right(
+      /*buffer_index*/ 0,
+      /*uv*/ {.5f, 1, 0, 1}, gvr->right_eye_viewport_transform_, GVR_RIGHT_EYE,
+      GVR_EXTERNAL_SURFACE_ID_NONE, GVR_REPROJECTION_FULL);
+
+  viewport_list->viewports.resize(2);
+  viewport_list->viewports[0] = left;
+  viewport_list->viewports[1] = right;
+}
+
+void gvr_get_screen_buffer_viewports(const gvr_context* gvr,
+                                     gvr_buffer_viewport_list* viewport_list) {
+  gvr_get_recommended_buffer_viewports(gvr, viewport_list);
+}
+
+gvr_sizei gvr_get_maximum_effective_render_target_size(const gvr_context* gvr) {
+  return gvr_sizei{
+      static_cast<int32_t>(gvr->display_metrics_.distorted_width),
+      static_cast<int32_t>(gvr->display_metrics_.distorted_height)};
+}
+
+gvr_sizei gvr_get_screen_target_size(const gvr_context* gvr) {
+  // DisplayMetrics returns native_width and native_height for the display in
+  // portrait orientation, which our device is never in. Swap the width and
+  // height to account for this.
+  return gvr_sizei{
+      static_cast<int32_t>(gvr->display_metrics_.display_native_height),
+      static_cast<int32_t>(gvr->display_metrics_.display_native_width)};
+}
+
+void gvr_set_surface_size(gvr_context* gvr,
+                          gvr_sizei /* surface_size_pixels */) {
+  // TODO(leandrogracia): this needs to be properly implemented.
+  ALOGE("gvr_set_surface_size not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+}
+
+void gvr_distort_to_screen(
+    gvr_context* gvr, int32_t /* texture_id */,
+    const gvr_buffer_viewport_list* /* viewport_list */,
+    gvr_mat4f /* head_space_from_start_space */,
+    gvr_clock_time_point /* target_presentation_time */) {
+  // TODO(leandrogracia): this needs to be properly implemented.
+  ALOGE("gvr_distort_to_screen not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Viewports and viewport lists
+/////////////////////////////////////////////////////////////////////////////
+
+bool gvr_buffer_viewport::operator==(const gvr_buffer_viewport_& other) const {
+  return buffer_index == other.buffer_index && uv == other.uv &&
+         eye == other.eye && external_surface_id == other.external_surface_id &&
+         reprojection == other.reprojection &&
+         MatricesAlmostEqual(transform, other.transform, 1e-5f);
+}
+
+gvr_buffer_viewport* gvr_buffer_viewport_create(gvr_context* /* gvr */) {
+  return new gvr_buffer_viewport;
+}
+
+void gvr_buffer_viewport_destroy(gvr_buffer_viewport** viewport) {
+  if (viewport) {
+    delete *viewport;
+    *viewport = nullptr;
+  }
+}
+
+gvr_rectf gvr_buffer_viewport_get_source_uv(
+    const gvr_buffer_viewport* viewport) {
+  return viewport->uv;
+}
+
+void gvr_buffer_viewport_set_source_uv(gvr_buffer_viewport* viewport,
+                                       gvr_rectf uv) {
+  viewport->uv = uv;
+}
+
+gvr_rectf gvr_buffer_viewport_get_source_fov(
+    const gvr_buffer_viewport* viewport) {
+  return ViewportTransformToFov(viewport->transform);
+}
+
+void gvr_buffer_viewport_set_source_fov(gvr_buffer_viewport* viewport,
+                                        gvr_rectf fov) {
+  viewport->transform = FovToViewportTransform(fov);
+}
+
+gvr_mat4f gvr_buffer_viewport_get_transform(
+    const gvr_buffer_viewport* viewport) {
+  return viewport->transform;
+}
+
+void gvr_buffer_viewport_set_transform(gvr_buffer_viewport* viewport,
+                                       gvr_mat4f transform) {
+  viewport->transform = transform;
+}
+
+int32_t gvr_buffer_viewport_get_target_eye(
+    const gvr_buffer_viewport* viewport) {
+  return viewport->eye;
+}
+
+void gvr_buffer_viewport_set_target_eye(gvr_buffer_viewport* viewport,
+                                        int32_t index) {
+  viewport->eye = index;
+}
+
+int32_t gvr_buffer_viewport_get_source_buffer_index(
+    const gvr_buffer_viewport* viewport) {
+  return viewport->buffer_index;
+}
+
+void gvr_buffer_viewport_set_source_buffer_index(gvr_buffer_viewport* viewport,
+                                                 int32_t buffer_index) {
+  viewport->buffer_index = buffer_index;
+}
+
+int32_t gvr_buffer_viewport_get_external_surface_id(
+    const gvr_buffer_viewport* viewport) {
+  return viewport->external_surface_id;
+}
+
+void gvr_buffer_viewport_set_external_surface_id(gvr_buffer_viewport* viewport,
+                                                 int32_t external_surface_id) {
+  viewport->external_surface_id = external_surface_id;
+}
+
+int32_t gvr_buffer_viewport_get_reprojection(
+    const gvr_buffer_viewport* viewport) {
+  return viewport->reprojection;
+}
+
+void gvr_buffer_viewport_set_reprojection(gvr_buffer_viewport* viewport,
+                                          int32_t reprojection) {
+  viewport->reprojection = static_cast<gvr_reprojection>(reprojection);
+}
+
+bool gvr_buffer_viewport_equal(const gvr_buffer_viewport* a,
+                               const gvr_buffer_viewport* b) {
+  return *a == *b;
+}
+
+gvr_buffer_viewport_list* gvr_buffer_viewport_list_create(
+    const gvr_context* /* gvr */) {
+  return new gvr_buffer_viewport_list;
+}
+
+void gvr_buffer_viewport_list_destroy(
+    gvr_buffer_viewport_list** viewport_list) {
+  if (!viewport_list || !(*viewport_list)) {
+    ALOGW("gvr_buffer_viewport_list_destroy: Invalid list pointer.");
+    return;
+  }
+  delete *viewport_list;
+  *viewport_list = nullptr;
+}
+
+size_t gvr_buffer_viewport_list_get_size(
+    const gvr_buffer_viewport_list* viewport_list) {
+  return viewport_list->viewports.size();
+}
+
+void gvr_buffer_viewport_list_get_item(
+    const gvr_buffer_viewport_list* viewport_list, size_t index,
+    gvr_buffer_viewport* viewport) {
+  *viewport = viewport_list->viewports[index];
+}
+
+void gvr_buffer_viewport_list_set_item(gvr_buffer_viewport_list* viewport_list,
+                                       size_t index,
+                                       const gvr_buffer_viewport* viewport) {
+  if (index < viewport_list->viewports.size())
+    viewport_list->viewports[index] = *viewport;
+  else
+    viewport_list->viewports.push_back(*viewport);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Swapchains and frames
+/////////////////////////////////////////////////////////////////////////////
+
+gvr_buffer_spec* gvr_buffer_spec_create(gvr_context* /* gvr */) {
+  return new gvr_buffer_spec;
+}
+
+void gvr_buffer_spec_destroy(gvr_buffer_spec** spec) {
+  if (spec) {
+    delete *spec;
+    *spec = nullptr;
+  }
+}
+
+gvr_sizei gvr_buffer_spec_get_size(const gvr_buffer_spec* spec) {
+  return spec->size;
+}
+
+void gvr_buffer_spec_set_size(gvr_buffer_spec* spec, gvr_sizei size) {
+  spec->size = size;
+}
+
+int32_t gvr_buffer_spec_get_samples(const gvr_buffer_spec* spec) {
+  return spec->msaa_samples;
+}
+
+void gvr_buffer_spec_set_samples(gvr_buffer_spec* spec, int32_t num_samples) {
+  spec->msaa_samples = num_samples;
+}
+
+void gvr_buffer_spec_set_color_format(gvr_buffer_spec* spec,
+                                      int32_t color_format) {
+  spec->color_format = color_format;
+}
+
+void gvr_buffer_spec_set_depth_stencil_format(gvr_buffer_spec* spec,
+                                              int32_t depth_stencil_format) {
+  spec->depth_stencil_format = depth_stencil_format;
+}
+
+void gvr_buffer_spec_set_z_order(gvr_buffer_spec* spec, int z_order) {
+  spec->z_order = z_order;
+}
+
+void gvr_buffer_spec_set_visibility(gvr_buffer_spec* spec,
+                                    int32_t visibility) {
+  spec->initially_visible = (visibility != GVR_INVISIBLE);
+}
+
+void gvr_buffer_spec_set_blur_behind(gvr_buffer_spec* spec,
+                                     int32_t blur_behind) {
+  spec->blur_behind = (blur_behind != GVR_BLUR_BEHIND_FALSE);
+}
+
+void gvr_buffer::SetDefaults() {
+  spec = gvr_buffer_spec();
+  frame_buffer = 0;
+  color_render_buffer = 0;
+  depth_stencil_render_buffer = 0;
+  requested_size = {-1, -1};
+}
+
+gvr_buffer::gvr_buffer() { SetDefaults(); }
+
+gvr_buffer::gvr_buffer(gvr_context* gvr, const gvr_buffer_spec& spec_in,
+                       GLuint texture_id, GLenum texture_target) {
+  SetDefaults();
+  spec = spec_in;
+
+  glGetError();  // Clear error state
+  glGenFramebuffers(1, &frame_buffer);
+  glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);
+
+  if (texture_id == 0) {
+    GLenum gl_color_format;
+    if (!GetGlColorFormat(spec.color_format, &gl_color_format)) {
+      ALOGE("Unknown color format: %d", spec.color_format);
+      gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+      FreeGl();
+      return;
+    }
+
+    glGenRenderbuffers(1, &color_render_buffer);
+    glBindRenderbuffer(GL_RENDERBUFFER, color_render_buffer);
+    if (spec.msaa_samples < 2) {
+      glRenderbufferStorage(GL_RENDERBUFFER, gl_color_format, spec.size.width,
+                            spec.size.height);
+    } else {
+      glRenderbufferStorageMultisample(GL_RENDERBUFFER, spec.msaa_samples,
+                                       gl_color_format, spec.size.width,
+                                       spec.size.height);
+    }
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                              GL_RENDERBUFFER, color_render_buffer);
+  } else {
+    if (spec.msaa_samples < 2) {
+      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                             texture_target, texture_id, 0);
+    } else {
+      glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                           texture_target, texture_id, 0,
+                                           spec.msaa_samples);
+    }
+  }
+
+  if (spec.depth_stencil_format != GVR_DEPTH_STENCIL_FORMAT_NONE) {
+    GLenum gl_depth_format;
+    if (!GetGlDepthFormat(spec.depth_stencil_format, &gl_depth_format)) {
+      ALOGE("Unknown depth/stencil format: %d", spec.depth_stencil_format);
+      gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+      FreeGl();
+      return;
+    }
+
+    glGenRenderbuffers(1, &depth_stencil_render_buffer);
+    glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil_render_buffer);
+    if (spec.msaa_samples < 2) {
+      glRenderbufferStorage(GL_RENDERBUFFER, gl_depth_format, spec.size.width,
+                            spec.size.height);
+    } else {
+      glRenderbufferStorageMultisample(GL_RENDERBUFFER, spec.msaa_samples,
+                                       gl_depth_format, spec.size.width,
+                                       spec.size.height);
+    }
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                              GL_RENDERBUFFER, depth_stencil_render_buffer);
+  }
+
+  GLenum gl_error = glGetError();
+  if (gl_error != GL_NO_ERROR) {
+    ALOGE("GL error after creating framebuffer: %d", gl_error);
+    gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+    FreeGl();
+    return;
+  }
+
+  GLenum framebuffer_complete_result = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+  if (framebuffer_complete_result != GL_FRAMEBUFFER_COMPLETE) {
+    ALOGE("Framebuffer setup failed. glCheckFramebufferStatus returned %d",
+          framebuffer_complete_result);
+    gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+    FreeGl();
+    return;
+  }
+}
+
+void gvr_buffer::FreeGl() {
+  if (frame_buffer != 0) {
+    glDeleteFramebuffers(1, &frame_buffer);
+    frame_buffer = 0;
+  }
+  if (color_render_buffer != 0) {
+    glDeleteRenderbuffers(1, &color_render_buffer);
+    color_render_buffer = 0;
+  }
+  if (depth_stencil_render_buffer != 0) {
+    glDeleteRenderbuffers(1, &depth_stencil_render_buffer);
+    depth_stencil_render_buffer = 0;
+  }
+}
+
+gvr_buffer::~gvr_buffer() { FreeGl(); }
+
+gvr_buffer::gvr_buffer(gvr_buffer&& other) {
+  spec = other.spec;
+  frame_buffer = other.frame_buffer;
+  color_render_buffer = other.color_render_buffer;
+  depth_stencil_render_buffer = other.depth_stencil_render_buffer;
+  requested_size = other.requested_size;
+  other.SetDefaults();
+}
+
+gvr_buffer& gvr_buffer::operator=(gvr_buffer&& other) {
+  if (this == &other)
+    return *this;
+  spec = other.spec;
+  frame_buffer = other.frame_buffer;
+  color_render_buffer = other.color_render_buffer;
+  depth_stencil_render_buffer = other.depth_stencil_render_buffer;
+  requested_size = other.requested_size;
+  other.SetDefaults();
+  return *this;
+}
+
+gvr_swap_chain* gvr_swap_chain_create(gvr_context* gvr,
+                                      const gvr_buffer_spec** buffers,
+                                      int32_t count) {
+  if (count == 0) {
+    ALOGE("At least one buffer must be requested");
+    gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+    return nullptr;
+  }
+
+  // We only support one buffer, but it's common for gvr apps to use more than
+  // one. Print an error to the log if the app requests more than one buffer,
+  // but continue on. We'll only render the first buffer in that case.
+  if (count > 1) {
+    ALOGE(
+        "Only one buffer is supported but the app requested %d."
+        " Only the first buffer will be rendered.",
+        count);
+  }
+
+  std::unique_ptr<gvr_swap_chain> swap_chain(new gvr_swap_chain(gvr));
+
+  // The first buffer gets a DvrGraphicsContext, which contains the surface we
+  // pass to displayd for rendering.
+  swap_chain->buffers_.push_back(gvr_buffer());
+  swap_chain->buffers_.back().spec = *buffers[0];
+  if (!CreateDvrGraphicsContextAndGvrBuffer(swap_chain.get()))
+    return nullptr;
+
+  // The rest of the buffers, which we don't render for now, get color render
+  // buffers.
+  for (int i = 1; i < count; ++i) {
+    swap_chain->buffers_.push_back(
+        gvr_buffer(gvr, *buffers[i], 0, GL_TEXTURE_2D));
+    if (swap_chain->buffers_.back().frame_buffer == 0)
+      return nullptr;
+  }
+
+  gvr->swap_chains_.push_back(swap_chain.get());
+  return swap_chain.release();
+}
+
+gvr_swap_chain_::~gvr_swap_chain_() {
+  if (context) {
+    auto iter = std::find(std::begin(context->swap_chains_),
+                          std::end(context->swap_chains_), this);
+    if (iter != context->swap_chains_.end())
+      context->swap_chains_.erase(iter);
+  }
+  buffers_.clear();
+  if (graphics_context_ != nullptr)
+    dvrGraphicsContextDestroy(graphics_context_);
+}
+
+void gvr_swap_chain_destroy(gvr_swap_chain** swap_chain) {
+  if (!swap_chain || !(*swap_chain)) {
+    ALOGW("gvr_swap_chain_destroy: Invalid swap chain pointer.");
+    return;
+  }
+  delete *swap_chain;
+  *swap_chain = nullptr;
+}
+
+int32_t gvr_swap_chain_get_buffer_count(const gvr_swap_chain* swap_chain) {
+  return swap_chain ? static_cast<int32_t>(swap_chain->buffers_.size()) : 0;
+}
+
+gvr_sizei gvr_swap_chain_get_buffer_size(gvr_swap_chain* swap_chain,
+                                         int32_t index) {
+  if (!VerifyBufferIndex("gvr_swap_chain_get_buffer_size", swap_chain, index))
+    return gvr_sizei{0, 0};
+
+  gvr_buffer& buf = swap_chain->buffers_[index];
+  if (buf.requested_size != gvr_sizei{-1, -1})
+    return buf.requested_size;
+  else
+    return buf.spec.size;
+}
+
+void gvr_swap_chain_resize_buffer(gvr_swap_chain* swap_chain, int32_t index,
+                                  gvr_sizei size) {
+  if (!VerifyBufferIndex("gvr_swap_chain_resize_buffer", swap_chain, index))
+    return;
+
+  gvr_buffer& buf = swap_chain->buffers_[index];
+  if (size != buf.spec.size)
+    buf.requested_size = size;
+  else
+    buf.requested_size = {-1, -1};
+}
+
+gvr_frame* gvr_swap_chain_acquire_frame(gvr_swap_chain* swap_chain) {
+  if (!swap_chain)
+    return nullptr;
+
+  if (swap_chain->frame_acquired_) {
+    gvr_set_error(swap_chain->context, GVR_ERROR_NO_FRAME_AVAILABLE);
+    return nullptr;
+  }
+
+  // Resize buffers if necessary
+  for (int i = 0; i < static_cast<int>(swap_chain->buffers_.size()); ++i) {
+    gvr_buffer& buf = swap_chain->buffers_[i];
+    if (buf.requested_size != gvr_sizei{-1, -1}) {
+      if (!SwapChainResizeBuffer(swap_chain, i))
+        return nullptr;
+    }
+  }
+
+  // Only call gvr_wait_next_frame() if the app didn't call it already.
+  if (!swap_chain->wait_next_frame_called_by_app_)
+    WaitNextFrame(swap_chain, 0, nullptr, /*called_by_app*/ false);
+
+  int ret = dvrBeginRenderFrame(swap_chain->graphics_context_);
+  if (ret < 0) {
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    return nullptr;
+  }
+
+  swap_chain->frame_acquired_ = true;
+  return GetFrameFromSwapChain(swap_chain);
+}
+
+void gvr_frame_bind_buffer(gvr_frame* frame, int32_t index) {
+  gvr_swap_chain* swap_chain = GetSwapChainForFrame(frame);
+  if (!VerifyBufferIndex("gvr_frame_bind_buffer", swap_chain, index))
+    return;
+  glBindFramebuffer(GL_FRAMEBUFFER, swap_chain->buffers_[index].frame_buffer);
+}
+
+void gvr_frame_unbind(gvr_frame* /* frame */) {
+  glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+gvr_sizei gvr_frame_get_buffer_size(const gvr_frame* frame, int32_t index) {
+  const gvr_swap_chain* swap_chain = GetSwapChainForFrame(frame);
+  if (!VerifyBufferIndex("gvr_frame_get_buffer_size", swap_chain, index))
+    return gvr_sizei{0, 0};
+  return swap_chain->buffers_[index].spec.size;
+}
+
+int32_t gvr_frame_get_framebuffer_object(const gvr_frame* frame,
+                                         int32_t index) {
+  const gvr_swap_chain* swap_chain = GetSwapChainForFrame(frame);
+  if (!VerifyBufferIndex("gvr_frame_get_framebuffer_object", swap_chain, index))
+    return 0;
+  return swap_chain->buffers_[index].frame_buffer;
+}
+
+void gvr_frame_submit(gvr_frame** frame, const gvr_buffer_viewport_list* list,
+                      gvr_mat4f head_space_from_start_space) {
+  if (!frame)
+    return;
+
+  gvr_swap_chain* swap_chain = GetSwapChainForFrame(*frame);
+
+  if (!swap_chain->frame_acquired_) {
+    ALOGE("Frame was never acquired before being submitted");
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    return;
+  }
+
+  *frame = nullptr;
+  swap_chain->frame_acquired_ = false;
+
+  // Currently, support for arbitrary buffer viewport configs is very limited.
+  // We assume that the first two viewports have to be the recommended color
+  // buffer viewports, followed by pairs of external external buffer viewports
+  // for video rendering.
+  gvr_buffer_viewport_list supported_viewports;
+  gvr_get_recommended_buffer_viewports(swap_chain->context,
+                                       &supported_viewports);
+  for (size_t i = 0; i < supported_viewports.viewports.size(); ++i) {
+    if (i >= list->viewports.size() ||
+        supported_viewports.viewports[i] != list->viewports[i]) {
+      ALOGE("Custom viewport configurations are not fully supported.");
+      gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+      return;
+    }
+  }
+
+  for (size_t i = supported_viewports.viewports.size();
+       i < list->viewports.size(); ++i) {
+    int32_t external_surface_id = list->viewports[i].external_surface_id;
+    // Ignore additional custom buffer viewport for now, only those buffer
+    // viewports backed by external surfaces are supported.
+    // TODO(b/31442094, b/31771861, 28954457) Add full GVR buffer viewport
+    // support.
+    if (external_surface_id == GVR_EXTERNAL_SURFACE_ID_NONE)
+      continue;
+
+    auto surface_it = swap_chain->external_surfaces_.find(external_surface_id);
+    if (surface_it == swap_chain->external_surfaces_.end()) {
+      ALOGE("Cannot find external_surface by id: %d.", external_surface_id);
+      gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+      return;
+    }
+
+    // Pass the transfrom matrix of video mesh to displayd.
+    dvrGraphicsVideoMeshSurfacePresent(
+        swap_chain->graphics_context_, surface_it->second->video_surface,
+        list->viewports[i].eye,
+        GvrToEigenMatrix(list->viewports[i].transform).data());
+  }
+
+  float32x4_t pose_orientation, pose_translation;
+  GvrToDvrPose(head_space_from_start_space, &pose_orientation,
+               &pose_translation);
+  int ret = dvrSetEdsPose(swap_chain->graphics_context_, pose_orientation,
+                          pose_translation);
+  if (ret < 0)
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+
+  ret = dvrPresent(swap_chain->graphics_context_);
+  if (ret < 0) {
+    gvr_set_error(swap_chain->context, GVR_ERROR_INTERNAL);
+    return;
+  }
+}
+
+void gvr_bind_default_framebuffer(gvr_context* /* gvr */) {
+  glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Head tracking
+/////////////////////////////////////////////////////////////////////////////
+
+gvr_clock_time_point gvr_get_time_point_now() {
+  return gvr_clock_time_point{GetSystemClockNs()};
+}
+
+gvr_mat4f gvr_get_head_space_from_start_space_rotation(
+    const gvr_context* gvr, const gvr_clock_time_point /* time */) {
+  // TODO(steventhomas): Implement prediction according to the supplied time
+  // value.
+  return gvr->force_6dof_ ? gvr->next_frame_6dof_pose_
+                          : Gvr6dofTo3dof(gvr->next_frame_6dof_pose_);
+}
+
+gvr_mat4f gvr_apply_neck_model(const gvr_context* /* gvr */,
+                               gvr_mat4f head_space_from_start_space_rotation,
+                               float /* factor */) {
+  // TODO(leandrogracia): this needs to be properly implemented.
+  ALOGE("gvr_apply_neck_model not implemented.");
+  return head_space_from_start_space_rotation;
+}
+
+// This is used to turn off sensors to save power. Not relevant for our all in
+// one device.
+void gvr_pause_tracking(gvr_context* /* gvr */) {}
+
+// This is used to turn on sensors. Not relevant for our all in one device.
+void gvr_resume_tracking(gvr_context* /* gvr */) {}
+
+void gvr_reset_tracking(gvr_context* gvr) {
+  // TODO(leandrogracia): this needs to be properly implemented.
+  ALOGE("gvr_reset_tracking not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+}
+
+void gvr_recenter_tracking(gvr_context* gvr) {
+  // TODO(leandrogracia): this needs to be properly implemented.
+  ALOGE("gvr_recenter_tracking not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Head mounted display
+/////////////////////////////////////////////////////////////////////////////
+
+bool gvr_set_default_viewer_profile(gvr_context* gvr,
+                                    const char* /* viewer_profile_uri */) {
+  // TODO(leandrogracia): this needs to be properly implemented.
+  ALOGE("gvr_set_default_viewer_profile not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+  return false;
+}
+
+void gvr_refresh_viewer_profile(gvr_context* /* gvr */) {}
+
+const char* gvr_get_viewer_vendor(const gvr_context* /* gvr */) {
+  return kViewerVendor;
+}
+
+const char* gvr_get_viewer_model(const gvr_context* /* gvr */) {
+  return kViewerModel;
+}
+
+int32_t gvr_get_viewer_type(const gvr_context* /* gvr */) {
+  // TODO(leandrogracia): this needs to be properly implemented.
+  // In this case, we will probably need to define a new viewer type that
+  // has 6DoF support.
+  return GVR_VIEWER_TYPE_DAYDREAM;
+}
+
+gvr_mat4f gvr_get_eye_from_head_matrix(const gvr_context* gvr,
+                                       const int32_t eye) {
+  float eye_mult = eye == GVR_LEFT_EYE ? 1 : -1;
+  return GvrTranslationMatrix(
+      .5f * eye_mult * gvr->display_metrics_.inter_lens_distance_m, 0, 0);
+}
+
+gvr_recti gvr_get_window_bounds(const gvr_context* gvr) {
+  // Our app windows are always full screen
+  gvr_sizei screen_size = gvr_get_screen_target_size(gvr);
+  return gvr_recti{0, screen_size.width, 0, screen_size.height};
+}
+
+void gvr_compute_distorted_point(const gvr_context* /* gvr */,
+                                 const int32_t /* eye */,
+                                 const gvr_vec2f /* uv_in */,
+                                 gvr_vec2f /* uv_out */[3]) {
+  // TODO(leandrogracia): this needs to be properly implemented.
+  ALOGE("gvr_compute_distorted_point not implemented.");
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// GVR API extension (from gvr_ext.h)
+/////////////////////////////////////////////////////////////////////////////
+
+gvr_frame_schedule* gvr_frame_schedule_create() {
+  return new gvr_frame_schedule;
+}
+
+void gvr_frame_schedule_destroy(gvr_frame_schedule** schedule) {
+  if (!schedule || !(*schedule)) {
+    ALOGW("gvr_frame_schedule_destroy: Invalid frame schedule pointer.");
+    return;
+  }
+  delete *schedule;
+  *schedule = nullptr;
+}
+
+uint32_t gvr_frame_schedule_get_vsync_count(gvr_frame_schedule* schedule) {
+  return schedule->vsync_count;
+}
+
+gvr_clock_time_point gvr_frame_schedule_get_scheduled_finish(
+    gvr_frame_schedule* schedule) {
+  return schedule->scheduled_finish;
+}
+
+void gvr_wait_next_frame(gvr_swap_chain* swap_chain, int64_t start_delay_nanos,
+                         gvr_frame_schedule* out_next_frame_schedule) {
+  WaitNextFrame(swap_chain, start_delay_nanos, out_next_frame_schedule,
+                /*called_by_app*/ true);
+}
+
+gvr_mat4f gvr_get_6dof_head_pose_in_start_space(gvr_context* gvr,
+                                                uint32_t vsync_count) {
+  DvrPoseAsync pose;
+  int ret = dvrPoseGet(gvr->pose_client_, vsync_count, &pose);
+  if (ret < 0) {
+    ALOGW("dvrPoseGet failed: %d", ret);
+    gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+    return GvrIdentityMatrix();
+  }
+
+  return PosefToGvrMatrix(ToPosef(pose));
+}
+
+gvr_mat4f gvr_get_head_space_from_start_space_pose(
+    gvr_context* gvr, const gvr_clock_time_point /* time */) {
+  // TODO(leandrogracia): implement prediction based on the provided time.
+  // We need to do the same for the 3dof version too.
+  return gvr->next_frame_6dof_pose_;
+}
+
+void gvr_swap_chain_set_z_order(const gvr_swap_chain* swap_chain, int z_order) {
+  dvrGraphicsSurfaceSetZOrder(swap_chain->graphics_context_, z_order);
+}
+
+bool gvr_experimental_is_feature_supported(const gvr_context* /* gvr */,
+                                           int32_t feature) {
+  switch (feature) {
+    case GVR_ASYNC_REPROJECTION:
+    case GVR_6DOF_HEAD_POSE:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool gvr_experimental_register_perf_event_callback(
+    gvr_context* gvr, int* /* out_handle */, void* /* user_data */,
+    void (* /* event_callback */)(void*, int, float)) {
+  ALOGE("gvr_experimental_register_perf_event_callback not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+  return false;
+}
+
+bool gvr_experimental_unregister_perf_event_callback(gvr_context* gvr,
+                                                     int /* handle */) {
+  ALOGE("gvr_experimental_unregister_perf_event_callback not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+  return false;
+}
+
+const gvr_analytics* gvr_get_analytics(gvr_context* gvr) {
+  ALOGE("gvr_get_analytics not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+  return nullptr;
+}
+
+const gvr_analytics_sample* gvr_analytics_create_sample(
+    const gvr_analytics* analytics) {
+  ALOGE("gvr_analytics_create_sample not implemented.");
+  return nullptr;
+}
+
+const char* gvr_analytics_sample_get_buffer(const gvr_analytics_sample* sample) {
+  ALOGE("gvr_analytics_sample_get_buffer not implemented.");
+  return nullptr;
+}
+
+size_t gvr_analytics_sample_get_buffer_length(
+    const gvr_analytics_sample* sample) {
+  ALOGE("gvr_analytics_sample_get_buffer_length not implemented.");
+  return 0;
+}
+
+void gvr_analytics_destroy_sample(const gvr_analytics_sample** sample) {
+  ALOGE("gvr_analytics_destroy_sample not implemented.");
+}
+
+bool gvr_user_prefs_get_performance_monitoring_enabled(
+    const gvr_user_prefs* /* user_prefs */) {
+  ALOGW("gvr_user_prefs_get_performance_monitoring_enabled not implemented.");
+  return false;
+}
+
+void gvr_enable_context_sharing(gvr_context* gvr,
+                                gvr_egl_context_listener /* handler */,
+                                void* /* user_data */) {
+  ALOGW("gvr_enable_context_sharing not implemented.");
+  gvr_set_error(gvr, GVR_ERROR_INTERNAL);
+}
+
+gvr_mat4f gvr_get_start_space_from_controller_space_pose(
+    gvr_context* gvr, int controller_id,
+    const gvr_clock_time_point /* time */) {
+  if (controller_id < 0 || controller_id >= kControllerCount) {
+    return GvrIdentityMatrix();
+  }
+
+  // TODO(leandrogracia): implement prediction based on the provided time.
+  // We need to do the same for the 3dof version too.
+  return gvr->next_frame_controller_pose_[controller_id];
+}
+
+gvr_external_surface* gvr_external_surface_create(gvr_context* context) {
+  // A |gvr_external_surface| is bound to a DVR Graphics context at the
+  // moment, which means we need an |gvr_swap_chain| created prior to the call
+  // of |gvr_external_surface_create|. Check whether the current GVR context
+  // has |gvr_swap_chain| created. Fail if there is no swap chain created
+  // already.
+  if (context->swap_chains_.empty()) {
+    ALOGE("gvr_external_surface_create: No swapchain has been created yet.");
+    return nullptr;
+  }
+
+  // In case there are multiple swap chains in the context, the first is
+  // implicitly chosen. Actually, this should not happen as current scanline
+  // racing based GVR implementation only supports single swap chain per GVR
+  // context.
+  if (context->swap_chains_.size() > 1) {
+    ALOGW("gvr_external_surface_create: Multiple swap chains detected. "
+          "Choosing the first one but this may yield unexpected results.");
+  }
+  gvr_swap_chain* swap_chain = context->swap_chains_[0];
+  DvrVideoMeshSurface* video_surface = dvrGraphicsVideoMeshSurfaceCreate(
+      swap_chain->graphics_context_);
+
+  if (video_surface == nullptr) {
+    ALOGE("gvr_external_surface_create: Failed to create video mesh surface.");
+    return nullptr;
+  }
+
+  gvr_external_surface* surface = new gvr_external_surface;
+  surface->id = swap_chain->next_external_surface_id_++;
+  surface->swap_chain = swap_chain;
+  surface->video_surface = video_surface;
+
+  // Insert the surface into a lookup table in swap_chain. This will be
+  // needed to by the external_surface_id in |gvr_buffer_viewport|.
+  swap_chain->external_surfaces_.insert({surface->id, surface});
+  return surface;
+}
+
+void gvr_external_surface_destroy(gvr_external_surface** surface) {
+  if (!surface || !(*surface)) {
+    ALOGW("gvr_external_surface_destroy: Invalid external surface pointer.");
+    return;
+  }
+
+  (*surface)->swap_chain->external_surfaces_.erase((*surface)->id);
+  if ((*surface)->video_surface != nullptr) {
+    dvrGraphicsVideoMeshSurfaceDestroy((*surface)->video_surface);
+  }
+
+  delete *surface;
+  *surface = nullptr;
+}
+
+void* gvr_external_surface_get_surface(const gvr_external_surface* surface) {
+  CHECK(surface->swap_chain != nullptr &&
+        surface->swap_chain->context != nullptr &&
+        surface->swap_chain->context->jni_env_ != nullptr)
+      << "gvr_external_surface_get_surface: Surface must be constructed within "
+      << "a JNIEnv. Check |gvr_create| call.";
+
+  CHECK(surface->video_surface != nullptr)
+      << "gvr_external_surface_get_surface: Invalid surface.";
+
+  std::shared_ptr<android::dvr::ProducerQueue> producer_queue =
+      surface->video_surface->client->GetProducerQueue();
+  std::shared_ptr<android::dvr::BufferHubQueueCore> core =
+      android::dvr::BufferHubQueueCore::Create(producer_queue);
+
+  return android_view_Surface_createFromIGraphicBufferProducer(
+      surface->swap_chain->context->jni_env_,
+      new android::dvr::BufferHubQueueProducer(core));
+}
+
+int32_t gvr_external_surface_get_surface_id(
+    const gvr_external_surface* surface) {
+  return surface->id;
+}
diff --git a/libs/vr/libgvr/shim_gvr_controller.cpp b/libs/vr/libgvr/shim_gvr_controller.cpp
new file mode 100644
index 0000000..54bc270
--- /dev/null
+++ b/libs/vr/libgvr/shim_gvr_controller.cpp
@@ -0,0 +1,168 @@
+#define LOG_TAG "libgvr_controller_shim"
+
+#include <cutils/log.h>
+#include <vr/gvr/capi/include/gvr_controller.h>
+#include <vr/gvr/capi/include/gvr_types.h>
+
+gvr_controller_context* gvr_controller_create_and_init(int32_t options,
+                                                       gvr_context* context) {
+  ALOGE("gvr_controller_create_and_init not implemented.");
+  return nullptr;
+}
+
+gvr_controller_context* gvr_controller_create_and_init_android(
+    JNIEnv* env, jobject android_context, jobject class_loader, int32_t options,
+    gvr_context* context) {
+  ALOGE("gvr_controller_create_and_init_android not implemented.");
+  return nullptr;
+}
+
+void gvr_controller_destroy(gvr_controller_context** api) {
+  ALOGE("gvr_controller_destroy not implemented.");
+}
+
+gvr_controller_state* gvr_controller_state_create() {
+  ALOGE("gvr_controller_state_create not implemented.");
+  return nullptr;
+}
+
+void gvr_controller_state_destroy(gvr_controller_state** state) {
+  ALOGE("gvr_controller_state_destroy not implemented.");
+}
+
+void gvr_controller_state_update(gvr_controller_context* api, int32_t flags,
+                                 gvr_controller_state* out_state) {
+  ALOGE("gvr_controller_state_update not implemented.");
+}
+
+int64_t gvr_controller_state_get_last_button_timestamp(
+    const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_last_button_timestamp not implemented.");
+  return 0;
+}
+
+bool gvr_controller_state_get_button_state(const gvr_controller_state* state,
+                                           int32_t button) {
+  ALOGE("gvr_controller_state_get_button_state not implemented.");
+  return false;
+}
+
+bool gvr_controller_state_get_button_down(const gvr_controller_state* state,
+                                          int32_t button) {
+  ALOGE("gvr_controller_state_get_button_down not implemented.");
+  return false;
+}
+
+bool gvr_controller_state_get_button_up(const gvr_controller_state* state,
+                                        int32_t button) {
+  ALOGE("gvr_controller_state_get_button_up not implemented.");
+  return false;
+}
+
+bool gvr_controller_state_is_touching(const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_is_touching not implemented.");
+  return false;
+}
+
+gvr_vec2f gvr_controller_state_get_touch_pos(
+    const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_touch_pos not implemented.");
+  return {0.0f, 0.0f};
+}
+
+bool gvr_controller_state_get_touch_down(const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_touch_down not implemented.");
+  return false;
+}
+
+bool gvr_controller_state_get_touch_up(const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_touch_up not implemented.");
+  return false;
+}
+
+int64_t gvr_controller_state_get_last_touch_timestamp(
+    const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_last_touch_timestamp not implemented.");
+  return 0;
+}
+
+gvr_quatf gvr_controller_state_get_orientation(
+    const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_orientation not implemented.");
+  return {0.0f, 0.0f, 0.0f, 0.0f};
+}
+
+int64_t gvr_controller_state_get_last_orientation_timestamp(
+    const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_last_orientation_timestamp not implemented.");
+  return 0;
+}
+
+const char* gvr_controller_api_status_to_string(int32_t status) {
+  ALOGE("gvr_controller_api_status_to_string not implemented.");
+  return nullptr;
+}
+
+const char* gvr_controller_connection_state_to_string(int32_t state) {
+  ALOGE("gvr_controller_connection_state_to_string not implemented.");
+  return nullptr;
+}
+
+const char* gvr_controller_button_to_string(int32_t button) {
+  ALOGE("gvr_controller_button_to_string not implemented.");
+  return nullptr;
+}
+
+int32_t gvr_controller_get_default_options() {
+  ALOGE("gvr_controller_get_default_options not implemented.");
+  return 0;
+}
+
+void gvr_controller_pause(gvr_controller_context* api) {
+  ALOGE("gvr_controller_pause not implemented.");
+}
+
+void gvr_controller_resume(gvr_controller_context* api) {
+  ALOGE("gvr_controller_resume not implemented.");
+}
+
+int32_t gvr_controller_state_get_api_status(const gvr_controller_state* state) {
+  return GVR_CONTROLLER_API_OK;
+}
+
+int32_t gvr_controller_state_get_connection_state(
+    const gvr_controller_state* state) {
+  return GVR_CONTROLLER_CONNECTED;
+}
+
+gvr_vec3f gvr_controller_state_get_gyro(const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_gyro not implemented.");
+  return {0.0, 0.0, 0.0};
+}
+
+gvr_vec3f gvr_controller_state_get_accel(const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_accel not implemented.");
+  return {0.0, 0.0, 0.0};
+}
+
+int64_t gvr_controller_state_get_last_gyro_timestamp(
+    const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_last_gyro_timestamp not implemented.");
+  return 0;
+}
+
+int64_t gvr_controller_state_get_last_accel_timestamp(
+    const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_last_accel_timestamp not implemented.");
+  return 0;
+}
+
+bool gvr_controller_state_get_recentered(const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_recentered not implemented.");
+  return false;
+}
+
+bool gvr_controller_state_get_recentering(const gvr_controller_state* state) {
+  ALOGE("gvr_controller_state_get_recentering not implemented.");
+  return false;
+}
diff --git a/libs/vr/libgvr/shim_gvr_private.cpp b/libs/vr/libgvr/shim_gvr_private.cpp
new file mode 100644
index 0000000..6ab6971
--- /dev/null
+++ b/libs/vr/libgvr/shim_gvr_private.cpp
@@ -0,0 +1,234 @@
+#define LOG_TAG "libgvr_shim_private"
+
+#include <cutils/log.h>
+#include <private/dvr/display_rpc.h>
+#include <private/dvr/internal_types.h>
+#include <vr/gvr/capi/include/gvr.h>
+#include <vr/gvr/capi/src/gvr_private.h>
+
+#include <pdx/rpc/remote_method.h>
+#include "deviceparams/CardboardDevice.nolite.pb.h"
+
+bool gvr_set_async_reprojection_enabled(gvr_context* /* gvr */,
+                                        bool /* enabled */) {
+  return true;
+}
+
+void gvr_on_surface_created_reprojection_thread(gvr_context* /* gvr */) {}
+
+void gvr_render_reprojection_thread(gvr_context* /* gvr */) {}
+
+void gvr_on_pause_reprojection_thread(gvr_context* /* gvr */) {}
+
+void gvr_update_surface_reprojection_thread(
+    gvr_context* /* gvr */, int32_t /* surface_id */, int32_t /* texture_id */,
+    gvr_clock_time_point /* timestamp */, gvr_mat4f /* surface_transform */) {
+  ALOGE("gvr_update_surface_reprojection_thread not implemented");
+}
+
+void gvr_remove_all_surfaces_reprojection_thread(gvr_context* /* gvr */) {
+  ALOGE("gvr_remove_all_surfaces_reprojection_thread not implemented");
+}
+
+void gvr_reconnect_sensors(gvr_context* /* gvr */) {
+  ALOGE("gvr_reconnect_sensors not implemented");
+}
+
+bool gvr_set_viewer_params(gvr_context* gvr,
+                           const void* serialized_viewer_params,
+                           size_t serialized_viewer_params_size_bytes) {
+  std::string serialized_device_params_string(
+      reinterpret_cast<const char*>(serialized_viewer_params),
+      serialized_viewer_params_size_bytes);
+  std::unique_ptr<proto::DeviceParams> device_params(new proto::DeviceParams);
+  if (!device_params->ParseFromString(serialized_device_params_string)) {
+    LOG(ERROR) << "Invalid serialized Cardboard DeviceParams";
+    return false;
+  }
+
+  android::dvr::ViewerParams viewer_params;
+
+  viewer_params.screen_to_lens_distance =
+      device_params->screen_to_lens_distance();
+  viewer_params.inter_lens_distance = device_params->inter_lens_distance();
+  for (int i = 0; i < device_params->left_eye_field_of_view_angles_size();
+       ++i) {
+    viewer_params.left_eye_field_of_view_angles.push_back(
+        device_params->left_eye_field_of_view_angles(i));
+  }
+
+  viewer_params.vertical_alignment =
+      static_cast<android::dvr::ViewerParams::VerticalAlignmentType>(
+          device_params->vertical_alignment());
+  viewer_params.tray_to_lens_distance = device_params->tray_to_lens_distance();
+
+  // TODO(hendrikw) Leave the g and b coefficients empty until we support
+  // chromatic aberration correction.
+  for (int i = 0; i < device_params->distortion_coefficients_size(); ++i) {
+    viewer_params.distortion_coefficients_r.push_back(
+        device_params->distortion_coefficients(i));
+  }
+
+  viewer_params.screen_center_to_lens_distance =
+      viewer_params.inter_lens_distance / 2.0;
+  if (device_params->has_internal()) {
+    for (int i = 0; i < device_params->internal().eye_orientations_size();
+         ++i) {
+      viewer_params.eye_orientations.push_back(
+          static_cast<android::dvr::ViewerParams::EyeOrientation>(
+              device_params->internal().eye_orientations(i)));
+    }
+
+    if (device_params->internal().has_screen_center_to_lens_distance())
+      viewer_params.screen_center_to_lens_distance =
+          device_params->internal().screen_center_to_lens_distance();
+  }
+
+  if (device_params->has_daydream_internal()) {
+    viewer_params.daydream_internal.version =
+        device_params->daydream_internal().version();
+    for (int i = 0;
+         i < device_params->daydream_internal().alignment_markers_size(); ++i) {
+      viewer_params.daydream_internal.alignment_markers.push_back(
+          {device_params->daydream_internal().alignment_markers(i).horizontal(),
+           device_params->daydream_internal().alignment_markers(i).vertical()});
+    }
+  }
+
+  gvr->display_client_->SetViewerParams(viewer_params);
+  return true;
+}
+
+void gvr_set_lens_offset(gvr_context* /* gvr */, gvr_vec2f /* offset */) {
+  ALOGE("gvr_set_lens_offset not implemented");
+}
+
+void gvr_set_display_metrics(gvr_context* /* gvr */,
+                             gvr_sizei /* size_pixels */,
+                             gvr_vec2f /* meters_per_pixel */,
+                             float /* border_size_meters */) {
+  ALOGE("gvr_set_display_metrics not implemented");
+}
+
+void gvr_set_display_output_rotation(gvr_context* /* gvr */,
+                                     int /* display_output_rotation */) {
+  ALOGE("gvr_set_display_output_rotation not implemented");
+}
+
+float gvr_get_border_size_meters(const gvr_context* /* gvr */) {
+  ALOGE("gvr_get_border_size_meters not implemented");
+  return 0.0f;
+}
+
+bool gvr_check_surface_size_changed(gvr_context* /* gvr */) { return false; }
+
+gvr_sizei gvr_get_surface_size(const gvr_context* /* gvr */) {
+  ALOGE("gvr_get_surface_size not implemented");
+  return {0, 0};
+}
+
+void gvr_set_back_gesture_event_handler(gvr_context* /* gvr */,
+                                        event_handler /* handler */,
+                                        void* /* user_data */) {
+  ALOGE("gvr_set_back_gesture_event_handler not implemented");
+}
+
+gvr_tracker_state* gvr_pause_tracking_get_state(gvr_context* /* gvr */) {
+  ALOGE("gvr_pause_tracking_get_state not implemented");
+  return nullptr;
+}
+
+void gvr_resume_tracking_set_state(gvr_context* /* gvr */,
+                                   gvr_tracker_state* /* tracker_state */) {
+  ALOGE("gvr_resume_tracking_set_state not implemented");
+}
+
+void gvr_set_ignore_manual_tracker_pause_resume(gvr_context* /* gvr */,
+                                                bool /* should_ignore */) {
+  ALOGE("gvr_set_ignore_manual_tracker_pause_resume not implemented");
+}
+
+gvr_tracker_state* gvr_tracker_state_create(
+    const char* /* tracker_state_buffer */, size_t /* buf_size */) {
+  ALOGE("gvr_tracker_state_create not implemented");
+  return nullptr;
+}
+
+size_t gvr_tracker_state_get_buffer_size(
+    gvr_tracker_state* /* tracker_state */) {
+  ALOGE("gvr_tracker_state_get_buffer_size not implemented");
+  return 0;
+}
+
+const char* gvr_tracker_state_get_buffer(
+    gvr_tracker_state* /* tracker_state */) {
+  ALOGE("gvr_tracker_state_get_buffer not implemented");
+  return nullptr;
+}
+
+void gvr_tracker_state_destroy(gvr_tracker_state** /* tracker_state */) {
+  ALOGE("gvr_tracker_state_destroy not implemented");
+}
+
+gvr_display_synchronizer* gvr_display_synchronizer_create() {
+  // We don't actually support (or need) any of the synchronizer functionality,
+  // but if we return null here the gvr setup code in the app fails. Instead
+  // return a dummy object that does nothing, which allows gvr apps to work.
+  return new gvr_display_synchronizer;
+}
+
+void gvr_display_synchronizer_destroy(gvr_display_synchronizer** synchronizer) {
+  if (synchronizer) {
+    delete *synchronizer;
+    *synchronizer = nullptr;
+  }
+}
+
+void gvr_display_synchronizer_reset(
+    gvr_display_synchronizer* /* synchronizer */,
+    int64_t /* expected_interval_nanos */, int64_t /* vsync_offset_nanos */) {}
+
+void gvr_display_synchronizer_update(
+    gvr_display_synchronizer* /* synchronizer */,
+    gvr_clock_time_point /* vsync_time */, int32_t /* rotation */) {}
+
+void gvr_set_display_synchronizer(
+    gvr_context* /* gvr */, gvr_display_synchronizer* /* synchronizer */) {}
+
+void gvr_set_error(gvr_context* gvr, int32_t error_code) {
+  if (gvr->last_error_ != GVR_ERROR_NONE) {
+    ALOGW("Overwriting existing error code: %d (%s)", gvr->last_error_,
+          gvr_get_error_string(gvr->last_error_));
+  }
+  gvr->last_error_ = error_code;
+}
+
+void gvr_pause(gvr_context* gvr) {
+  if (gvr == nullptr) {
+    ALOGW("gvr_pause called with a null gvr_context. This is a bug.");
+    return;
+  }
+  for (gvr_swap_chain* swap_chain : gvr->swap_chains_) {
+    if (swap_chain->graphics_context_)
+      dvrGraphicsSurfaceSetVisible(swap_chain->graphics_context_, 0);
+  }
+}
+
+void gvr_resume(gvr_context* gvr) {
+  if (gvr == nullptr) {
+    ALOGW("gvr_resume called with a null gvr_context. This is a bug.");
+    return;
+  }
+  for (gvr_swap_chain* swap_chain : gvr->swap_chains_) {
+    if (swap_chain->graphics_context_)
+      dvrGraphicsSurfaceSetVisible(swap_chain->graphics_context_, 1);
+  }
+}
+
+void gvr_dump_debug_data(gvr_context* /* gvr */) {}
+
+bool gvr_using_vr_display_service(gvr_context* /* gvr */) { return true; }
+
+void gvr_request_context_sharing(gvr_context* /* gvr */,
+                                 gvr_egl_context_listener /* handler */,
+                                 void* /* user_data */) {}
diff --git a/libs/vr/libimageio/Android.mk b/libs/vr/libimageio/Android.mk
new file mode 100644
index 0000000..b3b88ac
--- /dev/null
+++ b/libs/vr/libimageio/Android.mk
@@ -0,0 +1,23 @@
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	image_io.cpp \
+	image_io_png.cpp \
+	image_io_ppm.cpp
+
+includeFiles := \
+  $(LOCAL_PATH)/include
+
+sharedLibraries := \
+	libcutils \
+	libpng
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES += $(includeFiles)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_CFLAGS := -Wall -Wextra
+LOCAL_MODULE := libimageio
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_STATIC_LIBRARY)
diff --git a/libs/vr/libimageio/image_io.cpp b/libs/vr/libimageio/image_io.cpp
new file mode 100644
index 0000000..5ad6c2d
--- /dev/null
+++ b/libs/vr/libimageio/image_io.cpp
@@ -0,0 +1,92 @@
+#define LOG_TAG "ImageIo"
+
+#include <private/dvr/image_io.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include <private/dvr/image_io_base.h>
+#include <private/dvr/image_io_logging.h>
+#include <private/dvr/image_io_png.h>
+#include <private/dvr/image_io_ppm.h>
+
+namespace {
+
+// Returns true if |str| ends with |suffix|.
+bool EndsWith(const std::string& str, const std::string& suffix) {
+  if (str.length() < suffix.length())
+    return false;
+
+  return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
+}
+
+// Returns lower case copy of the input string.
+std::string ToLower(std::string str) {
+  std::transform(str.begin(), str.end(), str.begin(),
+                 [](char x) { return std::tolower(x); });
+  return str;
+}
+
+}  // namespace
+
+std::unique_ptr<ImageIoReader> ImageIoReader::Create(const char* filename) {
+  std::unique_ptr<ImageIoReader> reader;
+  std::string filename_lower = ToLower(filename);
+
+  if (EndsWith(filename_lower, ".ppm"))
+    reader.reset(new ImageIoPpmReader(filename));
+
+  if (!reader) {
+    ALOGE("Unknown/unsupported image file format.");
+    return nullptr;
+  }
+
+  return reader;
+}
+
+std::unique_ptr<ImageIoWriter> ImageIoWriter::Create(const char* filename,
+                                                     int width, int height,
+                                                     const uint8_t* image) {
+  std::unique_ptr<ImageIoWriter> writer;
+  std::string filename_lower = ToLower(filename);
+
+  if (EndsWith(filename_lower, ".ppm"))
+    writer.reset(new ImageIoPpmWriter(filename, width, height, image));
+  else if (EndsWith(filename_lower, ".png"))
+    writer.reset(new ImageIoPngWriter(filename, width, height, image));
+
+  if (!writer) {
+    ALOGE("Unknown/unsupported image file format.");
+    return nullptr;
+  }
+
+  return writer;
+}
+
+extern "C" {
+
+bool image_io_write_rgb888(const char* filename, int width, int height,
+                           const uint8_t* image) {
+  auto writer = ImageIoWriter::Create(filename, width, height, image);
+  if (!writer)
+    return false;
+  return writer->WriteRgb888();
+}
+
+bool image_io_read_rgb888(const char* filename, int* width, int* height,
+                          uint8_t** image) {
+  auto reader = ImageIoReader::Create(filename);
+  if (!reader)
+    return false;
+  if (!reader->ReadRgb888())
+    return false;
+  *width = reader->width();
+  *height = reader->height();
+  *image = reader->ReleaseImage();
+  return true;
+}
+
+void image_io_release_buffer(uint8_t* image) { delete[] image; }
+
+}  // extern "C"
diff --git a/libs/vr/libimageio/image_io_png.cpp b/libs/vr/libimageio/image_io_png.cpp
new file mode 100644
index 0000000..e0a118b
--- /dev/null
+++ b/libs/vr/libimageio/image_io_png.cpp
@@ -0,0 +1,87 @@
+#define LOG_TAG "ImageIo"
+
+#include <private/dvr/image_io_png.h>
+
+#include <fstream>
+#include <string>
+#include <vector>
+
+#include <private/dvr/image_io_logging.h>
+
+#include "png.h"
+
+namespace {
+
+void WriteChunkCallback(png_structp out_ptr, png_bytep chunk_ptr,
+                        png_size_t chunk_size) {
+  auto* writer = static_cast<ImageIoPngWriter*>(png_get_io_ptr(out_ptr));
+  const char* chunk = reinterpret_cast<const char*>(chunk_ptr);
+  writer->WriteChunk(chunk, chunk_size);
+}
+
+}  // namespace
+
+ImageIoPngWriter::ImageIoPngWriter(const char* filename, int width, int height,
+                                   const uint8_t* image)
+    : ImageIoWriter(filename, width, height, image),
+      out_(filename_),
+      write_failed_(false) {}
+
+bool ImageIoPngWriter::WriteChunk(const char* chunk, int chunk_size) {
+  out_.write(chunk, chunk_size);
+  if (!out_) {
+    if (write_failed_) {
+      // Error was already logged once.
+      return false;
+    }
+
+    ALOGE("Failed to write .png image to %s.", filename_.c_str());
+    write_failed_ = true;
+    return false;
+  }
+  return true;
+}
+
+// Writes RGB888 image to png file.
+// Refactored from Chromium:
+// WebKit/Source/platform/image-encoders/skia/PNGImageEncoder.cpp
+bool ImageIoPngWriter::WriteRgb888() {
+  if (width_ <= 0 || height_ <= 0) {
+    ALOGE("Invalid width or height.");
+    return false;
+  }
+
+  if (!out_) {
+    ALOGE("Failed to open output file %s.", filename_.c_str());
+    return false;
+  }
+
+  png_struct* png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
+  png_info* info = png_create_info_struct(png);
+  if (!png || !info || setjmp(png_jmpbuf(png))) {
+    png_destroy_write_struct(png ? &png : 0, info ? &info : 0);
+    return false;
+  }
+
+  png_set_compression_level(png, 3);
+  png_set_filter(png, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB);
+
+  png_set_write_fn(png, this, WriteChunkCallback, 0);
+  png_set_IHDR(png, info, width_, height_, 8, PNG_COLOR_TYPE_RGB, 0, 0, 0);
+  png_write_info(png, info);
+
+  unsigned char* pixels =
+      reinterpret_cast<unsigned char*>(const_cast<uint8_t*>(image_));
+  const size_t stride = width_ * 3;
+  for (int y = 0; y < height_; ++y) {
+    png_write_row(png, pixels);
+    if (write_failed_)
+      return false;
+    pixels += stride;
+  }
+
+  png_write_end(png, info);
+  png_destroy_write_struct(&png, &info);
+
+  return !write_failed_;
+}
diff --git a/libs/vr/libimageio/image_io_ppm.cpp b/libs/vr/libimageio/image_io_ppm.cpp
new file mode 100644
index 0000000..2411888
--- /dev/null
+++ b/libs/vr/libimageio/image_io_ppm.cpp
@@ -0,0 +1,93 @@
+#define LOG_TAG "ImageIo"
+
+#include <private/dvr/image_io_ppm.h>
+
+#include <cwctype>
+#include <fstream>
+#include <string>
+
+#include <private/dvr/image_io_logging.h>
+
+bool ImageIoPpmWriter::WriteRgb888() {
+  std::ofstream out(filename_);
+  if (!out) {
+    ALOGE("Failed to open output file %s.", filename_.c_str());
+    return false;
+  }
+
+  // Write a PPM header. See http://netpbm.sourceforge.net/doc/ppm.html for
+  // the format specification.
+  constexpr int maximum_intensity = 255;
+  out << "P6\n"
+      << width_ << "\n"
+      << height_ << "\n"
+      << maximum_intensity << "\n";
+
+  // Write out the image itself.
+  out.write(reinterpret_cast<const char*>(image_), 3 * width_ * height_);
+
+  if (!out) {
+    ALOGE("Failed to write .ppm image to %s.", filename_.c_str());
+    return false;
+  }
+  return true;
+}
+
+bool ImageIoPpmReader::ReadRgb888() {
+  std::ifstream in(filename_);
+  if (!in) {
+    ALOGE("Failed to open input file %s.", filename_.c_str());
+    return false;
+  }
+
+  // Read PPM header. See http://netpbm.sourceforge.net/doc/ppm.html for
+  // the format specification.
+  char magic_number[2];
+  in.read(magic_number, 2);
+  if (magic_number[0] != 'P' || magic_number[1] != '6') {
+    ALOGE("Failed to read PPM, not a P6 file %s.", filename_.c_str());
+    return false;
+  }
+
+  int maximum_intensity = 0;
+
+  in >> width_;
+  in >> height_;
+  in >> maximum_intensity;
+
+  char delimiter;
+  in.read(&delimiter, 1);
+
+  if (!iswspace(delimiter) || width_ <= 0 || height_ <= 0 ||
+      maximum_intensity <= 0) {
+    ALOGE("Failed to parse PPM header for %s.", filename_.c_str());
+    return false;
+  }
+
+  if (maximum_intensity != 255) {
+    ALOGE("Failed to read PPM, only 8-bit depth supported %s.",
+          filename_.c_str());
+    return false;
+  }
+
+  // Read RGB data.
+  const int data_begin = in.tellg();
+  in.seekg(0, in.end);
+  const int data_end = in.tellg();
+  in.seekg(data_begin, in.beg);
+
+  const int data_size = data_end - data_begin;
+  if (data_size != 3 * width_ * height_) {
+    ALOGE("Failed to read PPM, unexpected data size %s.", filename_.c_str());
+    return false;
+  }
+
+  image_.reset(new uint8_t[data_size]);
+  char* data = reinterpret_cast<char*>(image_.get());
+
+  const auto it_data_begin = std::istreambuf_iterator<char>(in);
+  const auto it_data_end = std::istreambuf_iterator<char>();
+  std::copy(it_data_begin, it_data_end, data);
+
+  return true;
+}
diff --git a/libs/vr/libimageio/include/CPPLINT.cfg b/libs/vr/libimageio/include/CPPLINT.cfg
new file mode 100644
index 0000000..2f8a3c0
--- /dev/null
+++ b/libs/vr/libimageio/include/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-build/header_guard
diff --git a/libs/vr/libimageio/include/private/dvr/image_io.h b/libs/vr/libimageio/include/private/dvr/image_io.h
new file mode 100644
index 0000000..5cb115d
--- /dev/null
+++ b/libs/vr/libimageio/include/private/dvr/image_io.h
@@ -0,0 +1,32 @@
+#ifndef DVR_IMAGE_IO_H_
+#define DVR_IMAGE_IO_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+// Supported filetypes.
+#define DVR_IMAGE_IO_SUPPORTED_WRITE "png, ppm"
+#define DVR_IMAGE_IO_SUPPORTED_READ "ppm"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Writes an RGB888 image to file. Intended file type is autodetected
+// based on the extension. Currently supported formats: PNG, PPM.
+bool image_io_write_rgb888(const char* filename, int width, int height,
+                           const uint8_t* image);
+
+// Reads an RGB888 image from file. Image buffer needs to be released with
+// image_io_release_image. Currently supported formats: PPM.
+bool image_io_read_rgb888(const char* filename, int* width, int* height,
+                          uint8_t** image);
+
+// Releases image buffer allocated within the library.
+void image_io_release_buffer(uint8_t* image);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DVR_IMAGE_IO_H_
diff --git a/libs/vr/libimageio/include/private/dvr/image_io_base.h b/libs/vr/libimageio/include/private/dvr/image_io_base.h
new file mode 100644
index 0000000..009cad4
--- /dev/null
+++ b/libs/vr/libimageio/include/private/dvr/image_io_base.h
@@ -0,0 +1,56 @@
+#ifndef LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_BASE_H_
+#define LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_BASE_H_
+
+#include <memory>
+#include <string>
+
+class ImageIoReader {
+ public:
+  virtual ~ImageIoReader() {}
+
+  static std::unique_ptr<ImageIoReader> Create(const char* filename);
+
+  virtual bool ReadRgb888() = 0;
+
+  int width() const { return width_; }
+
+  int height() const { return height_; }
+
+  uint8_t* ReleaseImage() { return image_.release(); }
+
+ protected:
+  int width_;
+  int height_;
+  std::unique_ptr<uint8_t[]> image_;
+  const std::string filename_;
+
+  explicit ImageIoReader(const char* filename)
+      : width_(0), height_(0), filename_(filename) {}
+
+  ImageIoReader() = delete;
+};
+
+class ImageIoWriter {
+ public:
+  virtual ~ImageIoWriter() {}
+
+  static std::unique_ptr<ImageIoWriter> Create(const char* filename, int width,
+                                               int height,
+                                               const uint8_t* image);
+
+  virtual bool WriteRgb888() = 0;
+
+ protected:
+  const int width_;
+  const int height_;
+  const uint8_t* image_;
+  const std::string filename_;
+
+  ImageIoWriter(const char* filename, int width, int height,
+                const uint8_t* image)
+      : width_(width), height_(height), image_(image), filename_(filename) {}
+
+  ImageIoWriter() = delete;
+};
+
+#endif  // LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_BASE_H_
diff --git a/libs/vr/libimageio/include/private/dvr/image_io_logging.h b/libs/vr/libimageio/include/private/dvr/image_io_logging.h
new file mode 100644
index 0000000..3c0f2a5
--- /dev/null
+++ b/libs/vr/libimageio/include/private/dvr/image_io_logging.h
@@ -0,0 +1,39 @@
+#ifndef LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_LOGGING_H_
+#define LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_LOGGING_H_
+
+// This header acts as cutils/log.h if LOG_TO_STDERR is not defined.
+// If LOG_TO_STDERR is defined, then android logging macros (such as ALOGE)
+// would log to stderr. This is useful if the code is also being used/tested on
+// a desktop.
+
+#ifdef LOG_TO_STDERR
+#include <stdarg.h>
+#include <cstdio>
+
+#ifndef LOG_TAG
+#define LOG_TAG " "
+#endif  // LOG_TAG
+
+inline void LogToStderr(const char* severity, const char* fmt, ...) {
+  fprintf(stderr, "%s %s: ", LOG_TAG, severity);
+  va_list args;
+  va_start(args, fmt);
+  vfprintf(stderr, fmt, args);
+  va_end(args);
+  fprintf(stderr, "\n");
+  fflush(stderr);
+}
+
+#define ALOGE(fmt, ...) LogToStderr("ERROR", fmt, ##__VA_ARGS__)
+
+#define ALOGW(fmt, ...) LogToStderr("WARNING", fmt, ##__VA_ARGS__)
+
+#define ALOGI(fmt, ...) LogToStderr("INFO", fmt, ##__VA_ARGS__)
+
+#define ALOGV(fmt, ...) LogToStderr("VERBOSE", fmt, ##__VA_ARGS__)
+
+#else  // LOG_TO_STDERR
+#include <cutils/log.h>
+#endif  // LOG_TO_STDERR
+
+#endif  // LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_LOGGING_H_
diff --git a/libs/vr/libimageio/include/private/dvr/image_io_png.h b/libs/vr/libimageio/include/private/dvr/image_io_png.h
new file mode 100644
index 0000000..e3b19db
--- /dev/null
+++ b/libs/vr/libimageio/include/private/dvr/image_io_png.h
@@ -0,0 +1,24 @@
+#ifndef LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_PNG_H_
+#define LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_PNG_H_
+
+#include <fstream>
+
+#include <private/dvr/image_io_base.h>
+
+class ImageIoPngWriter : public ImageIoWriter {
+ public:
+  bool WriteRgb888() override;
+
+  bool WriteChunk(const char* chunk, int chunk_size);
+
+ private:
+  ImageIoPngWriter(const char* filename, int width, int height,
+                   const uint8_t* image);
+
+  std::ofstream out_;
+  bool write_failed_;
+
+  friend class ImageIoWriter;
+};
+
+#endif  // LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_PNG_H_
diff --git a/libs/vr/libimageio/include/private/dvr/image_io_ppm.h b/libs/vr/libimageio/include/private/dvr/image_io_ppm.h
new file mode 100644
index 0000000..00264bd
--- /dev/null
+++ b/libs/vr/libimageio/include/private/dvr/image_io_ppm.h
@@ -0,0 +1,28 @@
+#ifndef LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_PPM_H_
+#define LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_PPM_H_
+
+#include <private/dvr/image_io_base.h>
+
+class ImageIoPpmReader : public ImageIoReader {
+ public:
+  bool ReadRgb888() override;
+
+ private:
+  explicit ImageIoPpmReader(const char* filename) : ImageIoReader(filename) {}
+
+  friend class ImageIoReader;
+};
+
+class ImageIoPpmWriter : public ImageIoWriter {
+ public:
+  bool WriteRgb888() override;
+
+ private:
+  ImageIoPpmWriter(const char* filename, int width, int height,
+                   const uint8_t* image)
+      : ImageIoWriter(filename, width, height, image) {}
+
+  friend class ImageIoWriter;
+};
+
+#endif  // LIB_LIBIMAGEIO_PRIVATE_DREAMOS_IMAGE_IO_PPM_H_
diff --git a/libs/vr/libpdx/Android.bp b/libs/vr/libpdx/Android.bp
new file mode 100644
index 0000000..69300d6
--- /dev/null
+++ b/libs/vr/libpdx/Android.bp
@@ -0,0 +1,59 @@
+cc_library_static {
+    name: "libpdx",
+    clang: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    export_include_dirs: ["private"],
+    local_include_dirs: ["private"],
+    srcs: [
+        "client.cpp",
+        "service.cpp",
+        "status.cpp",
+    ],
+}
+
+cc_test {
+    name: "pdx_tests",
+    clang: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    srcs: [
+        "client_tests.cpp",
+        "mock_tests.cpp",
+        "serialization_tests.cpp",
+        "service_tests.cpp",
+        "status_tests.cpp",
+        "thread_local_buffer_tests.cpp",
+        "variant_tests.cpp",
+    ],
+    static_libs: [
+        "libgmock",
+        "libpdx",
+        "liblog",
+        "libutils",
+    ],
+}
+
+// Code analysis target.
+cc_test {
+    name: "pdx_encoder_performance_test",
+    clang: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-O2",
+    ],
+    srcs: [
+        "encoder_performance_test.cpp",
+    ],
+    static_libs: [
+        "libpdx",
+    ],
+}
diff --git a/libs/vr/libpdx/client.cpp b/libs/vr/libpdx/client.cpp
new file mode 100644
index 0000000..c318628
--- /dev/null
+++ b/libs/vr/libpdx/client.cpp
@@ -0,0 +1,290 @@
+#include "pdx/client.h"
+
+#define LOG_TAG "ServiceFramework"
+#include <log/log.h>
+
+#include <pdx/trace.h>
+#include "errno_guard.h"
+
+namespace android {
+namespace pdx {
+
+void Client::EnableAutoReconnect(int64_t reconnect_timeout_ms) {
+  if (channel_factory_) {
+    reconnect_timeout_ms_ = reconnect_timeout_ms;
+    auto_reconnect_enabled_ = true;
+  }
+}
+
+void Client::DisableAutoReconnect() { auto_reconnect_enabled_ = false; }
+
+bool Client::IsConnected() const { return channel_.get() != nullptr; }
+
+Status<void> Client::CheckReconnect() {
+  Status<void> ret;
+  bool was_disconnected = !IsConnected();
+  if (auto_reconnect_enabled_ && was_disconnected && channel_factory_) {
+    auto status = channel_factory_->Connect(reconnect_timeout_ms_);
+    if (!status) {
+      error_ = -status.error();
+      ret.SetError(status.error());
+      return ret;
+    }
+    channel_ = status.take();
+  }
+
+  if (!IsConnected()) {
+    ret.SetError(ESHUTDOWN);
+  } else {
+    // Call the subclass OnConnect handler. The subclass may choose to close the
+    // connection in the handler, in which case error_ will be non-zero.
+    if (was_disconnected)
+      OnConnect();
+    if (!IsConnected())
+      ret.SetError(-error_);
+    else
+      ret.SetValue();
+  }
+
+  return ret;
+}
+
+bool Client::NeedToDisconnectChannel(int error) const {
+  return error == ESHUTDOWN && auto_reconnect_enabled_;
+}
+
+void Client::CheckDisconnect(int error) {
+  if (NeedToDisconnectChannel(error))
+    Close(error);
+}
+
+Client::Client(std::unique_ptr<ClientChannel> channel)
+    : channel_{std::move(channel)} {}
+
+Client::Client(std::unique_ptr<ClientChannelFactory> channel_factory,
+               int64_t timeout_ms)
+    : channel_factory_{std::move(channel_factory)} {
+  auto status = channel_factory_->Connect(timeout_ms);
+  if (!status) {
+    ALOGE("Client::Client: Failed to connect to service because: %s",
+          status.GetErrorMessage().c_str());
+    error_ = -status.error();
+  } else {
+    channel_ = status.take();
+  }
+}
+
+bool Client::IsInitialized() const {
+  return IsConnected() || (channel_factory_ && auto_reconnect_enabled_);
+}
+
+void Client::OnConnect() {}
+
+int Client::error() const { return error_; }
+
+Status<void> Client::SendImpulse(int opcode) {
+  PDX_TRACE_NAME("Client::SendImpulse");
+  ErrnoGuard errno_guard;
+
+  auto status = CheckReconnect();
+  if (!status)
+    return status;
+
+  status = channel_->SendImpulse(opcode, nullptr, 0);
+  CheckDisconnect(status);
+  return status;
+}
+
+Status<void> Client::SendImpulse(int opcode, const void* buffer,
+                                 size_t length) {
+  PDX_TRACE_NAME("Client::SendImpulse");
+  ErrnoGuard errno_guard;
+
+  auto status = CheckReconnect();
+  if (!status)
+    return status;
+
+  status = channel_->SendImpulse(opcode, buffer, length);
+  CheckDisconnect(status);
+  return status;
+}
+
+void Client::Close(int error) {
+  ErrnoGuard errno_guard;
+  channel_.reset();
+  // Normalize error codes to negative integer space.
+  error_ = error <= 0 ? error : -error;
+}
+
+int Client::event_fd() const {
+  return IsConnected() ? channel_->event_fd() : -1;
+}
+
+LocalChannelHandle& Client::GetChannelHandle() {
+  return channel_->GetChannelHandle();
+}
+
+///////////////////////////// Transaction implementation //////////////////////
+
+Transaction::Transaction(Client& client) : client_{client} {}
+
+Transaction::~Transaction() {
+  if (state_allocated_ && client_.GetChannel())
+    client_.GetChannel()->FreeTransactionState(state_);
+}
+
+bool Transaction::EnsureStateAllocated() {
+  if (!state_allocated_ && client_.GetChannel()) {
+    state_ = client_.GetChannel()->AllocateTransactionState();
+    state_allocated_ = true;
+  }
+  return state_allocated_;
+}
+
+void Transaction::SendTransaction(int opcode, Status<void>* ret,
+                                  const iovec* send_vector, size_t send_count,
+                                  const iovec* receive_vector,
+                                  size_t receive_count) {
+  *ret = client_.CheckReconnect();
+  if (!*ret)
+    return;
+
+  if (!EnsureStateAllocated()) {
+    ret->SetError(ESHUTDOWN);
+    return;
+  }
+
+  auto status = client_.GetChannel()->SendWithInt(
+      state_, opcode, send_vector, send_count, receive_vector, receive_count);
+
+  if (status) {
+    ret->SetValue();
+  } else {
+    ret->SetError(status.error());
+  }
+  CheckDisconnect(status);
+}
+
+void Transaction::SendTransaction(int opcode, Status<int>* ret,
+                                  const iovec* send_vector, size_t send_count,
+                                  const iovec* receive_vector,
+                                  size_t receive_count) {
+  auto status = client_.CheckReconnect();
+  if (!status) {
+    ret->SetError(status.error());
+    return;
+  }
+
+  if (!EnsureStateAllocated()) {
+    ret->SetError(ESHUTDOWN);
+    return;
+  }
+
+  *ret = client_.GetChannel()->SendWithInt(
+      state_, opcode, send_vector, send_count, receive_vector, receive_count);
+
+  CheckDisconnect(*ret);
+}
+
+void Transaction::SendTransaction(int opcode, Status<LocalHandle>* ret,
+                                  const iovec* send_vector, size_t send_count,
+                                  const iovec* receive_vector,
+                                  size_t receive_count) {
+  auto status = client_.CheckReconnect();
+  if (!status) {
+    ret->SetError(status.error());
+    return;
+  }
+
+  if (!EnsureStateAllocated()) {
+    ret->SetError(ESHUTDOWN);
+    return;
+  }
+
+  *ret = client_.GetChannel()->SendWithFileHandle(
+      state_, opcode, send_vector, send_count, receive_vector, receive_count);
+
+  CheckDisconnect(*ret);
+}
+
+void Transaction::SendTransaction(int opcode, Status<LocalChannelHandle>* ret,
+                                  const iovec* send_vector, size_t send_count,
+                                  const iovec* receive_vector,
+                                  size_t receive_count) {
+  auto status = client_.CheckReconnect();
+  if (!status) {
+    ret->SetError(status.error());
+    return;
+  }
+
+  if (!EnsureStateAllocated()) {
+    ret->SetError(ESHUTDOWN);
+    return;
+  }
+
+  *ret = client_.GetChannel()->SendWithChannelHandle(
+      state_, opcode, send_vector, send_count, receive_vector, receive_count);
+
+  CheckDisconnect(*ret);
+}
+
+FileReference Transaction::PushFileHandle(const LocalHandle& handle) {
+  return client_.CheckReconnect() && EnsureStateAllocated()
+             ? client_.GetChannel()->PushFileHandle(state_, handle)
+             : -1;
+}
+
+FileReference Transaction::PushFileHandle(const BorrowedHandle& handle) {
+  return client_.CheckReconnect() && EnsureStateAllocated()
+             ? client_.GetChannel()->PushFileHandle(state_, handle)
+             : -1;
+}
+
+FileReference Transaction::PushFileHandle(const RemoteHandle& handle) {
+  return handle.Get();
+}
+
+ChannelReference Transaction::PushChannelHandle(
+    const LocalChannelHandle& handle) {
+  return client_.CheckReconnect() && EnsureStateAllocated()
+             ? client_.GetChannel()->PushChannelHandle(state_, handle)
+             : -1;
+}
+
+ChannelReference Transaction::PushChannelHandle(
+    const BorrowedChannelHandle& handle) {
+  return client_.CheckReconnect() && EnsureStateAllocated()
+             ? client_.GetChannel()->PushChannelHandle(state_, handle)
+             : -1;
+}
+
+ChannelReference Transaction::PushChannelHandle(
+    const RemoteChannelHandle& handle) {
+  return handle.value();
+}
+
+bool Transaction::GetFileHandle(FileReference ref, LocalHandle* handle) {
+  return client_.CheckReconnect() && EnsureStateAllocated() &&
+         client_.GetChannel()->GetFileHandle(state_, ref, handle);
+}
+
+bool Transaction::GetChannelHandle(ChannelReference ref,
+                                   LocalChannelHandle* handle) {
+  return client_.CheckReconnect() && EnsureStateAllocated() &&
+         client_.GetChannel()->GetChannelHandle(state_, ref, handle);
+}
+
+void Transaction::CheckDisconnect(int error) {
+  if (client_.NeedToDisconnectChannel(error)) {
+    if (state_allocated_) {
+      if (client_.GetChannel())
+        client_.GetChannel()->FreeTransactionState(state_);
+      state_ = nullptr;
+      state_allocated_ = false;
+    }
+    client_.Close(error);
+  }
+}
+
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx/client_tests.cpp b/libs/vr/libpdx/client_tests.cpp
new file mode 100644
index 0000000..f1fb6d1
--- /dev/null
+++ b/libs/vr/libpdx/client_tests.cpp
@@ -0,0 +1,566 @@
+#include <pdx/client.h>
+
+#include <gmock/gmock.h>
+#include <sys/eventfd.h>
+
+#include <pdx/mock_client_channel.h>
+#include <pdx/mock_client_channel_factory.h>
+#include <pdx/rpc/remote_method.h>
+
+using android::pdx::BorrowedChannelHandle;
+using android::pdx::BorrowedHandle;
+using android::pdx::ClientBase;
+using android::pdx::ClientChannel;
+using android::pdx::ClientChannelFactory;
+using android::pdx::ErrorStatus;
+using android::pdx::LocalChannelHandle;
+using android::pdx::LocalHandle;
+using android::pdx::MockClientChannel;
+using android::pdx::MockClientChannelFactory;
+using android::pdx::RemoteChannelHandle;
+using android::pdx::RemoteHandle;
+using android::pdx::Status;
+using android::pdx::Transaction;
+using android::pdx::rpc::Void;
+
+using testing::A;
+using testing::AnyNumber;
+using testing::ByMove;
+using testing::Invoke;
+using testing::Ne;
+using testing::Return;
+using testing::_;
+
+namespace {
+
+inline void* IntToPtr(intptr_t addr) { return reinterpret_cast<void*>(addr); }
+inline const void* IntToConstPtr(intptr_t addr) {
+  return reinterpret_cast<const void*>(addr);
+}
+
+struct TestInterface final {
+  // Op codes.
+  enum {
+    kOpAdd = 0,
+    kOpSendFile,
+    kOpGetFile,
+    kOpPushChannel,
+  };
+
+  // Methods.
+  PDX_REMOTE_METHOD(Add, kOpAdd, int(int, int));
+  PDX_REMOTE_METHOD(SendFile, kOpSendFile, void(const LocalHandle& fd));
+  PDX_REMOTE_METHOD(GetFile, kOpGetFile, LocalHandle(const std::string&, int));
+  PDX_REMOTE_METHOD(PushChannel, kOpPushChannel, LocalChannelHandle(Void));
+
+  PDX_REMOTE_API(API, Add, SendFile, GetFile, PushChannel);
+};
+
+class SimpleClient : public ClientBase<SimpleClient> {
+ public:
+  explicit SimpleClient(std::unique_ptr<ClientChannel> channel)
+      : BASE{std::move(channel)} {}
+  SimpleClient(std::unique_ptr<ClientChannelFactory> channel_factory,
+               int64_t timeout_ms)
+      : BASE{std::move(channel_factory), timeout_ms} {
+    EnableAutoReconnect(timeout_ms);
+  }
+
+  using BASE::SendImpulse;
+  using BASE::InvokeRemoteMethod;
+  using BASE::InvokeRemoteMethodInPlace;
+  using BASE::Close;
+  using BASE::IsConnected;
+  using BASE::EnableAutoReconnect;
+  using BASE::DisableAutoReconnect;
+  using BASE::event_fd;
+  using BASE::GetChannel;
+
+  MOCK_METHOD0(OnConnect, void());
+};
+
+class FailingClient : public ClientBase<FailingClient> {
+ public:
+  explicit FailingClient(std::unique_ptr<ClientChannel> channel, int error_code)
+      : BASE{std::move(channel)} {
+    Close(error_code);
+  }
+};
+
+class ClientChannelTest : public testing::Test {
+ public:
+  ClientChannelTest()
+      : client_{SimpleClient::Create(
+            std::make_unique<testing::StrictMock<MockClientChannel>>())} {}
+
+  MockClientChannel* mock_channel() {
+    return static_cast<MockClientChannel*>(client_->GetChannel());
+  }
+
+  std::unique_ptr<SimpleClient> client_;
+};
+
+class ClientChannelFactoryTest : public testing::Test {
+ public:
+  ClientChannelFactoryTest() {
+    auto factory =
+        std::make_unique<testing::NiceMock<MockClientChannelFactory>>();
+    ON_CALL(*factory, Connect(kTimeout))
+        .WillByDefault(Invoke(this, &ClientChannelFactoryTest::OnConnect));
+    client_ = SimpleClient::Create(std::move(factory), kTimeout);
+  }
+
+  MockClientChannel* mock_channel() {
+    return static_cast<MockClientChannel*>(client_->GetChannel());
+  }
+
+  Status<std::unique_ptr<ClientChannel>> OnConnect(int64_t /*timeout_ms*/) {
+    if (on_connect_error_)
+      return ErrorStatus(on_connect_error_);
+    std::unique_ptr<MockClientChannel> channel =
+        std::make_unique<testing::StrictMock<MockClientChannel>>();
+    if (on_connect_callback_)
+      on_connect_callback_(channel.get());
+    return Status<std::unique_ptr<ClientChannel>>{std::move(channel)};
+  }
+
+  void OnConnectCallback(std::function<void(MockClientChannel*)> callback) {
+    on_connect_callback_ = callback;
+  }
+  void SetOnConnectError(int error) { on_connect_error_ = error; }
+  void ResetOnConnectError() { on_connect_error_ = 0; }
+
+  constexpr static int64_t kTimeout = 123;
+  std::unique_ptr<SimpleClient> client_;
+  std::function<void(MockClientChannel*)> on_connect_callback_;
+  int on_connect_error_{0};
+};
+
+constexpr int64_t ClientChannelFactoryTest::kTimeout;
+
+class ClientTransactionTest : public ClientChannelTest {
+ public:
+  ClientTransactionTest() : transaction_{*client_} {}
+
+  Transaction transaction_;
+};
+
+}  // anonymous namespace
+
+TEST_F(ClientChannelTest, IsInitialized) {
+  ASSERT_NE(client_.get(), nullptr);
+  EXPECT_TRUE(client_->IsInitialized());
+  EXPECT_TRUE(client_->IsConnected());
+}
+
+TEST_F(ClientChannelTest, CloseOnConstruction) {
+  FailingClient failed_client1{std::make_unique<MockClientChannel>(), EACCES};
+  ASSERT_FALSE(failed_client1.IsInitialized());
+  EXPECT_EQ(-EACCES, failed_client1.error());
+
+  FailingClient failed_client2{std::make_unique<MockClientChannel>(), -EACCES};
+  ASSERT_FALSE(failed_client2.IsInitialized());
+  EXPECT_EQ(-EACCES, failed_client2.error());
+
+  auto failed_client3 =
+      FailingClient::Create(std::make_unique<MockClientChannel>(), EIO);
+  ASSERT_EQ(failed_client3.get(), nullptr);
+}
+
+TEST_F(ClientChannelTest, IsConnected) {
+  EXPECT_TRUE(client_->IsConnected());
+  EXPECT_EQ(0, client_->error());
+  client_->Close(-EINVAL);
+  EXPECT_FALSE(client_->IsConnected());
+  EXPECT_EQ(-EINVAL, client_->error());
+}
+
+TEST_F(ClientChannelTest, event_fd) {
+  EXPECT_CALL(*mock_channel(), event_fd()).WillOnce(Return(12));
+  EXPECT_EQ(12, client_->event_fd());
+}
+
+TEST_F(ClientChannelTest, SendImpulse) {
+  EXPECT_CALL(*mock_channel(), SendImpulse(123, nullptr, 0))
+      .WillOnce(Return(Status<void>{}));
+  EXPECT_TRUE(client_->SendImpulse(123));
+
+  EXPECT_CALL(*mock_channel(), SendImpulse(17, nullptr, 0))
+      .WillOnce(Return(ErrorStatus{EIO}));
+  auto status = client_->SendImpulse(17);
+  ASSERT_FALSE(status);
+  EXPECT_EQ(EIO, status.error());
+
+  const void* const kTestPtr = IntToConstPtr(1234);
+  EXPECT_CALL(*mock_channel(), SendImpulse(1, kTestPtr, 17))
+      .WillOnce(Return(Status<void>{}));
+  EXPECT_TRUE(client_->SendImpulse(1, kTestPtr, 17));
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodNullTransactionState) {
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(nullptr));
+  EXPECT_CALL(*mock_channel(),
+              SendWithInt(nullptr, TestInterface::kOpAdd, _, _, nullptr, 0))
+      .WillOnce(Return(9));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(nullptr));
+  EXPECT_TRUE(client_->InvokeRemoteMethod<TestInterface::Add>(4, 5));
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodAddSuccess) {
+  void* const kTransactionState = IntToPtr(123);
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(
+      *mock_channel(),
+      SendWithInt(kTransactionState, TestInterface::kOpAdd, _, _, nullptr, 0))
+      .WillOnce(Return(3));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  Status<int> status = client_->InvokeRemoteMethod<TestInterface::Add>(1, 2);
+  ASSERT_TRUE(status);
+  EXPECT_EQ(3, status.get());
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodAddFailure) {
+  void* const kTransactionState = IntToPtr(123);
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(
+      *mock_channel(),
+      SendWithInt(kTransactionState, TestInterface::kOpAdd, _, _, nullptr, 0))
+      .WillOnce(Return(ErrorStatus{EIO}));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  Status<int> status = client_->InvokeRemoteMethod<TestInterface::Add>(1, 2);
+  ASSERT_FALSE(status);
+  EXPECT_EQ(EIO, status.error());
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodGetFileSuccess) {
+  void* const kTransactionState = IntToPtr(123);
+  int fd = eventfd(0, 0);
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(*mock_channel(),
+              SendWithFileHandle(kTransactionState, TestInterface::kOpGetFile,
+                                 _, _, nullptr, 0))
+      .WillOnce(Return(ByMove(LocalHandle{fd})));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  Status<LocalHandle> status =
+      client_->InvokeRemoteMethod<TestInterface::GetFile>();
+  ASSERT_TRUE(status);
+  EXPECT_EQ(fd, status.get().Get());
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodGetFileFailure) {
+  void* const kTransactionState = IntToPtr(123);
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(*mock_channel(),
+              SendWithFileHandle(kTransactionState, TestInterface::kOpGetFile,
+                                 _, _, nullptr, 0))
+      .WillOnce(Return(ByMove(ErrorStatus{EACCES})));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  Status<LocalHandle> status =
+      client_->InvokeRemoteMethod<TestInterface::GetFile>("file", 0);
+  ASSERT_FALSE(status);
+  EXPECT_EQ(EACCES, status.error());
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodPushChannelSuccess) {
+  void* const kTransactionState = IntToPtr(123);
+  const int32_t kHandleValue = 17;
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(
+      *mock_channel(),
+      SendWithChannelHandle(kTransactionState, TestInterface::kOpPushChannel, _,
+                            _, nullptr, 0))
+      .WillOnce(Return(ByMove(LocalChannelHandle{nullptr, kHandleValue})));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  Status<LocalChannelHandle> status =
+      client_->InvokeRemoteMethod<TestInterface::PushChannel>();
+  ASSERT_TRUE(status);
+  EXPECT_EQ(kHandleValue, status.get().value());
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodPushChannelFailure) {
+  void* const kTransactionState = IntToPtr(123);
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(
+      *mock_channel(),
+      SendWithChannelHandle(kTransactionState, TestInterface::kOpPushChannel, _,
+                            _, nullptr, 0))
+      .WillOnce(Return(ByMove(ErrorStatus{EACCES})));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  Status<LocalChannelHandle> status =
+      client_->InvokeRemoteMethod<TestInterface::PushChannel>();
+  ASSERT_FALSE(status);
+  EXPECT_EQ(EACCES, status.error());
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodSendFileSuccess) {
+  void* const kTransactionState = IntToPtr(123);
+  LocalHandle fd{eventfd(0, 0)};
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(*mock_channel(),
+              PushFileHandle(kTransactionState, A<const LocalHandle&>()))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*mock_channel(),
+              SendWithInt(kTransactionState, TestInterface::kOpSendFile, _, _,
+                          nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  EXPECT_TRUE(client_->InvokeRemoteMethod<TestInterface::SendFile>(fd));
+}
+
+TEST_F(ClientChannelTest, InvokeRemoteMethodSendFileFailure) {
+  void* const kTransactionState = IntToPtr(123);
+  LocalHandle fd{eventfd(0, 0)};
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(*mock_channel(),
+              PushFileHandle(kTransactionState, A<const LocalHandle&>()))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*mock_channel(),
+              SendWithInt(kTransactionState, TestInterface::kOpSendFile, _, _,
+                          nullptr, 0))
+      .WillOnce(Return(ErrorStatus{EACCES}));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  EXPECT_FALSE(client_->InvokeRemoteMethod<TestInterface::SendFile>(fd));
+}
+
+TEST_F(ClientChannelFactoryTest, IsInitialized) {
+  ASSERT_NE(client_.get(), nullptr);
+  EXPECT_TRUE(client_->IsInitialized());
+  EXPECT_TRUE(client_->IsConnected());
+}
+
+TEST_F(ClientChannelFactoryTest, NotConnectedButInitialized) {
+  auto factory =
+      std::make_unique<testing::NiceMock<MockClientChannelFactory>>();
+  EXPECT_CALL(*factory, Connect(kTimeout))
+      .WillOnce(Return(ByMove(ErrorStatus(ESHUTDOWN))))
+      .WillOnce(Invoke(this, &ClientChannelFactoryTest::OnConnect));
+  client_ = SimpleClient::Create(std::move(factory), kTimeout);
+  ASSERT_NE(client_.get(), nullptr);
+  EXPECT_TRUE(client_->IsInitialized());
+  EXPECT_FALSE(client_->IsConnected());
+  client_->DisableAutoReconnect();
+  ASSERT_FALSE(client_->SendImpulse(17));
+  EXPECT_FALSE(client_->IsConnected());
+  client_->EnableAutoReconnect(kTimeout);
+  EXPECT_CALL(*client_, OnConnect());
+  OnConnectCallback([](auto* mock) {
+    EXPECT_CALL(*mock, SendImpulse(17, nullptr, 0))
+        .WillOnce(Return(Status<void>{}));
+  });
+  ASSERT_TRUE(client_->SendImpulse(17));
+  EXPECT_TRUE(client_->IsConnected());
+}
+
+TEST_F(ClientChannelFactoryTest, CheckDisconnect) {
+  EXPECT_CALL(*mock_channel(), SendImpulse(17, nullptr, 0))
+      .WillOnce(Return(ErrorStatus{ESHUTDOWN}));
+  ASSERT_FALSE(client_->SendImpulse(17));
+  EXPECT_FALSE(client_->IsConnected());
+  EXPECT_EQ(-ESHUTDOWN, client_->error());
+}
+
+TEST_F(ClientChannelFactoryTest, CheckReconnect) {
+  client_->Close(ESHUTDOWN);
+  ASSERT_FALSE(client_->IsConnected());
+
+  EXPECT_CALL(*client_, OnConnect());
+  OnConnectCallback([](auto* mock) {
+    EXPECT_CALL(*mock, SendImpulse(17, nullptr, 0))
+        .WillOnce(Return(Status<void>{}));
+  });
+  ASSERT_TRUE(client_->SendImpulse(17));
+  EXPECT_TRUE(client_->IsConnected());
+}
+
+TEST_F(ClientChannelFactoryTest, CloseOnConnect) {
+  client_->Close(ESHUTDOWN);
+
+  EXPECT_CALL(*client_, OnConnect()).WillOnce(Invoke([this] {
+    client_->Close(EIO);
+  }));
+  auto status = client_->SendImpulse(17);
+  ASSERT_FALSE(status);
+  EXPECT_EQ(EIO, status.error());
+  EXPECT_FALSE(client_->IsConnected());
+  EXPECT_EQ(-EIO, client_->error());
+}
+
+TEST_F(ClientChannelFactoryTest, DisableAutoReconnect) {
+  client_->Close(EIO);
+  ASSERT_FALSE(client_->IsConnected());
+  client_->DisableAutoReconnect();
+  auto status = client_->SendImpulse(17);
+  ASSERT_FALSE(status);
+  EXPECT_EQ(ESHUTDOWN, status.error());
+  EXPECT_FALSE(client_->IsConnected());
+  client_->EnableAutoReconnect(kTimeout);
+  EXPECT_CALL(*client_, OnConnect());
+  OnConnectCallback([](auto* mock) {
+    EXPECT_CALL(*mock, SendImpulse(17, nullptr, 0))
+        .WillOnce(Return(Status<void>{}));
+  });
+  ASSERT_TRUE(client_->SendImpulse(17));
+  EXPECT_TRUE(client_->IsConnected());
+}
+
+TEST_F(ClientTransactionTest, SendNoData) {
+  void* const kTransactionState = IntToPtr(123);
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+  EXPECT_CALL(*mock_channel(),
+              SendWithInt(kTransactionState, 1, nullptr, 0, nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.Send<void>(1));
+  EXPECT_CALL(*mock_channel(),
+              SendWithFileHandle(kTransactionState, 2, nullptr, 0, nullptr, 0))
+      .WillOnce(Return(ByMove(LocalHandle{-1})));
+  EXPECT_TRUE(transaction_.Send<LocalHandle>(2));
+  EXPECT_CALL(*mock_channel(), SendWithChannelHandle(kTransactionState, 3,
+                                                     nullptr, 0, nullptr, 0))
+      .WillOnce(Return(ByMove(LocalChannelHandle{nullptr, 1})));
+  EXPECT_TRUE(transaction_.Send<LocalChannelHandle>(3));
+}
+
+TEST_F(ClientTransactionTest, SendNoState) {
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(nullptr));
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 1, nullptr, 0, nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(nullptr));
+  EXPECT_TRUE(transaction_.Send<void>(1));
+}
+
+TEST_F(ClientTransactionTest, SendBuffers) {
+  const void* const kSendBuffer = IntToConstPtr(123);
+  const size_t kSendSize = 12;
+  void* const kReceiveBuffer = IntToPtr(456);
+  const size_t kReceiveSize = 34;
+
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(nullptr));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(nullptr));
+
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 1, nullptr, 0, nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.Send<void>(1, nullptr, 0, nullptr, 0));
+
+  EXPECT_CALL(*mock_channel(),
+              SendWithInt(nullptr, 2, Ne(nullptr), 1, nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.Send<void>(2, kSendBuffer, kSendSize, nullptr, 0));
+
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 3, nullptr, 0, nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.Send<void>(3, kSendBuffer, 0, nullptr, 0));
+
+  EXPECT_CALL(*mock_channel(),
+              SendWithInt(nullptr, 4, nullptr, 0, Ne(nullptr), 1))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(
+      transaction_.Send<void>(4, nullptr, 0, kReceiveBuffer, kReceiveSize));
+
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 5, nullptr, 0, nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.Send<void>(5, nullptr, 0, kReceiveBuffer, 0));
+
+  EXPECT_CALL(*mock_channel(),
+              SendWithInt(nullptr, 5, Ne(nullptr), 1, Ne(nullptr), 1))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.Send<void>(5, kSendBuffer, kSendSize, kReceiveBuffer,
+                                      kReceiveSize));
+}
+
+TEST_F(ClientTransactionTest, SendVector) {
+  iovec send[3] = {};
+  iovec recv[4] = {};
+
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(nullptr));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(nullptr));
+
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 1, nullptr, 0, nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.SendVector<void>(1, nullptr, 0, nullptr, 0));
+
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 2, send, 3, recv, 4))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.SendVector<void>(2, send, 3, recv, 4));
+
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 3, send, 3, nullptr, 0))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.SendVector<void>(3, send, nullptr));
+
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 4, nullptr, 0, recv, 4))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.SendVector<void>(4, nullptr, recv));
+
+  EXPECT_CALL(*mock_channel(), SendWithInt(nullptr, 5, send, 3, recv, 4))
+      .WillOnce(Return(0));
+  EXPECT_TRUE(transaction_.SendVector<void>(5, send, recv));
+}
+
+TEST_F(ClientTransactionTest, PushHandle) {
+  void* const kTransactionState = IntToPtr(123);
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+
+  EXPECT_CALL(*mock_channel(),
+              PushFileHandle(kTransactionState, A<const LocalHandle&>()))
+      .WillOnce(Return(1));
+  EXPECT_EQ(1, transaction_.PushFileHandle(LocalHandle{-1}));
+
+  EXPECT_CALL(*mock_channel(),
+              PushFileHandle(kTransactionState, A<const BorrowedHandle&>()))
+      .WillOnce(Return(2));
+  EXPECT_EQ(2, transaction_.PushFileHandle(BorrowedHandle{-1}));
+
+  EXPECT_EQ(3, transaction_.PushFileHandle(RemoteHandle{3}));
+
+  EXPECT_CALL(
+      *mock_channel(),
+      PushChannelHandle(kTransactionState, A<const LocalChannelHandle&>()))
+      .WillOnce(Return(11));
+  EXPECT_EQ(11, transaction_.PushChannelHandle(LocalChannelHandle{nullptr, 1}));
+
+  EXPECT_CALL(
+      *mock_channel(),
+      PushChannelHandle(kTransactionState, A<const BorrowedChannelHandle&>()))
+      .WillOnce(Return(12));
+  EXPECT_EQ(12, transaction_.PushChannelHandle(BorrowedChannelHandle{2}));
+
+  EXPECT_EQ(13, transaction_.PushChannelHandle(RemoteChannelHandle{13}));
+}
+
+TEST_F(ClientTransactionTest, GetHandle) {
+  void* const kTransactionState = IntToPtr(123);
+  EXPECT_CALL(*mock_channel(), AllocateTransactionState())
+      .WillOnce(Return(kTransactionState));
+  EXPECT_CALL(*mock_channel(), FreeTransactionState(kTransactionState));
+
+  EXPECT_CALL(*mock_channel(), GetFileHandle(kTransactionState, 1, _))
+      .WillOnce(Return(false))
+      .WillOnce(Return(true));
+
+  LocalHandle file_handle;
+  EXPECT_FALSE(transaction_.GetFileHandle(1, &file_handle));
+  EXPECT_TRUE(transaction_.GetFileHandle(1, &file_handle));
+
+  EXPECT_CALL(*mock_channel(), GetChannelHandle(kTransactionState, 2, _))
+      .WillOnce(Return(false))
+      .WillOnce(Return(true));
+
+  LocalChannelHandle channel_handle;
+  EXPECT_FALSE(transaction_.GetChannelHandle(2, &channel_handle));
+  EXPECT_TRUE(transaction_.GetChannelHandle(2, &channel_handle));
+}
diff --git a/libs/vr/libpdx/encoder_performance_test.cpp b/libs/vr/libpdx/encoder_performance_test.cpp
new file mode 100644
index 0000000..b7d94b3
--- /dev/null
+++ b/libs/vr/libpdx/encoder_performance_test.cpp
@@ -0,0 +1,515 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <iomanip>
+#include <iostream>
+#include <vector>
+
+#include <pdx/rpc/argument_encoder.h>
+#include <pdx/rpc/message_buffer.h>
+#include <pdx/rpc/payload.h>
+#include <pdx/utility.h>
+
+using namespace android::pdx::rpc;
+using namespace android::pdx;
+using std::placeholders::_1;
+using std::placeholders::_2;
+using std::placeholders::_3;
+using std::placeholders::_4;
+using std::placeholders::_5;
+using std::placeholders::_6;
+
+namespace {
+
+constexpr size_t kMaxStaticBufferSize = 20480;
+
+// Provide numpunct facet that formats numbers with ',' as thousands separators.
+class CommaNumPunct : public std::numpunct<char> {
+ protected:
+  char do_thousands_sep() const override { return ','; }
+  std::string do_grouping() const override { return "\03"; }
+};
+
+class TestPayload : public MessagePayload<SendBuffer>,
+                    public MessageWriter,
+                    public MessageReader,
+                    public NoOpResourceMapper {
+ public:
+  // MessageWriter
+  void* GetNextWriteBufferSection(size_t size) override {
+    const size_t section_offset = Size();
+    Extend(size);
+    return Data() + section_offset;
+  }
+
+  OutputResourceMapper* GetOutputResourceMapper() override { return this; }
+
+  // MessageReader
+  BufferSection GetNextReadBufferSection() override {
+    return {&*ConstCursor(), &*ConstEnd()};
+  }
+
+  void ConsumeReadBufferSectionData(const void* new_start) override {
+    std::advance(ConstCursor(), PointerDistance(new_start, &*ConstCursor()));
+  }
+
+  InputResourceMapper* GetInputResourceMapper() override { return this; }
+};
+
+class StaticBuffer : public MessageWriter,
+                     public MessageReader,
+                     public NoOpResourceMapper {
+ public:
+  void Clear() {
+    read_ptr_ = buffer_;
+    write_ptr_ = 0;
+  }
+  void Rewind() { read_ptr_ = buffer_; }
+
+  // MessageWriter
+  void* GetNextWriteBufferSection(size_t size) override {
+    void* ptr = buffer_ + write_ptr_;
+    write_ptr_ += size;
+    return ptr;
+  }
+
+  OutputResourceMapper* GetOutputResourceMapper() override { return this; }
+
+  // MessageReader
+  BufferSection GetNextReadBufferSection() override {
+    return {read_ptr_, std::end(buffer_)};
+  }
+
+  void ConsumeReadBufferSectionData(const void* new_start) override {
+    read_ptr_ = static_cast<const uint8_t*>(new_start);
+  }
+
+  InputResourceMapper* GetInputResourceMapper() override { return this; }
+
+ private:
+  uint8_t buffer_[kMaxStaticBufferSize];
+  const uint8_t* read_ptr_{buffer_};
+  size_t write_ptr_{0};
+};
+
+// Simple callback function to clear/reset the input/output buffers for
+// serialization. Using raw function pointer here instead of std::function to
+// minimize the overhead of invocation in the tight test loop over millions of
+// iterations.
+using ResetFunc = void(void*);
+
+// Serialization test function signature, used by the TestRunner.
+using SerializeTestSignature = std::chrono::nanoseconds(MessageWriter* writer,
+                                                        size_t iterations,
+                                                        ResetFunc* write_reset,
+                                                        void* reset_data);
+
+// Deserialization test function signature, used by the TestRunner.
+using DeserializeTestSignature = std::chrono::nanoseconds(
+    MessageReader* reader, MessageWriter* writer, size_t iterations,
+    ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data);
+
+// Generic serialization test runner method. Takes the |value| of type T and
+// serializes it into the output buffer represented by |writer|.
+template <typename T>
+std::chrono::nanoseconds SerializeTestRunner(MessageWriter* writer,
+                                             size_t iterations,
+                                             ResetFunc* write_reset,
+                                             void* reset_data, const T& value) {
+  auto start = std::chrono::high_resolution_clock::now();
+  for (size_t i = 0; i < iterations; i++) {
+    write_reset(reset_data);
+    Serialize(value, writer);
+  }
+  auto stop = std::chrono::high_resolution_clock::now();
+  return stop - start;
+}
+
+// Generic deserialization test runner method. Takes the |value| of type T and
+// temporarily serializes it into the output buffer, then repeatedly
+// deserializes the data back from that buffer.
+template <typename T>
+std::chrono::nanoseconds DeserializeTestRunner(
+    MessageReader* reader, MessageWriter* writer, size_t iterations,
+    ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data,
+    const T& value) {
+  write_reset(reset_data);
+  Serialize(value, writer);
+  T output_data;
+  auto start = std::chrono::high_resolution_clock::now();
+  for (size_t i = 0; i < iterations; i++) {
+    read_reset(reset_data);
+    Deserialize(&output_data, reader);
+  }
+  auto stop = std::chrono::high_resolution_clock::now();
+  if (output_data != value)
+    return start - stop;  // Return negative value to indicate error.
+  return stop - start;
+}
+
+// Special version of SerializeTestRunner that doesn't perform any serialization
+// but does all the same setup steps and moves data of size |data_size| into
+// the output buffer. Useful to determine the baseline to calculate time used
+// just for serialization layer.
+std::chrono::nanoseconds SerializeBaseTest(MessageWriter* writer,
+                                           size_t iterations,
+                                           ResetFunc* write_reset,
+                                           void* reset_data, size_t data_size) {
+  std::vector<uint8_t> dummy_data(data_size);
+  auto start = std::chrono::high_resolution_clock::now();
+  for (size_t i = 0; i < iterations; i++) {
+    write_reset(reset_data);
+    memcpy(writer->GetNextWriteBufferSection(dummy_data.size()),
+           dummy_data.data(), dummy_data.size());
+  }
+  auto stop = std::chrono::high_resolution_clock::now();
+  return stop - start;
+}
+
+// Special version of DeserializeTestRunner that doesn't perform any
+// deserialization but invokes Rewind on the input buffer repeatedly.
+// Useful to determine the baseline to calculate time used just for
+// deserialization layer.
+std::chrono::nanoseconds DeserializeBaseTest(
+    MessageReader* reader, MessageWriter* writer, size_t iterations,
+    ResetFunc* read_reset, ResetFunc* write_reset, void* reset_data,
+    size_t data_size) {
+  std::vector<uint8_t> dummy_data(data_size);
+  write_reset(reset_data);
+  memcpy(writer->GetNextWriteBufferSection(dummy_data.size()),
+         dummy_data.data(), dummy_data.size());
+  auto start = std::chrono::high_resolution_clock::now();
+  for (size_t i = 0; i < iterations; i++) {
+    read_reset(reset_data);
+    auto section = reader->GetNextReadBufferSection();
+    memcpy(dummy_data.data(), section.first, dummy_data.size());
+    reader->ConsumeReadBufferSectionData(
+        AdvancePointer(section.first, dummy_data.size()));
+  }
+  auto stop = std::chrono::high_resolution_clock::now();
+  return stop - start;
+}
+
+// The main class that accumulates individual tests to be executed.
+class TestRunner {
+ public:
+  struct BufferInfo {
+    BufferInfo(const std::string& buffer_name, MessageReader* reader,
+               MessageWriter* writer, ResetFunc* read_reset_func,
+               ResetFunc* write_reset_func, void* reset_data)
+        : name{buffer_name},
+          reader{reader},
+          writer{writer},
+          read_reset_func{read_reset_func},
+          write_reset_func{write_reset_func},
+          reset_data{reset_data} {}
+    std::string name;
+    MessageReader* reader;
+    MessageWriter* writer;
+    ResetFunc* read_reset_func;
+    ResetFunc* write_reset_func;
+    void* reset_data;
+  };
+
+  void AddTestFunc(const std::string& name,
+                   std::function<SerializeTestSignature> serialize_test,
+                   std::function<DeserializeTestSignature> deserialize_test,
+                   size_t data_size) {
+    tests_.emplace_back(name, std::move(serialize_test),
+                        std::move(deserialize_test), data_size);
+  }
+
+  template <typename T>
+  void AddSerializationTest(const std::string& name, T&& value) {
+    const size_t data_size = GetSerializedSize(value);
+    auto serialize_test =
+        std::bind(static_cast<std::chrono::nanoseconds (*)(
+                      MessageWriter*, size_t, ResetFunc*, void*, const T&)>(
+                      &SerializeTestRunner),
+                  _1, _2, _3, _4, std::forward<T>(value));
+    tests_.emplace_back(name, std::move(serialize_test),
+                        std::function<DeserializeTestSignature>{}, data_size);
+  }
+
+  template <typename T>
+  void AddDeserializationTest(const std::string& name, T&& value) {
+    const size_t data_size = GetSerializedSize(value);
+    auto deserialize_test =
+        std::bind(static_cast<std::chrono::nanoseconds (*)(
+                      MessageReader*, MessageWriter*, size_t, ResetFunc*,
+                      ResetFunc*, void*, const T&)>(&DeserializeTestRunner),
+                  _1, _2, _3, _4, _5, _6, std::forward<T>(value));
+    tests_.emplace_back(name, std::function<SerializeTestSignature>{},
+                        std::move(deserialize_test), data_size);
+  }
+
+  template <typename T>
+  void AddTest(const std::string& name, T&& value) {
+    const size_t data_size = GetSerializedSize(value);
+    if (data_size > kMaxStaticBufferSize) {
+      std::cerr << "Test '" << name << "' requires " << data_size
+                << " bytes in the serialization buffer but only "
+                << kMaxStaticBufferSize << " are available." << std::endl;
+      exit(1);
+    }
+    auto serialize_test =
+        std::bind(static_cast<std::chrono::nanoseconds (*)(
+                      MessageWriter*, size_t, ResetFunc*, void*, const T&)>(
+                      &SerializeTestRunner),
+                  _1, _2, _3, _4, value);
+    auto deserialize_test =
+        std::bind(static_cast<std::chrono::nanoseconds (*)(
+                      MessageReader*, MessageWriter*, size_t, ResetFunc*,
+                      ResetFunc*, void*, const T&)>(&DeserializeTestRunner),
+                  _1, _2, _3, _4, _5, _6, std::forward<T>(value));
+    tests_.emplace_back(name, std::move(serialize_test),
+                        std::move(deserialize_test), data_size);
+  }
+
+  std::string CenterString(std::string text, size_t column_width) {
+    if (text.size() < column_width) {
+      text = std::string((column_width - text.size()) / 2, ' ') + text;
+    }
+    return text;
+  }
+
+  void RunTests(size_t iteration_count,
+                const std::vector<BufferInfo>& buffers) {
+    using float_seconds = std::chrono::duration<double>;
+    const std::string name_column_separator = " : ";
+    const std::string buffer_column_separator = " || ";
+    const std::string buffer_timing_column_separator = " | ";
+    const size_t data_size_column_width = 6;
+    const size_t time_column_width = 9;
+    const size_t qps_column_width = 18;
+    const size_t buffer_column_width = time_column_width +
+                                       buffer_timing_column_separator.size() +
+                                       qps_column_width;
+
+    auto compare_name_length = [](const TestEntry& t1, const TestEntry& t2) {
+      return t1.name.size() < t2.name.size();
+    };
+    auto test_with_longest_name =
+        std::max_element(tests_.begin(), tests_.end(), compare_name_length);
+    size_t name_column_width = test_with_longest_name->name.size();
+
+    size_t total_width =
+        name_column_width + name_column_separator.size() +
+        data_size_column_width + buffer_column_separator.size() +
+        buffers.size() * (buffer_column_width + buffer_column_separator.size());
+
+    const std::string dbl_separator(total_width, '=');
+    const std::string separator(total_width, '-');
+
+    auto print_header = [&](const std::string& header) {
+      std::cout << dbl_separator << std::endl;
+      std::stringstream ss;
+      ss.imbue(std::locale(ss.getloc(), new CommaNumPunct));
+      ss << header << " (" << iteration_count << " iterations)";
+      std::cout << CenterString(ss.str(), total_width) << std::endl;
+      std::cout << dbl_separator << std::endl;
+      std::cout << std::setw(name_column_width) << "Test Name" << std::left
+                << name_column_separator << std::setw(data_size_column_width)
+                << CenterString("Size", data_size_column_width)
+                << buffer_column_separator;
+      for (const auto& buffer_info : buffers) {
+        std::cout << std::setw(buffer_column_width)
+                  << CenterString(buffer_info.name, buffer_column_width)
+                  << buffer_column_separator;
+      }
+      std::cout << std::endl;
+      std::cout << std::setw(name_column_width) << "" << name_column_separator
+                << std::setw(data_size_column_width)
+                << CenterString("bytes", data_size_column_width)
+                << buffer_column_separator << std::left;
+      for (size_t i = 0; i < buffers.size(); i++) {
+        std::cout << std::setw(time_column_width)
+                  << CenterString("Time, s", time_column_width)
+                  << buffer_timing_column_separator
+                  << std::setw(qps_column_width)
+                  << CenterString("QPS", qps_column_width)
+                  << buffer_column_separator;
+      }
+      std::cout << std::right << std::endl;
+      std::cout << separator << std::endl;
+    };
+
+    print_header("Serialization benchmarks");
+    for (const auto& test : tests_) {
+      if (test.serialize_test) {
+        std::cout << std::setw(name_column_width) << test.name << " : "
+                  << std::setw(data_size_column_width) << test.data_size
+                  << buffer_column_separator;
+        for (const auto& buffer_info : buffers) {
+          auto seconds =
+              std::chrono::duration_cast<float_seconds>(test.serialize_test(
+                  buffer_info.writer, iteration_count,
+                  buffer_info.write_reset_func, buffer_info.reset_data));
+          double qps = iteration_count / seconds.count();
+          std::cout << std::fixed << std::setprecision(3)
+                    << std::setw(time_column_width) << seconds.count()
+                    << buffer_timing_column_separator
+                    << std::setw(qps_column_width) << qps
+                    << buffer_column_separator;
+        }
+        std::cout << std::endl;
+      }
+    }
+
+    print_header("Deserialization benchmarks");
+    for (const auto& test : tests_) {
+      if (test.deserialize_test) {
+        std::cout << std::setw(name_column_width) << test.name << " : "
+                  << std::setw(data_size_column_width) << test.data_size
+                  << buffer_column_separator;
+        for (const auto& buffer_info : buffers) {
+          auto seconds =
+              std::chrono::duration_cast<float_seconds>(test.deserialize_test(
+                  buffer_info.reader, buffer_info.writer, iteration_count,
+                  buffer_info.read_reset_func, buffer_info.write_reset_func,
+                  buffer_info.reset_data));
+          double qps = iteration_count / seconds.count();
+          std::cout << std::fixed << std::setprecision(3)
+                    << std::setw(time_column_width) << seconds.count()
+                    << buffer_timing_column_separator
+                    << std::setw(qps_column_width) << qps
+                    << buffer_column_separator;
+        }
+        std::cout << std::endl;
+      }
+    }
+    std::cout << dbl_separator << std::endl;
+  }
+
+ private:
+  struct TestEntry {
+    TestEntry(const std::string& test_name,
+              std::function<SerializeTestSignature> serialize_test,
+              std::function<DeserializeTestSignature> deserialize_test,
+              size_t data_size)
+        : name{test_name},
+          serialize_test{std::move(serialize_test)},
+          deserialize_test{std::move(deserialize_test)},
+          data_size{data_size} {}
+    std::string name;
+    std::function<SerializeTestSignature> serialize_test;
+    std::function<DeserializeTestSignature> deserialize_test;
+    size_t data_size;
+  };
+
+  std::vector<TestEntry> tests_;
+};
+
+std::string GenerateContainerName(const std::string& type, size_t count) {
+  std::stringstream ss;
+  ss << type << "(" << count << ")";
+  return ss.str();
+}
+
+}  // anonymous namespace
+
+int main(int /*argc*/, char** /*argv*/) {
+  const size_t iteration_count = 10000000;  // 10M iterations.
+  TestRunner test_runner;
+  std::cout.imbue(std::locale(std::cout.getloc(), new CommaNumPunct));
+
+  // Baseline tests to figure out the overhead of buffer resizing and data
+  // transfers.
+  for (size_t len : {0, 1, 9, 66, 259}) {
+    auto serialize_base_test =
+        std::bind(&SerializeBaseTest, _1, _2, _3, _4, len);
+    auto deserialize_base_test =
+        std::bind(&DeserializeBaseTest, _1, _2, _3, _4, _5, _6, len);
+    test_runner.AddTestFunc("--Baseline--", std::move(serialize_base_test),
+                            std::move(deserialize_base_test), len);
+  }
+
+  // Individual serialization/deserialization tests.
+  test_runner.AddTest("bool", true);
+  test_runner.AddTest("int32_t", 12);
+
+  for (size_t len : {0, 1, 8, 64, 256}) {
+    test_runner.AddTest(GenerateContainerName("string", len),
+                        std::string(len, '*'));
+  }
+  // Serialization is too slow to handle such large strings, add this test for
+  // deserialization only.
+  test_runner.AddDeserializationTest(GenerateContainerName("string", 10240),
+                                     std::string(10240, '*'));
+
+  for (size_t len : {0, 1, 8, 64, 256}) {
+    std::vector<int32_t> int_vector(len);
+    std::iota(int_vector.begin(), int_vector.end(), 0);
+    test_runner.AddTest(GenerateContainerName("vector<int32_t>", len),
+                        std::move(int_vector));
+  }
+
+  std::vector<std::string> vector_of_strings = {
+      "012345678901234567890123456789", "012345678901234567890123456789",
+      "012345678901234567890123456789", "012345678901234567890123456789",
+      "012345678901234567890123456789",
+  };
+  test_runner.AddTest(
+      GenerateContainerName("vector<string>", vector_of_strings.size()),
+      std::move(vector_of_strings));
+
+  test_runner.AddTest("tuple<int, bool, string, double>",
+                      std::make_tuple(123, true, std::string{"foobar"}, 1.1));
+
+  for (size_t len : {0, 1, 8, 64}) {
+    std::map<int, std::string> test_map;
+    for (size_t i = 0; i < len; i++)
+      test_map.emplace(i, std::to_string(i));
+    test_runner.AddTest(GenerateContainerName("map<int, string>", len),
+                        std::move(test_map));
+  }
+
+  for (size_t len : {0, 1, 8, 64}) {
+    std::unordered_map<int, std::string> test_map;
+    for (size_t i = 0; i < len; i++)
+      test_map.emplace(i, std::to_string(i));
+    test_runner.AddTest(
+        GenerateContainerName("unordered_map<int, string>", len),
+        std::move(test_map));
+  }
+
+  // BufferWrapper can't be used with deserialization tests right now because
+  // it requires external buffer to be filled in, which is not available.
+  std::vector<std::vector<uint8_t>> data_buffers;
+  for (size_t len : {0, 1, 8, 64, 256}) {
+    data_buffers.emplace_back(len);
+    test_runner.AddSerializationTest(
+        GenerateContainerName("BufferWrapper<uint8_t*>", len),
+        BufferWrapper<uint8_t*>(data_buffers.back().data(),
+                                data_buffers.back().size()));
+  }
+
+  // Various backing buffers to run the tests on.
+  std::vector<TestRunner::BufferInfo> buffers;
+
+  Payload buffer;
+  buffers.emplace_back("Non-TLS Buffer", &buffer, &buffer,
+                       [](void* ptr) { static_cast<Payload*>(ptr)->Rewind(); },
+                       [](void* ptr) { static_cast<Payload*>(ptr)->Clear(); },
+                       &buffer);
+
+  TestPayload tls_buffer;
+  buffers.emplace_back(
+      "TLS Buffer", &tls_buffer, &tls_buffer,
+      [](void* ptr) { static_cast<TestPayload*>(ptr)->Rewind(); },
+      [](void* ptr) { static_cast<TestPayload*>(ptr)->Clear(); }, &tls_buffer);
+
+  StaticBuffer static_buffer;
+  buffers.emplace_back(
+      "Static Buffer", &static_buffer, &static_buffer,
+      [](void* ptr) { static_cast<StaticBuffer*>(ptr)->Rewind(); },
+      [](void* ptr) { static_cast<StaticBuffer*>(ptr)->Clear(); },
+      &static_buffer);
+
+  // Finally, run all the tests.
+  test_runner.RunTests(iteration_count, buffers);
+  return 0;
+}
diff --git a/libs/vr/libpdx/errno_guard.h b/libs/vr/libpdx/errno_guard.h
new file mode 100644
index 0000000..fc7dfdf
--- /dev/null
+++ b/libs/vr/libpdx/errno_guard.h
@@ -0,0 +1,34 @@
+#ifndef ANDROID_PDX_ERRNO_GUARD_H_
+#define ANDROID_PDX_ERRNO_GUARD_H_
+
+#include <errno.h>
+
+namespace android {
+namespace pdx {
+
+// Automatically saves and restores the system errno for API implementations to
+// prevent internal use errno from affecting API callers.
+class ErrnoGuard {
+ public:
+  ErrnoGuard() : saved_errno_(errno) {}
+  ~ErrnoGuard() { errno = saved_errno_; }
+
+  int saved_errno() const { return saved_errno_; }
+
+ private:
+  int saved_errno_;
+
+  ErrnoGuard(const ErrnoGuard&) = delete;
+  void operator=(const ErrnoGuard&) = delete;
+};
+
+// Checks |return_code| and returns either it or the negated system errno based
+// on the return code value.
+inline int ReturnCodeOrError(int return_code) {
+  return return_code < 0 ? -errno : return_code;
+}
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_ERRNO_GUARD_H_
diff --git a/libs/vr/libpdx/mock_tests.cpp b/libs/vr/libpdx/mock_tests.cpp
new file mode 100644
index 0000000..76fd154
--- /dev/null
+++ b/libs/vr/libpdx/mock_tests.cpp
@@ -0,0 +1,20 @@
+#include <gtest/gtest.h>
+#include <pdx/mock_client_channel.h>
+#include <pdx/mock_client_channel_factory.h>
+#include <pdx/mock_message_reader.h>
+#include <pdx/mock_message_writer.h>
+#include <pdx/mock_service_dispatcher.h>
+#include <pdx/mock_service_endpoint.h>
+
+TEST(MockTypes, Instantiation) {
+  // Make sure all our interfaces are mocked out properly and mock instances
+  // can be created.
+  android::pdx::MockClientChannel client_channel;
+  android::pdx::MockClientChannelFactory client_channel_factory;
+  android::pdx::MockInputResourceMapper input_resource_mapper;
+  android::pdx::MockMessageReader message_reader;
+  android::pdx::MockOutputResourceMapper output_resource_mapper;
+  android::pdx::MockMessageWriter message_writer;
+  android::pdx::MockServiceDispatcher service_dispatcher;
+  android::pdx::MockEndpoint endpoint;
+}
diff --git a/libs/vr/libpdx/private/pdx/channel_handle.h b/libs/vr/libpdx/private/pdx/channel_handle.h
new file mode 100644
index 0000000..1e62d25
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/channel_handle.h
@@ -0,0 +1,123 @@
+#ifndef ANDROID_PDX_CHANNEL_HANDLE_H_
+#define ANDROID_PDX_CHANNEL_HANDLE_H_
+
+#include <cstdint>
+#include <type_traits>
+
+namespace android {
+namespace pdx {
+
+enum class ChannelHandleMode {
+  Local,
+  Borrowed,
+  Remote,
+};
+
+class ChannelManagerInterface {
+ public:
+  virtual void CloseHandle(std::int32_t handle) = 0;
+
+ protected:
+  // Nobody should be allowed to delete the instance of channel manager
+  // through this interface.
+  virtual ~ChannelManagerInterface() = default;
+};
+
+class ChannelHandleBase {
+ public:
+  ChannelHandleBase() = default;
+  ChannelHandleBase(const int32_t& value) : value_{value} {}
+
+  ChannelHandleBase(const ChannelHandleBase&) = delete;
+  ChannelHandleBase& operator=(const ChannelHandleBase&) = delete;
+
+  std::int32_t value() const { return value_; }
+  bool valid() const { return value_ >= 0; }
+  explicit operator bool() const { return valid(); }
+
+  void Close() { value_ = kEmptyHandle; }
+
+ protected:
+  // Must not be used by itself. Must be derived from.
+  ~ChannelHandleBase() = default;
+  enum : std::int32_t { kEmptyHandle = -1 };
+
+  std::int32_t value_{kEmptyHandle};
+};
+
+template <ChannelHandleMode Mode>
+class ChannelHandle : public ChannelHandleBase {
+ public:
+  ChannelHandle() = default;
+  using ChannelHandleBase::ChannelHandleBase;
+  ChannelHandle(ChannelHandle&& other) : ChannelHandleBase{other.value_} {
+    other.value_ = kEmptyHandle;
+  }
+  ~ChannelHandle() = default;
+
+  ChannelHandle Duplicate() const { return ChannelHandle{value_}; }
+
+  ChannelHandle& operator=(ChannelHandle&& other) {
+    value_ = other.value_;
+    other.value_ = kEmptyHandle;
+    return *this;
+  }
+};
+
+template <>
+class ChannelHandle<ChannelHandleMode::Local> : public ChannelHandleBase {
+ public:
+  ChannelHandle() = default;
+  ChannelHandle(ChannelManagerInterface* manager, int32_t value)
+      : ChannelHandleBase{value}, manager_{manager} {}
+
+  ChannelHandle(const ChannelHandle&) = delete;
+  ChannelHandle& operator=(const ChannelHandle&) = delete;
+
+  ChannelHandle(ChannelHandle&& other)
+      : ChannelHandleBase{other.value_}, manager_{other.manager_} {
+    other.manager_ = nullptr;
+    other.value_ = kEmptyHandle;
+  }
+
+  ChannelHandle& operator=(ChannelHandle&& other) {
+    value_ = other.value_;
+    manager_ = other.manager_;
+    other.value_ = kEmptyHandle;
+    other.manager_ = nullptr;
+    return *this;
+  }
+
+  ~ChannelHandle() {
+    if (manager_)
+      manager_->CloseHandle(value_);
+  }
+
+  ChannelHandle<ChannelHandleMode::Borrowed> Borrow() const {
+    return ChannelHandle<ChannelHandleMode::Borrowed>{value_};
+  }
+
+  void Close() {
+    if (manager_)
+      manager_->CloseHandle(value_);
+    manager_ = nullptr;
+    value_ = kEmptyHandle;
+  }
+
+ private:
+  ChannelManagerInterface* manager_{nullptr};
+};
+
+using LocalChannelHandle = ChannelHandle<ChannelHandleMode::Local>;
+using BorrowedChannelHandle = ChannelHandle<ChannelHandleMode::Borrowed>;
+using RemoteChannelHandle = ChannelHandle<ChannelHandleMode::Remote>;
+
+// ChannelReference is a 32 bit integer used in IPC serialization to be
+// transferred across processes. You can convert this value to a local channel
+// handle by calling Transaction.GetChannelHandle().
+using ChannelReference = int32_t;
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_CHANNEL_HANDLE_H_
diff --git a/libs/vr/libpdx/private/pdx/client.h b/libs/vr/libpdx/private/pdx/client.h
new file mode 100644
index 0000000..4eafe76
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/client.h
@@ -0,0 +1,286 @@
+#ifndef ANDROID_PDX_CLIENT_H_
+#define ANDROID_PDX_CLIENT_H_
+
+#include <errno.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+#include <type_traits>
+
+#include <pdx/channel_handle.h>
+#include <pdx/client_channel.h>
+#include <pdx/client_channel_factory.h>
+#include <pdx/file_handle.h>
+#include <pdx/message_reader.h>
+#include <pdx/message_writer.h>
+#include <pdx/rpc/remote_method_type.h>
+#include <pdx/status.h>
+
+namespace android {
+namespace pdx {
+
+class Transaction;
+
+/*
+ * Base class of client-side service API classes.
+ */
+class Client {
+ public:
+  static const int64_t kInfiniteTimeout = -1;
+
+  virtual ~Client() = default;
+
+  /*
+   * Returns true if the Client instance successfully initialized, false
+   * otherwise. Subclasses that can fail to initialize must override this and
+   * AND their initialization result with this base class method's result.
+   *
+   * This method is not intended to perform initialization, only to report
+   * the status of the initialization.
+   */
+  virtual bool IsInitialized() const;
+
+  /*
+   * Returns the error code describing the Client initialization failure, or 0
+   * if there was no failure.
+   */
+  int error() const;
+
+  // Returns a reference to IPC channel handle.
+  LocalChannelHandle& GetChannelHandle();
+
+ protected:
+  friend Transaction;
+  explicit Client(std::unique_ptr<ClientChannel> channel);
+  explicit Client(std::unique_ptr<ClientChannelFactory> channel_factory,
+                  int64_t timeout_ms = kInfiniteTimeout);
+
+  /*
+   * Called by Client::Connect() after successfully connecting to the service
+   * endpoint. Subclasses may override this method to perform additional setup,
+   * including sending messages to complete the connection process.
+   *
+   * Subclasses may call Client::Close() within this method to terminate the
+   * connection; Client::Connect() returns the negated error passed to
+   * Client::Close() when this happens.
+   */
+  virtual void OnConnect();
+
+  enum : size_t { MAX_IMPULSE_LENGTH = sizeof(uint64_t) * 4 };
+
+  Status<void> SendImpulse(int opcode);
+  Status<void> SendImpulse(int opcode, const void* buffer, size_t length);
+
+  /*
+   * Remote method call API using pdx::rpc serialization.
+   * Include pdx/rpc/remote_method.h to use these methods.
+   */
+  template <typename RemoteMethodType, typename... Args>
+  Status<typename RemoteMethodType::Return> InvokeRemoteMethod(Args&&... args);
+
+  template <typename RemoteMethodType, typename ReturnType, typename... Args>
+  Status<void> InvokeRemoteMethodInPlace(ReturnType* return_value,
+                                         Args&&... args);
+
+  /*
+   * Close the endpoint file descriptor and optionally indicate an error, which
+   * may be retrieved through error(). Subclasses may use this in their
+   * constructor to signal failure during initialization or at other times
+   * during operation.
+   */
+  void Close(int error);
+
+  /*
+   * Returns true if the client is connected to the service, false otherwise.
+   */
+  bool IsConnected() const;
+
+  /*
+   * Enables auto-reconnect with the given timeout. Use kInfiniteTimeout (-1)
+   * for no timeout. Auto-reconnect can only be enabled if the Client class
+   * was constructed with a ClientChannelFactory.
+   */
+  void EnableAutoReconnect(int64_t reconnect_timeout_ms);
+
+  /*
+   * Disables auto-reconnect.
+   */
+  void DisableAutoReconnect();
+
+  int event_fd() const;
+  ClientChannel* GetChannel() const { return channel_.get(); }
+
+ private:
+  Client(const Client&) = delete;
+  void operator=(const Client&) = delete;
+
+  Status<void> CheckReconnect();
+  bool NeedToDisconnectChannel(int error) const;
+  void CheckDisconnect(int error);
+
+  template <typename T>
+  inline void CheckDisconnect(const Status<T>& status) {
+    if (!status)
+      CheckDisconnect(status.error());
+  }
+
+  std::unique_ptr<ClientChannel> channel_;
+  int error_{0};
+
+  // Reconnection state.
+  std::unique_ptr<ClientChannelFactory> channel_factory_;
+  int64_t reconnect_timeout_ms_{0};
+  bool auto_reconnect_enabled_{false};
+};
+
+/*
+ * Utility template base class for client-side service API classes. Handles
+ * initialization checks during allocation and automatically cleans up on
+ * failure.
+ *
+ * @tparam T Type of the class extending this one.
+ * @tparam C Client class to wrap. Defaults to the Client class.
+ */
+template <typename T, typename ParentClient = Client>
+class ClientBase : public ParentClient {
+ public:
+  // Type of the client this class wraps.
+  using ClientType = ParentClient;
+
+  static_assert(std::is_base_of<Client, ParentClient>::value,
+                "The provided parent client is not a Client subclass.");
+
+  /*
+   * Allocates a new instance of the superclass and checks for successful
+   * initialization.
+   *
+   * The variadic arguments must expand to match one of type T's constructors
+   * and are passed through unchanged. If a timeout is desired, subclasses are
+   * responsible for passing this through to the appropriate ClientBase
+   * constructor.
+   *
+   * Returns a unique_ptr to the new instance on success, or an empty unique_ptr
+   * otherwise.
+   */
+  template <typename... Args>
+  static inline std::unique_ptr<T> Create(Args&&... args) {
+    std::unique_ptr<T> client(new T(std::forward<Args>(args)...));
+    if (client->IsInitialized())
+      return client;
+    else
+      return nullptr;
+  }
+
+ protected:
+  /*
+   * Type of the base class. Useful for referencing the base class type and
+   * constructor in subclasses. Subclasses with non-public constructors
+   * must declare BASE a friend.
+   */
+  using BASE = ClientBase<T, ParentClient>;
+
+  /*
+   * Type of the unique_ptr deleter. Useful for friend declarations.
+   */
+  using deleter_type = typename std::unique_ptr<T>::deleter_type;
+
+  using ParentClient::ParentClient;
+};
+
+class Transaction final : public OutputResourceMapper,
+                          public InputResourceMapper {
+ public:
+  Transaction(Client& client);
+  ~Transaction();
+
+  template <typename T>
+  Status<T> Send(int opcode) {
+    return SendVector<T>(opcode, nullptr, 0, nullptr, 0);
+  }
+
+  template <typename T>
+  Status<T> Send(int opcode, const void* send_buffer, size_t send_length,
+                 void* receive_buffer, size_t receive_length) {
+    const bool send = (send_buffer && send_length);
+    const bool receive = (receive_buffer && receive_length);
+    const iovec send_vector = {const_cast<void*>(send_buffer), send_length};
+    const iovec receive_vector = {receive_buffer, receive_length};
+    return SendVector<T>(opcode, send ? &send_vector : nullptr, send ? 1 : 0,
+                         receive ? &receive_vector : nullptr, receive ? 1 : 0);
+  }
+
+  template <typename T>
+  Status<T> SendVector(int opcode, const iovec* send_vector, size_t send_count,
+                       const iovec* receive_vector, size_t receive_count) {
+    Status<T> ret;
+    SendTransaction(opcode, &ret, send_vector, send_count, receive_vector,
+                    receive_count);
+    return ret;
+  }
+
+  template <typename T, size_t send_count, size_t receive_count>
+  Status<T> SendVector(int opcode, const iovec (&send_vector)[send_count],
+                       const iovec (&receive_vector)[receive_count]) {
+    return SendVector<T>(opcode, send_vector, send_count, receive_vector,
+                         receive_count);
+  }
+
+  template <typename T, size_t send_count>
+  Status<T> SendVector(int opcode, const iovec (&send_vector)[send_count],
+                       std::nullptr_t) {
+    return SendVector<T>(opcode, send_vector, send_count, nullptr, 0);
+  }
+
+  template <typename T, size_t receive_count>
+  Status<T> SendVector(int opcode, std::nullptr_t,
+                       const iovec (&receive_vector)[receive_count]) {
+    return SendVector<T>(opcode, nullptr, 0, receive_vector, receive_count);
+  }
+
+  // OutputResourceMapper
+  FileReference PushFileHandle(const LocalHandle& handle) override;
+  FileReference PushFileHandle(const BorrowedHandle& handle) override;
+  FileReference PushFileHandle(const RemoteHandle& handle) override;
+  ChannelReference PushChannelHandle(const LocalChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      const BorrowedChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      const RemoteChannelHandle& handle) override;
+
+  // InputResourceMapper
+  bool GetFileHandle(FileReference ref, LocalHandle* handle) override;
+  bool GetChannelHandle(ChannelReference ref,
+                        LocalChannelHandle* handle) override;
+
+ private:
+  bool EnsureStateAllocated();
+  void SendTransaction(int opcode, Status<void>* ret, const iovec* send_vector,
+                       size_t send_count, const iovec* receive_vector,
+                       size_t receive_count);
+  void SendTransaction(int opcode, Status<int>* ret, const iovec* send_vector,
+                       size_t send_count, const iovec* receive_vector,
+                       size_t receive_count);
+  void SendTransaction(int opcode, Status<LocalHandle>* ret,
+                       const iovec* send_vector, size_t send_count,
+                       const iovec* receive_vector, size_t receive_count);
+  void SendTransaction(int opcode, Status<LocalChannelHandle>* ret,
+                       const iovec* send_vector, size_t send_count,
+                       const iovec* receive_vector, size_t receive_count);
+  void CheckDisconnect(int error);
+
+  template <typename T>
+  inline void CheckDisconnect(const Status<T>& status) {
+    if (!status)
+      CheckDisconnect(status.error());
+  }
+
+  Client& client_;
+  void* state_{nullptr};
+  bool state_allocated_{false};
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_CLIENT_H_
diff --git a/libs/vr/libpdx/private/pdx/client_channel.h b/libs/vr/libpdx/private/pdx/client_channel.h
new file mode 100644
index 0000000..e7ea475
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/client_channel.h
@@ -0,0 +1,56 @@
+#ifndef ANDROID_PDX_CLIENT_CHANNEL_H_
+#define ANDROID_PDX_CLIENT_CHANNEL_H_
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+#include <pdx/status.h>
+
+struct iovec;
+
+namespace android {
+namespace pdx {
+
+class ClientChannel {
+ public:
+  virtual ~ClientChannel() = default;
+
+  // Returns a tag that uniquely identifies a specific underlying IPC transport.
+  virtual uint32_t GetIpcTag() const = 0;
+
+  virtual int event_fd() const = 0;
+  virtual LocalChannelHandle& GetChannelHandle() = 0;
+  virtual void* AllocateTransactionState() = 0;
+  virtual void FreeTransactionState(void* state) = 0;
+
+  virtual Status<void> SendImpulse(int opcode, const void* buffer,
+                                   size_t length) = 0;
+
+  virtual Status<int> SendWithInt(void* transaction_state, int opcode,
+                                  const iovec* send_vector, size_t send_count,
+                                  const iovec* receive_vector,
+                                  size_t receive_count) = 0;
+  virtual Status<LocalHandle> SendWithFileHandle(
+      void* transaction_state, int opcode, const iovec* send_vector,
+      size_t send_count, const iovec* receive_vector, size_t receive_count) = 0;
+  virtual Status<LocalChannelHandle> SendWithChannelHandle(
+      void* transaction_state, int opcode, const iovec* send_vector,
+      size_t send_count, const iovec* receive_vector, size_t receive_count) = 0;
+
+  virtual FileReference PushFileHandle(void* transaction_state,
+                                       const LocalHandle& handle) = 0;
+  virtual FileReference PushFileHandle(void* transaction_state,
+                                       const BorrowedHandle& handle) = 0;
+  virtual ChannelReference PushChannelHandle(
+      void* transaction_state, const LocalChannelHandle& handle) = 0;
+  virtual ChannelReference PushChannelHandle(
+      void* transaction_state, const BorrowedChannelHandle& handle) = 0;
+  virtual bool GetFileHandle(void* transaction_state, FileReference ref,
+                             LocalHandle* handle) const = 0;
+  virtual bool GetChannelHandle(void* transaction_state, ChannelReference ref,
+                                LocalChannelHandle* handle) const = 0;
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_CLIENT_CHANNEL_H_
diff --git a/libs/vr/libpdx/private/pdx/client_channel_factory.h b/libs/vr/libpdx/private/pdx/client_channel_factory.h
new file mode 100644
index 0000000..a82ab70
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/client_channel_factory.h
@@ -0,0 +1,21 @@
+#ifndef ANDROID_PDX_CLIENT_CHANNEL_FACTORY_H_
+#define ANDROID_PDX_CLIENT_CHANNEL_FACTORY_H_
+
+#include <pdx/client_channel.h>
+#include <pdx/status.h>
+
+namespace android {
+namespace pdx {
+
+class ClientChannelFactory {
+ public:
+  virtual ~ClientChannelFactory() = default;
+
+  virtual Status<std::unique_ptr<ClientChannel>> Connect(
+      int64_t timeout_ms) const = 0;
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_CLIENT_CHANNEL_FACTORY_H_
diff --git a/libs/vr/libpdx/private/pdx/file_handle.h b/libs/vr/libpdx/private/pdx/file_handle.h
new file mode 100644
index 0000000..b3c3ad7
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/file_handle.h
@@ -0,0 +1,141 @@
+#ifndef ANDROID_PDX_FILE_HANDLE_H_
+#define ANDROID_PDX_FILE_HANDLE_H_
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <string>
+
+namespace android {
+namespace pdx {
+
+enum class FileHandleMode {
+  Local,
+  Remote,
+  Borrowed,
+};
+
+// Manages ownership, sharing, and lifetime of file descriptors.
+template <FileHandleMode Mode>
+class FileHandle {
+ public:
+  static constexpr int kEmptyFileHandle = -1;
+
+  // Constructs an empty FileHandle object.
+  FileHandle() : fd_(kEmptyFileHandle) {}
+
+  // Constructs a FileHandle from an integer file descriptor and takes
+  // ownership.
+  explicit FileHandle(int fd) : fd_(fd) {}
+
+  // Constructs a FileHandle by opening |path|. The arguments follow the
+  // semantics of open().
+  FileHandle(const std::string& path, int flags, mode_t mode = 0) {
+    fd_ = open(path.c_str(), flags, mode);
+  }
+
+  // Constructs a FileHandle by opening |path| relative to |dir_fd|, following
+  // the semantics of openat().
+  FileHandle(const int directory_fd, const std::string& path, int flags,
+             mode_t mode = 0) {
+    fd_ = openat(directory_fd, path.c_str(), flags, mode);
+  }
+
+  // Move constructor that assumes ownership of the file descriptor, leaving the
+  // other FileHandle object empty.
+  FileHandle(FileHandle&& other) {
+    fd_ = other.fd_;
+    other.fd_ = kEmptyFileHandle;
+  }
+
+  // Returns a FileHandle as a duplicate handle of |fd|. Borrowed handles and
+  // handles in remote handle space are not duplicated.
+  static FileHandle AsDuplicate(const int fd) {
+    if (Mode == FileHandleMode::Local)
+      return FileHandle(dup(fd));
+    else
+      return FileHandle(fd);
+  }
+
+  // Destructor closes the file descriptor when non-empty.
+  ~FileHandle() { Close(); }
+
+  // Move assignment operator that assumes ownership of the underlying file
+  // descriptor, leaving the other FileHandle object empty.
+  FileHandle& operator=(FileHandle&& other) {
+    if (this != &other) {
+      Reset(other.fd_);
+      other.fd_ = kEmptyFileHandle;
+    }
+    return *this;
+  }
+
+  // Resets the underlying file handle to |fd|.
+  void Reset(int fd) {
+    Close();
+    fd_ = fd;
+  }
+
+  // Closes the underlying file descriptor when non-empty.
+  void Close() {
+    if (IsValid() && Mode == FileHandleMode::Local)
+      close(fd_);
+    fd_ = kEmptyFileHandle;
+  }
+
+  // Return the internal fd, passing ownership to the caller.
+  int Release() {
+    int release_fd = fd_;
+    fd_ = kEmptyFileHandle;
+    return release_fd;
+  }
+
+  // Duplicates the underlying file descriptor and returns a FileHandle that
+  // owns the new file descriptor. File descriptors are not duplicated when Mode
+  // is Remote or Borrowed.
+  FileHandle Duplicate() const {
+    return FileHandle(Mode == FileHandleMode::Local ? dup(fd_) : fd_);
+  }
+
+  FileHandle<FileHandleMode::Borrowed> Borrow() const {
+    return FileHandle<FileHandleMode::Borrowed>(Get());
+  }
+
+  // Gets the underlying file descriptor value.
+  int Get() const { return fd_; }
+  bool IsValid() const { return fd_ >= 0; }
+  explicit operator bool() const { return IsValid(); }
+
+ private:
+  int fd_;
+
+  FileHandle(const FileHandle&) = delete;
+  void operator=(const FileHandle&) = delete;
+};
+
+// Alias for a file handle in the local process' handle space.
+using LocalHandle = FileHandle<FileHandleMode::Local>;
+
+// Alias for a file handle in another process' handle space. Handles returned
+// from pushing a file object or channel must be stored in this type of handle
+// class, which doesn't close the underlying file descriptor. The underlying
+// file descriptor in this wrapper should not be passed to close() because doing
+// so would close an unrelated file descriptor in the local handle space.
+using RemoteHandle = FileHandle<FileHandleMode::Remote>;
+
+// Alias for borrowed handles in the local process' handle space. A borrowed
+// file handle is not close() because this wrapper does not own the underlying
+// file descriptor. Care must be take to ensure that a borrowed file handle
+// remains valid during use.
+using BorrowedHandle = FileHandle<FileHandleMode::Borrowed>;
+
+// FileReference is a 16 bit integer used in IPC serialization to be
+// transferred across processes. You can convert this value to a local file
+// handle by calling Transaction.GetFileHandle() on client side and
+// Message.GetFileHandle() on service side.
+using FileReference = int16_t;
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_FILE_HANDLE_H_
diff --git a/libs/vr/libpdx/private/pdx/message_reader.h b/libs/vr/libpdx/private/pdx/message_reader.h
new file mode 100644
index 0000000..bc280cf
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/message_reader.h
@@ -0,0 +1,38 @@
+#ifndef ANDROID_PDX_MESSAGE_READER_H_
+#define ANDROID_PDX_MESSAGE_READER_H_
+
+#include <memory>
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+
+namespace android {
+namespace pdx {
+
+class InputResourceMapper {
+ public:
+  virtual bool GetFileHandle(FileReference ref, LocalHandle* handle) = 0;
+  virtual bool GetChannelHandle(ChannelReference ref,
+                                LocalChannelHandle* handle) = 0;
+
+ protected:
+  virtual ~InputResourceMapper() = default;
+};
+
+class MessageReader {
+ public:
+  // Pointers to start/end of the region in the read buffer.
+  using BufferSection = std::pair<const void*, const void*>;
+
+  virtual BufferSection GetNextReadBufferSection() = 0;
+  virtual void ConsumeReadBufferSectionData(const void* new_start) = 0;
+  virtual InputResourceMapper* GetInputResourceMapper() = 0;
+
+ protected:
+  virtual ~MessageReader() = default;
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_MESSAGE_READER_H_
diff --git a/libs/vr/libpdx/private/pdx/message_writer.h b/libs/vr/libpdx/private/pdx/message_writer.h
new file mode 100644
index 0000000..0cb6e40
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/message_writer.h
@@ -0,0 +1,38 @@
+#ifndef ANDROID_PDX_MESSAGE_WRITER_H_
+#define ANDROID_PDX_MESSAGE_WRITER_H_
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+
+namespace android {
+namespace pdx {
+
+class OutputResourceMapper {
+ public:
+  virtual FileReference PushFileHandle(const LocalHandle& handle) = 0;
+  virtual FileReference PushFileHandle(const BorrowedHandle& handle) = 0;
+  virtual FileReference PushFileHandle(const RemoteHandle& handle) = 0;
+  virtual ChannelReference PushChannelHandle(
+      const LocalChannelHandle& handle) = 0;
+  virtual ChannelReference PushChannelHandle(
+      const BorrowedChannelHandle& handle) = 0;
+  virtual ChannelReference PushChannelHandle(
+      const RemoteChannelHandle& handle) = 0;
+
+ protected:
+  virtual ~OutputResourceMapper() = default;
+};
+
+class MessageWriter {
+ public:
+  virtual void* GetNextWriteBufferSection(size_t size) = 0;
+  virtual OutputResourceMapper* GetOutputResourceMapper() = 0;
+
+ protected:
+  virtual ~MessageWriter() = default;
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_MESSAGE_WRITER_H_
diff --git a/libs/vr/libpdx/private/pdx/mock_client_channel.h b/libs/vr/libpdx/private/pdx/mock_client_channel.h
new file mode 100644
index 0000000..7780ee3
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/mock_client_channel.h
@@ -0,0 +1,55 @@
+#ifndef ANDROID_PDX_MOCK_CLIENT_CHANNEL_H_
+#define ANDROID_PDX_MOCK_CLIENT_CHANNEL_H_
+
+#include <gmock/gmock.h>
+#include <pdx/client_channel.h>
+
+namespace android {
+namespace pdx {
+
+class MockClientChannel : public ClientChannel {
+ public:
+  MOCK_CONST_METHOD0(GetIpcTag, uint32_t());
+  MOCK_CONST_METHOD0(event_fd, int());
+  MOCK_METHOD0(GetChannelHandle, LocalChannelHandle&());
+  MOCK_METHOD0(AllocateTransactionState, void*());
+  MOCK_METHOD1(FreeTransactionState, void(void* state));
+  MOCK_METHOD3(SendImpulse,
+               Status<void>(int opcode, const void* buffer, size_t length));
+  MOCK_METHOD6(SendWithInt,
+               Status<int>(void* transaction_state, int opcode,
+                           const iovec* send_vector, size_t send_count,
+                           const iovec* receive_vector, size_t receive_count));
+  MOCK_METHOD6(SendWithFileHandle,
+               Status<LocalHandle>(void* transaction_state, int opcode,
+                                   const iovec* send_vector, size_t send_count,
+                                   const iovec* receive_vector,
+                                   size_t receive_count));
+  MOCK_METHOD6(SendWithChannelHandle,
+               Status<LocalChannelHandle>(void* transaction_state, int opcode,
+                                          const iovec* send_vector,
+                                          size_t send_count,
+                                          const iovec* receive_vector,
+                                          size_t receive_count));
+  MOCK_METHOD2(PushFileHandle, FileReference(void* transaction_state,
+                                             const LocalHandle& handle));
+  MOCK_METHOD2(PushFileHandle, FileReference(void* transaction_state,
+                                             const BorrowedHandle& handle));
+  MOCK_METHOD2(PushChannelHandle,
+               ChannelReference(void* transaction_state,
+                                const LocalChannelHandle& handle));
+  MOCK_METHOD2(PushChannelHandle,
+               ChannelReference(void* transaction_state,
+                                const BorrowedChannelHandle& handle));
+  MOCK_CONST_METHOD3(GetFileHandle,
+                     bool(void* transaction_state, FileReference ref,
+                          LocalHandle* handle));
+  MOCK_CONST_METHOD3(GetChannelHandle,
+                     bool(void* transaction_state, ChannelReference ref,
+                          LocalChannelHandle* handle));
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_MOCK_CLIENT_CHANNEL_H_
diff --git a/libs/vr/libpdx/private/pdx/mock_client_channel_factory.h b/libs/vr/libpdx/private/pdx/mock_client_channel_factory.h
new file mode 100644
index 0000000..0190f5e
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/mock_client_channel_factory.h
@@ -0,0 +1,19 @@
+#ifndef ANDROID_PDX_MOCK_CLIENT_CHANNEL_FACTORY_H_
+#define ANDROID_PDX_MOCK_CLIENT_CHANNEL_FACTORY_H_
+
+#include <gmock/gmock.h>
+#include <pdx/client_channel_factory.h>
+
+namespace android {
+namespace pdx {
+
+class MockClientChannelFactory : public ClientChannelFactory {
+ public:
+  MOCK_CONST_METHOD1(
+      Connect, Status<std::unique_ptr<ClientChannel>>(int64_t timeout_ms));
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_MOCK_CLIENT_CHANNEL_FACTORY_H_
diff --git a/libs/vr/libpdx/private/pdx/mock_message_reader.h b/libs/vr/libpdx/private/pdx/mock_message_reader.h
new file mode 100644
index 0000000..85e96ef
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/mock_message_reader.h
@@ -0,0 +1,27 @@
+#ifndef ANDROID_PDX_MOCK_MESSAGE_READER_H_
+#define ANDROID_PDX_MOCK_MESSAGE_READER_H_
+
+#include <gmock/gmock.h>
+#include <pdx/message_reader.h>
+
+namespace android {
+namespace pdx {
+
+class MockInputResourceMapper : public InputResourceMapper {
+ public:
+  MOCK_METHOD2(GetFileHandle, bool(FileReference ref, LocalHandle* handle));
+  MOCK_METHOD2(GetChannelHandle,
+               bool(ChannelReference ref, LocalChannelHandle* handle));
+};
+
+class MockMessageReader : public MessageReader {
+ public:
+  MOCK_METHOD0(GetNextReadBufferSection, BufferSection());
+  MOCK_METHOD1(ConsumeReadBufferSectionData, void(const void* new_start));
+  MOCK_METHOD0(GetInputResourceMapper, InputResourceMapper*());
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_MOCK_MESSAGE_READER_H_
diff --git a/libs/vr/libpdx/private/pdx/mock_message_writer.h b/libs/vr/libpdx/private/pdx/mock_message_writer.h
new file mode 100644
index 0000000..3c513d7
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/mock_message_writer.h
@@ -0,0 +1,32 @@
+#ifndef ANDROID_PDX_MOCK_MESSAGE_WRITER_H_
+#define ANDROID_PDX_MOCK_MESSAGE_WRITER_H_
+
+#include <gmock/gmock.h>
+#include <pdx/message_writer.h>
+
+namespace android {
+namespace pdx {
+
+class MockOutputResourceMapper : public OutputResourceMapper {
+ public:
+  MOCK_METHOD1(PushFileHandle, FileReference(const LocalHandle& handle));
+  MOCK_METHOD1(PushFileHandle, FileReference(const BorrowedHandle& handle));
+  MOCK_METHOD1(PushFileHandle, FileReference(const RemoteHandle& handle));
+  MOCK_METHOD1(PushChannelHandle,
+               ChannelReference(const LocalChannelHandle& handle));
+  MOCK_METHOD1(PushChannelHandle,
+               ChannelReference(const BorrowedChannelHandle& handle));
+  MOCK_METHOD1(PushChannelHandle,
+               ChannelReference(const RemoteChannelHandle& handle));
+};
+
+class MockMessageWriter : public MessageWriter {
+ public:
+  MOCK_METHOD1(GetNextWriteBufferSection, void*(size_t size));
+  MOCK_METHOD0(GetOutputResourceMapper, OutputResourceMapper*());
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_MOCK_MESSAGE_WRITER_H_
diff --git a/libs/vr/libpdx/private/pdx/mock_service_dispatcher.h b/libs/vr/libpdx/private/pdx/mock_service_dispatcher.h
new file mode 100644
index 0000000..9b51d30
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/mock_service_dispatcher.h
@@ -0,0 +1,24 @@
+#ifndef ANDROID_PDX_MOCK_SERVICE_DISPATCHER_H_
+#define ANDROID_PDX_MOCK_SERVICE_DISPATCHER_H_
+
+#include <gmock/gmock.h>
+#include <pdx/service_dispatcher.h>
+
+namespace android {
+namespace pdx {
+
+class MockServiceDispatcher : public ServiceDispatcher {
+ public:
+  MOCK_METHOD1(AddService, int(const std::shared_ptr<Service>& service));
+  MOCK_METHOD1(RemoveService, int(const std::shared_ptr<Service>& service));
+  MOCK_METHOD0(ReceiveAndDispatch, int());
+  MOCK_METHOD1(ReceiveAndDispatch, int(int timeout));
+  MOCK_METHOD0(EnterDispatchLoop, int());
+  MOCK_METHOD1(SetCanceled, void(bool cancel));
+  MOCK_CONST_METHOD0(IsCanceled, bool());
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_MOCK_SERVICE_DISPATCHER_H_
diff --git a/libs/vr/libpdx/private/pdx/mock_service_endpoint.h b/libs/vr/libpdx/private/pdx/mock_service_endpoint.h
new file mode 100644
index 0000000..ead74d5
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/mock_service_endpoint.h
@@ -0,0 +1,66 @@
+#ifndef ANDROID_PDX_MOCK_ENDPOINT_H_
+#define ANDROID_PDX_MOCK_ENDPOINT_H_
+
+#include <gmock/gmock.h>
+#include <pdx/service_endpoint.h>
+
+namespace android {
+namespace pdx {
+
+class MockEndpoint : public Endpoint {
+ public:
+  MOCK_CONST_METHOD0(GetIpcTag, uint32_t());
+  MOCK_METHOD1(SetService, int(Service* service));
+  MOCK_METHOD2(SetChannel, int(int channel_id, Channel* channel));
+  MOCK_METHOD1(CloseChannel, int(int channel_id));
+  MOCK_METHOD3(ModifyChannelEvents,
+               int(int channel_id, int clear_mask, int set_mask));
+  MOCK_METHOD4(PushChannel,
+               Status<RemoteChannelHandle>(Message* message, int flags,
+                                           Channel* channel, int* channel_id));
+  MOCK_METHOD3(CheckChannel,
+               Status<int>(const Message* message, ChannelReference ref,
+                           Channel** channel));
+  MOCK_METHOD1(DefaultHandleMessage, int(const MessageInfo& info));
+  MOCK_METHOD1(MessageReceive, int(Message* message));
+  MOCK_METHOD2(MessageReply, int(Message* message, int return_code));
+  MOCK_METHOD2(MessageReplyFd, int(Message* message, unsigned int push_fd));
+  MOCK_METHOD2(MessageReplyChannelHandle,
+               int(Message* message, const LocalChannelHandle& handle));
+  MOCK_METHOD2(MessageReplyChannelHandle,
+               int(Message* message, const BorrowedChannelHandle& handle));
+  MOCK_METHOD2(MessageReplyChannelHandle,
+               int(Message* message, const RemoteChannelHandle& handle));
+  MOCK_METHOD3(ReadMessageData, ssize_t(Message* message, const iovec* vector,
+                                        size_t vector_length));
+  MOCK_METHOD3(WriteMessageData, ssize_t(Message* message, const iovec* vector,
+                                         size_t vector_length));
+  MOCK_METHOD2(PushFileHandle,
+               FileReference(Message* message, const LocalHandle& handle));
+  MOCK_METHOD2(PushFileHandle,
+               FileReference(Message* message, const BorrowedHandle& handle));
+  MOCK_METHOD2(PushFileHandle,
+               FileReference(Message* message, const RemoteHandle& handle));
+  MOCK_METHOD2(PushChannelHandle,
+               ChannelReference(Message* message,
+                                const LocalChannelHandle& handle));
+  MOCK_METHOD2(PushChannelHandle,
+               ChannelReference(Message* message,
+                                const BorrowedChannelHandle& handle));
+  MOCK_METHOD2(PushChannelHandle,
+               ChannelReference(Message* message,
+                                const RemoteChannelHandle& handle));
+  MOCK_CONST_METHOD2(GetFileHandle,
+                     LocalHandle(Message* message, FileReference ref));
+  MOCK_CONST_METHOD2(GetChannelHandle,
+                     LocalChannelHandle(Message* message,
+                                        ChannelReference ref));
+  MOCK_METHOD0(AllocateMessageState, void*());
+  MOCK_METHOD1(FreeMessageState, void(void* state));
+  MOCK_METHOD0(Cancel, int());
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_MOCK_ENDPOINT_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/argument_encoder.h b/libs/vr/libpdx/private/pdx/rpc/argument_encoder.h
new file mode 100644
index 0000000..e006284
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/argument_encoder.h
@@ -0,0 +1,184 @@
+#ifndef ANDROID_PDX_RPC_ARGUMENT_ENCODER_H_
+#define ANDROID_PDX_RPC_ARGUMENT_ENCODER_H_
+
+#include <cstdint>
+#include <tuple>
+#include <type_traits>
+
+#include <pdx/rpc/serialization.h>
+#include <pdx/service.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Provides automatic serialization of argument lists and return
+// values by analyzing the supplied function signature types.
+// Examples:
+//     ArgumentEncoder<int(int, float)> encoder(writer);
+//     encoder.EncodeArguments(1, 1.0);
+
+template <typename T>
+class ArgumentEncoder;
+
+// Specialization of ArgumentEncoder for void return types.
+template <typename... Args>
+class ArgumentEncoder<void(Args...)> {
+ public:
+  explicit ArgumentEncoder(MessageWriter* writer) : writer_{writer} {}
+
+  // Serializes the arguments as a tuple.
+  void EncodeArguments(Args... args) {
+    Serialize(std::forward_as_tuple(args...), writer_);
+  }
+
+ private:
+  MessageWriter* writer_;
+};
+
+// Specialization of ArgumentEncoder for non-void return types.
+template <typename Return, typename... Args>
+class ArgumentEncoder<Return(Args...)> {
+ public:
+  // Simplified types with reference and cv removed.
+  using ReturnType = typename std::decay<Return>::type;
+
+  explicit ArgumentEncoder(MessageWriter* writer) : writer_{writer} {}
+
+  // Serializes the arguments as a tuple.
+  void EncodeArguments(Args... args) {
+    Serialize(std::forward_as_tuple(args...), writer_);
+  }
+
+  // Serializes the return value for rvalue references.
+  void EncodeReturn(const ReturnType& return_value) {
+    Serialize(return_value, writer_);
+  }
+
+ private:
+  MessageWriter* writer_;
+};
+
+// Utility to build an ArgumentEncoder from a function pointer and a message
+// writer.
+template <typename Return, typename... Args>
+inline ArgumentEncoder<Return(Args...)> MakeArgumentEncoder(
+    Return (*)(Args...), MessageWriter* writer) {
+  return ArgumentEncoder<Return(Args...)>(writer);
+}
+
+// Utility to build an ArgumentEncoder from a method pointer and a message
+// writer.
+template <typename Class, typename Return, typename... Args>
+inline ArgumentEncoder<Return(Args...)> MakeArgumentEncoder(
+    Return (Class::*)(Args...), MessageWriter* writer) {
+  return ArgumentEncoder<Return(Args...)>(writer);
+}
+
+// Utility to build an ArgumentEncoder from a const method pointer and a
+// message writer.
+template <typename Class, typename Return, typename... Args>
+inline ArgumentEncoder<Return(Args...)> MakeArgumentEncoder(
+    Return (Class::*)(Args...) const, MessageWriter* writer) {
+  return ArgumentEncoder<Return(Args...)>(writer);
+}
+
+// Utility to build an ArgumentEncoder from a function type and a message
+// writer.
+template <typename Signature>
+inline ArgumentEncoder<Signature> MakeArgumentEncoder(MessageWriter* writer) {
+  return ArgumentEncoder<Signature>(writer);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Provides automatic deserialization of argument lists and return
+// values by analyzing the supplied function signature types.
+// Examples:
+//     auto decoder = MakeArgumentDecoder<std::string(void)>(reader);
+//     ErrorType error = decoder.DecodeReturn(&return_value);
+
+template <typename T>
+class ArgumentDecoder;
+
+// Specialization of ArgumentDecoder for void return types.
+template <typename... Args>
+class ArgumentDecoder<void(Args...)> {
+ public:
+  // Simplified types with reference and cv removed.
+  using ArgsTupleType = std::tuple<typename std::decay<Args>::type...>;
+
+  explicit ArgumentDecoder(MessageReader* reader) : reader_{reader} {}
+
+  // Deserializes arguments into a tuple.
+  ArgsTupleType DecodeArguments(ErrorType* error) {
+    ArgsTupleType value;
+    *error = Deserialize(&value, reader_);
+    return value;
+  }
+
+ private:
+  MessageReader* reader_;
+};
+
+// Specialization of ArgumentDecoder for non-void return types.
+template <typename Return, typename... Args>
+class ArgumentDecoder<Return(Args...)> {
+ public:
+  // Simplified types with reference and cv removed.
+  using ArgsTupleType = std::tuple<typename std::decay<Args>::type...>;
+  using ReturnType = typename std::decay<Return>::type;
+
+  explicit ArgumentDecoder(MessageReader* reader) : reader_{reader} {}
+
+  // Deserializes arguments into a tuple.
+  ArgsTupleType DecodeArguments(ErrorType* error) {
+    ArgsTupleType value;
+    *error = Deserialize(&value, reader_);
+    return value;
+  }
+
+  // Deserializes the return value.
+  ErrorType DecodeReturn(ReturnType* value) {
+    return Deserialize(value, reader_);
+  }
+
+ private:
+  MessageReader* reader_;
+};
+
+// Utility to build an ArgumentDecoder from a function pointer and a message
+// reader.
+template <typename Return, typename... Args>
+inline ArgumentDecoder<Return(Args...)> MakeArgumentDecoder(
+    Return (*)(Args...), MessageReader* reader) {
+  return ArgumentDecoder<Return(Args...)>(reader);
+}
+
+// Utility to build an ArgumentDecoder from a method pointer and a message
+// reader.
+template <typename Class, typename Return, typename... Args>
+inline ArgumentDecoder<Return(Args...)> MakeArgumentDecoder(
+    Return (Class::*)(Args...), MessageReader* reader) {
+  return ArgumentDecoder<Return(Args...)>(reader);
+}
+
+// Utility to build an ArgumentDecoder from a const method pointer and a
+// message reader.
+template <typename Class, typename Return, typename... Args>
+inline ArgumentDecoder<Return(Args...)> MakeArgumentDecoder(
+    Return (Class::*)(Args...) const, MessageReader* reader) {
+  return ArgumentDecoder<Return(Args...)>(reader);
+}
+
+// Utility to build an ArgumentDecoder from a function type and a message
+// reader.
+template <typename Signature>
+inline ArgumentDecoder<Signature> MakeArgumentDecoder(MessageReader* reader) {
+  return ArgumentDecoder<Signature>(reader);
+}
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_ARGUMENT_ENCODER_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/array_wrapper.h b/libs/vr/libpdx/private/pdx/rpc/array_wrapper.h
new file mode 100644
index 0000000..93d87f3
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/array_wrapper.h
@@ -0,0 +1,111 @@
+#ifndef ANDROID_PDX_RPC_ARRAY_WRAPPER_H_
+#define ANDROID_PDX_RPC_ARRAY_WRAPPER_H_
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <vector>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Wrapper class for C array buffers, providing an interface suitable for
+// SerializeObject and DeserializeObject. This class serializes to the same
+// format as std::vector, and may be substituted for std::vector during
+// serialization and deserialization. This substitution makes handling of C
+// arrays more efficient by avoiding unnecessary copies when remote method
+// signatures specify std::vector arguments or return values.
+template <typename T>
+class ArrayWrapper {
+ public:
+  // Define types in the style of STL containers to support STL operators.
+  typedef T value_type;
+  typedef std::size_t size_type;
+  typedef T& reference;
+  typedef const T& const_reference;
+  typedef T* pointer;
+  typedef const T* const_pointer;
+
+  ArrayWrapper() : buffer_(nullptr), capacity_(0), end_(0) {}
+
+  ArrayWrapper(pointer buffer, size_type capacity, size_type size)
+      : buffer_(&buffer[0]),
+        capacity_(capacity),
+        end_(capacity < size ? capacity : size) {}
+
+  ArrayWrapper(pointer buffer, size_type size)
+      : ArrayWrapper(buffer, size, size) {}
+
+  ArrayWrapper(const ArrayWrapper& other) { *this = other; }
+
+  ArrayWrapper(ArrayWrapper&& other) { *this = std::move(other); }
+
+  ArrayWrapper& operator=(const ArrayWrapper& other) {
+    if (&other == this) {
+      return *this;
+    } else {
+      buffer_ = other.buffer_;
+      capacity_ = other.capacity_;
+      end_ = other.end_;
+    }
+
+    return *this;
+  }
+
+  ArrayWrapper& operator=(ArrayWrapper&& other) {
+    if (&other == this) {
+      return *this;
+    } else {
+      buffer_ = other.buffer_;
+      capacity_ = other.capacity_;
+      end_ = other.end_;
+      other.buffer_ = nullptr;
+      other.capacity_ = 0;
+      other.end_ = 0;
+    }
+
+    return *this;
+  }
+
+  pointer data() { return buffer_; }
+  const_pointer data() const { return buffer_; }
+
+  pointer begin() { return &buffer_[0]; }
+  pointer end() { return &buffer_[end_]; }
+  const_pointer begin() const { return &buffer_[0]; }
+  const_pointer end() const { return &buffer_[end_]; }
+
+  size_type size() const { return end_; }
+  size_type max_size() const { return capacity_; }
+  size_type capacity() const { return capacity_; }
+
+  // Moves the end marker to |size|, clamping the end marker to the max capacity
+  // of the underlying array. This method does not change the size of the
+  // underlying array.
+  void resize(size_type size) {
+    if (size <= capacity_)
+      end_ = size;
+    else
+      end_ = capacity_;
+  }
+
+  reference operator[](size_type pos) { return buffer_[pos]; }
+  const_reference operator[](size_type pos) const { return buffer_[pos]; }
+
+ private:
+  pointer buffer_;
+  size_type capacity_;
+  size_type end_;
+};
+
+template <typename T, typename SizeType = std::size_t>
+ArrayWrapper<T> WrapArray(T* buffer, SizeType size) {
+  return ArrayWrapper<T>(buffer, size);
+}
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_ARRAY_WRAPPER_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/buffer_wrapper.h b/libs/vr/libpdx/private/pdx/rpc/buffer_wrapper.h
new file mode 100644
index 0000000..aa86531
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/buffer_wrapper.h
@@ -0,0 +1,177 @@
+#ifndef ANDROID_PDX_RPC_BUFFER_WRAPPER_H_
+#define ANDROID_PDX_RPC_BUFFER_WRAPPER_H_
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <vector>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Wrapper class for buffers, providing an interface suitable for
+// SerializeObject and DeserializeObject. This class supports serialization of
+// buffers as raw bytes.
+template <typename T>
+class BufferWrapper;
+
+template <typename T>
+class BufferWrapper<T*> {
+ public:
+  // Define types in the style of STL containers to support STL operators.
+  typedef T value_type;
+  typedef std::size_t size_type;
+  typedef T& reference;
+  typedef const T& const_reference;
+  typedef T* pointer;
+  typedef const T* const_pointer;
+
+  BufferWrapper() : buffer_(nullptr), capacity_(0), end_(0) {}
+
+  BufferWrapper(pointer buffer, size_type capacity, size_type size)
+      : buffer_(&buffer[0]),
+        capacity_(capacity),
+        end_(capacity < size ? capacity : size) {}
+
+  BufferWrapper(pointer buffer, size_type size)
+      : BufferWrapper(buffer, size, size) {}
+
+  BufferWrapper(const BufferWrapper& other) { *this = other; }
+
+  BufferWrapper(BufferWrapper&& other) { *this = std::move(other); }
+
+  BufferWrapper& operator=(const BufferWrapper& other) {
+    if (&other == this) {
+      return *this;
+    } else {
+      buffer_ = other.buffer_;
+      capacity_ = other.capacity_;
+      end_ = other.end_;
+    }
+
+    return *this;
+  }
+
+  BufferWrapper& operator=(BufferWrapper&& other) {
+    if (&other == this) {
+      return *this;
+    } else {
+      buffer_ = other.buffer_;
+      capacity_ = other.capacity_;
+      end_ = other.end_;
+      other.buffer_ = nullptr;
+      other.capacity_ = 0;
+      other.end_ = 0;
+    }
+
+    return *this;
+  }
+
+  pointer data() { return buffer_; }
+  const_pointer data() const { return buffer_; }
+
+  pointer begin() { return &buffer_[0]; }
+  pointer end() { return &buffer_[end_]; }
+  const_pointer begin() const { return &buffer_[0]; }
+  const_pointer end() const { return &buffer_[end_]; }
+
+  size_type size() const { return end_; }
+  size_type max_size() const { return capacity_; }
+  size_type capacity() const { return capacity_; }
+
+  void resize(size_type size) {
+    if (size <= capacity_)
+      end_ = size;
+    else
+      end_ = capacity_;
+  }
+
+  reference operator[](size_type pos) { return buffer_[pos]; }
+  const_reference operator[](size_type pos) const { return buffer_[pos]; }
+
+ private:
+  pointer buffer_;
+  size_type capacity_;
+  size_type end_;
+};
+
+template <typename T, typename Allocator>
+class BufferWrapper<std::vector<T, Allocator>> {
+ public:
+  using BufferType = typename std::vector<T, Allocator>;
+  using value_type = typename BufferType::value_type;
+  using size_type = typename BufferType::size_type;
+  using reference = typename BufferType::reference;
+  using const_reference = typename BufferType::const_reference;
+  using pointer = typename BufferType::pointer;
+  using const_pointer = typename BufferType::const_pointer;
+  using iterator = typename BufferType::iterator;
+  using const_iterator = typename BufferType::const_iterator;
+
+  BufferWrapper() {}
+  BufferWrapper(const BufferType& buffer) : buffer_(buffer) {}
+  BufferWrapper(const BufferType& buffer, const Allocator& allocator)
+      : buffer_(buffer, allocator) {}
+  BufferWrapper(BufferType&& buffer) : buffer_(std::move(buffer)) {}
+  BufferWrapper(BufferType&& buffer, const Allocator& allocator)
+      : buffer_(std::move(buffer), allocator) {}
+  BufferWrapper(const BufferWrapper&) = default;
+  BufferWrapper(BufferWrapper&&) = default;
+  BufferWrapper& operator=(const BufferWrapper&) = default;
+  BufferWrapper& operator=(BufferWrapper&&) = default;
+
+  pointer data() { return buffer_.data(); }
+  const_pointer data() const { return buffer_.data(); }
+
+  iterator begin() { return buffer_.begin(); }
+  iterator end() { return buffer_.end(); }
+  const_iterator begin() const { return buffer_.begin(); }
+  const_iterator end() const { return buffer_.end(); }
+
+  size_type size() const { return buffer_.size(); }
+  size_type max_size() const { return buffer_.capacity(); }
+  size_type capacity() const { return buffer_.capacity(); }
+
+  void resize(size_type size) { buffer_.resize(size); }
+  void reserve(size_type size) { buffer_.reserve(size); }
+
+  reference operator[](size_type pos) { return buffer_[pos]; }
+  const_reference operator[](size_type pos) const { return buffer_[pos]; }
+
+  BufferType& buffer() { return buffer_; }
+  const BufferType& buffer() const { return buffer_; }
+
+ private:
+  BufferType buffer_;
+};
+
+template <typename T, typename SizeType = std::size_t>
+BufferWrapper<T*> WrapBuffer(T* buffer, SizeType size) {
+  return BufferWrapper<T*>(buffer, size);
+}
+
+template <typename SizeType = std::size_t>
+BufferWrapper<std::uint8_t*> WrapBuffer(void* buffer, SizeType size) {
+  return BufferWrapper<std::uint8_t*>(static_cast<std::uint8_t*>(buffer), size);
+}
+
+template <typename SizeType = std::size_t>
+BufferWrapper<const std::uint8_t*> WrapBuffer(const void* buffer,
+                                              SizeType size) {
+  return BufferWrapper<const std::uint8_t*>(
+      static_cast<const std::uint8_t*>(buffer), size);
+}
+
+template <typename T, typename Allocator = std::allocator<T>>
+BufferWrapper<std::vector<T, Allocator>> WrapBuffer(
+    std::vector<T, Allocator>&& buffer) {
+  return BufferWrapper<std::vector<T, Allocator>>(
+      std::forward<std::vector<T, Allocator>>(buffer));
+}
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_BUFFER_WRAPPER_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/copy_cv_reference.h b/libs/vr/libpdx/private/pdx/rpc/copy_cv_reference.h
new file mode 100644
index 0000000..5ce34f8
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/copy_cv_reference.h
@@ -0,0 +1,43 @@
+#ifndef ANDROID_PDX_RPC_COPY_CV_REFERENCE_H_
+#define ANDROID_PDX_RPC_COPY_CV_REFERENCE_H_
+
+#include <type_traits>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Copies const, void, and reference qualifiers from type T to type U, such that
+// the new type U' carries the same cv-reference qualifiers as T, with the same
+// underlying type as U.
+template <typename T, typename U>
+class CopyCVReference {
+ private:
+  using R = typename std::remove_reference<T>::type;
+  using U1 =
+      typename std::conditional<std::is_const<R>::value,
+                                typename std::add_const<U>::type, U>::type;
+  using U2 =
+      typename std::conditional<std::is_volatile<R>::value,
+                                typename std::add_volatile<U1>::type, U1>::type;
+  using U3 =
+      typename std::conditional<std::is_lvalue_reference<T>::value,
+                                typename std::add_lvalue_reference<U2>::type,
+                                U2>::type;
+  using U4 =
+      typename std::conditional<std::is_rvalue_reference<T>::value,
+                                typename std::add_rvalue_reference<U3>::type,
+                                U3>::type;
+
+ public:
+  using Type = U4;
+};
+
+template <typename T, typename U>
+using CopyCVReferenceType = typename CopyCVReference<T, U>::Type;
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  //  ANDROID_PDX_RPC_COPY_CV_REFERENCE_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/default_initialization_allocator.h b/libs/vr/libpdx/private/pdx/rpc/default_initialization_allocator.h
new file mode 100644
index 0000000..b6e2980
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/default_initialization_allocator.h
@@ -0,0 +1,47 @@
+#ifndef ANDROID_PDX_RPC_DEFAULT_INITIALIZATION_ALLOCATOR_H_
+#define ANDROID_PDX_RPC_DEFAULT_INITIALIZATION_ALLOCATOR_H_
+
+#include <memory>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Allocator adaptor that interposes construct() calls to convert value
+// initialization into default initialization. All standard containers
+// value-initialize their elements when constructed with a single size_type
+// argument or when grown by a call to resize. This allocator avoids potentially
+// costly value-initialization in these situations for value types that are
+// default constructible. As a consequence, elements of non-class types are left
+// uninitialized; this is desirable when using std::vector as a resizable
+// buffer, for example.
+template <typename T, typename Allocator = std::allocator<T>>
+class DefaultInitializationAllocator : public Allocator {
+  typedef std::allocator_traits<Allocator> AllocatorTraits;
+
+ public:
+  template <typename U>
+  struct rebind {
+    using other = DefaultInitializationAllocator<
+        U, typename AllocatorTraits::template rebind_alloc<U>>;
+  };
+
+  using Allocator::Allocator;
+
+  template <typename U>
+  void construct(U* pointer) noexcept(
+      std::is_nothrow_default_constructible<U>::value) {
+    ::new (static_cast<void*>(pointer)) U;
+  }
+  template <typename U, typename... Args>
+  void construct(U* pointer, Args&&... args) {
+    AllocatorTraits::construct(static_cast<Allocator&>(*this), pointer,
+                               std::forward<Args>(args)...);
+  }
+};
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  //  ANDROID_PDX_RPC_DEFAULT_INITIALIZATION_ALLOCATOR_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/encoding.h b/libs/vr/libpdx/private/pdx/rpc/encoding.h
new file mode 100644
index 0000000..f51d807
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/encoding.h
@@ -0,0 +1,616 @@
+#ifndef ANDROID_PDX_RPC_ENCODING_H_
+#define ANDROID_PDX_RPC_ENCODING_H_
+
+#include <array>
+#include <cstdint>
+#include <cstring>
+#include <map>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <vector>
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+
+#include "array_wrapper.h"
+#include "buffer_wrapper.h"
+#include "string_wrapper.h"
+#include "variant.h"
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// This library uses a subset, or profile, of MessagePack (http://msgpack.org)
+// to encode supported data types during serialization and to verify the
+// expected data types during deserialization. One notable deviation from the
+// MessagePack specification is that little-endian byte order is used for
+// multi-byte numeric types to avoid unnecessary conversion on nearly all
+// relevant architectures.
+//
+// Some data types, integers for example, support multiple encoding strategies.
+// This library attempts to optimize for space based on the value of such types.
+// However, during decode all valid encodings for a given type are accepted.
+
+// Prefix byte for type encodings. This is the complete list of prefix bytes
+// from the MessagePack specification, even though not all types are used by
+// this library.
+enum EncodingPrefix {
+  ENCODING_TYPE_POSITIVE_FIXINT = 0x00,
+  ENCODING_TYPE_POSITIVE_FIXINT_MIN = 0x00,
+  ENCODING_TYPE_POSITIVE_FIXINT_MAX = 0x7f,
+  ENCODING_TYPE_POSITIVE_FIXINT_MASK = 0x7f,
+  ENCODING_TYPE_FIXMAP = 0x80,
+  ENCODING_TYPE_FIXMAP_MIN = 0x80,
+  ENCODING_TYPE_FIXMAP_MAX = 0x8f,
+  ENCODING_TYPE_FIXMAP_MASK = 0x0f,
+  ENCODING_TYPE_FIXARRAY = 0x90,
+  ENCODING_TYPE_FIXARRAY_MIN = 0x90,
+  ENCODING_TYPE_FIXARRAY_MAX = 0x9f,
+  ENCODING_TYPE_FIXARRAY_MASK = 0x0f,
+  ENCODING_TYPE_FIXSTR = 0xa0,
+  ENCODING_TYPE_FIXSTR_MIN = 0xa0,
+  ENCODING_TYPE_FIXSTR_MAX = 0xbf,
+  ENCODING_TYPE_FIXSTR_MASK = 0x1f,
+  ENCODING_TYPE_NIL = 0xc0,
+  ENCODING_TYPE_RESERVED = 0xc1,
+  ENCODING_TYPE_FALSE = 0xc2,
+  ENCODING_TYPE_TRUE = 0xc3,
+  ENCODING_TYPE_BIN8 = 0xc4,
+  ENCODING_TYPE_BIN16 = 0xc5,
+  ENCODING_TYPE_BIN32 = 0xc6,
+  ENCODING_TYPE_EXT8 = 0xc7,
+  ENCODING_TYPE_EXT16 = 0xc8,
+  ENCODING_TYPE_EXT32 = 0xc9,
+  ENCODING_TYPE_FLOAT32 = 0xca,
+  ENCODING_TYPE_FLOAT64 = 0xcb,
+  ENCODING_TYPE_UINT8 = 0xcc,
+  ENCODING_TYPE_UINT16 = 0xcd,
+  ENCODING_TYPE_UINT32 = 0xce,
+  ENCODING_TYPE_UINT64 = 0xcf,
+  ENCODING_TYPE_INT8 = 0xd0,
+  ENCODING_TYPE_INT16 = 0xd1,
+  ENCODING_TYPE_INT32 = 0xd2,
+  ENCODING_TYPE_INT64 = 0xd3,
+  ENCODING_TYPE_FIXEXT1 = 0xd4,
+  ENCODING_TYPE_FIXEXT2 = 0xd5,
+  ENCODING_TYPE_FIXEXT4 = 0xd6,
+  ENCODING_TYPE_FIXEXT8 = 0xd7,
+  ENCODING_TYPE_FIXEXT16 = 0xd8,
+  ENCODING_TYPE_STR8 = 0xd9,
+  ENCODING_TYPE_STR16 = 0xda,
+  ENCODING_TYPE_STR32 = 0xdb,
+  ENCODING_TYPE_ARRAY16 = 0xdc,
+  ENCODING_TYPE_ARRAY32 = 0xdd,
+  ENCODING_TYPE_MAP16 = 0xde,
+  ENCODING_TYPE_MAP32 = 0xdf,
+  ENCODING_TYPE_NEGATIVE_FIXINT = 0xe0,
+  ENCODING_TYPE_NEGATIVE_FIXINT_MIN = 0xe0,
+  ENCODING_TYPE_NEGATIVE_FIXINT_MAX = 0xff,
+};
+
+// Base encoding classes grouping multi-strategy encodings.
+enum EncodingClass {
+  ENCODING_CLASS_BOOL,
+  ENCODING_CLASS_NIL,
+  ENCODING_CLASS_INT,
+  ENCODING_CLASS_UINT,
+  ENCODING_CLASS_FLOAT,
+  ENCODING_CLASS_ARRAY,
+  ENCODING_CLASS_MAP,
+  ENCODING_CLASS_STRING,
+  ENCODING_CLASS_BINARY,
+  ENCODING_CLASS_EXTENSION,
+};
+
+// Encoding prefixes are unsigned bytes.
+typedef std::uint8_t EncodingType;
+
+// Extension encoding types defined by this library.
+enum EncodingExtType : int8_t {
+  ENCODING_EXT_TYPE_FILE_DESCRIPTOR,
+  ENCODING_EXT_TYPE_CHANNEL_HANDLE,
+};
+
+// Encoding predicates. Determines whether the given encoding is of a specific
+// type.
+inline constexpr bool IsFixintEncoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_NEGATIVE_FIXINT_MIN ... ENCODING_TYPE_NEGATIVE_FIXINT_MAX:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsUnsignedFixintEncoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsInt8Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_NEGATIVE_FIXINT_MIN ... ENCODING_TYPE_NEGATIVE_FIXINT_MAX:
+    case ENCODING_TYPE_INT8:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsUInt8Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_UINT8:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsInt16Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_NEGATIVE_FIXINT_MIN ... ENCODING_TYPE_NEGATIVE_FIXINT_MAX:
+    case ENCODING_TYPE_INT8:
+    case ENCODING_TYPE_INT16:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsUInt16Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_UINT8:
+    case ENCODING_TYPE_UINT16:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsInt32Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_NEGATIVE_FIXINT_MIN ... ENCODING_TYPE_NEGATIVE_FIXINT_MAX:
+    case ENCODING_TYPE_INT8:
+    case ENCODING_TYPE_INT16:
+    case ENCODING_TYPE_INT32:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsUInt32Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_UINT8:
+    case ENCODING_TYPE_UINT16:
+    case ENCODING_TYPE_UINT32:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsInt64Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_NEGATIVE_FIXINT_MIN ... ENCODING_TYPE_NEGATIVE_FIXINT_MAX:
+    case ENCODING_TYPE_INT8:
+    case ENCODING_TYPE_INT16:
+    case ENCODING_TYPE_INT32:
+    case ENCODING_TYPE_INT64:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsUInt64Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_UINT8:
+    case ENCODING_TYPE_UINT16:
+    case ENCODING_TYPE_UINT32:
+    case ENCODING_TYPE_UINT64:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsFixmapEncoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_FIXMAP_MIN ... ENCODING_TYPE_FIXMAP_MAX:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsFixarrayEncoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_FIXARRAY_MIN ... ENCODING_TYPE_FIXARRAY_MAX:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsFixstrEncoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_FIXSTR_MIN ... ENCODING_TYPE_FIXSTR_MAX:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsFixextEncoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_FIXEXT1:
+    case ENCODING_TYPE_FIXEXT2:
+    case ENCODING_TYPE_FIXEXT4:
+    case ENCODING_TYPE_FIXEXT8:
+    case ENCODING_TYPE_FIXEXT16:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsFloat32Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_FLOAT32:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsFloat64Encoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_FLOAT32:
+    case ENCODING_TYPE_FLOAT64:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr bool IsBoolEncoding(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_FALSE:
+    case ENCODING_TYPE_TRUE:
+      return true;
+    default:
+      return false;
+  }
+}
+
+inline constexpr std::size_t GetFixstrSize(EncodingType encoding) {
+  return encoding & ENCODING_TYPE_FIXSTR_MASK;
+}
+
+inline constexpr std::size_t GetFixarraySize(EncodingType encoding) {
+  return encoding & ENCODING_TYPE_FIXARRAY_MASK;
+}
+
+inline constexpr std::size_t GetFixmapSize(EncodingType encoding) {
+  return encoding & ENCODING_TYPE_FIXMAP_MASK;
+}
+
+inline constexpr std::size_t GetFixextSize(EncodingType encoding) {
+  switch (encoding) {
+    case ENCODING_TYPE_FIXEXT1:
+      return 1;
+    case ENCODING_TYPE_FIXEXT2:
+      return 2;
+    case ENCODING_TYPE_FIXEXT4:
+      return 4;
+    case ENCODING_TYPE_FIXEXT8:
+      return 8;
+    case ENCODING_TYPE_FIXEXT16:
+      return 16;
+    default:
+      return 0;  // Invalid fixext size.
+  }
+}
+
+// Gets the size of the encoding in bytes, not including external payload data.
+inline constexpr std::size_t GetEncodingSize(EncodingType encoding) {
+  switch (encoding) {
+    // Encoding is fully contained within the type value.
+    case ENCODING_TYPE_POSITIVE_FIXINT_MIN ... ENCODING_TYPE_POSITIVE_FIXINT_MAX:
+    case ENCODING_TYPE_NEGATIVE_FIXINT_MIN ... ENCODING_TYPE_NEGATIVE_FIXINT_MAX:
+    case ENCODING_TYPE_FIXMAP_MIN ... ENCODING_TYPE_FIXMAP_MAX:
+    case ENCODING_TYPE_FIXARRAY_MIN ... ENCODING_TYPE_FIXARRAY_MAX:
+    case ENCODING_TYPE_FIXSTR_MIN ... ENCODING_TYPE_FIXSTR_MAX:
+    case ENCODING_TYPE_NIL:
+    case ENCODING_TYPE_RESERVED:
+    case ENCODING_TYPE_FALSE:
+    case ENCODING_TYPE_TRUE:
+      return 1;
+
+    // Encoding type followed by one-byte size or immediate value.
+    case ENCODING_TYPE_BIN8:
+    case ENCODING_TYPE_EXT8:
+    case ENCODING_TYPE_UINT8:
+    case ENCODING_TYPE_INT8:
+    case ENCODING_TYPE_STR8:
+    // Encoding type followed by one-byte extension type.
+    case ENCODING_TYPE_FIXEXT1:
+    case ENCODING_TYPE_FIXEXT2:
+    case ENCODING_TYPE_FIXEXT4:
+    case ENCODING_TYPE_FIXEXT8:
+    case ENCODING_TYPE_FIXEXT16:
+      return 2;
+
+    // Encoding type followed by two-byte size or immediate value.
+    case ENCODING_TYPE_BIN16:
+    case ENCODING_TYPE_EXT16:
+    case ENCODING_TYPE_UINT16:
+    case ENCODING_TYPE_INT16:
+    case ENCODING_TYPE_STR16:
+    case ENCODING_TYPE_ARRAY16:
+    case ENCODING_TYPE_MAP16:
+      return 3;
+
+    // Encoding type followed by four-byte size or immediate value.
+    case ENCODING_TYPE_BIN32:
+    case ENCODING_TYPE_EXT32:
+    case ENCODING_TYPE_FLOAT32:
+    case ENCODING_TYPE_UINT32:
+    case ENCODING_TYPE_INT32:
+    case ENCODING_TYPE_STR32:
+    case ENCODING_TYPE_ARRAY32:
+    case ENCODING_TYPE_MAP32:
+      return 5;
+
+    // Encoding type followed by eight-byte immediate value.
+    case ENCODING_TYPE_FLOAT64:
+    case ENCODING_TYPE_UINT64:
+    case ENCODING_TYPE_INT64:
+      return 9;
+
+    default:
+      return 0;
+  }
+}
+
+// Encoding for standard types. Each supported data type has an associated
+// encoding or set of encodings. These functions determine the MessagePack
+// encoding based on the data type, value, and size of their arguments.
+
+inline constexpr EncodingType EncodeArrayType(std::size_t size) {
+  if (size < (1U << 4))
+    return ENCODING_TYPE_FIXARRAY | (size & ENCODING_TYPE_FIXARRAY_MASK);
+  else if (size < (1U << 16))
+    return ENCODING_TYPE_ARRAY16;
+  else
+    return ENCODING_TYPE_ARRAY32;
+}
+
+inline constexpr EncodingType EncodeMapType(std::size_t size) {
+  if (size < (1U << 4))
+    return ENCODING_TYPE_FIXMAP | (size & ENCODING_TYPE_FIXMAP_MASK);
+  else if (size < (1U << 16))
+    return ENCODING_TYPE_MAP16;
+  else
+    return ENCODING_TYPE_MAP32;
+}
+
+inline constexpr EncodingType EncodeStringType(std::size_t size) {
+  if (size < (1U << 5))
+    return ENCODING_TYPE_FIXSTR | (size & ENCODING_TYPE_FIXSTR_MASK);
+  else if (size < (1U << 8))
+    return ENCODING_TYPE_STR8;
+  else if (size < (1U << 16))
+    return ENCODING_TYPE_STR16;
+  else
+    return ENCODING_TYPE_STR32;
+}
+
+inline constexpr EncodingType EncodeBinType(std::size_t size) {
+  if (size < (1U << 8))
+    return ENCODING_TYPE_BIN8;
+  else if (size < (1U << 16))
+    return ENCODING_TYPE_BIN16;
+  else
+    return ENCODING_TYPE_BIN32;
+}
+
+inline EncodingType EncodeType(const EmptyVariant& /*empty*/) {
+  return ENCODING_TYPE_NIL;
+}
+
+// Variant is encoded as a single-element map, with the type index as the key.
+template <typename... Types>
+inline EncodingType EncodeType(const Variant<Types...>& /*variant*/) {
+  return EncodeMapType(1);
+}
+
+template <typename T>
+inline constexpr EncodingType EncodeType(const StringWrapper<T>& value) {
+  return EncodeStringType(value.length());
+}
+
+inline constexpr EncodingType EncodeType(const std::string& value) {
+  return EncodeStringType(value.length());
+}
+
+template <typename T, std::size_t Size>
+inline constexpr EncodingType EncodeType(const std::array<T, Size>& /*value*/) {
+  return EncodeArrayType(Size);
+}
+
+template <typename T>
+inline constexpr EncodingType EncodeType(const ArrayWrapper<T>& value) {
+  return EncodeArrayType(value.size());
+}
+
+template <typename T, typename Allocator>
+inline constexpr EncodingType EncodeType(
+    const std::vector<T, Allocator>& value) {
+  return EncodeArrayType(value.size());
+}
+
+template <typename Key, typename T, typename Compare, typename Allocator>
+inline constexpr EncodingType EncodeType(
+    const std::map<Key, T, Compare, Allocator>& value) {
+  return EncodeMapType(value.size());
+}
+
+template <typename Key, typename T, typename Hash, typename KeyEqual,
+          typename Allocator>
+inline constexpr EncodingType EncodeType(
+    const std::unordered_map<Key, T, Hash, KeyEqual, Allocator>& value) {
+  return EncodeMapType(value.size());
+}
+
+template <typename T>
+inline constexpr EncodingType EncodeType(const BufferWrapper<T>& value) {
+  // BIN size is in bytes.
+  return EncodeBinType(value.size() *
+                       sizeof(typename BufferWrapper<T>::value_type));
+}
+
+template <typename T, typename U>
+inline constexpr EncodingType EncodeType(const std::pair<T, U>& /*value*/) {
+  return EncodeArrayType(2);
+}
+
+template <typename... T>
+inline constexpr EncodingType EncodeType(const std::tuple<T...>& /*value*/) {
+  return EncodeArrayType(sizeof...(T));
+}
+
+// FileHandle is encoded as a FIXEXT2 with a type code for "FileDescriptor"
+// and a signed 16-bit index into the pushed fd array. Empty file descriptor
+// have an array index of -1.
+template <FileHandleMode Mode>
+inline constexpr EncodingType EncodeType(const FileHandle<Mode>& /*fd*/) {
+  return ENCODING_TYPE_FIXEXT2;
+}
+
+// ChannelHandle is encoded as a FIXEXT4 with a type of
+// ENCODING_EXT_TYPE_CHANNEL_HANDLE and a signed 32-bit value representing
+// a client channel in a remote process. Empty handle has a value of -1.
+template <ChannelHandleMode Mode>
+inline constexpr EncodingType EncodeType(
+    const ChannelHandle<Mode>& /*handle*/) {
+  return ENCODING_TYPE_FIXEXT4;
+}
+
+inline constexpr EncodingType EncodeType(const bool& value) {
+  return value ? ENCODING_TYPE_TRUE : ENCODING_TYPE_FALSE;
+}
+
+// Type 'char' is a little bit special in that it is distinct from 'signed char'
+// and 'unsigned char'. Treating it as an unsigned 8-bit value is safe for
+// encoding purposes and nicely handles 7-bit ASCII encodings as FIXINT.
+inline constexpr EncodingType EncodeType(const char& value) {
+  if (value < static_cast<char>(1 << 7))
+    return value;
+  else
+    return ENCODING_TYPE_UINT8;
+}
+
+inline constexpr EncodingType EncodeType(const uint8_t& value) {
+  if (value < (1U << 7))
+    return value;
+  else
+    return ENCODING_TYPE_UINT8;
+}
+inline constexpr EncodingType EncodeType(const int8_t& value) {
+  if (value >= -32)
+    return value;
+  else
+    return ENCODING_TYPE_INT8;
+}
+inline constexpr EncodingType EncodeType(const uint16_t& value) {
+  if (value < (1U << 7))
+    return static_cast<EncodingType>(value);
+  else if (value < (1U << 8))
+    return ENCODING_TYPE_UINT8;
+  else
+    return ENCODING_TYPE_UINT16;
+}
+inline constexpr EncodingType EncodeType(const int16_t& value) {
+  if (value >= -32 && value <= 127)
+    return static_cast<EncodingType>(value);
+  else if (value >= -128 && value <= 127)
+    return ENCODING_TYPE_INT8;
+  else
+    return ENCODING_TYPE_INT16;
+}
+inline constexpr EncodingType EncodeType(const uint32_t& value) {
+  if (value < (1U << 7))
+    return static_cast<EncodingType>(value);
+  else if (value < (1U << 8))
+    return ENCODING_TYPE_UINT8;
+  else if (value < (1U << 16))
+    return ENCODING_TYPE_UINT16;
+  else
+    return ENCODING_TYPE_UINT32;
+}
+inline constexpr EncodingType EncodeType(const int32_t& value) {
+  if (value >= -32 && value <= 127)
+    return static_cast<EncodingType>(value);
+  else if (value >= -128 && value <= 127)
+    return ENCODING_TYPE_INT8;
+  else if (value >= -32768 && value <= 32767)
+    return ENCODING_TYPE_INT16;
+  else
+    return ENCODING_TYPE_INT32;
+}
+inline constexpr EncodingType EncodeType(const uint64_t& value) {
+  if (value < (1ULL << 7))
+    return static_cast<EncodingType>(value);
+  else if (value < (1ULL << 8))
+    return ENCODING_TYPE_UINT8;
+  else if (value < (1ULL << 16))
+    return ENCODING_TYPE_UINT16;
+  else if (value < (1ULL << 32))
+    return ENCODING_TYPE_UINT32;
+  else
+    return ENCODING_TYPE_UINT64;
+}
+inline constexpr EncodingType EncodeType(const int64_t& value) {
+  if (value >= -32 && value <= 127)
+    return static_cast<EncodingType>(value);
+  else if (value >= -128 && value <= 127)  // Effectively [-128, -32).
+    return ENCODING_TYPE_INT8;
+  else if (value >= -32768 && value <= 32767)
+    return ENCODING_TYPE_INT16;
+  else if (value >= -2147483648 && value <= 2147483647)
+    return ENCODING_TYPE_INT32;
+  else
+    return ENCODING_TYPE_INT64;
+}
+
+inline constexpr EncodingType EncodeType(const float& /*value*/) {
+  return ENCODING_TYPE_FLOAT32;
+}
+
+inline constexpr EncodingType EncodeType(const double& /*value*/) {
+  return ENCODING_TYPE_FLOAT64;
+}
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_ENCODING_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/enumeration.h b/libs/vr/libpdx/private/pdx/rpc/enumeration.h
new file mode 100644
index 0000000..7a35d31
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/enumeration.h
@@ -0,0 +1,65 @@
+#ifndef ANDROID_PDX_RPC_ENUMERATION_H_
+#define ANDROID_PDX_RPC_ENUMERATION_H_
+
+#include <pdx/rpc/sequence.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Utility for manipulating lists of types. Provides operations to lookup an
+// element by type or index.
+
+namespace detail {
+
+// Helper type that captures type and index for each element of a type
+// enumeration.
+template <std::size_t I, typename T>
+struct IndexedElement {
+  using Type = T;
+  static constexpr std::size_t Index = I;
+};
+
+// Helper type that captures an IndexSequence and corresponding list of types.
+template <typename Is, typename... Ts>
+struct ElementIndexer;
+
+// Partial specialization that generates an instantiation of IndexElement<I, T>
+// for each element of a type enumeration using inheritance. Once a type
+// enumeration is instantiated this way the compiler is able to deduce either I
+// or T from the other using the method below.
+template <std::size_t... Is, typename... Ts>
+struct ElementIndexer<IndexSequence<Is...>, Ts...> : IndexedElement<Is, Ts>... {
+};
+
+// Helper function that causes the compiler to deduce an IndexedElement<I, T>
+// given T.
+template <typename T, std::size_t I>
+static IndexedElement<I, T> SelectElementByType(IndexedElement<I, T>);
+
+// Helper function that causes the compiler to deduce an IndexedElement<I, T>
+// given I.
+template <std::size_t I, typename T>
+static IndexedElement<I, T> SelectElementByIndex(IndexedElement<I, T>);
+
+}  // namespace detail
+
+// Deduces the IndexedElement<I, T> given T and a type sequence Ts. This may be
+// used to determine the index of T within Ts at compile time.
+template <typename T, typename... Ts>
+using ElementForType = decltype(detail::SelectElementByType<T>(
+    detail::ElementIndexer<typename IndexSequenceFor<Ts...>::type, Ts...>{}));
+
+// Deduces the IndexedElement<I, T> given I and a type sequence Ts. This may be
+// used to determine the type of the element at index I within Ts at compile
+// time. Tuple operations may also be used to accomplish the same task, however
+// this implementation is provided here for symmetry.
+template <std::size_t I, typename... Ts>
+using ElementForIndex = decltype(detail::SelectElementByIndex<I>(
+    detail::ElementIndexer<typename IndexSequenceFor<Ts...>::type, Ts...>{}));
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_ENUMERATION_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/find_replace.h b/libs/vr/libpdx/private/pdx/rpc/find_replace.h
new file mode 100644
index 0000000..b4b086b
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/find_replace.h
@@ -0,0 +1,45 @@
+#ifndef ANDROID_PDX_RPC_FIND_REPLACE_H_
+#define ANDROID_PDX_RPC_FIND_REPLACE_H_
+
+#include <type_traits>
+
+#include <pdx/rpc/copy_cv_reference.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Utility class to capture types to find and replace.
+template <typename Find, typename Replace>
+struct FindReplace;
+
+template <typename T, typename U>
+using IsSameBaseType = typename std::is_same<typename std::decay<T>::type,
+                                             typename std::decay<U>::type>;
+
+// Replaces the type Subject with type Replace if type Subject is the same type
+// as type Find, excluding cv-reference qualifiers in the match.
+template <typename Find, typename Replace, typename Subject>
+using ReplaceType =
+    typename std::conditional<IsSameBaseType<Find, Subject>::value,
+                              CopyCVReferenceType<Subject, Replace>,
+                              Subject>::type;
+
+// Determines whether the type Find (excluding cv-reference qualifiers) is in
+// the given parameter pack.
+template <typename Find, typename... Types>
+struct ContainsType : std::true_type {};
+
+template <typename Find, typename First, typename... Rest>
+struct ContainsType<Find, First, Rest...>
+    : std::conditional<IsSameBaseType<Find, First>::value, std::true_type,
+                       ContainsType<Find, Rest...>>::type {};
+
+template <typename Find>
+struct ContainsType<Find> : std::false_type {};
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  //  ANDROID_PDX_RPC_FIND_REPLACE_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/function_traits.h b/libs/vr/libpdx/private/pdx/rpc/function_traits.h
new file mode 100644
index 0000000..5fdad72
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/function_traits.h
@@ -0,0 +1,55 @@
+#ifndef ANDROID_PDX_RPC_FUNCTION_TRAITS_H_
+#define ANDROID_PDX_RPC_FUNCTION_TRAITS_H_
+
+#include <type_traits>
+
+#include <pdx/rpc/type_operators.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Utility type to capture return and argument types of a function signature.
+// Examples:
+//     typedef SignatureType<int(int)> SignatureType;
+//     using SignatureType = SignatureType<int(int)>;
+template <typename T>
+using SignatureType = T;
+
+// Utility class to extract return and argument types from function types.
+// Provides nested types for return value, arguments, and full signature. Also
+// provides accessor types for individual arguments, argument-arity, and type
+// substitution.
+template <typename T>
+struct FunctionTraits;
+
+template <typename Return_, typename... Args_>
+struct FunctionTraits<Return_(Args_...)> {
+  using Return = Return_;
+  using Args = std::tuple<Args_...>;
+  using Signature = SignatureType<Return_(Args_...)>;
+
+  enum : std::size_t { Arity = sizeof...(Args_) };
+
+  template <std::size_t Index>
+  using Arg = typename std::tuple_element<Index, Args>::type;
+
+  template <typename... Params>
+  using RewriteArgs =
+      SignatureType<Return_(ConditionalRewrite<Args_, Params>...)>;
+
+  template <typename ReturnType, typename... Params>
+  using RewriteSignature =
+      SignatureType<ConditionalRewrite<Return_, ReturnType>(
+          ConditionalRewrite<Args_, Params>...)>;
+
+  template <typename ReturnType>
+  using RewriteReturn =
+      SignatureType<ConditionalRewrite<Return_, ReturnType>(Args_...)>;
+};
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  //  ANDROID_PDX_RPC_FUNCTION_TRAITS_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/macros.h b/libs/vr/libpdx/private/pdx/rpc/macros.h
new file mode 100644
index 0000000..aeae9d3
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/macros.h
@@ -0,0 +1,148 @@
+#ifndef ANDROID_PDX_RPC_MACROS_H_
+#define ANDROID_PDX_RPC_MACROS_H_
+
+// Macros to apply other macros over all elements in a list.
+//
+// For example, for a macro A(x) and B(x, y):
+// - FOR_EACH(A, 1, 2, 3) -> A(1) A(2) A(3).
+// - FOR_EACH_BINARY(B, z, 1, 2, 3) -> B(z, 1) B(z, 2) B(z, 3)
+// - FOR_EACH_LIST(A, 1, 2, 3) -> A(1), B(2), C(3)
+// - FOR_EACH_BINARY_LIST(B, z, 1, 2, 3) -> B(z, 1), B(z, 2), B(z, 3)
+//
+// Empty lists are supported and will produce no output.
+
+// Recursive expansion macros.
+#define _PDX_EXPAND0(...) __VA_ARGS__
+#define _PDX_EXPAND1(...) _PDX_EXPAND0(_PDX_EXPAND0(_PDX_EXPAND0(__VA_ARGS__)))
+#define _PDX_EXPAND2(...) _PDX_EXPAND1(_PDX_EXPAND1(_PDX_EXPAND1(__VA_ARGS__)))
+#define _PDX_EXPAND3(...) _PDX_EXPAND2(_PDX_EXPAND2(_PDX_EXPAND2(__VA_ARGS__)))
+#define _PDX_EXPAND4(...) _PDX_EXPAND3(_PDX_EXPAND3(_PDX_EXPAND3(__VA_ARGS__)))
+#define _PDX_EXPAND(...) _PDX_EXPAND4(_PDX_EXPAND4(_PDX_EXPAND4(__VA_ARGS__)))
+
+// Required to workaround a bug in the VC++ preprocessor.
+#define _PDX_INDIRECT_EXPAND(macro, args) macro args
+
+// Defines a step separation for macro expansion.
+#define _PDX_SEPARATOR
+
+// Clears any remaining contents wrapped in parentheses.
+#define _PDX_CLEAR(...)
+
+// Introduces a first dummy argument and _PDX_CLEAR as second argument.
+#define _PDX_CLEAR_IF_LAST() _, _PDX_CLEAR
+
+// Returns the first argument of a list.
+#define _PDX_FIRST_ARG(first, ...) first
+
+// Returns the second argument of a list.
+#define _PDX_SECOND_ARG(_, second, ...) second
+
+// Expands the arguments and introduces a separator.
+#define _PDX_EXPAND_NEXT_FUNC(_, next_func, ...)        \
+  _PDX_INDIRECT_EXPAND(_PDX_SECOND_ARG, (_, next_func)) \
+  _PDX_SEPARATOR
+
+// Returns next_func if the next element is not (), or _PDX_CLEAR
+// otherwise.
+//
+// _PDX_CLEAR_IF_LAST inserts an extra first dummy argument if peek is ().
+#define _PDX_NEXT_FUNC(next_element, next_func) \
+  _PDX_EXPAND_NEXT_FUNC(_PDX_CLEAR_IF_LAST next_element, next_func)
+
+// Macros for the unary version of PDX_FOR_EACH.
+
+// Applies the unary macro. Duplicated for macro recursive expansion.
+#define _PDX_APPLY_1(macro, head, next, ...) \
+  macro(head) _PDX_NEXT_FUNC(next, _PDX_APPLY_2)(macro, next, __VA_ARGS__)
+
+// Applies the unary macro. Duplicated for macro recursive expansion.
+#define _PDX_APPLY_2(macro, head, next, ...) \
+  macro(head) _PDX_NEXT_FUNC(next, _PDX_APPLY_1)(macro, next, __VA_ARGS__)
+
+// Stops expansion if __VA_ARGS__ is empty, calling _PDX_APPLY_1
+// otherwise.
+#define _PDX_HANDLE_EMPTY_ARGS(macro, ...)                    \
+  _PDX_NEXT_FUNC(_PDX_FIRST_ARG(__VA_ARGS__()), _PDX_APPLY_1) \
+  (macro, __VA_ARGS__, ())
+
+// Applies a unary macro over all the elements in a list.
+#define PDX_FOR_EACH(macro, ...) \
+  _PDX_EXPAND(_PDX_HANDLE_EMPTY_ARGS(macro, __VA_ARGS__))
+
+// Applies the unary macro at the end of a list. Duplicated for macro recursive
+// expansion.
+#define _PDX_APPLY_LIST_1(macro, head, next, ...) \
+  , macro(head)                                   \
+        _PDX_NEXT_FUNC(next, _PDX_APPLY_LIST_2)(macro, next, __VA_ARGS__)
+
+// Applies the unary macro at the end of a list. Duplicated for macro recursive
+// expansion.
+#define _PDX_APPLY_LIST_2(macro, head, next, ...) \
+  , macro(head)                                   \
+        _PDX_NEXT_FUNC(next, _PDX_APPLY_LIST_1)(macro, next, __VA_ARGS__)
+
+// Applies the unary macro at the start of a list.
+#define _PDX_APPLY_LIST_0(macro, head, next, ...) \
+  macro(head) _PDX_NEXT_FUNC(next, _PDX_APPLY_LIST_1)(macro, next, __VA_ARGS__)
+
+// Stops expansion if __VA_ARGS__ is empty, calling _PDX_APPLY_LIST_0
+// otherwise.
+#define _PDX_HANDLE_EMPTY_LIST(macro, ...)                         \
+  _PDX_NEXT_FUNC(_PDX_FIRST_ARG(__VA_ARGS__()), _PDX_APPLY_LIST_0) \
+  (macro, __VA_ARGS__, ())
+
+// Applies a unary macro over all the elements in a list.
+#define PDX_FOR_EACH_LIST(macro, ...) \
+  _PDX_EXPAND(_PDX_HANDLE_EMPTY_LIST(macro, __VA_ARGS__))
+
+// Macros for the binary version of PDX_FOR_EACH.
+
+// Applies the binary macro. Duplicated for macro recursive expansion.
+#define _PDX_APPLY_BINARY_1(macro, arg, head, next, ...) \
+  macro(arg, head)                                       \
+      _PDX_NEXT_FUNC(next, _PDX_APPLY_BINARY_2)(macro, arg, next, __VA_ARGS__)
+
+// Applies the binary macro. Duplicated for macro recursive expansion.
+#define _PDX_APPLY_BINARY_2(macro, arg, head, next, ...) \
+  macro(arg, head)                                       \
+      _PDX_NEXT_FUNC(next, _PDX_APPLY_BINARY_1)(macro, arg, next, __VA_ARGS__)
+
+// Version of _PDX_HANDLE_EMPTY_ARGS that takes 1 fixed argument for a
+// binary macro.
+#define _PDX_HANDLE_EMPTY_ARGS_BINARY(macro, arg, ...)               \
+  _PDX_NEXT_FUNC(_PDX_FIRST_ARG(__VA_ARGS__()), _PDX_APPLY_BINARY_1) \
+  (macro, arg, __VA_ARGS__, ())
+
+// Applies a binary macro over all the elements in a list and a given argument.
+#define PDX_FOR_EACH_BINARY(macro, arg, ...) \
+  _PDX_EXPAND(_PDX_HANDLE_EMPTY_ARGS_BINARY(macro, arg, __VA_ARGS__))
+
+// Applies the binary macro at the end of a list. Duplicated for macro recursive
+// expansion.
+#define _PDX_APPLY_BINARY_LIST_1(macro, arg, head, next, ...)        \
+  , macro(arg, head) _PDX_NEXT_FUNC(next, _PDX_APPLY_BINARY_LIST_2)( \
+        macro, arg, next, __VA_ARGS__)
+
+// Applies the binary macro at the end of a list. Duplicated for macro recursive
+// expansion.
+#define _PDX_APPLY_BINARY_LIST_2(macro, arg, head, next, ...)        \
+  , macro(arg, head) _PDX_NEXT_FUNC(next, _PDX_APPLY_BINARY_LIST_1)( \
+        macro, arg, next, __VA_ARGS__)
+
+// Applies the binary macro at the start of a list. Duplicated for macro
+// recursive expansion.
+#define _PDX_APPLY_BINARY_LIST_0(macro, arg, head, next, ...)      \
+  macro(arg, head) _PDX_NEXT_FUNC(next, _PDX_APPLY_BINARY_LIST_1)( \
+      macro, arg, next, __VA_ARGS__)
+
+// Version of _PDX_HANDLE_EMPTY_LIST that takes 1 fixed argument for a
+// binary macro.
+#define _PDX_HANDLE_EMPTY_LIST_BINARY(macro, arg, ...)                    \
+  _PDX_NEXT_FUNC(_PDX_FIRST_ARG(__VA_ARGS__()), _PDX_APPLY_BINARY_LIST_0) \
+  (macro, arg, __VA_ARGS__, ())
+
+// Applies a binary macro over all the elements in a list and a given argument.
+#define PDX_FOR_EACH_BINARY_LIST(macro, arg, ...) \
+  _PDX_EXPAND(_PDX_HANDLE_EMPTY_LIST_BINARY(macro, arg, __VA_ARGS__))
+
+#endif  // ANDROID_PDX_RPC_MACROS_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/message_buffer.h b/libs/vr/libpdx/private/pdx/rpc/message_buffer.h
new file mode 100644
index 0000000..ba4e86e
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/message_buffer.h
@@ -0,0 +1,22 @@
+#ifndef ANDROID_PDX_RPC_MESSAGE_BUFFER_H_
+#define ANDROID_PDX_RPC_MESSAGE_BUFFER_H_
+
+#include <pdx/rpc/thread_local_buffer.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Utility type for thread-local buffers, providing suitable defaults for most
+// situations. Independent thread-local buffers may be created by using
+// different types for Slot -- ThreadLocalSlot, ThreadLocalTypedSlot and
+// ThreadLocalIndexedSlot provide utilities for building these types.
+template <typename Slot, std::size_t Capacity = 4096, typename T = std::uint8_t,
+          typename Allocator = DefaultInitializationAllocator<T>>
+using MessageBuffer = ThreadLocalBuffer<T, Allocator, Capacity, Slot>;
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_MESSAGE_BUFFER_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/payload.h b/libs/vr/libpdx/private/pdx/rpc/payload.h
new file mode 100644
index 0000000..a48a64c
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/payload.h
@@ -0,0 +1,157 @@
+#ifndef ANDROID_PDX_RPC_PAYLOAD_H_
+#define ANDROID_PDX_RPC_PAYLOAD_H_
+
+#include <iterator>
+
+#include <pdx/client.h>
+#include <pdx/rpc/message_buffer.h>
+#include <pdx/service.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Implements the payload interface, required by Serialize/Deserialize, on top
+// of a thread-local MessageBuffer.
+template <typename Slot>
+class MessagePayload {
+ public:
+  using BufferType = typename MessageBuffer<Slot>::BufferType;
+  using ValueType = typename MessageBuffer<Slot>::ValueType;
+
+  // Constructs a MessagePayload with an empty TLS buffer.
+  MessagePayload()
+      : buffer_(MessageBuffer<Slot>::GetEmptyBuffer()),
+        cursor_(buffer_.begin()),
+        const_cursor_(buffer_.cbegin()) {}
+
+  // Returns a reference to the cursor iterator to be used during serialization
+  // into the underlying MessageBuffer.
+  typename BufferType::iterator& Cursor() { return cursor_; }
+
+  // Returns a reference to the const cursor iterator at the beginning of the
+  // underlying MessageBuffer.
+  typename BufferType::const_iterator& ConstCursor() { return const_cursor_; }
+
+  // Returns a const iterator marking the end of the underlying MessageBuffer.
+  typename BufferType::const_iterator ConstEnd() { return buffer_.cend(); }
+
+  // Resizes the underlying MessageBuffer and sets the cursor to the beginning.
+  void Resize(std::size_t size) {
+    buffer_.resize(size);
+    cursor_ = buffer_.begin();
+    const_cursor_ = buffer_.cbegin();
+  }
+
+  // Resets the read cursor so that data can be read from the buffer again.
+  void Rewind() { const_cursor_ = buffer_.cbegin(); }
+
+  // Adds |size| bytes to the size of the underlying MessageBuffer and positions
+  // the cursor at the beginning of the extended region.
+  void Extend(std::size_t size) {
+    const std::size_t offset = buffer_.size();
+    buffer_.resize(offset + size);
+    cursor_ = buffer_.begin() + offset;
+    const_cursor_ = buffer_.cbegin() + offset;
+  }
+
+  // Clears the underlying MessageBuffer and sets the cursor to the beginning.
+  void Clear() {
+    buffer_.clear();
+    cursor_ = buffer_.begin();
+    const_cursor_ = buffer_.cbegin();
+  }
+
+  ValueType* Data() { return buffer_.data(); }
+  const ValueType* Data() const { return buffer_.data(); }
+  std::size_t Size() const { return buffer_.size(); }
+  std::size_t Capacity() const { return buffer_.capacity(); }
+
+ private:
+  BufferType& buffer_;
+  typename BufferType::iterator cursor_;
+  typename BufferType::const_iterator const_cursor_;
+
+  MessagePayload(const MessagePayload<Slot>&) = delete;
+  void operator=(const MessagePayload<Slot>&) = delete;
+};
+
+// Implements the payload interface for service-side RPCs. Handles translating
+// between remote and local handle spaces automatically.
+template <typename Slot>
+class ServicePayload : public MessagePayload<Slot>,
+                       public MessageWriter,
+                       public MessageReader {
+ public:
+  ServicePayload(Message& message) : message_(message) {}
+
+  // MessageWriter
+  void* GetNextWriteBufferSection(size_t size) override {
+    this->Extend(size);
+    return &*this->Cursor();
+  }
+
+  OutputResourceMapper* GetOutputResourceMapper() override { return &message_; }
+
+  // MessageReader
+  BufferSection GetNextReadBufferSection() override {
+    return {&*this->ConstCursor(), &*this->ConstEnd()};
+  }
+
+  void ConsumeReadBufferSectionData(const void* new_start) override {
+    std::advance(this->ConstCursor(),
+                 PointerDistance(new_start, &*this->ConstCursor()));
+  }
+
+  InputResourceMapper* GetInputResourceMapper() override { return &message_; }
+
+ private:
+  Message& message_;
+};
+
+// Implements the payload interface for client-side RPCs. Handles gathering file
+// handles to be sent over IPC automatically.
+template <typename Slot>
+class ClientPayload : public MessagePayload<Slot>,
+                      public MessageWriter,
+                      public MessageReader {
+ public:
+  using ContainerType =
+      MessageBuffer<ThreadLocalTypeSlot<ClientPayload<Slot>>, 1024u, int>;
+  using BufferType = typename ContainerType::BufferType;
+
+  ClientPayload(Transaction& transaction) : transaction_{transaction} {}
+
+  // MessageWriter
+  void* GetNextWriteBufferSection(size_t size) override {
+    this->Extend(size);
+    return &*this->Cursor();
+  }
+
+  OutputResourceMapper* GetOutputResourceMapper() override {
+    return &transaction_;
+  }
+
+  // MessageReader
+  BufferSection GetNextReadBufferSection() override {
+    return {&*this->ConstCursor(), &*this->ConstEnd()};
+  }
+
+  void ConsumeReadBufferSectionData(const void* new_start) override {
+    std::advance(this->ConstCursor(),
+                 PointerDistance(new_start, &*this->ConstCursor()));
+  }
+
+  InputResourceMapper* GetInputResourceMapper() override {
+    return &transaction_;
+  }
+
+ private:
+  Transaction& transaction_;
+};
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_PAYLOAD_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/pointer_wrapper.h b/libs/vr/libpdx/private/pdx/rpc/pointer_wrapper.h
new file mode 100644
index 0000000..d496719
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/pointer_wrapper.h
@@ -0,0 +1,38 @@
+#ifndef ANDROID_PDX_RPC_POINTER_WRAPPER_H_
+#define ANDROID_PDX_RPC_POINTER_WRAPPER_H_
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Wrapper class for pointers to any serializable type. This class is used by
+// serialization/deserialization to handle pointers to objects that are to be
+// serialized or deserialized.
+template <typename T>
+class PointerWrapper {
+ public:
+  using BaseType = T;
+
+  PointerWrapper(T* pointer) : pointer_(pointer) {}
+  PointerWrapper(const PointerWrapper&) = default;
+  PointerWrapper(PointerWrapper&&) = default;
+  PointerWrapper& operator=(const PointerWrapper&) = default;
+  PointerWrapper& operator=(PointerWrapper&&) = default;
+
+  T& Dereference() { return *pointer_; }
+  const T& Dereference() const { return *pointer_; }
+
+ private:
+  T* pointer_;
+};
+
+template <typename T>
+PointerWrapper<T> WrapPointer(T* pointer) {
+  return PointerWrapper<T>(pointer);
+}
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_POINTER_WRAPPER_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/remote_method.h b/libs/vr/libpdx/private/pdx/rpc/remote_method.h
new file mode 100644
index 0000000..49bee40
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/remote_method.h
@@ -0,0 +1,550 @@
+#ifndef ANDROID_PDX_RPC_REMOTE_METHOD_H_
+#define ANDROID_PDX_RPC_REMOTE_METHOD_H_
+
+#include <tuple>
+#include <type_traits>
+
+#include <pdx/client.h>
+#include <pdx/rpc/argument_encoder.h>
+#include <pdx/rpc/message_buffer.h>
+#include <pdx/rpc/payload.h>
+#include <pdx/rpc/remote_method_type.h>
+#include <pdx/service.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+#ifdef __clang__
+// Stand-in type to avoid Clang compiler bug. Clang currently has a bug where
+// performing parameter pack expansion for arguments with empty packs causes a
+// compiler crash. Provide a substitute void type and specializations/overloads
+// of CheckArgumentTypes and DispatchRemoteMethod to work around this problem.
+struct Void {};
+
+// Evaluates to true if the method type is <any>(Void), false otherwise.
+template <typename RemoteMethodType>
+using IsVoidMethod = typename std::integral_constant<
+    bool,
+    RemoteMethodType::Traits::Arity == 1 &&
+        std::is_same<typename RemoteMethodType::Traits::template Arg<0>,
+                     Void>::value>;
+
+// Utility to determine if a method is of type <any>(Void).
+template <typename RemoteMethodType>
+using EnableIfVoidMethod =
+    typename std::enable_if<IsVoidMethod<RemoteMethodType>::value>::type;
+
+// Utility to determine if a method is not of type <any>(Void).
+template <typename RemoteMethodType>
+using EnableIfNotVoidMethod =
+    typename std::enable_if<!IsVoidMethod<RemoteMethodType>::value>::type;
+
+#else
+// GCC works fine with void argument types, always enable the regular
+// implementation of DispatchRemoteMethod.
+using Void = void;
+template <typename RemoteMethodType>
+using EnableIfVoidMethod = void;
+template <typename RemoteMethodType>
+using EnableIfNotVoidMethod = void;
+#endif
+
+// Helper type trait to specialize InvokeRemoteMethods for return types that
+// can be obtained directly from Transaction::Send<T>() without deserializing
+// reply payload.
+template <typename T>
+struct IsDirectReturn : std::false_type {};
+
+template <>
+struct IsDirectReturn<void> : std::true_type {};
+
+template <>
+struct IsDirectReturn<int> : std::true_type {};
+
+template <>
+struct IsDirectReturn<LocalHandle> : std::true_type {};
+
+template <>
+struct IsDirectReturn<LocalChannelHandle> : std::true_type {};
+
+template <typename Return, typename Type = void>
+using EnableIfDirectReturn =
+    typename std::enable_if<IsDirectReturn<Return>::value, Type>::type;
+
+template <typename Return, typename Type = void>
+using EnableIfNotDirectReturn =
+    typename std::enable_if<!IsDirectReturn<Return>::value, Type>::type;
+
+// Utility class to invoke a method with arguments packed in a tuple.
+template <typename Class, typename T>
+class UnpackArguments;
+
+// Utility class to invoke a method with arguments packed in a tuple.
+template <typename Class, typename Return, typename... Args>
+class UnpackArguments<Class, Return(Args...)> {
+ public:
+  using ArgsTupleType = std::tuple<typename std::decay<Args>::type...>;
+  using MethodType = Return (Class::*)(Message&, Args...);
+
+  UnpackArguments(Class& instance, MethodType method, Message& message,
+                  ArgsTupleType& parameters)
+      : instance_(instance),
+        method_(method),
+        message_(message),
+        parameters_(parameters) {}
+
+  // Invokes method_ on intance_ with the packed arguments from parameters_.
+  Return Invoke() {
+    constexpr auto Arity = sizeof...(Args);
+    return static_cast<Return>(InvokeHelper(MakeIndexSequence<Arity>{}));
+  }
+
+ private:
+  Class& instance_;
+  MethodType method_;
+  Message& message_;
+  ArgsTupleType& parameters_;
+
+  template <std::size_t... Index>
+  Return InvokeHelper(IndexSequence<Index...>) {
+    return static_cast<Return>((instance_.*method_)(
+        message_,
+        std::get<Index>(std::forward<ArgsTupleType>(parameters_))...));
+  }
+
+  UnpackArguments(const UnpackArguments&) = delete;
+  void operator=(const UnpackArguments&) = delete;
+};
+
+// Returns an error code from a remote method to the client. May be called
+// either during dispatch of the remote method handler or at a later time if the
+// message is moved for delayed response.
+inline void RemoteMethodError(Message& message, int error_code) {
+  const int ret = message.ReplyError(error_code);
+  ALOGE_IF(ret < 0, "RemoteMethodError: Failed to reply to message: %s",
+           strerror(-ret));
+}
+
+// Returns a value from a remote method to the client. The usual method to
+// return a value during dispatch of a remote method is to simply use a return
+// statement in the handler. If the message is moved however, these methods may
+// be used to return a value at a later time, outside of initial dispatch.
+
+// Overload for direct return types.
+template <typename RemoteMethodType, typename Return>
+EnableIfDirectReturn<typename RemoteMethodType::Return> RemoteMethodReturn(
+    Message& message, const Return& return_value) {
+  const int ret = message.Reply(return_value);
+  ALOGE_IF(ret < 0, "RemoteMethodReturn: Failed to reply to message: %s",
+           strerror(-ret));
+}
+
+// Overload for non-direct return types.
+template <typename RemoteMethodType, typename Return>
+EnableIfNotDirectReturn<typename RemoteMethodType::Return> RemoteMethodReturn(
+    Message& message, const Return& return_value) {
+  using Signature = typename RemoteMethodType::template RewriteReturn<Return>;
+  rpc::ServicePayload<ReplyBuffer> payload(message);
+  MakeArgumentEncoder<Signature>(&payload).EncodeReturn(return_value);
+
+  int ret;
+  auto size = message.Write(payload.Data(), payload.Size());
+  if (size < static_cast<decltype(size)>(payload.Size()))
+    ret = message.ReplyError(EIO);
+  else
+    ret = message.Reply(0);
+  ALOGE_IF(ret < 0, "RemoteMethodReturn: Failed to reply to message: %s",
+           strerror(-ret));
+}
+
+// Dispatches a method by deserializing arguments from the given Message, with
+// compile-time interface check. Overload for void return types.
+template <typename RemoteMethodType, typename Class, typename... Args,
+          typename = EnableIfNotVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance,
+                          void (Class::*method)(Message&, Args...),
+                          Message& message,
+                          std::size_t max_capacity = InitialBufferCapacity) {
+  using Signature = typename RemoteMethodType::template RewriteArgs<Args...>;
+  rpc::ServicePayload<ReceiveBuffer> payload(message);
+  payload.Resize(max_capacity);
+
+  auto size = message.Read(payload.Data(), payload.Size());
+  if (size < 0) {
+    RemoteMethodError(message, -size);
+    return;
+  }
+
+  payload.Resize(size);
+
+  ErrorType error;
+  auto decoder = MakeArgumentDecoder<Signature>(&payload);
+  auto arguments = decoder.DecodeArguments(&error);
+  if (error) {
+    RemoteMethodError(message, EIO);
+    return;
+  }
+
+  UnpackArguments<Class, Signature>(instance, method, message, arguments)
+      .Invoke();
+  // Return to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, 0);
+}
+
+// Dispatches a method by deserializing arguments from the given Message, with
+// compile-time interface check. Overload for int return types.
+template <typename RemoteMethodType, typename Class, typename... Args,
+          typename = EnableIfNotVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance,
+                          int (Class::*method)(Message&, Args...),
+                          Message& message,
+                          std::size_t max_capacity = InitialBufferCapacity) {
+  using Signature = typename RemoteMethodType::template RewriteArgs<Args...>;
+  rpc::ServicePayload<ReceiveBuffer> payload(message);
+  payload.Resize(max_capacity);
+
+  auto size = message.Read(payload.Data(), payload.Size());
+  if (size < 0) {
+    RemoteMethodError(message, -size);
+    return;
+  }
+
+  payload.Resize(size);
+
+  ErrorType error;
+  auto decoder = MakeArgumentDecoder<Signature>(&payload);
+  auto arguments = decoder.DecodeArguments(&error);
+  if (error) {
+    RemoteMethodError(message, EIO);
+    return;
+  }
+
+  auto return_value =
+      UnpackArguments<Class, Signature>(instance, method, message, arguments)
+          .Invoke();
+  // Return the value to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, return_value);
+}
+
+// Dispatches a method by deserializing arguments from the given Message, with
+// compile-time interface check. Overload for FileHandle return types.
+template <typename RemoteMethodType, FileHandleMode Mode, typename Class,
+          typename... Args, typename = EnableIfNotVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance,
+                          FileHandle<Mode> (Class::*method)(Message&, Args...),
+                          Message& message,
+                          std::size_t max_capacity = InitialBufferCapacity) {
+  using Signature =
+      typename RemoteMethodType::template RewriteSignature<FileHandle<Mode>,
+                                                           Args...>;
+  rpc::ServicePayload<ReceiveBuffer> payload(message);
+  payload.Resize(max_capacity);
+
+  auto size = message.Read(payload.Data(), payload.Size());
+  if (size < 0) {
+    RemoteMethodError(message, -size);
+    return;
+  }
+
+  payload.Resize(size);
+
+  ErrorType error;
+  auto decoder = MakeArgumentDecoder<Signature>(&payload);
+  auto arguments = decoder.DecodeArguments(&error);
+  if (error) {
+    RemoteMethodError(message, EIO);
+    return;
+  }
+
+  auto return_value =
+      UnpackArguments<Class, Signature>(instance, method, message, arguments)
+          .Invoke();
+  // Return the value to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, return_value);
+}
+
+// Dispatches a method by deserializing arguments from the given Message, with
+// compile-time interface check. Overload for ChannelHandle return types.
+template <typename RemoteMethodType, ChannelHandleMode Mode, typename Class,
+          typename... Args, typename = EnableIfNotVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(
+    Class& instance, ChannelHandle<Mode> (Class::*method)(Message&, Args...),
+    Message& message, std::size_t max_capacity = InitialBufferCapacity) {
+  using Signature = typename RemoteMethodType::template RewriteArgs<Args...>;
+  rpc::ServicePayload<ReceiveBuffer> payload(message);
+  payload.Resize(max_capacity);
+
+  auto size = message.Read(payload.Data(), payload.Size());
+  if (size < 0) {
+    RemoteMethodError(message, -size);
+    return;
+  }
+
+  payload.Resize(size);
+
+  ErrorType error;
+  auto decoder = MakeArgumentDecoder<Signature>(&payload);
+  auto arguments = decoder.DecodeArguments(&error);
+  if (error) {
+    RemoteMethodError(message, EIO);
+    return;
+  }
+
+  auto return_value =
+      UnpackArguments<Class, Signature>(instance, method, message, arguments)
+          .Invoke();
+  // Return the value to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, return_value);
+}
+
+// Dispatches a method by deserializing arguments from the given Message, with
+// compile-time interface signature check. Overload for generic return types.
+template <typename RemoteMethodType, typename Class, typename Return,
+          typename... Args, typename = EnableIfNotVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance,
+                          Return (Class::*method)(Message&, Args...),
+                          Message& message,
+                          std::size_t max_capacity = InitialBufferCapacity) {
+  using Signature =
+      typename RemoteMethodType::template RewriteSignature<Return, Args...>;
+  rpc::ServicePayload<ReceiveBuffer> payload(message);
+  payload.Resize(max_capacity);
+
+  auto size = message.Read(payload.Data(), payload.Size());
+  if (size < 0) {
+    RemoteMethodError(message, -size);
+    return;
+  }
+
+  payload.Resize(size);
+
+  ErrorType error;
+  auto decoder = MakeArgumentDecoder<Signature>(&payload);
+  auto arguments = decoder.DecodeArguments(&error);
+  if (error) {
+    RemoteMethodError(message, EIO);
+    return;
+  }
+
+  auto return_value =
+      UnpackArguments<Class, Signature>(instance, method, message, arguments)
+          .Invoke();
+  // Return the value to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, return_value);
+}
+
+#ifdef __clang__
+// Overloads to handle Void argument type without exploding clang.
+
+// Overload for void return type.
+template <typename RemoteMethodType, typename Class,
+          typename = EnableIfVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance, void (Class::*method)(Message&),
+                          Message& message) {
+  (instance.*method)(message);
+  // Return to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, 0);
+}
+
+// Overload for int return type.
+template <typename RemoteMethodType, typename Class,
+          typename = EnableIfVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance, int (Class::*method)(Message&),
+                          Message& message) {
+  const int return_value = (instance.*method)(message);
+  // Return the value to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, return_value);
+}
+
+// Overload for FileHandle return type.
+template <typename RemoteMethodType, typename Class, FileHandleMode Mode,
+          typename = EnableIfVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance,
+                          FileHandle<Mode> (Class::*method)(Message&),
+                          Message& message) {
+  FileHandle<Mode> return_value = (instance.*method)(message);
+  // Return the value to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, return_value);
+}
+
+// Overload for ChannelHandle return types.
+template <typename RemoteMethodType, typename Class, ChannelHandleMode Mode,
+          typename = EnableIfVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance,
+                          ChannelHandle<Mode> (Class::*method)(Message&),
+                          Message& message) {
+  ChannelHandle<Mode> return_value = (instance.*method)(message);
+  // Return the value to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, return_value);
+}
+
+// Overload for generic return type.
+template <typename RemoteMethodType, typename Class, typename Return,
+          typename = EnableIfVoidMethod<RemoteMethodType>>
+void DispatchRemoteMethod(Class& instance, Return (Class::*method)(Message&),
+                          Message& message) {
+  auto return_value = (instance.*method)(message);
+  // Return the value to the caller unless the message was moved.
+  if (message)
+    RemoteMethodReturn<RemoteMethodType>(message, return_value);
+}
+#endif
+
+}  // namespace rpc
+
+// Definitions for template methods declared in pdx/client.h.
+
+template <int Opcode, typename T>
+struct CheckArgumentTypes;
+
+template <int Opcode, typename Return, typename... Args>
+struct CheckArgumentTypes<Opcode, Return(Args...)> {
+  template <typename R>
+  static typename rpc::EnableIfDirectReturn<R, Status<R>> Invoke(Client& client,
+                                                                 Args... args) {
+    Transaction trans{client};
+    rpc::ClientPayload<rpc::SendBuffer> payload{trans};
+    rpc::MakeArgumentEncoder<Return(Args...)>(&payload).EncodeArguments(
+        std::forward<Args>(args)...);
+    return trans.Send<R>(Opcode, payload.Data(), payload.Size(), nullptr, 0);
+  }
+
+  template <typename R>
+  static typename rpc::EnableIfNotDirectReturn<R, Status<R>> Invoke(
+      Client& client, Args... args) {
+    Transaction trans{client};
+
+    rpc::ClientPayload<rpc::SendBuffer> send_payload{trans};
+    rpc::MakeArgumentEncoder<Return(Args...)>(&send_payload)
+        .EncodeArguments(std::forward<Args>(args)...);
+
+    rpc::ClientPayload<rpc::ReplyBuffer> reply_payload{trans};
+    reply_payload.Resize(reply_payload.Capacity());
+
+    Status<R> result;
+    auto status =
+        trans.Send<void>(Opcode, send_payload.Data(), send_payload.Size(),
+                         reply_payload.Data(), reply_payload.Size());
+    if (!status) {
+      result.SetError(status.error());
+    } else {
+      R return_value;
+      rpc::ErrorType error =
+          rpc::MakeArgumentDecoder<Return(Args...)>(&reply_payload)
+              .DecodeReturn(&return_value);
+
+      switch (error.error_code()) {
+        case rpc::ErrorCode::NO_ERROR:
+          result.SetValue(std::move(return_value));
+          break;
+
+        // This error is returned when ArrayWrapper/StringWrapper is too
+        // small to receive the payload.
+        case rpc::ErrorCode::INSUFFICIENT_DESTINATION_SIZE:
+          result.SetError(ENOBUFS);
+          break;
+
+        default:
+          result.SetError(EIO);
+          break;
+      }
+    }
+    return result;
+  }
+
+  template <typename R>
+  static typename rpc::EnableIfDirectReturn<R, Status<void>> InvokeInPlace(
+      Client& client, R* return_value, Args... args) {
+    Transaction trans{client};
+
+    rpc::ClientPayload<rpc::SendBuffer> send_payload{trans};
+    rpc::MakeArgumentEncoder<Return(Args...)>(&send_payload)
+        .EncodeArguments(std::forward<Args>(args)...);
+
+    Status<void> result;
+    auto status = trans.Send<R>(Opcode, send_payload.Data(),
+                                send_payload.Size(), nullptr, 0);
+    if (status) {
+      *return_value = status.take();
+      result.SetValue();
+    } else {
+      result.SetError(status.error());
+    }
+    return result;
+  }
+
+  template <typename R>
+  static typename rpc::EnableIfNotDirectReturn<R, Status<void>> InvokeInPlace(
+      Client& client, R* return_value, Args... args) {
+    Transaction trans{client};
+
+    rpc::ClientPayload<rpc::SendBuffer> send_payload{trans};
+    rpc::MakeArgumentEncoder<Return(Args...)>(&send_payload)
+        .EncodeArguments(std::forward<Args>(args)...);
+
+    rpc::ClientPayload<rpc::ReplyBuffer> reply_payload{trans};
+    reply_payload.Resize(reply_payload.Capacity());
+
+    auto result =
+        trans.Send<void>(Opcode, send_payload.Data(), send_payload.Size(),
+                         reply_payload.Data(), reply_payload.Size());
+    if (result) {
+      rpc::ErrorType error =
+          rpc::MakeArgumentDecoder<Return(Args...)>(&reply_payload)
+              .DecodeReturn(return_value);
+
+      switch (error.error_code()) {
+        case rpc::ErrorCode::NO_ERROR:
+          result.SetValue();
+          break;
+
+        // This error is returned when ArrayWrapper/StringWrapper is too
+        // small to receive the payload.
+        case rpc::ErrorCode::INSUFFICIENT_DESTINATION_SIZE:
+          result.SetError(ENOBUFS);
+          break;
+
+        default:
+          result.SetError(EIO);
+          break;
+      }
+    }
+    return result;
+  }
+};
+
+// Invokes the remote method with opcode and signature described by
+// |RemoteMethodType|.
+template <typename RemoteMethodType, typename... Args>
+Status<typename RemoteMethodType::Return> Client::InvokeRemoteMethod(
+    Args&&... args) {
+  return CheckArgumentTypes<
+      RemoteMethodType::Opcode,
+      typename RemoteMethodType::template RewriteArgs<Args...>>::
+      template Invoke<typename RemoteMethodType::Return>(
+          *this, std::forward<Args>(args)...);
+}
+
+template <typename RemoteMethodType, typename Return, typename... Args>
+Status<void> Client::InvokeRemoteMethodInPlace(Return* return_value,
+                                               Args&&... args) {
+  return CheckArgumentTypes<
+      RemoteMethodType::Opcode,
+      typename RemoteMethodType::template RewriteSignature<Return, Args...>>::
+      template InvokeInPlace(*this, return_value, std::forward<Args>(args)...);
+}
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_REMOTE_METHOD_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/remote_method_type.h b/libs/vr/libpdx/private/pdx/rpc/remote_method_type.h
new file mode 100644
index 0000000..de9a3cc
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/remote_method_type.h
@@ -0,0 +1,67 @@
+#ifndef ANDROID_PDX_RPC_REMOTE_METHOD_TYPE_H_
+#define ANDROID_PDX_RPC_REMOTE_METHOD_TYPE_H_
+
+#include <cstddef>
+#include <tuple>
+#include <type_traits>
+
+#include <pdx/rpc/enumeration.h>
+#include <pdx/rpc/function_traits.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Utility class binding a remote method opcode to its function signature.
+// Describes the interface between RPC clients and services for a single method.
+template <int Opcode_, typename Signature_>
+struct RemoteMethodType {
+  typedef FunctionTraits<Signature_> Traits;
+
+  enum : int { Opcode = Opcode_ };
+
+  typedef typename Traits::Signature Signature;
+  typedef typename Traits::Return Return;
+  typedef typename Traits::Args Args;
+
+  template <typename... Params>
+  using RewriteArgs = typename Traits::template RewriteArgs<Params...>;
+
+  template <typename ReturnType, typename... Params>
+  using RewriteSignature =
+      typename Traits::template RewriteSignature<ReturnType, Params...>;
+
+  template <typename ReturnType>
+  using RewriteReturn = typename Traits::template RewriteReturn<ReturnType>;
+};
+
+// Utility class representing a set of related RemoteMethodTypes. Describes the
+// interface between RPC clients and services as a set of methods.
+template <typename... MethodTypes>
+struct RemoteAPI {
+  typedef std::tuple<MethodTypes...> Methods;
+  enum : std::size_t { Length = sizeof...(MethodTypes) };
+
+  template <std::size_t Index>
+  using Method = typename std::tuple_element<Index, Methods>::type;
+
+  template <typename MethodType>
+  static constexpr std::size_t MethodIndex() {
+    return ElementForType<MethodType, MethodTypes...>::Index;
+  }
+};
+
+// Macro to simplify defining remote method signatures. Remote method signatures
+// are specified by defining a RemoteMethodType for each remote method.
+#define PDX_REMOTE_METHOD(name, opcode, ... /*signature*/) \
+  using name = ::android::pdx::rpc::RemoteMethodType<opcode, __VA_ARGS__>
+
+// Macro to simplify defining a set of remote method signatures.
+#define PDX_REMOTE_API(name, ... /*methods*/) \
+  using name = ::android::pdx::rpc::RemoteAPI<__VA_ARGS__>
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_REMOTE_METHOD_TYPE_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/sequence.h b/libs/vr/libpdx/private/pdx/rpc/sequence.h
new file mode 100644
index 0000000..5fd898a
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/sequence.h
@@ -0,0 +1,56 @@
+#ifndef ANDROID_PDX_RPC_SEQUENCE_H_
+#define ANDROID_PDX_RPC_SEQUENCE_H_
+
+#include <cstdint>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Provides a C++11 implementation of C++14 index_sequence and
+// make_index_sequence for compatibility with common compilers. This
+// implementation may be conditionally replaced with compiler-provided versions
+// when C++14 support is available.
+
+// Utility to capture a sequence of unsigned indices.
+template <std::size_t... I>
+struct IndexSequence {
+  using type = IndexSequence;
+  using value_type = std::size_t;
+  static constexpr std::size_t size() { return sizeof...(I); }
+};
+
+namespace detail {
+
+// Helper class to merge and renumber sequence parts in log N instantiations.
+template <typename S1, typename S2>
+struct MergeSequencesAndRenumber;
+
+template <std::size_t... I1, std::size_t... I2>
+struct MergeSequencesAndRenumber<IndexSequence<I1...>, IndexSequence<I2...>>
+    : IndexSequence<I1..., (sizeof...(I1) + I2)...> {};
+
+}  // namespace detail
+
+// Utility to build an IndexSequence with N indices.
+template <std::size_t N>
+struct MakeIndexSequence : detail::MergeSequencesAndRenumber<
+                               typename MakeIndexSequence<N / 2>::type,
+                               typename MakeIndexSequence<N - N / 2>::type> {};
+
+// Identity sequences.
+template <>
+struct MakeIndexSequence<0> : IndexSequence<> {};
+template <>
+struct MakeIndexSequence<1> : IndexSequence<0> {};
+
+// Utility to build an IndexSequence with indices for each element of a
+// parameter pack.
+template <typename... T>
+using IndexSequenceFor = MakeIndexSequence<sizeof...(T)>;
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_SEQUENCE_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/serializable.h b/libs/vr/libpdx/private/pdx/rpc/serializable.h
new file mode 100644
index 0000000..04a4352
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/serializable.h
@@ -0,0 +1,150 @@
+#ifndef ANDROID_PDX_RPC_SERIALIZABLE_H_
+#define ANDROID_PDX_RPC_SERIALIZABLE_H_
+
+#include <cstddef>
+#include <string>
+#include <tuple>
+
+#include <pdx/message_reader.h>
+#include <pdx/message_writer.h>
+
+#include "macros.h"
+#include "serialization.h"
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// This file provides utilities to define serializable types for communication
+// between clients and services. Supporting efficient, typed communication
+// protocols is the primary goal, NOT providing a general-purpose solution for
+// all your C++ serialization needs. Features that are not aligned to the goals
+// are not supported, such as static/const member serialization and serializable
+// types with virtual methods (requiring a virtual destructor).
+
+// Captures the type and value of a pointer to member. Pointer to members are
+// essentially compile-time constant offsets that can be stored in the type
+// system without adding to the size of the structures they describe. This
+// library uses this property to implement a limited form of reflection for
+// serialization/deserialization functions.
+template <typename T, T>
+struct MemberPointer;
+
+template <typename Type, typename Class, Type Class::*Pointer>
+struct MemberPointer<Type Class::*, Pointer> {
+  // Type of the member pointer this type represents.
+  using PointerType = Type Class::*;
+
+  // Resolves a pointer to member with the given instance, yielding a
+  // reference to the member in that instance.
+  static Type& Resolve(Class& instance) { return (instance.*Pointer); }
+  static const Type& Resolve(const Class& instance) {
+    return (instance.*Pointer);
+  }
+};
+
+// Describes a set of members to be serialized/deserialized by this library. The
+// parameter pack MemberPointers takes a list of MemberPointer types that
+// describe each member to participate in serialization/deserialization.
+template <typename T, typename... MemberPointers>
+struct SerializableMembersType {
+  using Type = T;
+
+  // The number of member pointers described by this type.
+  enum : std::size_t { MemberCount = sizeof...(MemberPointers) };
+
+  // The member pointers described by this type.
+  using Members = std::tuple<MemberPointers...>;
+
+  // Accessor for individual member pointer types.
+  template <std::size_t Index>
+  using At = typename std::tuple_element<Index, Members>::type;
+};
+
+// Classes must do the following to correctly define a serializable type:
+//     1. Define a type called "SerializableMembers" as a template instantiation
+//        of SerializableMembersType, describing the members of the class to
+//        participate in serialization (presumably all of them). Use the macro
+//        PDX_SERIALIZABLE_MEMBERS(...) below to aid the correct type
+//        definition. This type should be private to prevent leaking member
+//        access information.
+//     2. Make SerializableTraits and HasSerilizableMembers types a friend of
+//        the class. The macro PDX_SERIALIZABLE_MEMEBRS(...) takes care of
+//        this automatically.
+//     3. Define a public default constructor, if necessary. Deserialization
+//        requires instances to be default-constructible.
+//
+// Example usage:
+//     class MySerializableType : public AnotherBaseType {
+//      public:
+//       MySerializableType();
+//       ...
+//      private:
+//       int a;
+//       string b;
+//       PDX_SERIALIZABLE_MEMBERS(MySerializableType, a, b);
+//     };
+//
+// Note that const and static member serialization is not supported.
+
+template <typename T>
+class SerializableTraits {
+ public:
+  // Gets the serialized size of type T.
+  static std::size_t GetSerializedSize(const T& value) {
+    return GetEncodingSize(EncodeArrayType(SerializableMembers::MemberCount)) +
+           GetMembersSize<SerializableMembers>(value);
+  }
+
+  // Serializes type T.
+  static void SerializeObject(const T& value, MessageWriter* writer,
+                              void*& buffer) {
+    SerializeArrayEncoding(EncodeArrayType(SerializableMembers::MemberCount),
+                           SerializableMembers::MemberCount, buffer);
+    SerializeMembers<SerializableMembers>(value, writer, buffer);
+  }
+
+  // Deserializes type T.
+  static ErrorType DeserializeObject(T* value, MessageReader* reader,
+                                     const void*& start, const void* end) {
+    EncodingType encoding;
+    std::size_t size;
+
+    if (const auto error =
+            DeserializeArrayType(&encoding, &size, reader, start, end)) {
+      return error;
+    } else if (size != SerializableMembers::MemberCount) {
+      return ErrorCode::UNEXPECTED_TYPE_SIZE;
+    } else {
+      return DeserializeMembers<SerializableMembers>(value, reader, start, end);
+    }
+  }
+
+ private:
+  using SerializableMembers = typename T::SerializableMembers;
+};
+
+// Utility macro to define a MemberPointer type for a member name.
+#define PDX_MEMBER_POINTER(type, member) \
+  ::android::pdx::rpc::MemberPointer<decltype(&type::member), &type::member>
+
+// Defines a list of MemberPointer types given a list of member names.
+#define PDX_MEMBERS(type, ... /*members*/) \
+  PDX_FOR_EACH_BINARY_LIST(PDX_MEMBER_POINTER, type, __VA_ARGS__)
+
+// Defines the serializable members of a type given a list of member names and
+// befriends SerializableTraits and HasSerializableMembers for the class. This
+// macro handles requirements #1 and #2 above.
+#define PDX_SERIALIZABLE_MEMBERS(type, ... /*members*/)                     \
+  template <typename T>                                                     \
+  friend class ::android::pdx::rpc::SerializableTraits;                     \
+  template <typename, typename>                                             \
+  friend struct ::android::pdx::rpc::HasSerializableMembers;                \
+  using SerializableMembers = ::android::pdx::rpc::SerializableMembersType< \
+      type, PDX_MEMBERS(type, __VA_ARGS__)>
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_SERIALIZABLE_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/serialization.h b/libs/vr/libpdx/private/pdx/rpc/serialization.h
new file mode 100644
index 0000000..fccd028
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/serialization.h
@@ -0,0 +1,1996 @@
+#ifndef ANDROID_PDX_RPC_SERIALIZATION_H_
+#define ANDROID_PDX_RPC_SERIALIZATION_H_
+
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <map>
+#include <numeric>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+#include <pdx/message_reader.h>
+#include <pdx/message_writer.h>
+#include <pdx/trace.h>
+#include <pdx/utility.h>
+
+#include "array_wrapper.h"
+#include "default_initialization_allocator.h"
+#include "encoding.h"
+#include "pointer_wrapper.h"
+#include "string_wrapper.h"
+#include "variant.h"
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Automatic serialization/deserialization library based on MessagePack
+// (http://msgpack.org). This library provides top level Serialize() and
+// Deserialize() functions to encode/decode a variety of data types.
+//
+// The following data types are supported:
+//   * Standard signed integer types: int8_t, int16_t, int32_t, and int64_t.
+//   * Regular signed integer types equivalent to the standard types:
+//     signed char, short, int, long, and long long.
+//   * Standard unsigned integer types: uint8_t, uint16_t, uint32_t, and
+//     uint64_t.
+//   * Regular unsigned integer types equivalent to the standard types:
+//     unsigned char, unsigned short, unsigned int, unsigned long,
+//     and unsigned long long.
+//   * char without signed/unsigned qualifiers.
+//   * bool.
+//   * std::vector with value type of any supported type, including nesting.
+//   * std::string.
+//   * std::tuple with elements of any supported type, including nesting.
+//   * std::pair with elements of any supported type, including nesting.
+//   * std::map with keys and values of any supported type, including nesting.
+//   * std::unordered_map with keys and values of any supported type, including
+//     nesting.
+//   * std::array with values of any supported type, including nesting.
+//   * ArrayWrapper of any supported basic type.
+//   * BufferWrapper of any POD type.
+//   * StringWrapper of any supported char type.
+//   * User types with correctly defined SerializableMembers member type.
+//
+// Planned support for:
+//   * std::basic_string with all supported char types.
+
+// Counting template for managing template recursion.
+template <std::size_t N>
+struct Index {};
+
+// Forward declaration of traits type to access types with a SerializedMembers
+// member type.
+template <typename T>
+class SerializableTraits;
+
+template <typename T, typename... MemberPointers>
+struct SerializableMembersType;
+
+// Utility to deduce the template type from a derived type.
+template <template <typename...> class TT, typename... Ts>
+std::true_type DeduceTemplateType(const TT<Ts...>*);
+template <template <typename...> class TT>
+std::false_type DeduceTemplateType(...);
+
+// Utility determining whether template type TT<...> is a base of type T.
+template <template <typename...> class TT, typename T>
+using IsTemplateBaseOf = decltype(DeduceTemplateType<TT>(std::declval<T*>()));
+
+// Utility type for SFINAE in HasHasSerializableMembers.
+template <typename... Ts>
+using TrySerializableMembersType = void;
+
+// Determines whether type T has a member type named SerializableMembers of
+// template type SerializableMembersType.
+template <typename, typename = void>
+struct HasSerializableMembers : std::false_type {};
+template <typename T>
+struct HasSerializableMembers<
+    T, TrySerializableMembersType<typename T::SerializableMembers>>
+    : std::integral_constant<
+          bool, IsTemplateBaseOf<SerializableMembersType,
+                                 typename T::SerializableMembers>::value> {};
+
+// Utility to simplify overload enable expressions for types with correctly
+// defined SerializableMembers.
+template <typename T>
+using EnableIfHasSerializableMembers =
+    typename std::enable_if<HasSerializableMembers<T>::value>::type;
+
+// Utility to simplify overload enable expressions for enum types.
+template <typename T, typename ReturnType = void>
+using EnableIfEnum =
+    typename std::enable_if<std::is_enum<T>::value, ReturnType>::type;
+
+///////////////////////////////////////////////////////////////////////////////
+// Error Reporting //
+///////////////////////////////////////////////////////////////////////////////
+
+// Error codes returned by the deserialization code.
+enum class ErrorCode {
+  NO_ERROR = 0,
+  UNEXPECTED_ENCODING,
+  UNEXPECTED_TYPE_SIZE,
+  INSUFFICIENT_BUFFER,
+  INSUFFICIENT_DESTINATION_SIZE,
+  GET_FILE_DESCRIPTOR_FAILED,
+  GET_CHANNEL_HANDLE_FAILED,
+  INVALID_VARIANT_ELEMENT,
+};
+
+// Type for errors returned by the deserialization code.
+class ErrorType {
+ public:
+  ErrorType() : error_code_(ErrorCode::NO_ERROR) {}
+
+  // ErrorType constructor for generic error codes. Explicitly not explicit,
+  // implicit conversion from ErrorCode to ErrorType is desirable behavior.
+  // NOLINTNEXTLINE(runtime/explicit)
+  ErrorType(ErrorCode error_code) : error_code_(error_code) {}
+
+  // ErrorType constructor for encoding type errors.
+  ErrorType(ErrorCode error_code, EncodingClass encoding_class,
+            EncodingType encoding_type)
+      : error_code_(error_code) {
+    unexpected_encoding_.encoding_class = encoding_class;
+    unexpected_encoding_.encoding_type = encoding_type;
+  }
+
+  // Evaluates to true if the ErrorType represents an error.
+  explicit operator bool() const { return error_code_ != ErrorCode::NO_ERROR; }
+
+  operator ErrorCode() const { return error_code_; }
+  ErrorCode error_code() const { return error_code_; }
+
+  // Accessors for extra info about unexpected encoding errors.
+  EncodingClass encoding_class() const {
+    return unexpected_encoding_.encoding_class;
+  }
+  EncodingType encoding_type() const {
+    return unexpected_encoding_.encoding_type;
+  }
+
+  operator std::string() const {
+    std::ostringstream stream;
+
+    switch (error_code_) {
+      case ErrorCode::NO_ERROR:
+        return "NO_ERROR";
+      case ErrorCode::UNEXPECTED_ENCODING:
+        stream << "UNEXPECTED_ENCODING: " << static_cast<int>(encoding_class())
+               << ", " << static_cast<int>(encoding_type());
+        return stream.str();
+      case ErrorCode::UNEXPECTED_TYPE_SIZE:
+        return "UNEXPECTED_TYPE_SIZE";
+      case ErrorCode::INSUFFICIENT_BUFFER:
+        return "INSUFFICIENT_BUFFER";
+      case ErrorCode::INSUFFICIENT_DESTINATION_SIZE:
+        return "INSUFFICIENT_DESTINATION_SIZE";
+      default:
+        return "[Unknown Error]";
+    }
+  }
+
+ private:
+  ErrorCode error_code_;
+
+  // Union of extra information for different error code types.
+  union {
+    // UNEXPECTED_ENCODING.
+    struct {
+      EncodingClass encoding_class;
+      EncodingType encoding_type;
+    } unexpected_encoding_;
+  };
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Object Size //
+///////////////////////////////////////////////////////////////////////////////
+
+inline constexpr std::size_t GetSerializedSize(const bool& b) {
+  return GetEncodingSize(EncodeType(b));
+}
+
+// Overloads of GetSerializedSize() for standard integer types.
+inline constexpr std::size_t GetSerializedSize(const char& c) {
+  return GetEncodingSize(EncodeType(c));
+}
+inline constexpr std::size_t GetSerializedSize(const std::uint8_t& i) {
+  return GetEncodingSize(EncodeType(i));
+}
+inline constexpr std::size_t GetSerializedSize(const std::int8_t& i) {
+  return GetEncodingSize(EncodeType(i));
+}
+inline constexpr std::size_t GetSerializedSize(const std::uint16_t& i) {
+  return GetEncodingSize(EncodeType(i));
+}
+inline constexpr std::size_t GetSerializedSize(const std::int16_t& i) {
+  return GetEncodingSize(EncodeType(i));
+}
+inline constexpr std::size_t GetSerializedSize(const std::uint32_t& i) {
+  return GetEncodingSize(EncodeType(i));
+}
+inline constexpr std::size_t GetSerializedSize(const std::int32_t& i) {
+  return GetEncodingSize(EncodeType(i));
+}
+inline constexpr std::size_t GetSerializedSize(const std::uint64_t& i) {
+  return GetEncodingSize(EncodeType(i));
+}
+inline constexpr std::size_t GetSerializedSize(const std::int64_t& i) {
+  return GetEncodingSize(EncodeType(i));
+}
+
+inline constexpr std::size_t GetSerializedSize(const float& f) {
+  return GetEncodingSize(EncodeType(f));
+}
+inline constexpr std::size_t GetSerializedSize(const double& d) {
+  return GetEncodingSize(EncodeType(d));
+}
+
+// Overload for enum types.
+template <typename T>
+inline EnableIfEnum<T, std::size_t> GetSerializedSize(T v) {
+  return GetSerializedSize(static_cast<std::underlying_type_t<T>>(v));
+}
+
+// Forward declaration for nested definitions.
+inline std::size_t GetSerializedSize(const EmptyVariant&);
+template <typename... Types>
+inline std::size_t GetSerializedSize(const Variant<Types...>&);
+template <typename T, typename Enabled>
+inline constexpr std::size_t GetSerializedSize(const T&);
+template <typename T>
+inline constexpr std::size_t GetSerializedSize(const PointerWrapper<T>&);
+inline constexpr std::size_t GetSerializedSize(const std::string&);
+template <typename T>
+inline constexpr std::size_t GetSerializedSize(const StringWrapper<T>&);
+template <typename T>
+inline constexpr std::size_t GetSerializedSize(const BufferWrapper<T>&);
+template <FileHandleMode Mode>
+inline constexpr std::size_t GetSerializedSize(const FileHandle<Mode>&);
+template <ChannelHandleMode Mode>
+inline constexpr std::size_t GetSerializedSize(const ChannelHandle<Mode>&);
+template <typename T, typename Allocator>
+inline std::size_t GetSerializedSize(const std::vector<T, Allocator>& v);
+template <typename Key, typename T, typename Compare, typename Allocator>
+inline std::size_t GetSerializedSize(
+    const std::map<Key, T, Compare, Allocator>& m);
+template <typename Key, typename T, typename Hash, typename KeyEqual,
+          typename Allocator>
+inline std::size_t GetSerializedSize(
+    const std::unordered_map<Key, T, Hash, KeyEqual, Allocator>&);
+template <typename T>
+inline std::size_t GetSerializedSize(const ArrayWrapper<T>&);
+template <typename T, std::size_t Size>
+inline std::size_t GetSerializedSize(const std::array<T, Size>& v);
+template <typename T, typename U>
+inline std::size_t GetSerializedSize(const std::pair<T, U>& p);
+template <typename... T>
+inline std::size_t GetSerializedSize(const std::tuple<T...>& tuple);
+
+// Overload for empty variant type.
+inline std::size_t GetSerializedSize(const EmptyVariant& empty) {
+  return GetEncodingSize(EncodeType(empty));
+}
+
+// Overload for Variant types.
+template <typename... Types>
+inline std::size_t GetSerializedSize(const Variant<Types...>& variant) {
+  return GetEncodingSize(EncodeType(variant)) +
+         GetSerializedSize(variant.index()) +
+         variant.Visit(
+             [](const auto& value) { return GetSerializedSize(value); });
+}
+
+// Overload for structs/classes with SerializableMembers defined.
+template <typename T, typename Enabled = EnableIfHasSerializableMembers<T>>
+inline constexpr std::size_t GetSerializedSize(const T& value) {
+  return SerializableTraits<T>::GetSerializedSize(value);
+}
+
+// Overload for PointerWrapper.
+template <typename T>
+inline constexpr std::size_t GetSerializedSize(const PointerWrapper<T>& p) {
+  return GetSerializedSize(p.Dereference());
+}
+
+// Overload for std::string.
+inline constexpr std::size_t GetSerializedSize(const std::string& s) {
+  return GetEncodingSize(EncodeType(s)) +
+         s.length() * sizeof(std::string::value_type);
+}
+
+// Overload for StringWrapper.
+template <typename T>
+inline constexpr std::size_t GetSerializedSize(const StringWrapper<T>& s) {
+  return GetEncodingSize(EncodeType(s)) +
+         s.length() * sizeof(typename StringWrapper<T>::value_type);
+}
+
+// Overload for BufferWrapper types.
+template <typename T>
+inline constexpr std::size_t GetSerializedSize(const BufferWrapper<T>& b) {
+  return GetEncodingSize(EncodeType(b)) +
+         b.size() * sizeof(typename BufferWrapper<T>::value_type);
+}
+
+// Overload for FileHandle. FileHandle is encoded as a FIXEXT2, with a type code
+// of "FileHandle" and a signed 16-bit offset into the pushed fd array. Empty
+// FileHandles are encoded with an array index of -1.
+template <FileHandleMode Mode>
+inline constexpr std::size_t GetSerializedSize(const FileHandle<Mode>& fd) {
+  return GetEncodingSize(EncodeType(fd)) + sizeof(std::int16_t);
+}
+
+// Overload for ChannelHandle. ChannelHandle is encoded as a FIXEXT4, with a
+// type code of "ChannelHandle" and a signed 32-bit offset into the pushed
+// channel array. Empty ChannelHandles are encoded with an array index of -1.
+template <ChannelHandleMode Mode>
+inline constexpr std::size_t GetSerializedSize(
+    const ChannelHandle<Mode>& channel_handle) {
+  return GetEncodingSize(EncodeType(channel_handle)) + sizeof(std::int32_t);
+}
+
+// Overload for standard vector types.
+template <typename T, typename Allocator>
+inline std::size_t GetSerializedSize(const std::vector<T, Allocator>& v) {
+  return std::accumulate(v.begin(), v.end(), GetEncodingSize(EncodeType(v)),
+                         [](const std::size_t& sum, const T& object) {
+                           return sum + GetSerializedSize(object);
+                         });
+}
+
+// Overload for standard map types.
+template <typename Key, typename T, typename Compare, typename Allocator>
+inline std::size_t GetSerializedSize(
+    const std::map<Key, T, Compare, Allocator>& v) {
+  return std::accumulate(
+      v.begin(), v.end(), GetEncodingSize(EncodeType(v)),
+      [](const std::size_t& sum, const std::pair<Key, T>& object) {
+        return sum + GetSerializedSize(object.first) +
+               GetSerializedSize(object.second);
+      });
+}
+
+// Overload for standard unordered_map types.
+template <typename Key, typename T, typename Hash, typename KeyEqual,
+          typename Allocator>
+inline std::size_t GetSerializedSize(
+    const std::unordered_map<Key, T, Hash, KeyEqual, Allocator>& v) {
+  return std::accumulate(
+      v.begin(), v.end(), GetEncodingSize(EncodeType(v)),
+      [](const std::size_t& sum, const std::pair<Key, T>& object) {
+        return sum + GetSerializedSize(object.first) +
+               GetSerializedSize(object.second);
+      });
+}
+
+// Overload for ArrayWrapper types.
+template <typename T>
+inline std::size_t GetSerializedSize(const ArrayWrapper<T>& v) {
+  return std::accumulate(v.begin(), v.end(), GetEncodingSize(EncodeType(v)),
+                         [](const std::size_t& sum, const T& object) {
+                           return sum + GetSerializedSize(object);
+                         });
+}
+
+// Overload for std::array types.
+template <typename T, std::size_t Size>
+inline std::size_t GetSerializedSize(const std::array<T, Size>& v) {
+  return std::accumulate(v.begin(), v.end(), GetEncodingSize(EncodeType(v)),
+                         [](const std::size_t& sum, const T& object) {
+                           return sum + GetSerializedSize(object);
+                         });
+}
+
+// Overload for std::pair.
+template <typename T, typename U>
+inline std::size_t GetSerializedSize(const std::pair<T, U>& p) {
+  return GetEncodingSize(EncodeType(p)) + GetSerializedSize(p.first) +
+         GetSerializedSize(p.second);
+}
+
+// Stops template recursion when the last tuple element is reached.
+template <typename... T>
+inline std::size_t GetTupleSize(const std::tuple<T...>&, Index<0>) {
+  return 0;
+}
+
+// Gets the size of each element in a tuple recursively.
+template <typename... T, std::size_t index>
+inline std::size_t GetTupleSize(const std::tuple<T...>& tuple, Index<index>) {
+  return GetTupleSize(tuple, Index<index - 1>()) +
+         GetSerializedSize(std::get<index - 1>(tuple));
+}
+
+// Overload for tuple types. Gets the size of the tuple, recursing
+// through the elements.
+template <typename... T>
+inline std::size_t GetSerializedSize(const std::tuple<T...>& tuple) {
+  return GetEncodingSize(EncodeType(tuple)) +
+         GetTupleSize(tuple, Index<sizeof...(T)>());
+}
+
+// Stops template recursion when the last member of a Serializable
+// type is reached.
+template <typename Members, typename T>
+inline std::size_t GetMemberSize(const T&, Index<0>) {
+  return 0;
+}
+
+// Gets the size of each member of a Serializable type recursively.
+template <typename Members, typename T, std::size_t index>
+inline std::size_t GetMemberSize(const T& object, Index<index>) {
+  return GetMemberSize<Members>(object, Index<index - 1>()) +
+         GetSerializedSize(Members::template At<index - 1>::Resolve(object));
+}
+
+// Gets the size of a type using the given SerializableMembersType
+// type.
+template <typename Members, typename T>
+inline std::size_t GetMembersSize(const T& object) {
+  return GetMemberSize<Members>(object, Index<Members::MemberCount>());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Object Serialization //
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// SerializeRaw() converts a primitive array or type into a raw byte string.
+// These functions are named differently from SerializeObject() expressly to
+// avoid catch-all specialization of that template, which can be difficult to
+// detect otherwise.
+//
+
+inline void WriteRawData(void*& dest, const void* src, size_t size) {
+  memcpy(dest, src, size);
+  dest = static_cast<uint8_t*>(dest) + size;
+}
+
+// Serializes a primitive array into a raw byte string.
+template <typename T,
+          typename = typename std::enable_if<std::is_pod<T>::value>::type>
+inline void SerializeRaw(const T& value, void*& buffer) {
+  WriteRawData(buffer, &value, sizeof(value));
+}
+
+inline void SerializeEncoding(EncodingType encoding, void*& buffer) {
+  SerializeRaw(encoding, buffer);
+}
+
+inline void SerializeType(const bool& value, void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+}
+
+// Serializes the type code, extended type code, and size for
+// extension types.
+inline void SerializeExtEncoding(EncodingType encoding,
+                                 EncodingExtType ext_type, std::size_t size,
+                                 void*& buffer) {
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_EXT8) {
+    std::uint8_t length = size;
+    SerializeRaw(length, buffer);
+  } else if (encoding == ENCODING_TYPE_EXT16) {
+    std::uint16_t length = size;
+    SerializeRaw(length, buffer);
+  } else if (encoding == ENCODING_TYPE_EXT32) {
+    std::uint32_t length = size;
+    SerializeRaw(length, buffer);
+  } else /* if (IsFixextEncoding(encoding) */ {
+    // Encoding byte contains the fixext length, nothing else to do.
+  }
+  SerializeRaw(ext_type, buffer);
+}
+
+// Serializes the type code for file descriptor types.
+template <FileHandleMode Mode>
+inline void SerializeType(const FileHandle<Mode>& value, void*& buffer) {
+  SerializeExtEncoding(EncodeType(value), ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 2,
+                       buffer);
+}
+
+// Serializes the type code for channel handle types.
+template <ChannelHandleMode Mode>
+inline void SerializeType(const ChannelHandle<Mode>& handle, void*& buffer) {
+  SerializeExtEncoding(EncodeType(handle), ENCODING_EXT_TYPE_CHANNEL_HANDLE, 4,
+                       buffer);
+}
+
+// Serializes type code for variant types.
+template <typename... Types>
+inline void SerializeType(const Variant<Types...>& value, void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+}
+
+// Serializes the type code for string types.
+template <typename StringType>
+inline void SerializeStringType(const StringType& value, void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_STR8) {
+    std::uint8_t length = value.length();
+    SerializeRaw(length, buffer);
+  } else if (encoding == ENCODING_TYPE_STR16) {
+    std::uint16_t length = value.length();
+    SerializeRaw(length, buffer);
+  } else if (encoding == ENCODING_TYPE_STR32) {
+    std::uint32_t length = value.length();
+    SerializeRaw(length, buffer);
+  } else /* if (IsFixstrEncoding(encoding) */ {
+    // Encoding byte contains the fixstr length, nothing else to do.
+  }
+}
+
+// Serializes the type code for std::string and StringWrapper. These types are
+// interchangeable and must serialize to the same format.
+inline void SerializeType(const std::string& value, void*& buffer) {
+  SerializeStringType(value, buffer);
+}
+template <typename T>
+inline void SerializeType(const StringWrapper<T>& value, void*& buffer) {
+  SerializeStringType(value, buffer);
+}
+
+// Serializes the type code for bin types.
+inline void SerializeBinEncoding(EncodingType encoding, std::size_t size,
+                                 void*& buffer) {
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_BIN8) {
+    std::uint8_t length = size;
+    SerializeRaw(length, buffer);
+  } else if (encoding == ENCODING_TYPE_BIN16) {
+    std::uint16_t length = size;
+    SerializeRaw(length, buffer);
+  } else if (encoding == ENCODING_TYPE_BIN32) {
+    std::uint32_t length = size;
+    SerializeRaw(length, buffer);
+  } else {
+    // Invalid encoding for BIN type.
+  }
+}
+
+// Serializes the type code for BufferWrapper types.
+template <typename T>
+inline void SerializeType(const BufferWrapper<T>& value, void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeBinEncoding(
+      encoding, value.size() * sizeof(typename BufferWrapper<T>::value_type),
+      buffer);
+}
+
+// Serializes the array encoding type and length.
+inline void SerializeArrayEncoding(EncodingType encoding, std::size_t size,
+                                   void*& buffer) {
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_ARRAY16) {
+    std::uint16_t length = size;
+    SerializeRaw(length, buffer);
+  } else if (encoding == ENCODING_TYPE_ARRAY32) {
+    std::uint32_t length = size;
+    SerializeRaw(length, buffer);
+  } else /* if (IsFixarrayEncoding(encoding) */ {
+    // Encoding byte contains the fixarray length, nothing else to do.
+  }
+}
+
+// Serializes the map encoding type and length.
+inline void SerializeMapEncoding(EncodingType encoding, std::size_t size,
+                                 void*& buffer) {
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_MAP16) {
+    std::uint16_t length = size;
+    SerializeRaw(length, buffer);
+  } else if (encoding == ENCODING_TYPE_MAP32) {
+    std::uint32_t length = size;
+    SerializeRaw(length, buffer);
+  } else /* if (IsFixmapEncoding(encoding) */ {
+    // Encoding byte contains the fixmap length, nothing else to do.
+  }
+}
+
+// Serializes the type code for array types.
+template <typename ArrayType>
+inline void SerializeArrayType(const ArrayType& value, std::size_t size,
+                               void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeArrayEncoding(encoding, size, buffer);
+}
+
+// Serializes the type code for map types.
+template <typename MapType>
+inline void SerializeMapType(const MapType& value, std::size_t size,
+                             void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeMapEncoding(encoding, size, buffer);
+}
+
+// Serializes the type code for std::vector and ArrayWrapper. These types are
+// interchangeable and must serialize to the same format.
+template <typename T, typename Allocator>
+inline void SerializeType(const std::vector<T, Allocator>& value,
+                          void*& buffer) {
+  SerializeArrayType(value, value.size(), buffer);
+}
+template <typename T>
+inline void SerializeType(const ArrayWrapper<T>& value, void*& buffer) {
+  SerializeArrayType(value, value.size(), buffer);
+}
+
+// Serializes the type code for std::array. This type serializes to the same
+// format as std::vector and ArrayWrapper and is interchangeable in certain
+// situations.
+template <typename T, std::size_t Size>
+inline void SerializeType(const std::array<T, Size>& value, void*& buffer) {
+  SerializeArrayType(value, Size, buffer);
+}
+
+// Serializes the type code for std::map types.
+template <typename Key, typename T, typename Compare, typename Allocator>
+inline void SerializeType(const std::map<Key, T, Compare, Allocator>& value,
+                          void*& buffer) {
+  SerializeMapType(value, value.size(), buffer);
+}
+
+// Serializes the type code for std::unordered_map types.
+template <typename Key, typename T, typename Hash, typename KeyEqual,
+          typename Allocator>
+inline void SerializeType(
+    const std::unordered_map<Key, T, Hash, KeyEqual, Allocator>& value,
+    void*& buffer) {
+  SerializeMapType(value, value.size(), buffer);
+}
+
+// Serializes the type code for std::pair types.
+template <typename T, typename U>
+inline void SerializeType(const std::pair<T, U>& value, void*& buffer) {
+  SerializeArrayType(value, 2, buffer);
+}
+
+// Serializes the type code for std::tuple types.
+template <typename... T>
+inline void SerializeType(const std::tuple<T...>& value, void*& buffer) {
+  SerializeArrayType(value, sizeof...(T), buffer);
+}
+
+// Specialization of SerializeObject for boolean type.
+inline void SerializeObject(const bool& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  SerializeType(value, buffer);
+  // Encoding contains the boolean value, nothing else to do.
+}
+
+// Overloads of SerializeObject for float and double types.
+inline void SerializeObject(const float& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  SerializeRaw(value, buffer);
+}
+
+inline void SerializeObject(const double& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  SerializeRaw(value, buffer);
+}
+
+// Overloads of SerializeObject() for standard integer types.
+inline void SerializeObject(const char& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_UINT8) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsUnsignedFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+inline void SerializeObject(const int8_t& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_INT8) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+inline void SerializeObject(const uint8_t& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_UINT8) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsUnsignedFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+inline void SerializeObject(const int16_t& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_INT8) {
+    const int8_t byte = value;
+    SerializeRaw(byte, buffer);
+  } else if (encoding == ENCODING_TYPE_INT16) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+inline void SerializeObject(const uint16_t& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_UINT8) {
+    const uint8_t byte = value;
+    SerializeRaw(byte, buffer);
+  } else if (encoding == ENCODING_TYPE_UINT16) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsUnsignedFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+inline void SerializeObject(const int32_t& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_INT8) {
+    const int8_t byte = value;
+    SerializeRaw(byte, buffer);
+  } else if (encoding == ENCODING_TYPE_INT16) {
+    const int16_t half = value;
+    SerializeRaw(half, buffer);
+  } else if (encoding == ENCODING_TYPE_INT32) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+inline void SerializeObject(const uint32_t& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_UINT8) {
+    const uint8_t byte = value;
+    SerializeRaw(byte, buffer);
+  } else if (encoding == ENCODING_TYPE_UINT16) {
+    const uint16_t half = value;
+    SerializeRaw(half, buffer);
+  } else if (encoding == ENCODING_TYPE_UINT32) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsUnsignedFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+inline void SerializeObject(const int64_t& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_INT8) {
+    const int8_t byte = value;
+    SerializeRaw(byte, buffer);
+  } else if (encoding == ENCODING_TYPE_INT16) {
+    const int16_t half = value;
+    SerializeRaw(half, buffer);
+  } else if (encoding == ENCODING_TYPE_INT32) {
+    const int32_t word = value;
+    SerializeRaw(word, buffer);
+  } else if (encoding == ENCODING_TYPE_INT64) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+inline void SerializeObject(const uint64_t& value, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  const EncodingType encoding = EncodeType(value);
+  SerializeEncoding(encoding, buffer);
+  if (encoding == ENCODING_TYPE_UINT8) {
+    const uint8_t byte = value;
+    SerializeRaw(byte, buffer);
+  } else if (encoding == ENCODING_TYPE_UINT16) {
+    const uint16_t half = value;
+    SerializeRaw(half, buffer);
+  } else if (encoding == ENCODING_TYPE_UINT32) {
+    const uint32_t word = value;
+    SerializeRaw(word, buffer);
+  } else if (encoding == ENCODING_TYPE_UINT64) {
+    SerializeRaw(value, buffer);
+  } else /* if (IsUnsignedFixintEncoding(encoding) */ {
+    // Encoding byte contains the value, nothing else to do.
+  }
+}
+
+// Serialize enum types.
+template <typename T>
+inline EnableIfEnum<T> SerializeObject(const T& value, MessageWriter* writer,
+                                       void*& buffer) {
+  SerializeObject(static_cast<std::underlying_type_t<T>>(value), writer,
+                  buffer);
+}
+
+// Forward declaration for nested definitions.
+inline void SerializeObject(const EmptyVariant&, MessageWriter*, void*&);
+template <typename... Types>
+inline void SerializeObject(const Variant<Types...>&, MessageWriter*, void*&);
+template <typename T, typename Enabled>
+inline void SerializeObject(const T&, MessageWriter*, void*&);
+template <typename T>
+inline void SerializeObject(const PointerWrapper<T>&, MessageWriter*, void*&);
+template <FileHandleMode Mode>
+inline void SerializeObject(const FileHandle<Mode>&, MessageWriter*, void*&);
+template <ChannelHandleMode Mode>
+inline void SerializeObject(const ChannelHandle<Mode>&, MessageWriter*, void*&);
+template <typename T, typename Allocator>
+inline void SerializeObject(const BufferWrapper<std::vector<T, Allocator>>&, MessageWriter*, void*&);
+template <typename T>
+inline void SerializeObject(const BufferWrapper<T*>&, MessageWriter*, void*&);
+inline void SerializeObject(const std::string&, MessageWriter*, void*&);
+template <typename T>
+inline void SerializeObject(const StringWrapper<T>&, MessageWriter*, void*&);
+template <typename T, typename Allocator>
+inline void SerializeObject(const std::vector<T, Allocator>&, MessageWriter*, void*&);
+template <typename T>
+inline void SerializeObject(const ArrayWrapper<T>&, MessageWriter*, void*&);
+template <typename T, std::size_t Size>
+inline void SerializeObject(const std::array<T, Size>&, MessageWriter*, void*&);
+template <typename Key, typename T, typename Compare, typename Allocator>
+inline void SerializeObject(const std::map<Key, T, Compare, Allocator>&, MessageWriter*, void*&);
+template <typename Key, typename T, typename Hash, typename KeyEqual,
+          typename Allocator>
+inline void SerializeObject(
+    const std::unordered_map<Key, T, Hash, KeyEqual, Allocator>&, MessageWriter*, void*&);
+template <typename T, typename U>
+inline void SerializeObject(const std::pair<T, U>&, MessageWriter*, void*&);
+template <typename... T>
+inline void SerializeObject(const std::tuple<T...>&, MessageWriter*, void*&);
+
+// Overload for empty variant type.
+inline void SerializeObject(const EmptyVariant& empty,
+                            MessageWriter* /*writer*/, void*& buffer) {
+  const EncodingType encoding = EncodeType(empty);
+  SerializeEncoding(encoding, buffer);
+}
+
+// Overload for Variant types.
+template <typename... Types>
+inline void SerializeObject(const Variant<Types...>& variant,
+                            MessageWriter* writer, void*& buffer) {
+  SerializeType(variant, buffer);
+  SerializeObject(variant.index(), writer, buffer);
+  return variant.Visit([writer, &buffer](const auto& value) {
+    return SerializeObject(value, writer, buffer);
+  });
+}
+
+// Overload for serializable structure/class types.
+template <typename T, typename Enabled = EnableIfHasSerializableMembers<T>>
+inline void SerializeObject(const T& value, MessageWriter* writer,
+                            void*& buffer) {
+  SerializableTraits<T>::SerializeObject(value, writer, buffer);
+}
+
+// Serializes the payload of a PointerWrapper.
+template <typename T>
+inline void SerializeObject(const PointerWrapper<T>& pointer,
+                            MessageWriter* writer, void*& buffer) {
+  SerializeObject(pointer.Dereference(), writer, buffer);
+}
+
+// Serializes the payload of file descriptor types.
+template <FileHandleMode Mode>
+inline void SerializeObject(const FileHandle<Mode>& fd, MessageWriter* writer,
+                            void*& buffer) {
+  SerializeType(fd, buffer);
+  const FileReference value =
+      writer->GetOutputResourceMapper()->PushFileHandle(fd);
+  SerializeRaw(value, buffer);
+}
+
+// Serializes the payload of channel handle types.
+template <ChannelHandleMode Mode>
+inline void SerializeObject(const ChannelHandle<Mode>& handle,
+                            MessageWriter* writer, void*& buffer) {
+  SerializeType(handle, buffer);
+  const ChannelReference value =
+      writer->GetOutputResourceMapper()->PushChannelHandle(handle);
+  SerializeRaw(value, buffer);
+}
+
+// Serializes the payload of BufferWrapper types.
+template <typename T, typename Allocator>
+inline void SerializeObject(const BufferWrapper<std::vector<T, Allocator>>& b,
+                            MessageWriter* /*writer*/, void*& buffer) {
+  const auto value_type_size =
+      sizeof(typename BufferWrapper<std::vector<T, Allocator>>::value_type);
+  SerializeType(b, buffer);
+  WriteRawData(buffer, b.data(), b.size() * value_type_size);
+}
+template <typename T>
+inline void SerializeObject(const BufferWrapper<T*>& b,
+                            MessageWriter* /*writer*/, void*& buffer) {
+  const auto value_type_size = sizeof(typename BufferWrapper<T*>::value_type);
+  SerializeType(b, buffer);
+  WriteRawData(buffer, b.data(), b.size() * value_type_size);
+}
+
+// Serializes the payload of string types.
+template <typename StringType>
+inline void SerializeString(const StringType& s, void*& buffer) {
+  const auto value_type_size = sizeof(typename StringType::value_type);
+  SerializeType(s, buffer);
+  WriteRawData(buffer, s.data(), s.length() * value_type_size);
+}
+
+// Overload of SerializeObject() for std::string and StringWrapper. These types
+// are interchangeable and must serialize to the same format.
+inline void SerializeObject(const std::string& s, MessageWriter* /*writer*/,
+                            void*& buffer) {
+  SerializeString(s, buffer);
+}
+template <typename T>
+inline void SerializeObject(const StringWrapper<T>& s,
+                            MessageWriter* /*writer*/, void*& buffer) {
+  SerializeString(s, buffer);
+}
+
+// Serializes the payload of array types.
+template <typename ArrayType>
+inline void SerializeArray(const ArrayType& v, MessageWriter* writer,
+                           void*& buffer) {
+  SerializeType(v, buffer);
+  for (const auto& element : v)
+    SerializeObject(element, writer, buffer);
+}
+
+// Serializes the payload for map types.
+template <typename MapType>
+inline void SerializeMap(const MapType& v, MessageWriter* writer,
+                         void*& buffer) {
+  SerializeType(v, buffer);
+  for (const auto& element : v) {
+    SerializeObject(element.first, writer, buffer);
+    SerializeObject(element.second, writer, buffer);
+  }
+}
+
+// Overload of SerializeObject() for std::vector and ArrayWrapper types. These
+// types are interchangeable and must serialize to the same format.
+template <typename T, typename Allocator>
+inline void SerializeObject(const std::vector<T, Allocator>& v,
+                            MessageWriter* writer, void*& buffer) {
+  SerializeArray(v, writer, buffer);
+}
+template <typename T>
+inline void SerializeObject(const ArrayWrapper<T>& v, MessageWriter* writer,
+                            void*& buffer) {
+  SerializeArray(v, writer, buffer);
+}
+
+// Overload of SerializeObject() for std::array types. These types serialize to
+// the same format at std::vector and ArrayWrapper and are interchangeable in
+// certain situations.
+template <typename T, std::size_t Size>
+inline void SerializeObject(const std::array<T, Size>& v, MessageWriter* writer,
+                            void*& buffer) {
+  SerializeArray(v, writer, buffer);
+}
+
+// Overload of SerializeObject() for std::map types.
+template <typename Key, typename T, typename Compare, typename Allocator>
+inline void SerializeObject(const std::map<Key, T, Compare, Allocator>& v,
+                            MessageWriter* writer, void*& buffer) {
+  SerializeMap(v, writer, buffer);
+}
+
+// Overload of SerializeObject() for std::unordered_map types.
+template <typename Key, typename T, typename Hash, typename KeyEqual,
+          typename Allocator>
+inline void SerializeObject(
+    const std::unordered_map<Key, T, Hash, KeyEqual, Allocator>& v,
+    MessageWriter* writer, void*& buffer) {
+  SerializeMap(v, writer, buffer);
+}
+
+// Overload of SerializeObject() for std:pair types.
+template <typename T, typename U>
+inline void SerializeObject(const std::pair<T, U>& pair, MessageWriter* writer,
+                            void*& buffer) {
+  SerializeType(pair, buffer);
+  SerializeObject(pair.first, writer, buffer);
+  SerializeObject(pair.second, writer, buffer);
+}
+
+// Stops template recursion when the last tuple element is reached.
+template <typename... T>
+inline void SerializeTuple(const std::tuple<T...>&, MessageWriter*, void*&,
+                           Index<0>) {}
+
+// Serializes each element of a tuple recursively.
+template <typename... T, std::size_t index>
+inline void SerializeTuple(const std::tuple<T...>& tuple, MessageWriter* writer,
+                           void*& buffer, Index<index>) {
+  SerializeTuple(tuple, writer, buffer, Index<index - 1>());
+  SerializeObject(std::get<index - 1>(tuple), writer, buffer);
+}
+
+// Overload of SerializeObject() for tuple types.
+template <typename... T>
+inline void SerializeObject(const std::tuple<T...>& tuple,
+                            MessageWriter* writer, void*& buffer) {
+  SerializeType(tuple, buffer);
+  SerializeTuple(tuple, writer, buffer, Index<sizeof...(T)>());
+}
+
+// Stops template recursion when the last member pointer is reached.
+template <typename Members, typename T>
+inline void SerializeMember(const T&, MessageWriter*, void*&, Index<0>) {}
+
+// Serializes each member pointer recursively.
+template <typename Members, typename T, std::size_t index>
+inline void SerializeMember(const T& object, MessageWriter* writer,
+                            void*& buffer, Index<index>) {
+  SerializeMember<Members>(object, writer, buffer, Index<index - 1>());
+  SerializeObject(Members::template At<index - 1>::Resolve(object), writer,
+                  buffer);
+}
+
+// Serializes the members of a type using the given SerializableMembersType
+// type.
+template <typename Members, typename T>
+inline void SerializeMembers(const T& object, MessageWriter* writer,
+                             void*& buffer) {
+  SerializeMember<Members>(object, writer, buffer,
+                           Index<Members::MemberCount>());
+}
+
+// Top level serialization function that replaces the buffer's contents.
+template <typename T>
+inline void Serialize(const T& object, MessageWriter* writer) {
+  PDX_TRACE_NAME("Serialize");
+  const std::size_t size = GetSerializedSize(object);
+
+  // Reserve the space needed for the object(s).
+  void* buffer = writer->GetNextWriteBufferSection(size);
+  SerializeObject(object, writer, buffer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Object Deserialization //
+///////////////////////////////////////////////////////////////////////////////
+
+inline ErrorType ReadRawDataFromNextSection(void* dest, MessageReader* reader,
+                                            const void*& start,
+                                            const void*& end, size_t size) {
+  while (AdvancePointer(start, size) > end) {
+    auto remaining_size = PointerDistance(end, start);
+    if (remaining_size > 0) {
+      memcpy(dest, start, remaining_size);
+      dest = AdvancePointer(dest, remaining_size);
+      size -= remaining_size;
+    }
+    reader->ConsumeReadBufferSectionData(AdvancePointer(start, remaining_size));
+    std::tie(start, end) = reader->GetNextReadBufferSection();
+    if (start == end)
+      return ErrorCode::INSUFFICIENT_BUFFER;
+  }
+  memcpy(dest, start, size);
+  start = AdvancePointer(start, size);
+  return ErrorCode::NO_ERROR;
+}
+
+inline ErrorType ReadRawData(void* dest, MessageReader* /*reader*/,
+                             const void*& start, const void*& end,
+                             size_t size) {
+  if (PDX_UNLIKELY(AdvancePointer(start, size) > end)) {
+    // TODO(avakulenko): Enabling reading from next sections of input buffer
+    // (using ReadRawDataFromNextSection) screws up clang compiler optimizations
+    // (probably inefficient inlining) making the whole deserialization
+    // code path about twice as slow. Investigate and enable more generic
+    // deserialization code, but right now we don't really need/support this
+    // scenario, so I keep this commented out for the time being...
+
+    // return ReadRawDataFromNextSection(dest, reader, start, end, size);
+    return ErrorCode::INSUFFICIENT_BUFFER;
+  }
+  memcpy(dest, start, size);
+  start = AdvancePointer(start, size);
+  return ErrorCode::NO_ERROR;
+}
+
+// Deserializes a primitive object from raw bytes.
+template <typename T,
+          typename = typename std::enable_if<std::is_pod<T>::value>::type>
+inline ErrorType DeserializeRaw(T* value, MessageReader* reader,
+                                const void*& start, const void*& end) {
+  return ReadRawData(value, reader, start, end, sizeof(T));
+}
+
+// Utility to deserialize POD types when the serialized type is different
+// (smaller) than the target real type. This happens when values are serialized
+// into more compact encodings.
+template <typename SerializedType, typename RealType>
+ErrorType DeserializeValue(RealType* real_value, MessageReader* reader,
+                           const void*& start, const void*& end) {
+  SerializedType serialized_value;
+  if (const auto error =
+          DeserializeRaw(&serialized_value, reader, start, end)) {
+    return error;
+  } else {
+    *real_value = serialized_value;
+    return ErrorCode::NO_ERROR;
+  }
+}
+
+inline ErrorType DeserializeEncoding(EncodingType* encoding,
+                                     MessageReader* reader, const void*& start,
+                                     const void*& end) {
+  return DeserializeRaw(encoding, reader, start, end);
+}
+
+// Overload to deserialize bool type.
+inline ErrorType DeserializeObject(bool* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsBoolEncoding(encoding)) {
+    *value = (encoding == ENCODING_TYPE_TRUE);
+    return ErrorCode::NO_ERROR;
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_BOOL,
+                     encoding);
+  }
+}
+
+// Specializations to deserialize float and double types.
+inline ErrorType DeserializeObject(float* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsFloat32Encoding(encoding)) {
+    return DeserializeValue<float>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_FLOAT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(double* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsFloat32Encoding(encoding)) {
+    return DeserializeValue<float>(value, reader, start, end);
+  } else if (IsFloat64Encoding(encoding)) {
+    return DeserializeValue<double>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_FLOAT,
+                     encoding);
+  }
+}
+
+// Specializations to deserialize standard integer types.
+inline ErrorType DeserializeObject(char* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsUnsignedFixintEncoding(encoding)) {
+    *value = static_cast<char>(encoding);
+    return ErrorCode::NO_ERROR;
+  } else if (IsUInt8Encoding(encoding)) {
+    return DeserializeValue<char>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_UINT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(std::int8_t* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsFixintEncoding(encoding)) {
+    *value = static_cast<std::int8_t>(encoding);
+    return ErrorCode::NO_ERROR;
+  } else if (IsInt8Encoding(encoding)) {
+    return DeserializeValue<std::int8_t>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_INT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(std::uint8_t* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsUnsignedFixintEncoding(encoding)) {
+    *value = encoding;
+    return ErrorCode::NO_ERROR;
+  } else if (IsUInt8Encoding(encoding)) {
+    return DeserializeValue<std::uint8_t>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_UINT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(std::int16_t* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsFixintEncoding(encoding)) {
+    *value = static_cast<std::int8_t>(encoding);
+    return ErrorCode::NO_ERROR;
+  } else if (IsInt8Encoding(encoding)) {
+    return DeserializeValue<std::int8_t>(value, reader, start, end);
+  } else if (IsInt16Encoding(encoding)) {
+    return DeserializeValue<std::int16_t>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_INT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(std::uint16_t* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsUnsignedFixintEncoding(encoding)) {
+    *value = encoding;
+    return ErrorCode::NO_ERROR;
+  } else if (IsUInt8Encoding(encoding)) {
+    return DeserializeValue<std::uint8_t>(value, reader, start, end);
+  } else if (IsUInt16Encoding(encoding)) {
+    return DeserializeValue<std::uint16_t>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_UINT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(std::int32_t* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsFixintEncoding(encoding)) {
+    *value = static_cast<std::int8_t>(encoding);
+    return ErrorCode::NO_ERROR;
+  } else if (IsInt8Encoding(encoding)) {
+    return DeserializeValue<std::int8_t>(value, reader, start, end);
+  } else if (IsInt16Encoding(encoding)) {
+    return DeserializeValue<std::int16_t>(value, reader, start, end);
+  } else if (IsInt32Encoding(encoding)) {
+    return DeserializeValue<std::int32_t>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_INT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(std::uint32_t* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsUnsignedFixintEncoding(encoding)) {
+    *value = encoding;
+    return ErrorCode::NO_ERROR;
+  } else if (IsUInt8Encoding(encoding)) {
+    return DeserializeValue<std::uint8_t>(value, reader, start, end);
+  } else if (IsUInt16Encoding(encoding)) {
+    return DeserializeValue<std::uint16_t>(value, reader, start, end);
+  } else if (IsUInt32Encoding(encoding)) {
+    return DeserializeValue<std::uint32_t>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_UINT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(std::int64_t* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsFixintEncoding(encoding)) {
+    *value = static_cast<std::int8_t>(encoding);
+    return ErrorCode::NO_ERROR;
+  } else if (IsInt8Encoding(encoding)) {
+    return DeserializeValue<std::int8_t>(value, reader, start, end);
+  } else if (IsInt16Encoding(encoding)) {
+    return DeserializeValue<std::int16_t>(value, reader, start, end);
+  } else if (IsInt32Encoding(encoding)) {
+    return DeserializeValue<std::int32_t>(value, reader, start, end);
+  } else if (IsInt64Encoding(encoding)) {
+    return DeserializeValue<std::int64_t>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_INT,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(std::uint64_t* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (IsUnsignedFixintEncoding(encoding)) {
+    *value = encoding;
+    return ErrorCode::NO_ERROR;
+  } else if (IsUInt8Encoding(encoding)) {
+    return DeserializeValue<std::uint8_t>(value, reader, start, end);
+  } else if (IsUInt16Encoding(encoding)) {
+    return DeserializeValue<std::uint16_t>(value, reader, start, end);
+  } else if (IsUInt32Encoding(encoding)) {
+    return DeserializeValue<std::uint32_t>(value, reader, start, end);
+  } else if (IsUInt64Encoding(encoding)) {
+    return DeserializeValue<std::uint64_t>(value, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_UINT,
+                     encoding);
+  }
+}
+
+template <typename T>
+inline EnableIfEnum<T, ErrorType> DeserializeObject(T* value,
+                                                    MessageReader* reader,
+                                                    const void*& start,
+                                                    const void*& end) {
+  std::underlying_type_t<T> enum_value;
+  ErrorType error = DeserializeObject(&enum_value, reader, start, end);
+  if (!error)
+    *value = static_cast<T>(enum_value);
+  return error;
+}
+
+// Forward declarations for nested definitions.
+template <typename T, typename Enabled>
+inline ErrorType DeserializeObject(T*, MessageReader*, const void*&,
+                                   const void*&);
+template <typename T>
+inline ErrorType DeserializeObject(PointerWrapper<T>*, MessageReader*,
+                                   const void*&, const void*&);
+inline ErrorType DeserializeObject(LocalHandle*, MessageReader*, const void*&,
+                                   const void*&);
+inline ErrorType DeserializeObject(LocalChannelHandle*, MessageReader*,
+                                   const void*&, const void*&);
+template <typename T, typename Allocator>
+inline ErrorType DeserializeObject(BufferWrapper<std::vector<T, Allocator>>*,
+                                   MessageReader*, const void*&, const void*&);
+template <typename T>
+inline ErrorType DeserializeObject(BufferWrapper<T*>*, MessageReader*,
+                                   const void*&, const void*&);
+inline ErrorType DeserializeObject(std::string*, MessageReader*, const void*&,
+                                   const void*&);
+template <typename T>
+inline ErrorType DeserializeObject(StringWrapper<T>*, MessageReader*,
+                                   const void*&, const void*&);
+template <typename T, typename U>
+inline ErrorType DeserializeObject(std::pair<T, U>*, MessageReader*,
+                                   const void*&, const void*&);
+template <typename... T>
+inline ErrorType DeserializeObject(std::tuple<T...>*, MessageReader*,
+                                   const void*&, const void*&);
+template <typename T, typename Allocator>
+inline ErrorType DeserializeObject(std::vector<T, Allocator>*, MessageReader*,
+                                   const void*&, const void*&);
+template <typename Key, typename T, typename Compare, typename Allocator>
+inline ErrorType DeserializeObject(std::map<Key, T, Compare, Allocator>*,
+                                   MessageReader*, const void*&, const void*&);
+template <typename Key, typename T, typename Hash, typename KeyEqual,
+          typename Allocator>
+inline ErrorType DeserializeObject(
+    std::unordered_map<Key, T, Hash, KeyEqual, Allocator>*, MessageReader*,
+    const void*&, const void*&);
+template <typename T>
+inline ErrorType DeserializeObject(ArrayWrapper<T>*, MessageReader*,
+                                   const void*&, const void*&);
+template <typename T, std::size_t Size>
+inline ErrorType DeserializeObject(std::array<T, Size>*, MessageReader*,
+                                   const void*&, const void*&);
+template <typename T, typename U>
+inline ErrorType DeserializeObject(std::pair<T, U>*, MessageReader*,
+                                   const void*&, const void*&);
+template <typename... T>
+inline ErrorType DeserializeObject(std::tuple<T...>*, MessageReader*,
+                                   const void*&, const void*&);
+inline ErrorType DeserializeObject(EmptyVariant*,
+                                   MessageReader*, const void*&,
+                                   const void*&);
+template <typename... Types>
+inline ErrorType DeserializeObject(Variant<Types...>*,
+                                   MessageReader*, const void*&,
+                                   const void*&);
+
+// Deserializes a Serializable type.
+template <typename T, typename Enable = EnableIfHasSerializableMembers<T>>
+inline ErrorType DeserializeObject(T* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  return SerializableTraits<T>::DeserializeObject(value, reader, start, end);
+}
+
+// Deserializes a PointerWrapper.
+template <typename T>
+inline ErrorType DeserializeObject(PointerWrapper<T>* pointer,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  return DeserializeObject(&pointer->Dereference(), reader, start, end);
+}
+
+// Deserializes the type code and size for extension types.
+inline ErrorType DeserializeExtType(EncodingType* encoding,
+                                    EncodingExtType* type, std::size_t* size,
+                                    MessageReader* reader, const void*& start,
+                                    const void*& end) {
+  if (const auto error = DeserializeEncoding(encoding, reader, start, end)) {
+    return error;
+  } else if (IsFixextEncoding(*encoding)) {
+    *size = GetFixextSize(*encoding);
+  } else if (*encoding == ENCODING_TYPE_EXT8) {
+    if (const auto error =
+            DeserializeValue<std::uint8_t>(size, reader, start, end))
+      return error;
+  } else if (*encoding == ENCODING_TYPE_EXT16) {
+    if (const auto error =
+            DeserializeValue<std::uint16_t>(size, reader, start, end))
+      return error;
+  } else if (*encoding == ENCODING_TYPE_EXT32) {
+    if (const auto error =
+            DeserializeValue<std::uint32_t>(size, reader, start, end))
+      return error;
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_EXTENSION,
+                     *encoding);
+  }
+
+  // The extension type code follows the encoding and size.
+  return DeserializeRaw(type, reader, start, end);
+}
+
+// Deserializes a file handle and performs handle space translation, if
+// required.
+inline ErrorType DeserializeObject(LocalHandle* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  EncodingExtType type;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeExtType(&encoding, &type, &size, reader, start, end)) {
+    return error;
+  } else if (size != 2) {
+    return ErrorType(ErrorCode::UNEXPECTED_TYPE_SIZE, ENCODING_CLASS_EXTENSION,
+                     encoding);
+  } else if (type == ENCODING_EXT_TYPE_FILE_DESCRIPTOR) {
+    // Read the encoded file descriptor value.
+    FileReference ref;
+    if (const auto error = DeserializeRaw(&ref, reader, start, end)) {
+      return error;
+    }
+
+    return reader->GetInputResourceMapper()->GetFileHandle(ref, value)
+               ? ErrorCode::NO_ERROR
+               : ErrorCode::GET_FILE_DESCRIPTOR_FAILED;
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_EXTENSION,
+                     encoding);
+  }
+}
+
+inline ErrorType DeserializeObject(LocalChannelHandle* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  EncodingType encoding;
+  EncodingExtType type;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeExtType(&encoding, &type, &size, reader, start, end)) {
+    return error;
+  } else if (size != 4) {
+    return ErrorType(ErrorCode::UNEXPECTED_TYPE_SIZE, ENCODING_CLASS_EXTENSION,
+                     encoding);
+  } else if (type == ENCODING_EXT_TYPE_CHANNEL_HANDLE) {
+    // Read the encoded channel handle value.
+    ChannelReference ref;
+    if (const auto error = DeserializeRaw(&ref, reader, start, end)) {
+      return error;
+    }
+    return reader->GetInputResourceMapper()->GetChannelHandle(ref, value)
+               ? ErrorCode::NO_ERROR
+               : ErrorCode::GET_CHANNEL_HANDLE_FAILED;
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_EXTENSION,
+                     encoding);
+  }
+}
+
+// Deserializes the type code and size for bin types.
+inline ErrorType DeserializeBinType(EncodingType* encoding, std::size_t* size,
+                                    MessageReader* reader, const void*& start,
+                                    const void*& end) {
+  if (const auto error = DeserializeEncoding(encoding, reader, start, end)) {
+    return error;
+  } else if (*encoding == ENCODING_TYPE_BIN8) {
+    return DeserializeValue<std::uint8_t>(size, reader, start, end);
+  } else if (*encoding == ENCODING_TYPE_BIN16) {
+    return DeserializeValue<std::uint16_t>(size, reader, start, end);
+  } else if (*encoding == ENCODING_TYPE_BIN32) {
+    return DeserializeValue<std::uint32_t>(size, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_BINARY,
+                     *encoding);
+  }
+}
+
+// Overload of DeserializeObject() for BufferWrapper types.
+template <typename T, typename Allocator>
+inline ErrorType DeserializeObject(
+    BufferWrapper<std::vector<T, Allocator>>* value, MessageReader* reader,
+    const void*& start, const void*& end) {
+  const auto value_type_size =
+      sizeof(typename BufferWrapper<std::vector<T, Allocator>>::value_type);
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeBinType(&encoding, &size, reader, start, end))
+    return error;
+
+  // Try to resize the BufferWrapper to the size of the payload.
+  value->resize(size / value_type_size);
+
+  if (size > value->size() * value_type_size || size % value_type_size != 0) {
+    return ErrorCode::INSUFFICIENT_DESTINATION_SIZE;
+  } else if (size == 0U) {
+    return ErrorCode::NO_ERROR;
+  } else {
+    return ReadRawData(value->data(), reader, start, end, size);
+  }
+}
+template <typename T>
+inline ErrorType DeserializeObject(BufferWrapper<T*>* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  const auto value_type_size = sizeof(typename BufferWrapper<T*>::value_type);
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeBinType(&encoding, &size, reader, start, end))
+    return error;
+
+  // Try to resize the BufferWrapper to the size of the payload.
+  value->resize(size / value_type_size);
+
+  if (size > value->size() * value_type_size || size % value_type_size != 0) {
+    return ErrorCode::INSUFFICIENT_DESTINATION_SIZE;
+  } else if (size == 0U) {
+    return ErrorCode::NO_ERROR;
+  } else {
+    return ReadRawData(value->data(), reader, start, end, size);
+  }
+}
+
+// Deserializes the type code and size for string types.
+inline ErrorType DeserializeStringType(EncodingType* encoding,
+                                       std::size_t* size, MessageReader* reader,
+                                       const void*& start, const void*& end) {
+  if (const auto error = DeserializeEncoding(encoding, reader, start, end)) {
+    return error;
+  } else if (IsFixstrEncoding(*encoding)) {
+    *size = GetFixstrSize(*encoding);
+    return ErrorCode::NO_ERROR;
+  } else if (*encoding == ENCODING_TYPE_STR8) {
+    return DeserializeValue<std::uint8_t>(size, reader, start, end);
+  } else if (*encoding == ENCODING_TYPE_STR16) {
+    return DeserializeValue<std::uint16_t>(size, reader, start, end);
+  } else if (*encoding == ENCODING_TYPE_STR32) {
+    return DeserializeValue<std::uint32_t>(size, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_STRING,
+                     *encoding);
+  }
+}
+
+// Overload of DeserializeObject() for std::string types.
+inline ErrorType DeserializeObject(std::string* value, MessageReader* reader,
+                                   const void*& start, const void*& end) {
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeStringType(&encoding, &size, reader, start, end)) {
+    return error;
+  } else if (size == 0U) {
+    value->clear();
+    return ErrorCode::NO_ERROR;
+  } else {
+    value->resize(size);
+    return ReadRawData(&(*value)[0], reader, start, end, size);
+  }
+}
+
+// Overload of DeserializeObject() for StringWrapper types.
+template <typename T>
+inline ErrorType DeserializeObject(StringWrapper<T>* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  const auto value_type_size = sizeof(typename StringWrapper<T>::value_type);
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeStringType(&encoding, &size, reader, start, end))
+    return error;
+
+  // Try to resize the StringWrapper to the size of the payload
+  // string.
+  value->resize(size / value_type_size);
+
+  if (size > value->length() * value_type_size || size % value_type_size != 0) {
+    return ErrorCode::INSUFFICIENT_DESTINATION_SIZE;
+  } else if (size == 0U) {
+    return ErrorCode::NO_ERROR;
+  } else {
+    return ReadRawData(value->data(), reader, start, end, size);
+  }
+}
+
+// Deserializes the type code and size of array types.
+inline ErrorType DeserializeArrayType(EncodingType* encoding, std::size_t* size,
+                                      MessageReader* reader, const void*& start,
+                                      const void*& end) {
+  if (const auto error = DeserializeEncoding(encoding, reader, start, end)) {
+    return error;
+  } else if (IsFixarrayEncoding(*encoding)) {
+    *size = GetFixarraySize(*encoding);
+    return ErrorCode::NO_ERROR;
+  } else if (*encoding == ENCODING_TYPE_ARRAY16) {
+    return DeserializeValue<std::uint16_t>(size, reader, start, end);
+  } else if (*encoding == ENCODING_TYPE_ARRAY32) {
+    return DeserializeValue<std::uint32_t>(size, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_ARRAY,
+                     *encoding);
+  }
+}
+
+// Deserializes the type code and size of map types.
+inline ErrorType DeserializeMapType(EncodingType* encoding, std::size_t* size,
+                                    MessageReader* reader, const void*& start,
+                                    const void*& end) {
+  if (const auto error = DeserializeEncoding(encoding, reader, start, end)) {
+    return error;
+  } else if (IsFixmapEncoding(*encoding)) {
+    *size = GetFixmapSize(*encoding);
+    return ErrorCode::NO_ERROR;
+  } else if (*encoding == ENCODING_TYPE_MAP16) {
+    return DeserializeValue<std::uint16_t>(size, reader, start, end);
+  } else if (*encoding == ENCODING_TYPE_MAP32) {
+    return DeserializeValue<std::uint32_t>(size, reader, start, end);
+  } else {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_MAP,
+                     *encoding);
+  }
+}
+
+// Overload for std::vector types.
+template <typename T, typename Allocator>
+inline ErrorType DeserializeObject(std::vector<T, Allocator>* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeArrayType(&encoding, &size, reader, start, end))
+    return error;
+
+  std::vector<T, Allocator> result(size);
+  for (std::size_t i = 0; i < size; i++) {
+    if (const auto error = DeserializeObject(&result[i], reader, start, end))
+      return error;
+  }
+
+  *value = std::move(result);
+  return ErrorCode::NO_ERROR;
+
+// TODO(eieio): Consider the benefits and trade offs of this alternative.
+#if 0
+  value->resize(size);
+  for (std::size_t i = 0; i < size; i++) {
+    if (const auto error = DeserializeObject(&(*value)[i], reader, start, end))
+      return error;
+  }
+  return ErrorCode::NO_ERROR;
+#endif
+}
+
+// Deserializes an EmptyVariant value.
+inline ErrorType DeserializeObject(EmptyVariant* /*empty*/,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  EncodingType encoding;
+
+  if (const auto error = DeserializeEncoding(&encoding, reader, start, end)) {
+    return error;
+  } else if (encoding != ENCODING_TYPE_NIL) {
+    return ErrorType(ErrorCode::UNEXPECTED_ENCODING, ENCODING_CLASS_MAP,
+                     encoding);
+  } else {
+    return ErrorCode::NO_ERROR;
+  }
+}
+
+// Deserializes a Variant type.
+template <typename... Types>
+inline ErrorType DeserializeObject(Variant<Types...>* variant,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeMapType(&encoding, &size, reader, start, end)) {
+    return error;
+  }
+
+  if (size != 1)
+    return ErrorType(ErrorCode::UNEXPECTED_TYPE_SIZE, ENCODING_CLASS_MAP,
+                     encoding);
+
+  std::int32_t type;
+  if (const auto error = DeserializeObject(&type, reader, start, end)) {
+    return error;
+  } else if (type < Variant<Types...>::kEmptyIndex ||
+             type >= static_cast<std::int32_t>(sizeof...(Types))) {
+    return ErrorCode::INVALID_VARIANT_ELEMENT;
+  } else {
+    variant->Become(type);
+    return variant->Visit([reader, &start, &end](auto&& value) {
+      return DeserializeObject(&value, reader, start, end);
+    });
+  }
+}
+
+// Deserializes map types.
+template <typename MapType>
+inline ErrorType DeserializeMap(MapType* value, MessageReader* reader,
+                                const void*& start, const void*& end) {
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeMapType(&encoding, &size, reader, start, end))
+    return error;
+
+  MapType result;
+  for (std::size_t i = 0; i < size; i++) {
+    std::pair<typename MapType::key_type, typename MapType::mapped_type>
+        element;
+    if (const auto error =
+            DeserializeObject(&element.first, reader, start, end))
+      return error;
+    if (const auto error =
+            DeserializeObject(&element.second, reader, start, end))
+      return error;
+    result.emplace(std::move(element));
+  }
+
+  *value = std::move(result);
+  return ErrorCode::NO_ERROR;
+}
+
+// Overload for std::map types.
+template <typename Key, typename T, typename Compare, typename Allocator>
+inline ErrorType DeserializeObject(std::map<Key, T, Compare, Allocator>* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  return DeserializeMap(value, reader, start, end);
+}
+
+// Overload for std::unordered_map types.
+template <typename Key, typename T, typename Hash, typename KeyEqual,
+          typename Allocator>
+inline ErrorType DeserializeObject(
+    std::unordered_map<Key, T, Hash, KeyEqual, Allocator>* value,
+    MessageReader* reader, const void*& start, const void*& end) {
+  return DeserializeMap(value, reader, start, end);
+}
+
+// Overload for ArrayWrapper types.
+template <typename T>
+inline ErrorType DeserializeObject(ArrayWrapper<T>* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeArrayType(&encoding, &size, reader, start, end)) {
+    return error;
+  }
+
+  // Try to resize the wrapper.
+  value->resize(size);
+
+  // Make sure there is enough space in the ArrayWrapper for the
+  // payload.
+  if (size > value->capacity())
+    return ErrorCode::INSUFFICIENT_DESTINATION_SIZE;
+
+  for (std::size_t i = 0; i < size; i++) {
+    if (const auto error = DeserializeObject(&(*value)[i], reader, start, end))
+      return error;
+  }
+
+  return ErrorCode::NO_ERROR;
+}
+
+// Overload for std::array types.
+template <typename T, std::size_t Size>
+inline ErrorType DeserializeObject(std::array<T, Size>* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeArrayType(&encoding, &size, reader, start, end)) {
+    return error;
+  }
+
+  if (size != Size)
+    return ErrorCode::INSUFFICIENT_DESTINATION_SIZE;
+
+  for (std::size_t i = 0; i < size; i++) {
+    if (const auto error = DeserializeObject(&(*value)[i], reader, start, end))
+      return error;
+  }
+
+  return ErrorCode::NO_ERROR;
+}
+
+// Deserializes std::pair types.
+template <typename T, typename U>
+inline ErrorType DeserializeObject(std::pair<T, U>* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeArrayType(&encoding, &size, reader, start, end)) {
+    return error;
+  } else if (size != 2) {
+    return ErrorCode::UNEXPECTED_TYPE_SIZE;
+  } else if (const auto error =
+                 DeserializeObject(&value->first, reader, start, end)) {
+    return error;
+  } else if (const auto error =
+                 DeserializeObject(&value->second, reader, start, end)) {
+    return error;
+  } else {
+    return ErrorCode::NO_ERROR;
+  }
+}
+
+// Stops template recursion when the last tuple element is reached.
+template <typename... T>
+inline ErrorType DeserializeTuple(std::tuple<T...>*, MessageReader*,
+                                  const void*&, const void*, Index<0>) {
+  return ErrorCode::NO_ERROR;
+}
+
+// Deserializes each element of a tuple recursively.
+template <typename... T, std::size_t index>
+inline ErrorType DeserializeTuple(std::tuple<T...>* tuple,
+                                  MessageReader* reader, const void*& start,
+                                  const void*& end, Index<index>) {
+  if (const auto error =
+          DeserializeTuple(tuple, reader, start, end, Index<index - 1>()))
+    return error;
+  else
+    return DeserializeObject(&std::get<index - 1>(*tuple), reader, start, end);
+}
+
+// Overload for standard tuple types.
+template <typename... T>
+inline ErrorType DeserializeObject(std::tuple<T...>* value,
+                                   MessageReader* reader, const void*& start,
+                                   const void*& end) {
+  EncodingType encoding;
+  std::size_t size;
+
+  if (const auto error =
+          DeserializeArrayType(&encoding, &size, reader, start, end)) {
+    return error;
+  } else if (size != sizeof...(T)) {
+    return ErrorCode::UNEXPECTED_TYPE_SIZE;
+  } else {
+    return DeserializeTuple(value, reader, start, end, Index<sizeof...(T)>());
+  }
+}
+
+// Stops template recursion when the last member of a Serializable type is
+// reached.
+template <typename Members, typename T>
+inline ErrorType DeserializeMember(T*, MessageReader*, const void*&,
+                                   const void*, Index<0>) {
+  return ErrorCode::NO_ERROR;
+}
+
+// Deserializes each member of a Serializable type recursively.
+template <typename Members, typename T, std::size_t index>
+inline ErrorType DeserializeMember(T* value, MessageReader* reader,
+                                   const void*& start, const void*& end,
+                                   Index<index>) {
+  if (const auto error = DeserializeMember<Members>(value, reader, start, end,
+                                                    Index<index - 1>()))
+    return error;
+  else
+    return DeserializeObject(&Members::template At<index - 1>::Resolve(*value),
+                             reader, start, end);
+}
+
+// Deserializes the members of a Serializable type using the given
+// SerializableMembersType type.
+template <typename Members, typename T>
+inline ErrorType DeserializeMembers(T* value, MessageReader* reader,
+                                    const void*& start, const void*& end) {
+  return DeserializeMember<Members>(value, reader, start, end,
+                                    Index<Members::MemberCount>());
+}
+
+// Top level deserialization function.
+template <typename T>
+inline ErrorType Deserialize(T* value, MessageReader* reader) {
+  PDX_TRACE_NAME("Deserialize");
+  MessageReader::BufferSection section = reader->GetNextReadBufferSection();
+  if (section.first == section.second)
+    return ErrorCode::INSUFFICIENT_BUFFER;
+  ErrorType error =
+      DeserializeObject(value, reader, section.first, section.second);
+  reader->ConsumeReadBufferSectionData(section.first);
+  return error;
+}
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_SERIALIZATION_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/string_wrapper.h b/libs/vr/libpdx/private/pdx/rpc/string_wrapper.h
new file mode 100644
index 0000000..19fc4c1
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/string_wrapper.h
@@ -0,0 +1,129 @@
+#ifndef ANDROID_PDX_RPC_STRING_WRAPPER_H_
+#define ANDROID_PDX_RPC_STRING_WRAPPER_H_
+
+#include <cstddef>
+#include <cstring>
+#include <string>
+#include <type_traits>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Wrapper class for C string buffers, providing an interface suitable for
+// SerializeObject and DeserializeObject. This class serializes to the same
+// format as std::basic_string, and may be substituted for std::basic_string
+// during serialization and deserialization. This substitution makes handling of
+// C strings more efficient by avoiding unnecessary copies when remote method
+// signatures specify std::basic_string arguments or return values.
+template <typename CharT = std::string::value_type,
+          typename Traits = std::char_traits<CharT>>
+class StringWrapper {
+ public:
+  // Define types in the style of STL strings to support STL operators.
+  typedef Traits traits_type;
+  typedef typename Traits::char_type value_type;
+  typedef std::size_t size_type;
+  typedef value_type& reference;
+  typedef const value_type& const_reference;
+  typedef value_type* pointer;
+  typedef const value_type* const_pointer;
+
+  StringWrapper() : buffer_(nullptr), capacity_(0), end_(0) {}
+
+  StringWrapper(pointer buffer, size_type capacity, size_type size)
+      : buffer_(&buffer[0]),
+        capacity_(capacity),
+        end_(capacity < size ? capacity : size) {}
+
+  StringWrapper(pointer buffer, size_type size)
+      : StringWrapper(buffer, size, size) {}
+
+  explicit StringWrapper(pointer buffer)
+      : StringWrapper(buffer, std::strlen(buffer)) {}
+
+  StringWrapper(const StringWrapper& other) { *this = other; }
+
+  StringWrapper(StringWrapper&& other) { *this = std::move(other); }
+
+  StringWrapper& operator=(const StringWrapper& other) {
+    if (&other == this) {
+      return *this;
+    } else {
+      buffer_ = other.buffer_;
+      capacity_ = other.capacity_;
+      end_ = other.end_;
+    }
+
+    return *this;
+  }
+
+  StringWrapper& operator=(StringWrapper&& other) {
+    if (&other == this) {
+      return *this;
+    } else {
+      buffer_ = other.buffer_;
+      capacity_ = other.capacity_;
+      end_ = other.end_;
+      other.buffer_ = nullptr;
+      other.capacity_ = 0;
+      other.end_ = 0;
+    }
+
+    return *this;
+  }
+
+  pointer data() { return buffer_; }
+  const_pointer data() const { return buffer_; }
+
+  pointer begin() { return &buffer_[0]; }
+  pointer end() { return &buffer_[end_]; }
+  const_pointer begin() const { return &buffer_[0]; }
+  const_pointer end() const { return &buffer_[end_]; }
+
+  size_type size() const { return end_; }
+  size_type length() const { return end_; }
+  size_type max_size() const { return capacity_; }
+  size_type capacity() const { return capacity_; }
+
+  void resize(size_type size) {
+    if (size <= capacity_)
+      end_ = size;
+    else
+      end_ = capacity_;
+  }
+
+  reference operator[](size_type pos) { return buffer_[pos]; }
+  const_reference operator[](size_type pos) const { return buffer_[pos]; }
+
+ private:
+  pointer buffer_;
+  size_type capacity_;
+  size_type end_;
+};
+
+// Utility functions that infer the underlying type of the string, simplifying
+// the wrapper interface.
+
+// TODO(eieio): Wrapping std::basic_string is here for completeness, but is it
+// useful?
+template <typename T, typename... Any>
+StringWrapper<const T> WrapString(const std::basic_string<T, Any...>& s) {
+  return StringWrapper<const T>(s.c_str(), s.length());
+}
+
+template <typename T, typename SizeType = std::size_t>
+StringWrapper<T> WrapString(T* s, SizeType size) {
+  return StringWrapper<T>(s, size);
+}
+
+template <typename T>
+StringWrapper<T> WrapString(T* s) {
+  return StringWrapper<T>(s);
+}
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_STRING_WRAPPER_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/thread_local_buffer.h b/libs/vr/libpdx/private/pdx/rpc/thread_local_buffer.h
new file mode 100644
index 0000000..e5ef2aa
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/thread_local_buffer.h
@@ -0,0 +1,134 @@
+#ifndef ANDROID_PDX_RPC_THREAD_LOCAL_BUFFER_H_
+#define ANDROID_PDX_RPC_THREAD_LOCAL_BUFFER_H_
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include <pdx/rpc/default_initialization_allocator.h>
+#include <pdx/trace.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Utility class to distinguish between different thread local entries or
+// "slots" in the thread local variable table. Each slot is uniquely identified
+// by (T,Index) and is independent of any other slot.
+template <typename T, std::size_t Index>
+struct ThreadLocalSlot;
+
+// Utility class to specify thread local slots using only a type.
+template <typename T>
+struct ThreadLocalTypeSlot;
+
+// Utility class to specify thread local slots using only an index.
+template <std::size_t Index>
+struct ThreadLocalIndexSlot;
+
+// Initial capacity of thread local buffer, unless otherwise specified.
+constexpr std::size_t InitialBufferCapacity = 4096;
+
+// Thread local slots for buffers used by this library to send, receive, and
+// reply to messages.
+using SendBuffer = ThreadLocalIndexSlot<0>;
+using ReceiveBuffer = ThreadLocalIndexSlot<1>;
+using ReplyBuffer = ThreadLocalIndexSlot<2>;
+
+// Provides a simple interface to thread local buffers for large IPC messages.
+// Slot provides multiple thread local slots for a given T, Allocator, Capacity
+// combination.
+template <typename T, typename Allocator = DefaultInitializationAllocator<T>,
+          std::size_t Capacity = InitialBufferCapacity,
+          typename Slot = ThreadLocalSlot<void, 0>>
+class ThreadLocalBuffer {
+ public:
+  using BufferType = std::vector<T, Allocator>;
+  using ValueType = T;
+
+  // Reserves |capacity| number of elements of capacity in the underlying
+  // buffer. Call this during startup to avoid allocation during use.
+  static void Reserve(std::size_t capacity) {
+    PDX_TRACE_NAME("ThreadLocalBuffer::Reserve");
+    InitializeBuffer(capacity);
+    buffer_->reserve(capacity);
+  }
+
+  // Resizes the buffer to |size| elements.
+  static void Resize(std::size_t size) {
+    PDX_TRACE_NAME("ThreadLocalBuffer::Resize");
+    InitializeBuffer(size);
+    buffer_->resize(size);
+  }
+
+  // Gets a reference to the underlying buffer after reserving |capacity|
+  // elements. The current size of the buffer is left intact. The returned
+  // reference is valid until FreeBuffer() is called.
+  static BufferType& GetBuffer(std::size_t capacity = Capacity) {
+    PDX_TRACE_NAME("ThreadLocalBuffer::GetBuffer");
+    Reserve(capacity);
+    return *buffer_;
+  }
+
+  // Gets a reference to the underlying buffer after reserving |Capacity|
+  // elements. The current size of the buffer is set to zero. The returned
+  // reference is valid until FreeBuffer() is called.
+  static BufferType& GetEmptyBuffer() {
+    PDX_TRACE_NAME("ThreadLocalBuffer::GetEmptyBuffer");
+    Reserve(Capacity);
+    buffer_->clear();
+    return *buffer_;
+  }
+
+  // Gets a reference to the underlying buffer after resizing it to |size|
+  // elements. The returned reference is valid until FreeBuffer() is called.
+  static BufferType& GetSizedBuffer(std::size_t size = Capacity) {
+    PDX_TRACE_NAME("ThreadLocalBuffer::GetSizedBuffer");
+    Resize(size);
+    return *buffer_;
+  }
+
+  // Frees the underlying buffer. The buffer will be reallocated if any of the
+  // methods above are called.
+  static void FreeBuffer() {
+    if (buffer_) {
+      GetBufferGuard().reset(buffer_ = nullptr);
+    }
+  }
+
+ private:
+  friend class ThreadLocalBufferTest;
+
+  static void InitializeBuffer(std::size_t capacity) {
+    if (!buffer_) {
+      GetBufferGuard().reset(buffer_ = new BufferType(capacity));
+    }
+  }
+
+  // Work around performance issues with thread-local dynamic initialization
+  // semantics by using a normal pointer in parallel with a std::unique_ptr. The
+  // std::unique_ptr is never dereferenced, only assigned, to avoid the high
+  // cost of dynamic initialization checks, while still providing automatic
+  // cleanup. The normal pointer provides fast access to the buffer object.
+  // Never dereference buffer_guard or performance could be severely impacted
+  // by slow implementations of TLS dynamic initialization.
+  static thread_local BufferType* buffer_;
+
+  static std::unique_ptr<BufferType>& GetBufferGuard() {
+    PDX_TRACE_NAME("ThreadLocalBuffer::GetBufferGuard");
+    static thread_local std::unique_ptr<BufferType> buffer_guard;
+    return buffer_guard;
+  }
+};
+
+// Instantiation of the static ThreadLocalBuffer::buffer_ member.
+template <typename T, typename Allocator, std::size_t Capacity, typename Slot>
+thread_local
+    typename ThreadLocalBuffer<T, Allocator, Capacity, Slot>::BufferType*
+        ThreadLocalBuffer<T, Allocator, Capacity, Slot>::buffer_;
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_RPC_THREAD_LOCAL_BUFFER_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/type_operators.h b/libs/vr/libpdx/private/pdx/rpc/type_operators.h
new file mode 100644
index 0000000..811bd87
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/type_operators.h
@@ -0,0 +1,195 @@
+#ifndef ANDROID_PDX_RPC_TYPE_OPERATORS_H_
+#define ANDROID_PDX_RPC_TYPE_OPERATORS_H_
+
+#include <array>
+#include <map>
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+#include <pdx/rpc/array_wrapper.h>
+#include <pdx/rpc/buffer_wrapper.h>
+#include <pdx/rpc/copy_cv_reference.h>
+#include <pdx/rpc/pointer_wrapper.h>
+#include <pdx/rpc/string_wrapper.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Simplifies type expressions.
+template <typename T>
+using Decay = typename std::decay<T>::type;
+
+// Compares the underlying type of A and B.
+template <typename A, typename B>
+using IsEquivalent = typename std::is_same<Decay<A>, Decay<B>>::type;
+
+// Logical AND over template parameter pack.
+template <typename... T>
+struct And : std::false_type {};
+template <typename A, typename B>
+struct And<A, B> : std::integral_constant<bool, A::value && B::value> {};
+template <typename A, typename B, typename... Rest>
+struct And<A, B, Rest...> : And<A, And<B, Rest...>> {};
+
+// Determines whether A is convertible to B (serializes to the same format)
+// using these rules:
+//    1. std:vector<T, Any...> is convertible to ArrayWrapper<T>.
+//    2. ArrayWrapper<T> is convertible to std:vector<T, Any...>.
+//    3. std::basic_string<T, Any...> is convertible to StringWrapper<T>.
+//    4. StringWrapper<T> is convertible to std::basic_string<T, Any...>.
+//    5. BufferWrapper<T*> is convertible to BufferWrapper<std::vector<T,
+//    Any...>>.
+//    6. BufferWrapper<std::vector<T, ...>> is convertible to BufferWrapper<T*>.
+//    7. The value type T of A and B must match.
+
+// Compares A and B for convertibility. This base type determines convertibility
+// by equivalence of the underlying types of A and B. Specializations of this
+// type handle the rules for which complex types are convertible.
+template <typename A, typename B>
+struct IsConvertible : IsEquivalent<A, B> {};
+
+// Compares TT<A, ...> and TT<B, ...>; these are convertible if A and B are
+// convertible.
+template <template <typename, typename...> class TT, typename A, typename B,
+          typename... AnyA, typename... AnyB>
+struct IsConvertible<TT<A, AnyA...>, TT<B, AnyB...>>
+    : IsConvertible<Decay<A>, Decay<B>> {};
+
+// Compares TT<KeyA, ValueA, ...> and TT<KeyB, ValueB, ...>; these are
+// convertible if KeyA and KeyB are
+// convertible and ValueA and ValueB are convertible.
+template <template <typename, typename, typename...> class TT, typename KeyA,
+          typename ValueA, typename KeyB, typename ValueB, typename... AnyA,
+          typename... AnyB>
+struct IsConvertible<TT<KeyA, ValueA, AnyA...>, TT<KeyB, ValueB, AnyB...>>
+    : And<IsConvertible<Decay<KeyA>, Decay<KeyB>>,
+          IsConvertible<Decay<ValueA>, Decay<ValueB>>> {};
+
+// Compares two std::pairs to see if the corresponding elements are convertible.
+template <typename A, typename B, typename C, typename D>
+struct IsConvertible<std::pair<A, B>, std::pair<C, D>>
+    : And<IsConvertible<Decay<A>, Decay<C>>,
+          IsConvertible<Decay<B>, Decay<D>>> {};
+
+// Compares std::pair with a two-element std::tuple to see if the corresponding
+// elements are convertible.
+template <typename A, typename B, typename C, typename D>
+struct IsConvertible<std::pair<A, B>, std::tuple<C, D>>
+    : And<IsConvertible<Decay<A>, Decay<C>>,
+          IsConvertible<Decay<B>, Decay<D>>> {};
+template <typename A, typename B, typename C, typename D>
+struct IsConvertible<std::tuple<A, B>, std::pair<C, D>>
+    : And<IsConvertible<Decay<A>, Decay<C>>,
+          IsConvertible<Decay<B>, Decay<D>>> {};
+
+// Compares two std::tuples to see if the corresponding elements are
+// convertible.
+template <typename... A, typename... B>
+struct IsConvertible<std::tuple<A...>, std::tuple<B...>>
+    : And<IsConvertible<Decay<A>, Decay<B>>...> {};
+
+// Compares std::vector, std::array, and ArrayWrapper; these are convertible if
+// the value types are convertible.
+template <typename A, typename B, typename... Any>
+struct IsConvertible<std::vector<A, Any...>, ArrayWrapper<B>>
+    : IsConvertible<Decay<A>, Decay<B>> {};
+template <typename A, typename B, typename... Any>
+struct IsConvertible<ArrayWrapper<A>, std::vector<B, Any...>>
+    : IsConvertible<Decay<A>, Decay<B>> {};
+template <typename A, typename B, typename... Any, std::size_t Size>
+struct IsConvertible<std::vector<A, Any...>, std::array<B, Size>>
+    : IsConvertible<Decay<A>, Decay<B>> {};
+template <typename A, typename B, typename... Any, std::size_t Size>
+struct IsConvertible<std::array<A, Size>, std::vector<B, Any...>>
+    : IsConvertible<Decay<A>, Decay<B>> {};
+template <typename A, typename B, std::size_t Size>
+struct IsConvertible<ArrayWrapper<A>, std::array<B, Size>>
+    : IsConvertible<Decay<A>, Decay<B>> {};
+template <typename A, typename B, std::size_t Size>
+struct IsConvertible<std::array<A, Size>, ArrayWrapper<B>>
+    : IsConvertible<Decay<A>, Decay<B>> {};
+
+// Compares std::map and std::unordered_map; these are convertible if the keys
+// are convertible and the values are convertible.
+template <typename KeyA, typename ValueA, typename KeyB, typename ValueB,
+          typename... AnyA, typename... AnyB>
+struct IsConvertible<std::map<KeyA, ValueA, AnyA...>,
+                     std::unordered_map<KeyB, ValueB, AnyB...>>
+    : And<IsConvertible<Decay<KeyA>, Decay<KeyB>>,
+          IsConvertible<Decay<ValueA>, Decay<ValueB>>> {};
+template <typename KeyA, typename ValueA, typename KeyB, typename ValueB,
+          typename... AnyA, typename... AnyB>
+struct IsConvertible<std::unordered_map<KeyA, ValueA, AnyA...>,
+                     std::map<KeyB, ValueB, AnyB...>>
+    : And<IsConvertible<Decay<KeyA>, Decay<KeyB>>,
+          IsConvertible<Decay<ValueA>, Decay<ValueB>>> {};
+
+// Compares BufferWrapper<A*> and BufferWrapper<std::vector<B>>; these are
+// convertible if A and B are equivalent. Allocator types are not relevant to
+// convertibility.
+template <typename A, typename B, typename Allocator>
+struct IsConvertible<BufferWrapper<A*>,
+                     BufferWrapper<std::vector<B, Allocator>>>
+    : IsEquivalent<A, B> {};
+template <typename A, typename B, typename Allocator>
+struct IsConvertible<BufferWrapper<std::vector<A, Allocator>>,
+                     BufferWrapper<B*>> : IsEquivalent<A, B> {};
+template <typename A, typename B, typename AllocatorA, typename AllocatorB>
+struct IsConvertible<BufferWrapper<std::vector<A, AllocatorA>>,
+                     BufferWrapper<std::vector<B, AllocatorB>>>
+    : IsEquivalent<A, B> {};
+template <typename A, typename B>
+struct IsConvertible<BufferWrapper<A*>, BufferWrapper<B*>>
+    : IsEquivalent<A, B> {};
+
+// Compares std::basic_string<A, ...> and StringWrapper<B>; these are
+// convertible if A and B are equivalent.
+template <typename A, typename B, typename... Any>
+struct IsConvertible<std::basic_string<A, Any...>, StringWrapper<B>>
+    : IsEquivalent<A, B> {};
+template <typename A, typename B, typename... Any>
+struct IsConvertible<StringWrapper<A>, std::basic_string<B, Any...>>
+    : IsEquivalent<A, B> {};
+
+// Compares PointerWrapper<A> and B; these are convertible if A and B are
+// convertible.
+template <typename A, typename B>
+struct IsConvertible<PointerWrapper<A>, B> : IsConvertible<Decay<A>, Decay<B>> {
+};
+template <typename A, typename B>
+struct IsConvertible<A, PointerWrapper<B>> : IsConvertible<Decay<A>, Decay<B>> {
+};
+
+// LocalHandle is convertible to RemoteHandle on the service side. This means
+// that a RemoteHandle may be supplied by a service when the protocol calls for
+// a LocalHandle return value. The other way around is not safe and can leak
+// file descriptors. The ServicePayload class enforces this policy by only
+// supporting RemoteHandle for pushed handles.
+template <>
+struct IsConvertible<LocalHandle, RemoteHandle> : std::true_type {};
+template <>
+struct IsConvertible<LocalHandle, BorrowedHandle> : std::true_type {};
+
+template <>
+struct IsConvertible<LocalChannelHandle, RemoteChannelHandle> : std::true_type {
+};
+template <>
+struct IsConvertible<LocalChannelHandle, BorrowedChannelHandle>
+    : std::true_type {};
+
+// Conditionally "rewrites" type A as type B, including cv-reference qualifiers,
+// iff A is convertible to B.
+template <typename A, typename B>
+using ConditionalRewrite =
+    typename std::conditional<IsConvertible<Decay<A>, Decay<B>>::value,
+                              CopyCVReferenceType<A, B>, A>::type;
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+#endif  //  ANDROID_PDX_RPC_TYPE_OPERATORS_H_
diff --git a/libs/vr/libpdx/private/pdx/rpc/variant.h b/libs/vr/libpdx/private/pdx/rpc/variant.h
new file mode 100644
index 0000000..09789e5
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/rpc/variant.h
@@ -0,0 +1,693 @@
+#ifndef ANDROID_PDX_RPC_VARIANT_H_
+#define ANDROID_PDX_RPC_VARIANT_H_
+
+#include <cstdint>
+#include <tuple>
+#include <type_traits>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+// Type tag denoting an empty variant.
+struct EmptyVariant {};
+
+namespace detail {
+
+// Type for matching tagged overloads.
+template <typename T>
+struct TypeTag {};
+
+// Determines the type of the I-th element of Types....
+template <std::size_t I, typename... Types>
+using TypeForIndex = std::tuple_element_t<I, std::tuple<Types...>>;
+
+// Determines the type tag for the I-th element of Types....
+template <std::size_t I, typename... Types>
+using TypeTagForIndex = TypeTag<TypeForIndex<I, Types...>>;
+
+// Enable if T(Args...) is well formed.
+template <typename R, typename T, typename... Args>
+using EnableIfConstructible =
+    typename std::enable_if<std::is_constructible<T, Args...>::value, R>::type;
+// Enable if T(Args...) is not well formed.
+template <typename R, typename T, typename... Args>
+using EnableIfNotConstructible =
+    typename std::enable_if<!std::is_constructible<T, Args...>::value, R>::type;
+
+// Determines whether T is an element of Types...;
+template <typename... Types>
+struct HasType : std::false_type {};
+template <typename T, typename U>
+struct HasType<T, U> : std::is_same<T, U> {};
+template <typename T, typename First, typename... Rest>
+struct HasType<T, First, Rest...>
+    : std::integral_constant<
+          bool, std::is_same<T, First>::value || HasType<T, Rest...>::value> {};
+
+template <typename T, typename... Types>
+using HasTypeIgnoreRef =
+    HasType<typename std::remove_reference<T>::type, Types...>;
+
+// Defines set operations on a set of Types...
+template <typename... Types>
+struct Set {
+  // Default specialization catches the empty set, which is always a subset.
+  template <typename...>
+  struct IsSubset : std::true_type {};
+  template <typename T>
+  struct IsSubset<T> : HasType<T, Types...> {};
+  template <typename First, typename... Rest>
+  struct IsSubset<First, Rest...>
+      : std::integral_constant<
+            bool, IsSubset<First>::value && IsSubset<Rest...>::value> {};
+};
+
+// Determines the number of elements of Types... that are constructible from
+// From.
+template <typename... Types>
+struct ConstructibleCount;
+template <typename From, typename To>
+struct ConstructibleCount<From, To>
+    : std::integral_constant<std::size_t,
+                             std::is_constructible<To, From>::value> {};
+template <typename From, typename First, typename... Rest>
+struct ConstructibleCount<From, First, Rest...>
+    : std::integral_constant<std::size_t,
+                             std::is_constructible<First, From>::value +
+                                 ConstructibleCount<From, Rest...>::value> {};
+
+// Enable if T is an element of Types...
+template <typename R, typename T, typename... Types>
+using EnableIfElement =
+    typename std::enable_if<HasTypeIgnoreRef<T, Types...>::value, R>::type;
+// Enable if T is not an element of Types...
+template <typename R, typename T, typename... Types>
+using EnableIfNotElement =
+    typename std::enable_if<!HasTypeIgnoreRef<T, Types...>::value, R>::type;
+
+// Enable if T is convertible to an element of Types... T is considered
+// convertible IIF a single element of Types... is assignable from T and T is
+// not a direct element of Types...
+template <typename R, typename T, typename... Types>
+using EnableIfConvertible =
+    typename std::enable_if<!HasTypeIgnoreRef<T, Types...>::value &&
+                                ConstructibleCount<T, Types...>::value == 1,
+                            R>::type;
+
+// Enable if T is assignable to an element of Types... T is considered
+// assignable IFF a single element of Types... is constructible from T or T is a
+// direct element of Types.... Note that T is REQUIRED to be an element of
+// Types... when multiple elements are constructible from T to prevent ambiguity
+// in conversion.
+template <typename R, typename T, typename... Types>
+using EnableIfAssignable =
+    typename std::enable_if<HasTypeIgnoreRef<T, Types...>::value ||
+                                ConstructibleCount<T, Types...>::value == 1,
+                            R>::type;
+
+// Selects a type for SFINAE constructor selection.
+template <bool CondA, typename SelectA, typename SelectB>
+using Select = std::conditional_t<CondA, SelectA, SelectB>;
+
+// Recursive union type.
+template <typename... Types>
+union Union;
+
+// Specialization handling a singular type, terminating template recursion.
+template <typename Type>
+union Union<Type> {
+  Union() {}
+  ~Union() {}
+
+  template <typename T>
+  Union(std::int32_t index, std::int32_t* index_out, TypeTag<Type>, T&& value)
+      : first_(std::forward<T>(value)) {
+    *index_out = index;
+  }
+  template <typename T, typename = EnableIfAssignable<void, T, Type>>
+  Union(std::int32_t index, std::int32_t* index_out, T&& value)
+      : first_(std::forward<T>(value)) {
+    *index_out = index;
+  }
+
+  Type& get(TypeTag<Type>) { return first_; }
+  const Type& get(TypeTag<Type>) const { return first_; }
+  EmptyVariant get(TypeTag<EmptyVariant>) const { return {}; }
+  constexpr std::int32_t index(TypeTag<Type>) const { return 0; }
+
+  template <typename... Args>
+  std::int32_t Construct(TypeTag<Type>, Args&&... args) {
+    new (&first_) Type(std::forward<Args>(args)...);
+    return 0;
+  }
+  template <typename... Args>
+  EnableIfConstructible<std::int32_t, Type, Args...> Construct(Args&&... args) {
+    new (&first_) Type(std::forward<Args>(args)...);
+    return 0;
+  }
+
+  void Destruct(std::int32_t target_index) {
+    if (target_index == index(TypeTag<Type>{})) {
+      (&get(TypeTag<Type>{}))->~Type();
+    }
+  }
+
+  template <typename T>
+  bool Assign(TypeTag<Type>, std::int32_t target_index, T&& value) {
+    if (target_index == 0) {
+      first_ = std::forward<T>(value);
+      return true;
+    } else {
+      return false;
+    }
+  }
+  template <typename T>
+  EnableIfConstructible<bool, Type, T> Assign(std::int32_t target_index,
+                                              T&& value) {
+    if (target_index == 0) {
+      first_ = std::forward<T>(value);
+      return true;
+    } else {
+      return false;
+    }
+  }
+  template <typename T>
+  EnableIfNotConstructible<bool, Type, T> Assign(std::int32_t /*target_index*/,
+                                                 T&& /*value*/) {
+    return false;
+  }
+
+  template <typename Op>
+  decltype(auto) Visit(std::int32_t target_index, Op&& op) {
+    if (target_index == index(TypeTag<Type>{}))
+      return std::forward<Op>(op)(get(TypeTag<Type>{}));
+    else
+      return std::forward<Op>(op)(get(TypeTag<EmptyVariant>{}));
+  }
+  template <typename Op>
+  decltype(auto) Visit(std::int32_t target_index, Op&& op) const {
+    if (target_index == index(TypeTag<Type>{}))
+      return std::forward<Op>(op)(get(TypeTag<Type>{}));
+    else
+      return std::forward<Op>(op)(get(TypeTag<EmptyVariant>{}));
+  }
+
+  template <typename... Args>
+  bool Become(std::int32_t target_index, Args&&... args) {
+    if (target_index == index(TypeTag<Type>{})) {
+      Construct(TypeTag<Type>{}, std::forward<Args>(args)...);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+ private:
+  Type first_;
+};
+
+// Specialization that recursively unions types from the paramater pack.
+template <typename First, typename... Rest>
+union Union<First, Rest...> {
+  Union() {}
+  ~Union() {}
+
+  template <typename T>
+  Union(std::int32_t index, std::int32_t* index_out, TypeTag<First>, T&& value)
+      : first_(std::forward<T>(value)) {
+    *index_out = index;
+  }
+  template <typename T, typename U>
+  Union(std::int32_t index, std::int32_t* index_out, TypeTag<T>, U&& value)
+      : rest_(index + 1, index_out, TypeTag<T>{}, std::forward<U>(value)) {}
+
+  struct FirstType {};
+  struct RestType {};
+  template <typename T>
+  using SelectConstructor =
+      Select<ConstructibleCount<T, First>::value == 1, FirstType, RestType>;
+
+  template <typename T>
+  Union(std::int32_t index, std::int32_t* index_out, T&& value)
+      : Union(index, index_out, std::forward<T>(value),
+              SelectConstructor<T>{}) {}
+
+  template <typename T>
+  Union(std::int32_t index, std::int32_t* index_out, T&& value, FirstType)
+      : first_(std::forward<T>(value)) {
+    *index_out = index;
+  }
+  template <typename T>
+  Union(std::int32_t index, std::int32_t* index_out, T&& value, RestType)
+      : rest_(index + 1, index_out, std::forward<T>(value)) {}
+
+  First& get(TypeTag<First>) { return first_; }
+  const First& get(TypeTag<First>) const { return first_; }
+  constexpr std::int32_t index(TypeTag<First>) const { return 0; }
+
+  template <typename T>
+  T& get(TypeTag<T>) {
+    return rest_.template get(TypeTag<T>{});
+  }
+  template <typename T>
+  const T& get(TypeTag<T>) const {
+    return rest_.template get(TypeTag<T>{});
+  }
+  template <typename T>
+  constexpr std::int32_t index(TypeTag<T>) const {
+    return 1 + rest_.template index(TypeTag<T>{});
+  }
+
+  template <typename... Args>
+  std::int32_t Construct(TypeTag<First>, Args&&... args) {
+    new (&first_) First(std::forward<Args>(args)...);
+    return 0;
+  }
+  template <typename T, typename... Args>
+  std::int32_t Construct(TypeTag<T>, Args&&... args) {
+    return 1 +
+           rest_.template Construct(TypeTag<T>{}, std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  EnableIfConstructible<std::int32_t, First, Args...> Construct(
+      Args&&... args) {
+    new (&first_) First(std::forward<Args>(args)...);
+    return 0;
+  }
+  template <typename... Args>
+  EnableIfNotConstructible<std::int32_t, First, Args...> Construct(
+      Args&&... args) {
+    return 1 + rest_.template Construct(std::forward<Args>(args)...);
+  }
+
+  void Destruct(std::int32_t target_index) {
+    if (target_index == index(TypeTag<First>{})) {
+      (get(TypeTag<First>{})).~First();
+    } else {
+      rest_.Destruct(target_index - 1);
+    }
+  }
+
+  template <typename T>
+  bool Assign(TypeTag<First>, std::int32_t target_index, T&& value) {
+    if (target_index == 0) {
+      first_ = std::forward<T>(value);
+      return true;
+    } else {
+      return false;
+    }
+  }
+  template <typename T, typename U>
+  bool Assign(TypeTag<T>, std::int32_t target_index, U&& value) {
+    return rest_.Assign(TypeTag<T>{}, target_index - 1, std::forward<U>(value));
+  }
+  template <typename T>
+  EnableIfConstructible<bool, First, T> Assign(std::int32_t target_index,
+                                               T&& value) {
+    if (target_index == 0) {
+      first_ = std::forward<T>(value);
+      return true;
+    } else {
+      return rest_.Assign(target_index - 1, std::forward<T>(value));
+    }
+  }
+  template <typename T>
+  EnableIfNotConstructible<bool, First, T> Assign(std::int32_t target_index,
+                                                  T&& value) {
+    return rest_.Assign(target_index - 1, std::forward<T>(value));
+  }
+
+  // Recursively traverses the union and calls Op on the active value when the
+  // active type is found. If the union is empty Op is called on EmptyVariant.
+  // TODO(eieio): This could be refactored into an array or jump table. It's
+  // unclear whether this would be more efficient for practical variant arity.
+  template <typename Op>
+  decltype(auto) Visit(std::int32_t target_index, Op&& op) {
+    if (target_index == index(TypeTag<First>{}))
+      return std::forward<Op>(op)(get(TypeTag<First>{}));
+    else
+      return rest_.Visit(target_index - 1, std::forward<Op>(op));
+  }
+  template <typename Op>
+  decltype(auto) Visit(std::int32_t target_index, Op&& op) const {
+    if (target_index == index(TypeTag<First>{}))
+      return std::forward<Op>(op)(get(TypeTag<First>{}));
+    else
+      return rest_.Visit(target_index - 1, std::forward<Op>(op));
+  }
+
+  template <typename... Args>
+  bool Become(std::int32_t target_index, Args&&... args) {
+    if (target_index == index(TypeTag<First>{})) {
+      Construct(TypeTag<First>{}, std::forward<Args>(args)...);
+      return true;
+    } else {
+      return rest_.Become(target_index - 1, std::forward<Args>(args)...);
+    }
+  }
+
+ private:
+  First first_;
+  Union<Rest...> rest_;
+};
+
+}  // namespace detail
+
+template <typename... Types>
+class Variant {
+ private:
+  // Convenience types.
+  template <typename T>
+  using TypeTag = detail::TypeTag<T>;
+  template <typename T>
+  using TypeTagIgnoreRef = TypeTag<typename std::remove_reference<T>::type>;
+  template <std::size_t I>
+  using TypeForIndex = detail::TypeForIndex<I, Types...>;
+  template <std::size_t I>
+  using TypeTagForIndex = detail::TypeTagForIndex<I, Types...>;
+  template <typename T>
+  using HasType = detail::HasType<T, Types...>;
+  template <typename T>
+  using HasTypeIgnoreRef = detail::HasTypeIgnoreRef<T, Types...>;
+  template <typename R, typename T>
+  using EnableIfElement = detail::EnableIfElement<R, T, Types...>;
+  template <typename R, typename T>
+  using EnableIfConvertible = detail::EnableIfConvertible<R, T, Types...>;
+  template <typename R, typename T>
+  using EnableIfAssignable = detail::EnableIfAssignable<R, T, Types...>;
+
+  struct Direct {};
+  struct Convert {};
+  template <typename T>
+  using SelectConstructor =
+      detail::Select<HasTypeIgnoreRef<T>::value, Direct, Convert>;
+
+  // Constructs by type tag when T is an direct element of Types...
+  template <typename T>
+  explicit Variant(T&& value, Direct)
+      : value_(0, &index_, TypeTagIgnoreRef<T>{}, std::forward<T>(value)) {}
+  // Conversion constructor when T is not a direct element of Types...
+  template <typename T>
+  explicit Variant(T&& value, Convert)
+      : value_(0, &index_, std::forward<T>(value)) {}
+
+ public:
+  // Variants are default construcible, regardless of whether the elements are
+  // default constructible. Default consruction yields an empty Variant.
+  Variant() {}
+  explicit Variant(EmptyVariant) {}
+  ~Variant() { Destruct(); }
+
+  // Copy and move construction from Variant types. Each element of OtherTypes
+  // must be convertible to an element of Types.
+  template <typename... OtherTypes>
+  explicit Variant(const Variant<OtherTypes...>& other) {
+    other.Visit([this](const auto& value) { Construct(value); });
+  }
+  template <typename... OtherTypes>
+  explicit Variant(Variant<OtherTypes...>&& other) {
+    other.Visit([this](auto&& value) { Construct(std::move(value)); });
+  }
+
+  // Construction from non-Variant types.
+  template <typename T, typename = EnableIfAssignable<void, T>>
+  explicit Variant(T&& value)
+      : Variant(std::forward<T>(value), SelectConstructor<T>{}) {}
+
+  // Performs assignment from type T belonging to Types. This overload takes
+  // priority to prevent implicit conversion in cases where T is implicitly
+  // convertible to multiple elements of Types.
+  template <typename T>
+  EnableIfElement<Variant&, T> operator=(T&& value) {
+    Assign(TypeTagIgnoreRef<T>{}, std::forward<T>(value));
+    return *this;
+  }
+
+  // Performs assignment from type T not belonging to Types. This overload
+  // matches in cases where conversion is the only viable option.
+  template <typename T>
+  EnableIfConvertible<Variant&, T> operator=(T&& value) {
+    Assign(std::forward<T>(value));
+    return *this;
+  }
+
+  // Handles assignment from the empty type. This overload supports assignment
+  // in visitors using generic lambdas.
+  Variant& operator=(EmptyVariant) {
+    Assign(EmptyVariant{});
+    return *this;
+  }
+
+  // Assignment from Variant types. Each element of OtherTypes must be
+  // convertible to an element of Types. Forwards through non-Variant assignment
+  // operators to apply conversion checks.
+  template <typename... OtherTypes>
+  Variant& operator=(const Variant<OtherTypes...>& other) {
+    other.Visit([this](const auto& value) { *this = value; });
+    return *this;
+  }
+  template <typename... OtherTypes>
+  Variant& operator=(Variant<OtherTypes...>&& other) {
+    other.Visit([this](auto&& value) { *this = std::move(value); });
+    return *this;
+  }
+
+  // Becomes the target type, constructing a new element from the given
+  // arguments if necessary. No action is taken if the active element is already
+  // the target type. Otherwise the active element is destroyed and replaced by
+  // constructing an element of the new type using |Args|. An invalid target
+  // type index results in an empty Variant.
+  template <typename... Args>
+  void Become(std::int32_t target_index, Args&&... args) {
+    if (target_index != index()) {
+      Destruct();
+      index_ = value_.Become(target_index, std::forward<Args>(args)...)
+                   ? target_index
+                   : kEmptyIndex;
+    }
+  }
+
+  // Invokes |Op| on the active element. If the Variant is empty |Op| is invoked
+  // on EmptyVariant.
+  template <typename Op>
+  decltype(auto) Visit(Op&& op) {
+    return value_.Visit(index_, std::forward<Op>(op));
+  }
+  template <typename Op>
+  decltype(auto) Visit(Op&& op) const {
+    return value_.Visit(index_, std::forward<Op>(op));
+  }
+
+  // Index returned when the Variant is empty.
+  enum : std::int32_t { kEmptyIndex = -1 };
+
+  // Returns the index of the given type.
+  template <typename T>
+  constexpr std::int32_t index_of() const {
+    static_assert(HasType<T>::value, "T is not an element type of Variant.");
+    return value_.template index(TypeTag<T>{});
+  }
+
+  // Returns the index of the active type. If the Variant is empty -1 is
+  // returned.
+  std::int32_t index() const { return index_; }
+
+  // Returns true if the given type is active, false otherwise.
+  template <typename T>
+  bool is() const {
+    static_assert(HasType<T>::value, "T is not an element type of Variant.");
+    return index() == index_of<T>();
+  }
+
+  // Returns true if the Variant is empty, false otherwise.
+  bool empty() const { return index() == kEmptyIndex; }
+
+  // Element accessors. Returns a pointer to the active value if the given
+  // type/index is active, otherwise nullptr is returned.
+  template <typename T>
+  T* get() {
+    if (is<T>())
+      return &value_.template get(TypeTag<T>{});
+    else
+      return nullptr;
+  }
+  template <typename T>
+  const T* get() const {
+    if (is<T>())
+      return &value_.template get(TypeTag<T>{});
+    else
+      return nullptr;
+  }
+  template <std::size_t I>
+  TypeForIndex<I>* get() {
+    if (is<TypeForIndex<I>>())
+      return &value_.template get(TypeTagForIndex<I>{});
+    else
+      return nullptr;
+  }
+  template <std::size_t I>
+  const TypeForIndex<I>* get() const {
+    if (is<TypeForIndex<I>>())
+      return &value_.template get(TypeTagForIndex<I>{});
+    else
+      return nullptr;
+  }
+
+ private:
+  std::int32_t index_ = kEmptyIndex;
+  detail::Union<Types...> value_;
+
+  // Constructs an element from the given arguments and sets the Variant to the
+  // resulting type.
+  template <typename... Args>
+  void Construct(Args&&... args) {
+    index_ = value_.template Construct(std::forward<Args>(args)...);
+  }
+  void Construct(EmptyVariant) {}
+
+  // Destroys the active element of the Variant.
+  void Destruct() { value_.Destruct(index_); }
+
+  // Assigns the Variant when non-empty and the current type matches the target
+  // type, otherwise destroys the current value and constructs a element of the
+  // new type. Tagged assignment is used when T is an element of the Variant to
+  // prevent implicit conversion in cases where T is implicitly convertible to
+  // multiple element types.
+  template <typename T, typename U>
+  void Assign(TypeTag<T>, U&& value) {
+    if (!value_.template Assign(TypeTag<T>{}, index_, std::forward<U>(value))) {
+      Destruct();
+      Construct(TypeTag<T>{}, std::forward<U>(value));
+    }
+  }
+  template <typename T>
+  void Assign(T&& value) {
+    if (!value_.template Assign(index_, std::forward<T>(value))) {
+      Destruct();
+      Construct(std::forward<T>(value));
+    }
+  }
+  // Handles assignment from an empty Variant.
+  void Assign(EmptyVariant) { Destruct(); }
+};
+
+// Utility type to extract/convert values from a variant. This class simplifies
+// conditional logic to get/move/swap/action values from a variant when one or
+// more elements are compatible with the destination type.
+//
+// Example:
+//    Variant<int, bool, std::string> v(10);
+//    bool bool_value;
+//    if (IfAnyOf<int, bool>::Get(v, &bool_value)) {
+//      DoSomething(bool_value);
+//    } else {
+//      HandleInvalidType();
+//    }
+//    IfAnyOf<int>::Call(v, [](const auto& value) { DoSomething(value); });
+//
+template <typename... ValidTypes>
+struct IfAnyOf {
+  // Calls Op on the underlying value of the variant and returns true when the
+  // variant is a valid type, otherwise does nothing and returns false.
+  template <typename Op, typename... Types>
+  static bool Call(Variant<Types...>* variant, Op&& op) {
+    static_assert(
+        detail::Set<Types...>::template IsSubset<ValidTypes...>::value,
+        "ValidTypes may only contain element types from the Variant.");
+    return variant->Visit(CallOp<Op>{std::forward<Op>(op)});
+  }
+  template <typename Op, typename... Types>
+  static bool Call(const Variant<Types...>* variant, Op&& op) {
+    static_assert(
+        detail::Set<Types...>::template IsSubset<ValidTypes...>::value,
+        "ValidTypes may only contain element types from the Variant.");
+    return variant->Visit(CallOp<Op>{std::forward<Op>(op)});
+  }
+
+  // Gets/converts the underlying value of the variant to type T and returns
+  // true when the variant is a valid type, otherwise does nothing and returns
+  // false.
+  template <typename T, typename... Types>
+  static bool Get(const Variant<Types...>* variant, T* value_out) {
+    return Call(variant,
+                [value_out](const auto& value) { *value_out = value; });
+  }
+
+  // Moves the underlying value of the variant and returns true when the variant
+  // is a valid type, otherwise does nothing and returns false.
+  template <typename T, typename... Types>
+  static bool Take(Variant<Types...>* variant, T* value_out) {
+    return Call(variant,
+                [value_out](auto&& value) { *value_out = std::move(value); });
+  }
+
+  // Swaps the underlying value of the variant with |*value_out| and returns
+  // true when the variant is a valid type, otherwise does nothing and returns
+  // false.
+  template <typename T, typename... Types>
+  static bool Swap(Variant<Types...>* variant, T* value_out) {
+    return Call(variant,
+                [value_out](auto&& value) { std::swap(*value_out, value); });
+  }
+
+ private:
+  template <typename Op>
+  struct CallOp {
+    Op&& op;
+    template <typename U>
+    detail::EnableIfNotElement<bool, U, ValidTypes...> operator()(U&&) {
+      return false;
+    }
+    template <typename U>
+    detail::EnableIfElement<bool, U, ValidTypes...> operator()(const U& value) {
+      std::forward<Op>(op)(value);
+      return true;
+    }
+    template <typename U>
+    detail::EnableIfElement<bool, U, ValidTypes...> operator()(U&& value) {
+      std::forward<Op>(op)(std::forward<U>(value));
+      return true;
+    }
+  };
+};
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+// Overloads of std::get<T> and std::get<I> for android::pdx::rpc::Variant.
+namespace std {
+
+template <typename T, typename... Types>
+inline T& get(::android::pdx::rpc::Variant<Types...>& v) {
+  return *v.template get<T>();
+}
+template <typename T, typename... Types>
+inline T&& get(::android::pdx::rpc::Variant<Types...>&& v) {
+  return std::move(*v.template get<T>());
+}
+template <typename T, typename... Types>
+inline const T& get(const ::android::pdx::rpc::Variant<Types...>& v) {
+  return *v.template get<T>();
+}
+template <std::size_t I, typename... Types>
+inline ::android::pdx::rpc::detail::TypeForIndex<I, Types...>& get(
+    ::android::pdx::rpc::Variant<Types...>& v) {
+  return *v.template get<I>();
+}
+template <std::size_t I, typename... Types>
+inline ::android::pdx::rpc::detail::TypeForIndex<I, Types...>&& get(
+    ::android::pdx::rpc::Variant<Types...>&& v) {
+  return std::move(*v.template get<I>());
+}
+template <std::size_t I, typename... Types>
+inline const ::android::pdx::rpc::detail::TypeForIndex<I, Types...>& get(
+    const ::android::pdx::rpc::Variant<Types...>& v) {
+  return *v.template get<I>();
+}
+
+}  // namespace std
+
+#endif  // ANDROID_PDX_RPC_VARIANT_H_
diff --git a/libs/vr/libpdx/private/pdx/service.h b/libs/vr/libpdx/private/pdx/service.h
new file mode 100644
index 0000000..029e6bf
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/service.h
@@ -0,0 +1,703 @@
+#ifndef ANDROID_PDX_SERVICE_H_
+#define ANDROID_PDX_SERVICE_H_
+
+#include <cutils/log.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "pdx/channel_handle.h"
+#include "pdx/file_handle.h"
+#include "pdx/message_reader.h"
+#include "pdx/message_writer.h"
+#include "pdx/service_endpoint.h"
+
+namespace android {
+namespace pdx {
+
+class Service;
+
+namespace opcodes {
+
+/*
+ * Reserved message opcodes used by libpdx. The reserved opcodes start at the
+ * max positive signed integer for the system and go down.
+ * In contrast, service opcodes start at zero and go up. This scheme leaves
+ * most of the positive integer space for services, a tiny fraction of the
+ * positive integer space for the framework, and the entire negative integer
+ * space for the kernel.
+ */
+#define PDX_OPCODE(name, n) name = ((-1U >> 1) - (n))  // 0x7fff..ffff - n
+
+enum {
+  // System message sent when a new client channel is open.
+  CHANNEL_OPEN = -1,
+  // System message sent when a channel is closed.
+  CHANNEL_CLOSE = -2,
+  // Request the service to reload system properties.
+  PDX_OPCODE(REPORT_SYSPROP_CHANGE, 0),
+  // Request the service to dump state and return it in a text buffer.
+  PDX_OPCODE(DUMP_STATE, 1),
+};
+
+}  // namespace opcodes
+
+/*
+ * Base class of service-side per-channel context classes.
+ */
+class Channel : public std::enable_shared_from_this<Channel> {
+ public:
+  Channel() {}
+  virtual ~Channel() {}
+
+  /*
+   * Utility to get a shared_ptr reference from the channel context pointer.
+   */
+  static std::shared_ptr<Channel> GetFromMessageInfo(const MessageInfo& info);
+};
+
+/*
+ * Message class represents an RPC message, and implicitly the blocked sender
+ * waiting for a response. Every message should get a reply, at some point
+ * (unless the endpoint is closed), to prevent clients from blocking
+ * indefinitely. In order to enforce this and prevent leaking message ids,
+ * Message automatically replies with an error to the client on destruction,
+ * unless one of two things happens:
+ *
+ *     1. The service calls one of the reply methods before the Message is
+ *        destroyed.
+ *     2. The responsibility for the message is moved to another instance of
+ *        Message, using either move construction or move assignment.
+ *
+ * The second case is useful for services that need to delay responding to a
+ * sender until a later time. In this situation the service can move the
+ * Message to another instance in a suitable data structure for later use. The
+ * moved-to Message then takes on the same behavior and responsibilities
+ * described above.
+ */
+class Message : public OutputResourceMapper, public InputResourceMapper {
+ public:
+  Message();
+  Message(const MessageInfo& info);
+  ~Message();
+
+  /*
+   * Message objects support move construction and assignment.
+   */
+  Message(Message&& other);
+  Message& operator=(Message&& other);
+
+  /*
+   * Read/write payload, in either single buffer or iovec form.
+   */
+  ssize_t ReadVector(const iovec* vector, size_t vector_length);
+  ssize_t Read(void* buffer, size_t length);
+  ssize_t WriteVector(const iovec* vector, size_t vector_length);
+  ssize_t Write(const void* buffer, size_t length);
+
+  template <size_t N>
+  inline ssize_t ReadVector(const iovec (&vector)[N]) {
+    return ReadVector(vector, N);
+  }
+
+  template <size_t N>
+  inline ssize_t WriteVector(const iovec (&vector)[N]) {
+    return WriteVector(vector, N);
+  }
+
+  // OutputResourceMapper
+  FileReference PushFileHandle(const LocalHandle& handle) override;
+  FileReference PushFileHandle(const BorrowedHandle& handle) override;
+  FileReference PushFileHandle(const RemoteHandle& handle) override;
+  ChannelReference PushChannelHandle(const LocalChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      const BorrowedChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      const RemoteChannelHandle& handle) override;
+
+  // InputResourceMapper
+  bool GetFileHandle(FileReference ref, LocalHandle* handle) override;
+  bool GetChannelHandle(ChannelReference ref,
+                        LocalChannelHandle* handle) override;
+
+  /*
+   * Various ways to reply to a message.
+   */
+  int Reply(int return_code);
+  int ReplyError(unsigned error);
+  int ReplyFileDescriptor(unsigned int fd);
+  int Reply(const LocalHandle& handle);
+  int Reply(const BorrowedHandle& handle);
+  int Reply(const RemoteHandle& handle);
+  int Reply(const LocalChannelHandle& handle);
+  int Reply(const BorrowedChannelHandle& handle);
+  int Reply(const RemoteChannelHandle& handle);
+
+  template <typename T>
+  inline int Reply(const Status<T>& status) {
+    return status ? Reply(status.get()) : ReplyError(status.error());
+  }
+
+  /*
+   * Update the channel event bits with the given clear and set masks.
+   */
+  int ModifyChannelEvents(int clear_mask, int set_mask);
+
+  /*
+   * Create a new channel and push it as a file descriptor to the client. See
+   * Service::PushChannel() for a detail description of this method's operation.
+   */
+  Status<RemoteChannelHandle> PushChannel(
+      int flags, const std::shared_ptr<Channel>& channel, int* channel_id);
+
+  /*
+   * Create a new channel and push it as a file descriptor to the client. See
+   * Service::PushChannel() for a detail description of this method's operation.
+   */
+  Status<RemoteChannelHandle> PushChannel(
+      Service* service, int flags, const std::shared_ptr<Channel>& channel,
+      int* channel_id);
+
+  /*
+   * Check whether the |ref| is a reference to channel to this service.
+   * If the channel reference in question is valid, the Channel object is
+   * returned in |channel| when non-nullptr.
+   *
+   * Return values:
+   *  channel_id - id of the channel if the |ref| is a valid reference to
+   *               this service's channel.
+   * Errors:
+   *  EOPNOTSUPP - the file descriptor is not a channel or is a channel to
+   *  another service.
+   *  EBADF - the file descriptor is invalid.
+   *  FAULT - |channel_id| or |channel| are non-nullptr and point to invalid
+   *  memory addresses.
+   *  EINVAL - the value of |ref| is invalid or the message id for this
+   *           message is no longer valid.
+   */
+  Status<int> CheckChannel(ChannelReference ref,
+                           std::shared_ptr<Channel>* channel) const;
+
+  /*
+   * Overload of CheckChannel() that checks whether the channel reference is for
+   * a channel to the service |service|.
+   */
+  Status<int> CheckChannel(const Service* service, ChannelReference ref,
+                           std::shared_ptr<Channel>* channel) const;
+
+  /*
+   * Overload of CheckChannel() that automatically converts to shared pointers
+   * to types derived from Channel.
+   */
+  template <class C>
+  Status<int> CheckChannel(ChannelReference ref,
+                           std::shared_ptr<C>* channel) const {
+    std::shared_ptr<Channel> base_pointer;
+    const Status<int> ret =
+        CheckChannel(ref, channel ? &base_pointer : nullptr);
+    if (channel)
+      *channel = std::static_pointer_cast<C>(base_pointer);
+    return ret;
+  }
+
+  template <class C>
+  Status<int> CheckChannel(const Service* service, ChannelReference ref,
+                           std::shared_ptr<C>* channel) const {
+    std::shared_ptr<Channel> base_pointer;
+    const Status<int> ret =
+        CheckChannel(service, ref, channel ? &base_pointer : nullptr);
+    if (channel)
+      *channel = std::static_pointer_cast<C>(base_pointer);
+    return ret;
+  }
+
+  /*
+   * MessageInfo accessors.
+   */
+  pid_t GetProcessId() const;
+  pid_t GetThreadId() const;
+  uid_t GetEffectiveUserId() const;
+  gid_t GetEffectiveGroupId() const;
+  int GetChannelId() const;
+  int GetMessageId() const;
+  int GetOp() const;
+  int GetFlags() const;
+  size_t GetSendLength() const;
+  size_t GetReceiveLength() const;
+  size_t GetFileDescriptorCount() const;
+
+  /*
+   * Impulses are asynchronous and cannot be replied to. All impulses have this
+   * invalid message id.
+   */
+  enum { IMPULSE_MESSAGE_ID = -1 };
+
+  /*
+   * Returns true if this Message describes an asynchronous "impulse" message.
+   */
+  bool IsImpulse() const { return GetMessageId() == IMPULSE_MESSAGE_ID; }
+
+  /*
+   * Returns a pointer to the impulse payload. Impulses are a maximum of 32
+   * bytes in size and the start of the impulse payload is guaranteed to be
+   * 8-byte aligned. Use GetSendLength() to determine the payload size.
+   */
+  const std::uint8_t* ImpulseBegin() const;
+
+  /*
+   * Returns one byte past the end of the impulse payload, as conventional for
+   * STL iterators.
+   */
+  const std::uint8_t* ImpulseEnd() const;
+
+  /*
+   * Get/set the Channel object for the channel associated
+   * with this message. It is up to the caller to synchronize
+   * these in multi-threaded services.
+   */
+  std::shared_ptr<Channel> GetChannel() const;
+  void SetChannel(const std::shared_ptr<Channel>& channnel);
+
+  /*
+   * Get the Channel object for the channel associated with this message,
+   * automatically converted to the desired subclass of Channel.
+   */
+  template <class C>
+  std::shared_ptr<C> GetChannel() const {
+    return std::static_pointer_cast<C>(GetChannel());
+  }
+
+  /*
+   * Gets the service this message was received on. Returns nullptr if the
+   * service was destroyed.
+   */
+  std::shared_ptr<Service> GetService() const;
+
+  /*
+   * Raw access to the MessageInfo for this message.
+   */
+  const MessageInfo& GetInfo() const;
+
+  bool replied() const { return replied_; }
+  bool IsChannelExpired() const { return channel_.expired(); }
+  bool IsServiceExpired() const { return service_.expired(); }
+
+  /*
+   * Returns true if the message is non-empty; that is the message can be
+   * replied to using this instance.
+   */
+  explicit operator bool() const { return !replied_; }
+
+  const void* GetState() const { return state_; }
+  void* GetState() { return state_; }
+
+ private:
+  friend class Service;
+
+  Message(const Message&) = delete;
+  void operator=(const Message&) = delete;
+  void Destroy();
+
+  std::weak_ptr<Service> service_;
+  std::weak_ptr<Channel> channel_;
+  MessageInfo info_;
+  void* state_{nullptr};
+  bool replied_;
+};
+
+// Base class for RPC services.
+class Service : public std::enable_shared_from_this<Service> {
+ public:
+  Service(const std::string& name, std::unique_ptr<Endpoint> endpoint);
+  virtual ~Service();
+
+  /*
+   * Utility to get a shared_ptr reference from the service context pointer.
+   */
+  static std::shared_ptr<Service> GetFromMessageInfo(const MessageInfo& info);
+
+  /*
+   * Returns whether initialization was successful. Subclasses that override
+   * this must call this base method and AND the results with their own. This
+   * method is not intended to do any initialization work itself, only to
+   * signal success or failure.
+   */
+  virtual bool IsInitialized() const;
+
+  /*
+   * Called by defaultHandleMessage in response to a CHANNEL_OPEN message.
+   * This gives subclasses of Service a convenient hook to create per-channel
+   * context in the form of a Channel subclass.
+   *
+   * The Channel instance returned by this is used to set the channel context
+   * pointer for the channel that was just opened.
+   */
+  virtual std::shared_ptr<Channel> OnChannelOpen(Message& message);
+
+  /*
+   * Called by defaultHandleMessage in response to a CHANNEL_CLOSE message.
+   * This give subclasses of Service a convenient hook to clean up per-channel
+   * context.
+   */
+  virtual void OnChannelClose(Message& message,
+                              const std::shared_ptr<Channel>& channel);
+
+  /*
+   * Set the channel context for the given channel. This keeps a reference to
+   * the Channel object until the channel is closed or another call replaces
+   * the current value.
+   */
+  int SetChannel(int channel_id, const std::shared_ptr<Channel>& channel);
+
+  /*
+   * Get the channel context for the given channel id. This method should be
+   * used sparingly because of the performance characteristics of the underlying
+   * map; it is intended for limited, non-critical path access from outside of
+   * message dispatch. In most cases lookup by id should be unnecessary in a
+   * properly designed service; Message::GetChannel() should be used instead
+   * whenever an operation is in the context of a message.
+   *
+   * Again, if you lookup a channel context object for a service by id in a
+   * message handling path for the same service, you're probably doing something
+   * wrong.
+   */
+  std::shared_ptr<Channel> GetChannel(int channel_id) const;
+
+  /*
+   * Get a snapshot of the active channels for this service. This is the
+   * preferred way to access the set of channels because it avoids potential
+   * deadlocks and race conditions that may occur when operating on the channel
+   * map directly. However, it is more expensive than direct iteration because
+   * of dynamic memory allocation and shared pointer reference costs.
+   *
+   * Automatically converts returned items to shared pointers of the type
+   * std::shared_ptr<C>, where C is the subclass of Channel used by the service.
+   */
+  template <class C>
+  std::vector<std::shared_ptr<C>> GetChannels() const {
+    std::lock_guard<std::mutex> autolock(channels_mutex_);
+    std::vector<std::shared_ptr<C>> items;
+    items.reserve(channels_.size());
+
+    for (const auto& pair : channels_) {
+      items.push_back(std::static_pointer_cast<C>(pair.second));
+    }
+
+    return items;
+  }
+
+  /*
+   * Close a channel, signaling the client file object and freeing the channel
+   * id. Once closed, the client side of the channel always returns the error
+   * ESHUTDOWN and signals the poll/epoll events POLLHUP and POLLFREE.
+   *
+   * The internal reference to the Channel instance associated with the channel
+   * is removed, which may result in the Channel object being freed.
+   *
+   * OnChannelClosed is not called in response to this method call.
+   */
+  int CloseChannel(int channel_id);
+
+  /*
+   * Update the event bits for the given channel (given by id), using the
+   * given clear and set masks.
+   *
+   * This is useful for asynchronously signaling events that clients may be
+   * waiting for using select/poll/epoll.
+   */
+  int ModifyChannelEvents(int channel_id, int clear_mask, int set_mask);
+
+  /*
+   * Create a new channel and push it as a file descriptor to the process
+   * sending the |message|. |flags| may be set to O_NONBLOCK and/or
+   * O_CLOEXEC to control the initial behavior of the new file descriptor (the
+   * sending process may change these later using fcntl()). The internal Channel
+   * instance associated with this channel is set to |channel|, which may be
+   * nullptr. The new channel id allocated for this channel is returned in
+   * |channel_id|, which may also be nullptr if not needed.
+   *
+   * On success, returns the remote channel handle for the new channel in the
+   * sending process' handle space. This MUST be returned to the sender via
+   * Message::Reply(), Message::Write(), or Message::WriteVector().
+   *
+   * On error, returns an errno code describing the cause of the error.
+   *
+   * Service::OnChannelCreate() is not called in response to the creation of the
+   * new channel.
+   */
+  Status<RemoteChannelHandle> PushChannel(
+      Message* message, int flags, const std::shared_ptr<Channel>& channel,
+      int* channel_id);
+
+  /*
+   * Check whether the |ref| is a reference to a channel to this service.
+   * If the channel reference in question is valid, the Channel object is
+   * returned in |channel| when non-nullptr.
+   *
+   * Return values:
+   *  channel_id - id of the channel if the channel reference.
+   * Errors:
+   *  EOPNOTSUPP - the file descriptor is not a channel or is a channel to
+   *  another service.
+   *  EBADF - the file descriptor is invalid.
+   *  FAULT - |channel_id| or |channel| are non-nullptr and point to invalid
+   *  memory addresses.
+   *  EINVAL - the value of |ref| is invalid or the message id for this
+   *  message is no longer valid.
+   */
+  Status<int> CheckChannel(const Message* message, ChannelReference ref,
+                           std::shared_ptr<Channel>* channel) const;
+
+  /*
+   * Overload of CheckChannel() that automatically converts to shared pointers
+   * of types derived from Channel.
+   */
+  template <class C>
+  Status<int> CheckChannel(const Message* message, ChannelReference ref,
+                           std::shared_ptr<C>* channel) const {
+    std::shared_ptr<Channel> base_pointer;
+    const Status<int> ret =
+        CheckChannel(message, ref, channel ? &base_pointer : nullptr);
+    if (channel)
+      *channel = std::static_pointer_cast<C>(base_pointer);
+    return ret;
+  }
+
+  /*
+   * Handle a message. Subclasses override this to receive messages and decide
+   * how to dispatch them.
+   *
+   * The default implementation simply calls defaultHandleMessage().
+   * Subclasses should call the same for any unrecognized message opcodes.
+   */
+  virtual int HandleMessage(Message& message);
+
+  /*
+   * Handle an asynchronous message. Subclasses override this to receive
+   * asynchronous "impulse" messages. Impulses have a limited-size payload that
+   * is transferred upfront with the message description.
+   */
+  virtual void HandleImpulse(Message& impulse);
+
+  /*
+   * The default message handler. It is important that all messages
+   * (eventually) get a reply. This method should be called by subclasses for
+   * any unrecognized opcodes or otherwise unhandled messages to prevent
+   * erroneous requests from blocking indefinitely.
+   *
+   * Provides default handling of CHANNEL_OPEN and CHANNEL_CLOSE, calling
+   * OnChannelOpen() and OnChannelClose(), respectively.
+   *
+   * For all other message opcodes, this method replies with -ENOTSUP.
+   */
+  int DefaultHandleMessage(Message& message);
+
+  /*
+   * Called when system properties have changed. Subclasses should implement
+   * this method if they need to handle when system properties change.
+   */
+  virtual void OnSysPropChange();
+
+  /*
+   * Get the endpoint for the service.
+   */
+  Endpoint* endpoint() const { return endpoint_.get(); }
+
+  /*
+   * Cancels the endpoint, unblocking any receiver threads waiting in
+   * ReceiveAndDispatch().
+   */
+  int Cancel();
+
+  /*
+   * Iterator type for Channel map iterators.
+   */
+  using ChannelIterator =
+      std::unordered_map<int, std::shared_ptr<Channel>>::iterator;
+
+  /*
+   * Iterates over the Channel map and performs the action given by |action| on
+   * each channel map item (const ChannelIterator::value_type).
+   * |channels_mutex_| is not held; it is the responsibility of the caller to
+   * ensure serialization between threads that modify or iterate over the
+   * Channel map.
+   */
+  template <class A>
+  void ForEachChannelUnlocked(A action) const {
+    std::for_each(channels_.begin(), channels_.end(), action);
+  }
+
+  /*
+   * Iterates over the Channel map and performs the action given by |action| on
+   * each channel map item (const ChannelIterator::value_type).
+   * |channels_mutex_| is held to serialize access to the map; care must be
+   * taken to avoid recursively acquiring the mutex, for example, by calling
+   * Service::{GetChannel,SetChannel,CloseChannel,PushChannel}() or
+   * Message::SetChannel() in the action.
+   */
+  template <class A>
+  void ForEachChannel(A action) const {
+    std::lock_guard<std::mutex> autolock(channels_mutex_);
+    ForEachChannelUnlocked(action);
+  }
+
+  /*
+   * Subclasses of Service may override this method to provide a text string
+   * describing the state of the service. This method is called by
+   * HandleSystemMessage in response to the standard
+   * DUMP_STATE message. The string returned to the dump state client is
+   * truncated to |max_length| and reflects the maximum size the client can
+   * handle.
+   */
+  virtual std::string DumpState(size_t max_length);
+
+  /*
+   * Receives a message on this Service instance's endpoint and dispatches it.
+   * If the endpoint is in blocking mode this call blocks until a message is
+   * received, a signal is delivered to this thread, or the service is canceled.
+   * If the endpoint is in non-blocking mode and a message is not pending this
+   * call returns immediately with -ETIMEDOUT.
+   */
+  int ReceiveAndDispatch();
+
+ private:
+  friend class Message;
+
+  bool HandleSystemMessage(Message& message);
+
+  Service(const Service&);
+  void operator=(const Service&) = delete;
+
+  const std::string name_;
+  std::unique_ptr<Endpoint> endpoint_;
+
+  /*
+   * Maintains references to active channels.
+   */
+  mutable std::mutex channels_mutex_;
+  std::unordered_map<int, std::shared_ptr<Channel>> channels_;
+};
+
+/*
+ * Utility base class for services. This template handles allocation and
+ * initialization checks, reducing boiler plate code.
+ */
+template <typename TYPE>
+class ServiceBase : public Service {
+ public:
+  /*
+   * Static service allocation method that check for initialization errors.
+   * If errors are encountered these automatically clean up and return
+   * nullptr.
+   */
+  template <typename... Args>
+  static inline std::shared_ptr<TYPE> Create(Args&&... args) {
+    std::shared_ptr<TYPE> service(new TYPE(std::forward<Args>(args)...));
+    if (service->IsInitialized())
+      return service;
+    else
+      return nullptr;
+  }
+
+ protected:
+  /*
+   * Shorthand for subclasses to refer to this base, particularly
+   * to call the base class constructor.
+   */
+  typedef ServiceBase<TYPE> BASE;
+
+  ServiceBase(const std::string& name, std::unique_ptr<Endpoint> endpoint)
+      : Service(name, std::move(endpoint)) {}
+};
+
+#ifndef STRINGIFY
+#define STRINGIFY2(s) #s
+#define STRINGIFY(s) STRINGIFY2(s)
+#endif
+
+#define PDX_ERROR_PREFIX "[" __FILE__ ":" STRINGIFY(__LINE__) "]"
+
+/*
+ * Macros for replying to messages. Error handling can be tedious;
+ * these macros make things a little cleaner.
+ */
+#define CHECK_ERROR(cond, error, fmt, ...) \
+  do {                                     \
+    if ((cond)) {                          \
+      ALOGE(fmt, ##__VA_ARGS__);           \
+      goto error;                          \
+    }                                      \
+  } while (0)
+
+#define REPLY_ERROR(message, error, error_label)                              \
+  do {                                                                        \
+    int __ret = message.ReplyError(error);                                    \
+    CHECK_ERROR(__ret < 0, error_label,                                       \
+                PDX_ERROR_PREFIX " Failed to reply to message because: %s\n", \
+                strerror(-__ret));                                            \
+    goto error_label;                                                         \
+  } while (0)
+
+#define REPLY_ERROR_RETURN(message, error, ...)                          \
+  do {                                                                   \
+    int __ret = message.ReplyError(error);                               \
+    ALOGE_IF(__ret < 0,                                                  \
+             PDX_ERROR_PREFIX " Failed to reply to message because: %s", \
+             strerror(-__ret));                                          \
+    return __VA_ARGS__;                                                  \
+  } while (0)
+
+#define REPLY_MESSAGE(message, message_return_code, error_label)              \
+  do {                                                                        \
+    int __ret = message.Reply(message_return_code);                           \
+    CHECK_ERROR(__ret < 0, error_label,                                       \
+                PDX_ERROR_PREFIX " Failed to reply to message because: %s\n", \
+                strerror(-__ret));                                            \
+    goto error_label;                                                         \
+  } while (0)
+
+#define REPLY_SUCCESS(message, message_return_code, error_label) \
+  REPLY_MESSAGE(message, message_return_code, error_label)
+
+#define REPLY_MESSAGE_RETURN(message, message_return_code, ...)          \
+  do {                                                                   \
+    int __ret = message.Reply(message_return_code);                      \
+    ALOGE_IF(__ret < 0,                                                  \
+             PDX_ERROR_PREFIX " Failed to reply to message because: %s", \
+             strerror(-__ret));                                          \
+    return __VA_ARGS__;                                                  \
+  } while (0)
+
+#define REPLY_SUCCESS_RETURN(message, message_return_code, ...) \
+  REPLY_MESSAGE_RETURN(message, message_return_code, __VA_ARGS__)
+
+#define REPLY_FD(message, push_fd, error_label)                               \
+  do {                                                                        \
+    int __ret = message.ReplyFileDescriptor(push_fd);                         \
+    CHECK_ERROR(__ret < 0, error_label,                                       \
+                PDX_ERROR_PREFIX " Failed to reply to message because: %s\n", \
+                strerror(-__ret));                                            \
+    goto error_label;                                                         \
+  } while (0)
+
+#define REPLY_FD_RETURN(message, push_fd, ...)                           \
+  do {                                                                   \
+    int __ret = message.ReplyFileDescriptor(push_fd);                    \
+    ALOGE_IF(__ret < 0,                                                  \
+             PDX_ERROR_PREFIX " Failed to reply to message because: %s", \
+             strerror(-__ret));                                          \
+    return __VA_ARGS__;                                                  \
+  } while (0)
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_SERVICE_H_
diff --git a/libs/vr/libpdx/private/pdx/service_dispatcher.h b/libs/vr/libpdx/private/pdx/service_dispatcher.h
new file mode 100644
index 0000000..c5e342a
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/service_dispatcher.h
@@ -0,0 +1,79 @@
+#ifndef ANDROID_PDX_SERVICE_DISPATCHER_H_
+#define ANDROID_PDX_SERVICE_DISPATCHER_H_
+
+#include <memory>
+
+namespace android {
+namespace pdx {
+
+class Service;
+
+/*
+ * ServiceDispatcher manages a list of Service instances and handles message
+ * reception and dispatch to the services. This makes repetitive dispatch tasks
+ * easier to implement.
+ */
+class ServiceDispatcher {
+ public:
+  virtual ~ServiceDispatcher() = default;
+
+  /*
+   * Adds a service to the list of services handled by this dispatcher. This
+   * will fail if any threads are blocked waiting for messages in this
+   * dispatcher.
+   *
+   * Returns 0 on success; -EEXIST if the service was already added.
+   */
+  virtual int AddService(const std::shared_ptr<Service>& service) = 0;
+
+  /*
+   * Removes a service from this dispatcher. This will fail if any threads are
+   * blocked waiting for messages in this dispatcher.
+   *
+   * Returns 0 on success; -ENOENT if the service was not previously added;
+   * -EBUSY if there are threads in the dispatcher.
+   */
+  virtual int RemoveService(const std::shared_ptr<Service>& service) = 0;
+
+  /*
+   * Receive and dispatch one set of messages. Multiple threads may enter this
+   * method to create an implicit thread pool, as described for
+   * enterDispatchLoop() below, however this method exits after one dispatch
+   * cycle, requiring an external loop. This is useful when other work needs
+   * to be done in the service dispatch loop.
+   */
+  virtual int ReceiveAndDispatch() = 0;
+
+  /*
+   * Same as above with timeout in milliseconds. A negative value means
+   * infinite timeout, while a value of 0 means return immediately if no
+   * messages are available to receive.
+   */
+  virtual int ReceiveAndDispatch(int timeout) = 0;
+
+  /*
+   * Receive and dispatch messages until canceled. When more than one thread
+   * enters this method it creates an implicit thread pool to dispatch messages.
+   * Explicit thread pools may be created by using a single dispatch thread that
+   * hands Message instances (via move assignment) over to a queue of threads
+   * (or perhaps one of several) to handle.
+   */
+  virtual int EnterDispatchLoop() = 0;
+
+  /*
+   * Sets the canceled state of the dispatcher. When canceled is true, any
+   * threads blocked waiting for messages will return. This method waits until
+   * all dispatch threads have exited the dispatcher.
+   */
+  virtual void SetCanceled(bool cancel) = 0;
+
+  /*
+   * Gets the canceled state of the dispatcher.
+   */
+  virtual bool IsCanceled() const = 0;
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_SERVICE_DISPATCHER_H_
diff --git a/libs/vr/libpdx/private/pdx/service_endpoint.h b/libs/vr/libpdx/private/pdx/service_endpoint.h
new file mode 100644
index 0000000..613be7c
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/service_endpoint.h
@@ -0,0 +1,149 @@
+#ifndef ANDROID_PDX_ENDPOINT_H_
+#define ANDROID_PDX_ENDPOINT_H_
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+#include <pdx/status.h>
+
+struct iovec;
+
+namespace android {
+namespace pdx {
+
+class Service;
+class Channel;
+class Message;
+
+struct MessageInfo {
+  int pid{0};
+  int tid{0};
+  int cid{0};
+  int mid{0};
+  int euid{0};
+  int egid{0};
+  int32_t op{0};
+  uint32_t flags{0};
+  Service* service{nullptr};
+  Channel* channel{nullptr};
+  size_t send_len{0};
+  size_t recv_len{0};
+  size_t fd_count{0};
+  uint64_t impulse[4] = {};
+};
+
+// Wrapper around transport endpoint. Abstracts the underlying transport APIs in
+// a way, that the underlying IPC can be substituted for another technology
+// without changing the Service, Client and Message classes of this library.
+class Endpoint {
+ public:
+  virtual ~Endpoint() = default;
+
+  // Returns a tag that uniquely identifies a specific underlying IPC transport.
+  virtual uint32_t GetIpcTag() const = 0;
+
+  // Associates a Service instance with an endpoint by setting the service
+  // context pointer to the address of the Service. Only one Service may be
+  // associated with a given endpoint.
+  virtual int SetService(Service* service) = 0;
+
+  // Set the channel context for the given channel.
+  virtual int SetChannel(int channel_id, Channel* channel) = 0;
+
+  // Close a channel, signaling the client file object and freeing the channel
+  // id. Once closed, the client side of the channel always returns the error
+  // ESHUTDOWN and signals the poll/epoll events POLLHUP and POLLFREE.
+  virtual int CloseChannel(int channel_id) = 0;
+
+  // Update the event bits for the given channel (given by id), using the
+  // given clear and set masks.
+  virtual int ModifyChannelEvents(int channel_id, int clear_mask,
+                                  int set_mask) = 0;
+
+  // Create a new channel and push it as a file descriptor to the process
+  // sending the |message|. |flags| may be set to O_NONBLOCK and/or
+  // O_CLOEXEC to control the initial behavior of the new file descriptor (the
+  // sending process may change these later using fcntl()). The internal Channel
+  // instance associated with this channel is set to |channel|, which may be
+  // nullptr. The new channel id allocated for this channel is returned in
+  // |channel_id|, which may also be nullptr if not needed.
+  virtual Status<RemoteChannelHandle> PushChannel(Message* message, int flags,
+                                                  Channel* channel,
+                                                  int* channel_id) = 0;
+
+  // Check whether the |ref| is a reference to a channel to the service
+  // represented by the |endpoint|. If the channel reference in question is
+  // valid, the Channel object is returned in |channel| when non-nullptr and
+  // the channel ID is returned through the Status object.
+  virtual Status<int> CheckChannel(const Message* message, ChannelReference ref,
+                                   Channel** channel) = 0;
+
+  // The default message handler. It is important that all messages
+  // (eventually) get a reply. This method should be called for any unrecognized
+  // opcodes or otherwise unhandled messages to prevent erroneous requests from
+  // blocking indefinitely.
+  virtual int DefaultHandleMessage(const MessageInfo& info) = 0;
+
+  // Receives a message on the given endpoint file descriptor.
+  virtual int MessageReceive(Message* message) = 0;
+
+  // Replies to the message with a return code.
+  virtual int MessageReply(Message* message, int return_code) = 0;
+
+  // Replies to the message with a file descriptor.
+  virtual int MessageReplyFd(Message* message, unsigned int push_fd) = 0;
+
+  // Replies to the message with a local channel handle.
+  virtual int MessageReplyChannelHandle(Message* message,
+                                        const LocalChannelHandle& handle) = 0;
+
+  // Replies to the message with a borrowed local channel handle.
+  virtual int MessageReplyChannelHandle(
+      Message* message, const BorrowedChannelHandle& handle) = 0;
+
+  // Replies to the message with a remote channel handle.
+  virtual int MessageReplyChannelHandle(Message* message,
+                                        const RemoteChannelHandle& handle) = 0;
+
+  // Reads message data into an array of memory buffers.
+  virtual ssize_t ReadMessageData(Message* message, const iovec* vector,
+                                  size_t vector_length) = 0;
+
+  // Sends reply data for message.
+  virtual ssize_t WriteMessageData(Message* message, const iovec* vector,
+                                   size_t vector_length) = 0;
+
+  // Records a file descriptor into the message buffer and returns the remapped
+  // reference to be sent to the remote process.
+  virtual FileReference PushFileHandle(Message* message,
+                                       const LocalHandle& handle) = 0;
+  virtual FileReference PushFileHandle(Message* message,
+                                       const BorrowedHandle& handle) = 0;
+  virtual FileReference PushFileHandle(Message* message,
+                                       const RemoteHandle& handle) = 0;
+  virtual ChannelReference PushChannelHandle(
+      Message* message, const LocalChannelHandle& handle) = 0;
+  virtual ChannelReference PushChannelHandle(
+      Message* message, const BorrowedChannelHandle& handle) = 0;
+  virtual ChannelReference PushChannelHandle(
+      Message* message, const RemoteChannelHandle& handle) = 0;
+
+  // Obtains a file descriptor/channel handle from a message for the given
+  // reference.
+  virtual LocalHandle GetFileHandle(Message* message,
+                                    FileReference ref) const = 0;
+  virtual LocalChannelHandle GetChannelHandle(Message* message,
+                                              ChannelReference ref) const = 0;
+
+  // Transport-specific message state management.
+  virtual void* AllocateMessageState() = 0;
+  virtual void FreeMessageState(void* state) = 0;
+
+  // Cancels the endpoint, unblocking any receiver threads waiting for a
+  // message.
+  virtual int Cancel() = 0;
+};
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_ENDPOINT_H_
diff --git a/libs/vr/libpdx/private/pdx/status.h b/libs/vr/libpdx/private/pdx/status.h
new file mode 100644
index 0000000..ca2832c
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/status.h
@@ -0,0 +1,168 @@
+#ifndef ANDROID_PDX_STATUS_H_
+#define ANDROID_PDX_STATUS_H_
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+namespace android {
+namespace pdx {
+
+// This is a helper class for constructing Status<T> with an error code.
+struct ErrorStatus {
+ public:
+  ErrorStatus(int error) : error_{error} {}
+  int error() const { return error_; }
+
+  static std::string ErrorToString(int error_code);
+
+ private:
+  int error_;
+};
+
+// Status<T> is a container class that can be used to return a value of type T
+// or error code to the caller.
+template <typename T>
+class Status {
+ public:
+  // Default constructor so an empty Status object can be created.
+  Status() : error_{-1} {}
+
+  // Value copy/move constructors. These are intentionally not marked as
+  // explicit to allow direct value returns from functions without having
+  // to explicitly wrap them into Status<T>().
+  Status(const T& value) : value_{value} {}        // NOLINT(runtime/explicit)
+  Status(T&& value) : value_{std::move(value)} {}  // NOLINT(runtime/explicit)
+
+  // Constructor for storing an error code inside the Status object.
+  Status(const ErrorStatus& error_status)  // NOLINT(runtime/explicit)
+      : error_{error_status.error()} {}
+
+  // Copy/move constructors. Move constructor leaves |other| object in empty
+  // state.
+  Status(const Status& other) = default;
+  Status(Status&& other)
+      : value_{std::move(other.value_)}, error_{other.error_} {
+    other.error_ = -1;
+  }
+
+  // Assignment operators.
+  Status& operator=(const Status& other) = default;
+  Status& operator=(Status&& other) {
+    error_ = other.error_;
+    value_ = std::move(other.value_);
+    other.error_ = -1;
+    T empty;
+    std::swap(other.value_, empty);
+    return *this;
+  }
+
+  // Change the value/error code of the status object directly.
+  void SetValue(T value) {
+    error_ = 0;
+    value_ = std::move(value);
+  }
+  void SetError(int error) {
+    error_ = error;
+    T empty;
+    std::swap(value_, empty);
+  }
+
+  // If |other| is in error state, copy the error code to this object.
+  // Returns true if error was propagated
+  template<typename U>
+  bool PropagateError(const Status<U>& other) {
+    if (!other.ok() && !other.empty()) {
+      SetError(other.error());
+      return true;
+    }
+    return false;
+  }
+
+  // Returns true if the status object contains valid value for type T.
+  // This means, the object is not empty and does not contain an error code.
+  bool ok() const { return error_ == 0; }
+
+  // Checks if the object is empty (doesn't contain a valid value nor an error).
+  bool empty() const { return error_ < 0; }
+
+  // Explicit bool conversion, equivalent to invoking ok().
+  explicit operator bool() const { return ok(); }
+
+  // Accessors for the value stored in Status. Calling when ok() is false leads
+  // to undefined behavior.
+  const T& get() const { return value_; }
+  T&& take() {
+    error_ = -1;
+    return std::move(value_);
+  }
+
+  // Returns the error code stored in the object. These codes are positive
+  // non-zero values.
+  // Can be called only when an error is actually stored (that is, the object
+  // is not empty nor containing a valid value).
+  int error() const { return std::max(error_, 0); }
+
+  // Returns the error message associated with error code stored in the object.
+  // The message is the same as the string returned by strerror(status.error()).
+  // Can be called only when an error is actually stored (that is, the object
+  // is not empty nor containing a valid value).
+  std::string GetErrorMessage() const {
+    std::string message;
+    if (error_ > 0)
+      message = ErrorStatus::ErrorToString(error_);
+    return message;
+  }
+
+ private:
+  T value_{};
+  int error_{0};
+};
+
+// Specialization for status containing no other value but the error code.
+template <>
+class Status<void> {
+ public:
+  Status() = default;
+  Status(const ErrorStatus& error_status)  // NOLINT(runtime/explicit)
+      : error_{error_status.error()} {}
+  void SetValue() { error_ = 0; }
+  void SetError(int error) { error_ = error; }
+
+  template<typename U>
+  bool PropagateError(const Status<U>& other) {
+    if (!other.ok() && !other.empty()) {
+      SetError(other.error());
+      return true;
+    }
+    return false;
+  }
+
+  bool ok() const { return error_ == 0; }
+  bool empty() const { return false; }
+  explicit operator bool() const { return ok(); }
+  int error() const { return std::max(error_, 0); }
+  std::string GetErrorMessage() const {
+    std::string message;
+    if (error_ > 0)
+      message = ErrorStatus::ErrorToString(error_);
+    return message;
+  }
+
+ private:
+  int error_{0};
+};
+
+// TODO(avakulenko): Remove these function once all callers of it are gone.
+inline int ReturnStatusOrError(const Status<void>& status) {
+  return status ? 0 : -status.error();
+}
+
+inline int ReturnStatusOrError(const Status<int>& status) {
+  return status ? status.get() : -status.error();
+}
+
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_STATUS_H_
diff --git a/libs/vr/libpdx/private/pdx/trace.h b/libs/vr/libpdx/private/pdx/trace.h
new file mode 100644
index 0000000..ebe8491
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/trace.h
@@ -0,0 +1,35 @@
+#ifndef ANDROID_PDX_TRACE_H_
+#define ANDROID_PDX_TRACE_H_
+
+// Tracing utilities for libpdx. Tracing in the service framework is enabled
+// under these conditions:
+//    1. ATRACE_TAG is defined, AND
+//    2. ATRACE_TAG does not equal ATRACE_TAG_NEVER, AND
+//    3. PDX_TRACE_ENABLED is defined, AND
+//    4. PDX_TRACE_ENABLED is equal to logical true.
+//
+// If any of these conditions are not met tracing is completely removed from the
+// library and headers.
+
+// If ATRACE_TAG is not defined, default to never.
+#ifndef ATRACE_TAG
+#define ATRACE_TAG ATRACE_TAG_NEVER
+#endif
+
+// Include tracing functions after the trace tag is defined.
+#include <utils/Trace.h>
+
+// If PDX_TRACE_ENABLED is not defined, default to off.
+#ifndef PDX_TRACE_ENABLED
+#define PDX_TRACE_ENABLED 0
+#endif
+
+#if (ATRACE_TAG) != (ATRACE_TAG_NEVER) && (PDX_TRACE_ENABLED)
+#define PDX_TRACE_NAME ATRACE_NAME
+#else
+#define PDX_TRACE_NAME(name) \
+  do {                       \
+  } while (0)
+#endif
+
+#endif  // ANDROID_PDX_TRACE_H_
diff --git a/libs/vr/libpdx/private/pdx/utility.h b/libs/vr/libpdx/private/pdx/utility.h
new file mode 100644
index 0000000..c8c717c
--- /dev/null
+++ b/libs/vr/libpdx/private/pdx/utility.h
@@ -0,0 +1,367 @@
+#ifndef ANDROID_PDX_UTILITY_H_
+#define ANDROID_PDX_UTILITY_H_
+
+#include <cstdint>
+#include <iterator>
+
+#include <pdx/rpc/sequence.h>
+
+// Utilities for testing object serialization.
+
+namespace android {
+namespace pdx {
+
+class ByteBuffer {
+ public:
+  using iterator = uint8_t*;
+  using const_iterator = const uint8_t*;
+  using size_type = size_t;
+
+  ByteBuffer() = default;
+  ByteBuffer(const ByteBuffer& other) {
+    resize(other.size());
+    if (other.size())
+      memcpy(data_, other.data(), other.size());
+  }
+
+  ByteBuffer& operator=(const ByteBuffer& other) {
+    resize(other.size());
+    if (other.size())
+      memcpy(data_, other.data(), other.size());
+    return *this;
+  }
+
+  ByteBuffer& operator=(ByteBuffer&& other) {
+    std::swap(data_, other.data_);
+    std::swap(size_, other.size_);
+    std::swap(capacity_, other.capacity_);
+    other.clear();
+    return *this;
+  }
+
+  inline const uint8_t* data() const { return data_; }
+  inline uint8_t* data() { return data_; }
+  inline size_t size() const { return size_; }
+  inline size_t capacity() const { return capacity_; }
+
+  iterator begin() { return data_; }
+  const_iterator begin() const { return data_; }
+  iterator end() { return data_ + size_; }
+  const_iterator end() const { return data_ + size_; }
+
+  inline bool operator==(const ByteBuffer& other) const {
+    return size_ == other.size_ &&
+           (size_ == 0 || memcmp(data_, other.data_, size_) == 0);
+  }
+
+  inline bool operator!=(const ByteBuffer& other) const {
+    return !operator==(other);
+  }
+
+  inline void reserve(size_t size) {
+    if (size <= capacity_)
+      return;
+    // Find next power of 2 (assuming the size is 32 bits for now).
+    size--;
+    size |= size >> 1;
+    size |= size >> 2;
+    size |= size >> 4;
+    size |= size >> 8;
+    size |= size >> 16;
+    size++;
+    void* new_data = data_ ? realloc(data_, size) : malloc(size);
+    // TODO(avakulenko): Check for allocation failures.
+    data_ = static_cast<uint8_t*>(new_data);
+    capacity_ = size;
+  }
+
+  inline void resize(size_t size) {
+    reserve(size);
+    size_ = size;
+  }
+
+  inline uint8_t* grow_by(size_t size_delta) {
+    size_t old_size = size_;
+    resize(old_size + size_delta);
+    return data_ + old_size;
+  }
+
+  inline void clear() { size_ = 0; }
+
+ private:
+  uint8_t* data_{nullptr};
+  size_t size_{0};
+  size_t capacity_{0};
+};
+
+// Utility functions to increment/decrement void pointers to data buffers.
+template <typename OFFSET_T>
+inline const void* AdvancePointer(const void* ptr, OFFSET_T offset) {
+  return static_cast<const uint8_t*>(ptr) + offset;
+}
+
+template <typename OFFSET_T>
+inline void* AdvancePointer(void* ptr, OFFSET_T offset) {
+  return static_cast<uint8_t*>(ptr) + offset;
+}
+
+inline ptrdiff_t PointerDistance(const void* end, const void* begin) {
+  return static_cast<const uint8_t*>(end) - static_cast<const uint8_t*>(begin);
+}
+
+// Utility to build sequences of types.
+template <typename, typename>
+struct AppendTypeSequence;
+
+template <typename T, typename... S, template <typename...> class TT>
+struct AppendTypeSequence<T, TT<S...>> {
+  using type = TT<S..., T>;
+};
+
+// Utility to generate repeated types.
+template <typename T, std::size_t N, template <typename...> class TT>
+struct RepeatedType {
+  using type = typename AppendTypeSequence<
+      T, typename RepeatedType<T, N - 1, TT>::type>::type;
+};
+
+template <typename T, template <typename...> class TT>
+struct RepeatedType<T, 0, TT> {
+  using type = TT<>;
+};
+
+template <typename V, typename S>
+inline V ReturnValueHelper(V value, S /*ignore*/) {
+  return value;
+}
+
+template <typename R, typename V, size_t... S>
+inline R GetNTupleHelper(V value, rpc::IndexSequence<S...>) {
+  return std::make_tuple(ReturnValueHelper(value, S)...);
+}
+
+// Returns an N-tuple of type std::tuple<T,...T> containing |value| in each
+// element.
+template <size_t N, typename T,
+          typename R = typename RepeatedType<T, N, std::tuple>::type>
+inline R GetNTuple(T value) {
+  return GetNTupleHelper<R>(value, rpc::MakeIndexSequence<N>{});
+}
+
+class NoOpOutputResourceMapper : public OutputResourceMapper {
+ public:
+  FileReference PushFileHandle(const LocalHandle& handle) override {
+    return handle.Get();
+  }
+
+  FileReference PushFileHandle(const BorrowedHandle& handle) override {
+    return handle.Get();
+  }
+
+  FileReference PushFileHandle(const RemoteHandle& handle) override {
+    return handle.Get();
+  }
+
+  ChannelReference PushChannelHandle(
+      const LocalChannelHandle& handle) override {
+    return handle.value();
+  }
+
+  ChannelReference PushChannelHandle(
+      const BorrowedChannelHandle& handle) override {
+    return handle.value();
+  }
+
+  ChannelReference PushChannelHandle(
+      const RemoteChannelHandle& handle) override {
+    return handle.value();
+  }
+};
+
+class NoOpInputResourceMapper : public InputResourceMapper {
+ public:
+  bool GetFileHandle(FileReference ref, LocalHandle* handle) override {
+    *handle = LocalHandle{ref};
+    return true;
+  }
+
+  bool GetChannelHandle(ChannelReference ref,
+                        LocalChannelHandle* handle) override {
+    *handle = LocalChannelHandle{nullptr, ref};
+    return true;
+  }
+};
+
+class NoOpResourceMapper : public NoOpOutputResourceMapper,
+                           public NoOpInputResourceMapper {};
+
+// Simple implementation of the payload interface, required by
+// Serialize/Deserialize. This is intended for test cases, where compatibility
+// with std::vector is helpful.
+class Payload : public MessageWriter,
+                public MessageReader,
+                public OutputResourceMapper {
+ public:
+  using BaseType = ByteBuffer;
+  using iterator = typename BaseType::iterator;
+  using const_iterator = typename BaseType::const_iterator;
+  using size_type = typename BaseType::size_type;
+
+  Payload() = default;
+  explicit Payload(size_type count, uint8_t value = 0) { Append(count, value); }
+  Payload(const Payload& other) : buffer_(other.buffer_) {}
+  Payload(const std::initializer_list<uint8_t>& initializer) {
+    buffer_.resize(initializer.size());
+    std::copy(initializer.begin(), initializer.end(), buffer_.begin());
+  }
+
+  Payload& operator=(const Payload& other) {
+    buffer_ = other.buffer_;
+    read_pos_ = 0;
+    return *this;
+  }
+  Payload& operator=(const std::initializer_list<uint8_t>& initializer) {
+    buffer_.resize(initializer.size());
+    std::copy(initializer.begin(), initializer.end(), buffer_.begin());
+    read_pos_ = 0;
+    return *this;
+  }
+
+  // Compares Payload with Payload.
+  bool operator==(const Payload& other) const {
+    return buffer_ == other.buffer_;
+  }
+  bool operator!=(const Payload& other) const {
+    return buffer_ != other.buffer_;
+  }
+
+  // Compares Payload with std::vector.
+  template <typename Type, typename AllocatorType>
+  typename std::enable_if<sizeof(Type) == sizeof(uint8_t), bool>::type
+  operator==(const std::vector<Type, AllocatorType>& other) const {
+    return buffer_.size() == other.size() &&
+           memcmp(buffer_.data(), other.data(), other.size()) == 0;
+  }
+  template <typename Type, typename AllocatorType>
+  typename std::enable_if<sizeof(Type) == sizeof(uint8_t), bool>::type
+  operator!=(const std::vector<Type, AllocatorType>& other) const {
+    return !operator!=(other);
+  }
+
+  iterator begin() { return buffer_.begin(); }
+  const_iterator begin() const { return buffer_.begin(); }
+  iterator end() { return buffer_.end(); }
+  const_iterator end() const { return buffer_.end(); }
+
+  void Append(size_type count, uint8_t value) {
+    auto* data = buffer_.grow_by(count);
+    std::fill(data, data + count, value);
+  }
+
+  void Clear() {
+    buffer_.clear();
+    file_handles_.clear();
+    read_pos_ = 0;
+  }
+
+  void Rewind() { read_pos_ = 0; }
+
+  uint8_t* Data() { return buffer_.data(); }
+  const uint8_t* Data() const { return buffer_.data(); }
+  size_type Size() const { return buffer_.size(); }
+
+  // MessageWriter
+  void* GetNextWriteBufferSection(size_t size) override {
+    return buffer_.grow_by(size);
+  }
+
+  OutputResourceMapper* GetOutputResourceMapper() override { return this; }
+
+  // OutputResourceMapper
+  FileReference PushFileHandle(const LocalHandle& handle) override {
+    if (handle) {
+      const int ref = file_handles_.size();
+      file_handles_.push_back(handle.Get());
+      return ref;
+    } else {
+      return handle.Get();
+    }
+  }
+
+  FileReference PushFileHandle(const BorrowedHandle& handle) override {
+    if (handle) {
+      const int ref = file_handles_.size();
+      file_handles_.push_back(handle.Get());
+      return ref;
+    } else {
+      return handle.Get();
+    }
+  }
+
+  FileReference PushFileHandle(const RemoteHandle& handle) override {
+    return handle.Get();
+  }
+
+  ChannelReference PushChannelHandle(
+      const LocalChannelHandle& handle) override {
+    if (handle) {
+      const int ref = file_handles_.size();
+      file_handles_.push_back(handle.value());
+      return ref;
+    } else {
+      return handle.value();
+    }
+  }
+
+  ChannelReference PushChannelHandle(
+      const BorrowedChannelHandle& handle) override {
+    if (handle) {
+      const int ref = file_handles_.size();
+      file_handles_.push_back(handle.value());
+      return ref;
+    } else {
+      return handle.value();
+    }
+  }
+
+  ChannelReference PushChannelHandle(
+      const RemoteChannelHandle& handle) override {
+    return handle.value();
+  }
+
+  // MessageReader
+  BufferSection GetNextReadBufferSection() override {
+    return {buffer_.data() + read_pos_, &*buffer_.end()};
+  }
+
+  void ConsumeReadBufferSectionData(const void* new_start) override {
+    read_pos_ = PointerDistance(new_start, buffer_.data());
+  }
+
+  InputResourceMapper* GetInputResourceMapper() override {
+    return &input_resource_mapper_;
+  }
+
+  const int* FdArray() const { return file_handles_.data(); }
+  std::size_t FdCount() const { return file_handles_.size(); }
+
+ private:
+  NoOpInputResourceMapper input_resource_mapper_;
+  ByteBuffer buffer_;
+  std::vector<int> file_handles_;
+  size_t read_pos_{0};
+};
+
+}  // namespace pdx
+}  // namespace android
+
+// Helper macros for branch prediction hinting.
+#ifdef __GNUC__
+#define PDX_LIKELY(x) __builtin_expect(!!(x), true)
+#define PDX_UNLIKELY(x) __builtin_expect(!!(x), false)
+#else
+#define PDX_LIKELY(x) (x)
+#define PDX_UNLIKELY(x) (x)
+#endif
+
+#endif  // ANDROID_PDX_UTILITY_H_
diff --git a/libs/vr/libpdx/serialization_tests.cpp b/libs/vr/libpdx/serialization_tests.cpp
new file mode 100644
index 0000000..5ad1047
--- /dev/null
+++ b/libs/vr/libpdx/serialization_tests.cpp
@@ -0,0 +1,2505 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include <gtest/gtest.h>
+#include <pdx/rpc/argument_encoder.h>
+#include <pdx/rpc/array_wrapper.h>
+#include <pdx/rpc/default_initialization_allocator.h>
+#include <pdx/rpc/payload.h>
+#include <pdx/rpc/serializable.h>
+#include <pdx/rpc/serialization.h>
+#include <pdx/rpc/string_wrapper.h>
+#include <pdx/utility.h>
+
+using namespace android::pdx;
+using namespace android::pdx::rpc;
+
+// Tests the serialization/deserialization of all supported types, verifying all
+// reasonable boundary conditions for types with multiple encodings.
+//
+// NOTE: Sometimes this file uses the construct "var = decltype(var)({...})"
+// instead of the equivalent "var = {...}" to construct vectors. This is to
+// prevent clang-format from producing annoyingly vertical code from long
+// initializers.
+
+// TODO(eieio): Automatically generate some of these tests?
+
+namespace {
+
+// Test data for serialization/deserialization of floats.
+const float kZeroFloat = 0.0f;
+const float kOneFloat = 1.0f;
+const auto kZeroFloatBytes = reinterpret_cast<const std::uint8_t*>(&kZeroFloat);
+const auto kOneFloatBytes = reinterpret_cast<const std::uint8_t*>(&kOneFloat);
+const double kZeroDouble = 0.0;
+const double kOneDouble = 1.0;
+const auto kZeroDoubleBytes =
+    reinterpret_cast<const std::uint8_t*>(&kZeroDouble);
+const auto kOneDoubleBytes = reinterpret_cast<const std::uint8_t*>(&kOneDouble);
+
+struct TestType {
+  enum class Foo { kFoo, kBar, kBaz };
+
+  int a;
+  float b;
+  std::string c;
+  Foo d;
+
+  TestType() {}
+  TestType(int a, float b, const std::string& c, Foo d)
+      : a(a), b(b), c(c), d(d) {}
+
+  // Make gtest expressions simpler by defining equality operator. This is not
+  // needed for serialization.
+  bool operator==(const TestType& other) const {
+    return a == other.a && b == other.b && c == other.c && d == other.d;
+  }
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(TestType, a, b, c, d);
+};
+
+template <typename FileHandleType>
+struct TestTemplateType {
+  FileHandleType fd;
+
+  TestTemplateType() {}
+  TestTemplateType(FileHandleType fd) : fd(std::move(fd)) {}
+
+  bool operator==(const TestTemplateType& other) const {
+    return fd.Get() == other.fd.Get();
+  }
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(TestTemplateType<FileHandleType>, fd);
+};
+
+// Utilities to generate test maps and payloads.
+template <typename MapType>
+MapType MakeMap(std::size_t size) {
+  MapType result;
+  for (std::size_t i = 0; i < size; i++) {
+    result.emplace(i, i);
+  }
+  return result;
+}
+
+template <typename MapType>
+void InsertKeyValue(MessageWriter* writer, std::size_t size) {
+  MapType map;
+  for (std::size_t i = 0; i < size; i++) {
+    map.emplace(i, i);
+  }
+  for (const auto& element : map) {
+    Serialize(element.first, writer);
+    Serialize(element.second, writer);
+  }
+}
+
+}  // anonymous namespace
+
+TEST(SerializableTypes, Constructor) {
+  TestType tt(1, 2.0, "three", TestType::Foo::kBar);
+  EXPECT_EQ(1, tt.a);
+  EXPECT_EQ(2.0, tt.b);
+  EXPECT_EQ("three", tt.c);
+  EXPECT_EQ(TestType::Foo::kBar, tt.d);
+}
+
+TEST(SerializationTest, bool) {
+  Payload result;
+  Payload expected;
+  bool value;
+
+  // True.
+  value = true;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_TRUE};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // False.
+  value = false;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FALSE};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, uint8_t) {
+  Payload result;
+  Payload expected;
+  uint8_t value;
+
+  // Min FIXINT.
+  value = 0;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXINT.
+  value = (1 << 7) - 1;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT8.
+  value = (1 << 7);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT8, (1 << 7)};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT8.
+  value = 0xff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT8, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, uint16_t) {
+  Payload result;
+  Payload expected;
+  uint16_t value;
+
+  // Min FIXINT.
+  value = 0;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXINT.
+  value = (1 << 7) - 1;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT8.
+  value = (1 << 7);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT8, (1 << 7)};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT8.
+  value = 0xff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT8, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT16.
+  value = (1 << 8);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT16, 0, 1};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT16.
+  value = 0xffff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT16, 0xff, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, uint32_t) {
+  Payload result;
+  Payload expected;
+  uint32_t value;
+
+  // Min FIXINT.
+  value = 0;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXINT.
+  value = (1 << 7) - 1;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT8.
+  value = (1 << 7);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT8, (1 << 7)};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT8.
+  value = 0xff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT8, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT16.
+  value = (1 << 8);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT16, 0, 1};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT16.
+  value = 0xffff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT16, 0xff, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT32.
+  value = (1 << 16);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT32, 0, 0, 1, 0};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT32.
+  value = 0xffffffff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT32, 0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, uint64_t) {
+  Payload result;
+  Payload expected;
+  uint64_t value;
+
+  // Min FIXINT.
+  value = 0;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXINT.
+  value = (1 << 7) - 1;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT8.
+  value = (1 << 7);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT8, (1 << 7)};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT8.
+  value = 0xff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT8, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT16.
+  value = (1 << 8);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT16, 0, 1};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT16.
+  value = 0xffff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT16, 0xff, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT32.
+  value = (1 << 16);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT32, 0, 0, 1, 0};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT32.
+  value = 0xffffffff;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT32, 0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min UINT64.
+  value = (1ULL << 32);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_UINT64, 0, 0, 0, 0, 1, 0, 0, 0};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max UINT64.
+  value = 0xffffffffffffffffULL;
+  Serialize(value, &result);
+  expected = {
+      ENCODING_TYPE_UINT64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, int8_t) {
+  Payload result;
+  Payload expected;
+  int8_t value;
+
+  // Min NEGATIVE FIXINT.
+  value = -32;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_NEGATIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max NEGATIVE FIXINT.
+  value = -1;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_NEGATIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min FIXINT.
+  value = 0;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXINT.
+  value = 127;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT8.
+  value = -128;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT8, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT8.
+  value = -33;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT8, 0xdf};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, int16_t) {
+  Payload result;
+  Payload expected;
+  int16_t value;
+
+  // Min NEGATIVE FIXINT.
+  value = -32;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_NEGATIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max NEGATIVE FIXINT.
+  value = -1;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_NEGATIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min FIXINT.
+  value = 0;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXINT.
+  value = 127;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT8.
+  value = -128;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT8, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT8.
+  value = -33;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT8, 0xdf};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT16.
+  value = -32768;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT16, 0x00, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT16.
+  value = 32767;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT16, 0xff, 0x7f};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, int32_t) {
+  Payload result;
+  Payload expected;
+  int32_t value;
+
+  // Min NEGATIVE FIXINT.
+  value = -32;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_NEGATIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max NEGATIVE FIXINT.
+  value = -1;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_NEGATIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min FIXINT.
+  value = 0;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXINT.
+  value = 127;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT8.
+  value = -128;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT8, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT8.
+  value = -33;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT8, 0xdf};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT16.
+  value = -32768;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT16, 0x00, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT16.
+  value = 32767;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT16, 0xff, 0x7f};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT32.
+  value = -2147483648;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT32, 0x00, 0x00, 0x00, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT32.
+  value = 2147483647;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT32, 0xff, 0xff, 0xff, 0x7f};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, int64_t) {
+  Payload result;
+  Payload expected;
+  int64_t value;
+
+  // Min NEGATIVE FIXINT.
+  value = -32;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_NEGATIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max NEGATIVE FIXINT.
+  value = -1;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_NEGATIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min FIXINT.
+  value = 0;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXINT.
+  value = 127;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT8.
+  value = -128;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT8, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT8.
+  value = -33;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT8, 0xdf};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT16.
+  value = -32768;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT16, 0x00, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT16.
+  value = 32767;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT16, 0xff, 0x7f};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT32.
+  value = -2147483648;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT32, 0x00, 0x00, 0x00, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT32.
+  value = 2147483647;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_INT32, 0xff, 0xff, 0xff, 0x7f};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min INT64.
+  value = -9223372036854775808ULL;
+  Serialize(value, &result);
+  expected = {
+      ENCODING_TYPE_INT64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max INT64.
+  value = 9223372036854775807ULL;
+  Serialize(value, &result);
+  expected = {
+      ENCODING_TYPE_INT64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, float) {
+  Payload result;
+  Payload expected;
+  float value;
+
+  value = 0.0f;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FLOAT32, kZeroFloatBytes[0], kZeroFloatBytes[1],
+              kZeroFloatBytes[2], kZeroFloatBytes[3]};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  value = 1.0f;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FLOAT32, kOneFloatBytes[0], kOneFloatBytes[1],
+              kOneFloatBytes[2], kOneFloatBytes[3]};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, double) {
+  Payload result;
+  Payload expected;
+  double value;
+
+  value = 0.0f;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FLOAT64, kZeroDoubleBytes[0], kZeroDoubleBytes[1],
+              kZeroDoubleBytes[2],   kZeroDoubleBytes[3], kZeroDoubleBytes[4],
+              kZeroDoubleBytes[5],   kZeroDoubleBytes[6], kZeroDoubleBytes[7]};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  value = 1.0f;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FLOAT64, kOneDoubleBytes[0], kOneDoubleBytes[1],
+              kOneDoubleBytes[2],    kOneDoubleBytes[3], kOneDoubleBytes[4],
+              kOneDoubleBytes[5],    kOneDoubleBytes[6], kOneDoubleBytes[7]};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, Enum) {
+  Payload result;
+  Payload expected;
+
+  enum Foo { kFoo, kBar, kBaz };
+  Foo value = kBar;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN + 1};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, EnumClass) {
+  Payload result;
+  Payload expected;
+
+  enum class Foo { kFoo, kBar, kBaz };
+  Foo value = Foo::kBaz;
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_POSITIVE_FIXINT_MIN + 2};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, LocalHandle) {
+  Payload result;
+  Payload expected;
+  LocalHandle fd1;
+  LocalHandle fd2;
+
+  fd1 = LocalHandle(100);
+  Serialize(fd1, &result);
+  expected = {ENCODING_TYPE_FIXEXT2, ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 0, 0};
+  EXPECT_EQ(expected, result);
+  EXPECT_EQ(1u, result.FdCount());
+  EXPECT_EQ(100, result.FdArray()[0]);
+  result.Clear();
+
+  fd2 = LocalHandle(200);
+  Serialize(std::forward_as_tuple(fd1, fd2), &result);
+  expected = decltype(expected)(
+      {ENCODING_TYPE_FIXARRAY_MIN + 2, ENCODING_TYPE_FIXEXT2,
+       ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 0, 0, ENCODING_TYPE_FIXEXT2,
+       ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 1, 0});
+  EXPECT_EQ(expected, result);
+  EXPECT_EQ(2u, result.FdCount());
+  EXPECT_EQ(100, result.FdArray()[0]);
+  EXPECT_EQ(200, result.FdArray()[1]);
+  result.Clear();
+
+  fd1.Release();  // Don't try to close fd 100.
+  fd2.Release();  // Don't try to close fd 200.
+
+  fd1 = LocalHandle(-2);
+  Serialize(fd1, &result);
+  expected = {ENCODING_TYPE_FIXEXT2, ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 0xfe,
+              0xff};
+  EXPECT_EQ(expected, result);
+  EXPECT_EQ(0u, result.FdCount());
+  result.Clear();
+}
+
+TEST(SerializationTest, string) {
+  Payload result;
+  Payload expected;
+  std::string value;
+
+  // Min FIXSTR.
+  value = "";
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FIXSTR_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXSTR.
+  value = std::string((1 << 5) - 1, 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FIXSTR_MAX};
+  expected.Append((1 << 5) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min STR8.
+  value = std::string((1 << 5), 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_STR8, (1 << 5)};
+  expected.Append((1 << 5), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max STR8.
+  value = std::string((1 << 8) - 1, 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_STR8, (1 << 8) - 1};
+  expected.Append((1 << 8) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min STR16.
+  value = std::string((1 << 8), 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_STR16, 0x00, 0x01};
+  expected.Append((1 << 8), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max STR16.
+  value = std::string((1 << 16) - 1, 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_STR16, 0xff, 0xff};
+  expected.Append((1 << 16) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min STR32.
+  value = std::string((1 << 16), 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_STR32, 0x00, 0x00, 0x01, 0x00};
+  expected.Append((1 << 16), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, StringWrapper) {
+  Payload result;
+  Payload expected;
+  std::string value;
+
+  // Min FIXSTR.
+  value = "";
+  Serialize(WrapString(value), &result);
+  expected = {ENCODING_TYPE_FIXSTR_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXSTR.
+  value = std::string((1 << 5) - 1, 'x');
+  Serialize(WrapString(value), &result);
+  expected = {ENCODING_TYPE_FIXSTR_MAX};
+  expected.Append((1 << 5) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min STR8.
+  value = std::string((1 << 5), 'x');
+  Serialize(WrapString(value), &result);
+  expected = {ENCODING_TYPE_STR8, (1 << 5)};
+  expected.Append((1 << 5), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max STR8.
+  value = std::string((1 << 8) - 1, 'x');
+  Serialize(WrapString(value), &result);
+  expected = {ENCODING_TYPE_STR8, (1 << 8) - 1};
+  expected.Append((1 << 8) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min STR16.
+  value = std::string((1 << 8), 'x');
+  Serialize(WrapString(value), &result);
+  expected = {ENCODING_TYPE_STR16, 0x00, 0x01};
+  expected.Append((1 << 8), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max STR16.
+  value = std::string((1 << 16) - 1, 'x');
+  Serialize(WrapString(value), &result);
+  expected = {ENCODING_TYPE_STR16, 0xff, 0xff};
+  expected.Append((1 << 16) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min STR32.
+  value = std::string((1 << 16), 'x');
+  Serialize(WrapString(value), &result);
+  expected = {ENCODING_TYPE_STR32, 0x00, 0x00, 0x01, 0x00};
+  expected.Append((1 << 16), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, vector) {
+  Payload result;
+  Payload expected;
+  std::vector<uint8_t> value;
+
+  // Min FIXARRAY.
+  value = {};
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXARRAY.
+  value = decltype(value)((1 << 4) - 1, 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MAX};
+  expected.Append((1 << 4) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min ARRAY16.
+  value = decltype(value)((1 << 4), 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_ARRAY16, 0x10, 0x00};
+  expected.Append((1 << 4), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max ARRAY16.
+  value = decltype(value)((1 << 16) - 1, 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_ARRAY16, 0xff, 0xff};
+  expected.Append((1 << 16) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min ARRAY32.
+  value = decltype(value)((1 << 16), 'x');
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x01, 0x00};
+  expected.Append((1 << 16), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, map) {
+  Payload result;
+  Payload expected;
+  std::map<std::uint32_t, std::uint32_t> value;
+
+  // Min FIXMAP.
+  value = {};
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXMAP.
+  value = MakeMap<decltype(value)>((1 << 4) - 1);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MAX};
+  InsertKeyValue<decltype(value)>(&expected, (1 << 4) - 1);
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min MAP16.
+  value = MakeMap<decltype(value)>((1 << 4));
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_MAP16, 0x10, 0x00};
+  InsertKeyValue<decltype(value)>(&expected, (1 << 4));
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max MAP16.
+  value = MakeMap<decltype(value)>((1 << 16) - 1);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_MAP16, 0xff, 0xff};
+  InsertKeyValue<decltype(value)>(&expected, (1 << 16) - 1);
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min MAP32.
+  value = MakeMap<decltype(value)>((1 << 16));
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_MAP32, 0x00, 0x00, 0x01, 0x00};
+  InsertKeyValue<decltype(value)>(&expected, (1 << 16));
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, unordered_map) {
+  Payload result;
+  Payload expected;
+  std::unordered_map<std::uint32_t, std::uint32_t> value;
+
+  // Min FIXMAP.
+  value = {};
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXMAP.
+  value = MakeMap<decltype(value)>((1 << 4) - 1);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MAX};
+  InsertKeyValue<decltype(value)>(&expected, (1 << 4) - 1);
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min MAP16.
+  value = MakeMap<decltype(value)>((1 << 4));
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_MAP16, 0x10, 0x00};
+  InsertKeyValue<decltype(value)>(&expected, (1 << 4));
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max MAP16.
+  value = MakeMap<decltype(value)>((1 << 16) - 1);
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_MAP16, 0xff, 0xff};
+  InsertKeyValue<decltype(value)>(&expected, (1 << 16) - 1);
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min MAP32.
+  value = MakeMap<decltype(value)>((1 << 16));
+  Serialize(value, &result);
+  expected = {ENCODING_TYPE_MAP32, 0x00, 0x00, 0x01, 0x00};
+  InsertKeyValue<decltype(value)>(&expected, (1 << 16));
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, array) {
+  Payload result;
+  Payload expected;
+
+  // Min FIXARRAY.
+  std::array<std::uint8_t, 0> a0;
+  Serialize(a0, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXARRAY.
+  std::array<std::uint8_t, (1 << 4) - 1> a1;
+  for (auto& element : a1)
+    element = 'x';
+  Serialize(a1, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MAX};
+  expected.Append((1 << 4) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min ARRAY16.
+  std::array<std::uint8_t, (1 << 4)> a2;
+  for (auto& element : a2)
+    element = 'x';
+  Serialize(a2, &result);
+  expected = {ENCODING_TYPE_ARRAY16, 0x10, 0x00};
+  expected.Append((1 << 4), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max ARRAY16.
+  std::array<std::uint8_t, (1 << 16) - 1> a3;
+  for (auto& element : a3)
+    element = 'x';
+  Serialize(a3, &result);
+  expected = {ENCODING_TYPE_ARRAY16, 0xff, 0xff};
+  expected.Append((1 << 16) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min ARRAY32.
+  std::array<std::uint8_t, (1 << 16)> a4;
+  for (auto& element : a4)
+    element = 'x';
+  Serialize(a4, &result);
+  expected = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x01, 0x00};
+  expected.Append((1 << 16), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, ArrayWrapper) {
+  Payload result;
+  Payload expected;
+  std::vector<std::uint8_t, DefaultInitializationAllocator<std::uint8_t>> value;
+  ArrayWrapper<std::uint8_t> wrapper;
+
+  // Min FIXARRAY.
+  value = {};
+  Serialize(wrapper, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXARRAY.
+  value = decltype(value)((1 << 4) - 1, 'x');
+  wrapper = decltype(wrapper)(value.data(), value.capacity(), value.size());
+  Serialize(wrapper, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MAX};
+  expected.Append((1 << 4) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min ARRAY16.
+  value = decltype(value)((1 << 4), 'x');
+  wrapper = decltype(wrapper)(value.data(), value.capacity(), value.size());
+  Serialize(wrapper, &result);
+  expected = {ENCODING_TYPE_ARRAY16, 0x10, 0x00};
+  expected.Append((1 << 4), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max ARRAY16.
+  value = decltype(value)((1 << 16) - 1, 'x');
+  wrapper = decltype(wrapper)(value.data(), value.capacity(), value.size());
+  Serialize(wrapper, &result);
+  expected = {ENCODING_TYPE_ARRAY16, 0xff, 0xff};
+  expected.Append((1 << 16) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min ARRAY32.
+  value = decltype(value)((1 << 16), 'x');
+  wrapper = decltype(wrapper)(value.data(), value.capacity(), value.size());
+  Serialize(wrapper, &result);
+  expected = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x01, 0x00};
+  expected.Append((1 << 16), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, pair) {
+  Payload result;
+  Payload expected;
+
+  auto p1 = std::make_pair(1, 2);
+  Serialize(p1, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MIN + 2, 1, 2};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  auto p2 = std::make_pair('x', std::string("12345"));
+  Serialize(p2, &result);
+  expected = decltype(expected)({ENCODING_TYPE_FIXARRAY_MIN + 2, 'x',
+                                 ENCODING_TYPE_FIXSTR_MIN + 5, '1', '2', '3',
+                                 '4', '5'});
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, tuple) {
+  Payload result;
+  Payload expected;
+
+  // Min FIXARRAY.
+  auto t1 = std::make_tuple();
+  Serialize(t1, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MIN};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Max FIXARRAY.
+  auto t2 = GetNTuple<15>('x');
+  Serialize(t2, &result);
+  expected = {ENCODING_TYPE_FIXARRAY_MAX};
+  expected.Append((1 << 4) - 1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min ARRAY16.
+  auto t3 = GetNTuple<(1 << 4)>('x');
+  Serialize(t3, &result);
+  expected = {ENCODING_TYPE_ARRAY16, 0x10, 0x00};
+  expected.Append((1 << 4), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+// Template instantiation depth is an issue for these tests. They are commented
+// out to document the expected behavior, even though tuples of this order are
+// not expected in practice.
+#if 0
+  // Max ARRAY16.
+  auto t4 = GetNTuple<(1 << 16)-1>('x');
+  Serialize(t4, &result);
+  expected = {ENCODING_TYPE_ARRAY16, 0xff, 0xff};
+  expected.Append((1 << 16)-1, 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // Min ARRAY32.
+  auto t5 = GetNTuple<(1 << 16)>('x');
+  Serialize(t5, &result);
+  expected = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x01, 0x00};
+  expected.Append((1 << 16), 'x');
+  EXPECT_EQ(expected, result);
+  result.Clear();
+#endif
+}
+
+// TODO(eieio): More exhaustive testing of type nesting.
+TEST(SerializationTest, NestedTuple) {
+  Payload result;
+  Payload expected;
+
+  auto t1 = std::make_tuple('x', std::make_tuple<int, int>(1, 2));
+  Serialize(t1, &result);
+  expected = decltype(expected)({ENCODING_TYPE_FIXARRAY_MIN + 2, 'x',
+                                 ENCODING_TYPE_FIXARRAY_MIN + 2, 1, 2});
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  auto t2 = std::make_tuple('x', std::make_tuple<int, int>(1, 2),
+                            std::string("0123456789"));
+  Serialize(t2, &result);
+  expected = decltype(expected)({ENCODING_TYPE_FIXARRAY_MIN + 3, 'x',
+                                 ENCODING_TYPE_FIXARRAY_MIN + 2, 1, 2,
+                                 ENCODING_TYPE_FIXSTR | 10, '0', '1', '2', '3',
+                                 '4', '5', '6', '7', '8', '9'});
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  auto t3 = std::make_tuple(0.0f, std::uint64_t(10ULL),
+                            std::vector<char>{'a', 'b', 'c'});
+  Serialize(t3, &result);
+  expected = decltype(expected)(
+      {ENCODING_TYPE_FIXARRAY_MIN + 3, ENCODING_TYPE_FLOAT32,
+       kZeroFloatBytes[0], kZeroFloatBytes[1], kZeroFloatBytes[2],
+       kZeroFloatBytes[3], ENCODING_TYPE_POSITIVE_FIXINT_MIN + 10,
+       ENCODING_TYPE_FIXARRAY_MIN + 3, 'a', 'b', 'c'});
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, NestedMap) {
+  Payload result;
+  Payload expected;
+
+  std::map<int, std::pair<std::string, int>> m1 = {{0, {"a", 2}},
+                                                   {1, {"b", 10}}};
+  Serialize(m1, &result);
+  expected = decltype(expected)(
+      {ENCODING_TYPE_FIXMAP_MIN + 2, 0, ENCODING_TYPE_FIXARRAY_MIN + 2,
+       ENCODING_TYPE_FIXSTR_MIN + 1, 'a', 2, 1, ENCODING_TYPE_FIXARRAY_MIN + 2,
+       ENCODING_TYPE_FIXSTR_MIN + 1, 'b', 10});
+  EXPECT_EQ(expected, result);
+  result.Clear();
+}
+
+TEST(SerializationTest, Serializable) {
+  Payload result;
+  Payload expected;
+
+  TestType t1{10, 0.0, "12345", TestType::Foo::kBaz};
+  Serialize(t1, &result);
+  expected = decltype(expected)(
+      {ENCODING_TYPE_FIXARRAY_MIN + 4, 10, ENCODING_TYPE_FLOAT32,
+       kZeroFloatBytes[0], kZeroFloatBytes[1], kZeroFloatBytes[2],
+       kZeroFloatBytes[3], ENCODING_TYPE_FIXSTR_MIN + 5, '1', '2', '3', '4',
+       '5', ENCODING_TYPE_POSITIVE_FIXINT_MIN + 2});
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  TestTemplateType<LocalHandle> tt{LocalHandle(-1)};
+  Serialize(tt, &result);
+  expected =
+      decltype(expected)({ENCODING_TYPE_FIXARRAY_MIN + 1, ENCODING_TYPE_FIXEXT2,
+                          ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 0xff, 0xff});
+  EXPECT_EQ(expected, result);
+}
+
+TEST(SerializationTest, Variant) {
+  Payload result;
+  Payload expected;
+
+  Variant<int, bool, float> v;
+
+  // Empty variant.
+  Serialize(v, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MIN + 1, ENCODING_TYPE_NEGATIVE_FIXINT_MAX,
+              ENCODING_TYPE_NIL};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  v = 10;
+  Serialize(v, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MIN + 1,
+              ENCODING_TYPE_POSITIVE_FIXINT_MIN + 0,
+              ENCODING_TYPE_POSITIVE_FIXINT_MIN + 10};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  v = true;
+  Serialize(v, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MIN + 1,
+              ENCODING_TYPE_POSITIVE_FIXINT_MIN + 1, ENCODING_TYPE_TRUE};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  v = false;
+  Serialize(v, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MIN + 1,
+              ENCODING_TYPE_POSITIVE_FIXINT_MIN + 1, ENCODING_TYPE_FALSE};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  v = 1.0f;
+  Serialize(v, &result);
+  expected = {ENCODING_TYPE_FIXMAP_MIN + 1,
+              ENCODING_TYPE_POSITIVE_FIXINT_MIN + 2,
+              ENCODING_TYPE_FLOAT32,
+              kOneFloatBytes[0],
+              kOneFloatBytes[1],
+              kOneFloatBytes[2],
+              kOneFloatBytes[3]};
+  EXPECT_EQ(expected, result);
+  result.Clear();
+
+  // TODO(eieio): Add more serialization tests for Variant.
+}
+
+TEST(DeserializationTest, bool) {
+  Payload buffer;
+  bool result = false;
+  ErrorType error;
+
+  // True.
+  buffer = {ENCODING_TYPE_TRUE};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(1, result);  // Gtest generates warning from bool literals.
+
+  // False.
+  buffer = {ENCODING_TYPE_FALSE};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0, result);  // Gtest generates warning from bool literals.
+}
+
+TEST(DeserializationTest, uint8_t) {
+  Payload buffer;
+  std::uint8_t result = 0;
+  ErrorType error;
+
+  // Min FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127U, result);
+
+  // Min UINT8.
+  buffer = {ENCODING_TYPE_UINT8, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT8.
+  buffer = {ENCODING_TYPE_UINT8, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffU, result);
+
+  // UINT16 out of range.
+  buffer = {ENCODING_TYPE_UINT16};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_UINT16, error.encoding_type());
+
+  // UINT32 out of range.
+  buffer = {ENCODING_TYPE_UINT32};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_UINT32, error.encoding_type());
+
+  // UINT64 out of range.
+  buffer = {ENCODING_TYPE_UINT64};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_UINT64, error.encoding_type());
+}
+
+TEST(DeserializationTest, uint16_t) {
+  Payload buffer;
+  std::uint16_t result = 0;
+  ErrorType error;
+
+  // Min FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127U, result);
+
+  // Min UINT8.
+  buffer = {ENCODING_TYPE_UINT8, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT8.
+  buffer = {ENCODING_TYPE_UINT8, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffU, result);
+
+  // Min UINT16.
+  buffer = {ENCODING_TYPE_UINT16, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT16.
+  buffer = {ENCODING_TYPE_UINT16, 0xff, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffffU, result);
+
+  // UINT32 out of range.
+  buffer = {ENCODING_TYPE_UINT32};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_UINT32, error.encoding_type());
+
+  // UINT64 out of range.
+  buffer = {ENCODING_TYPE_UINT64};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_UINT64, error.encoding_type());
+}
+
+TEST(DeserializationTest, uint32_t) {
+  Payload buffer;
+  std::uint32_t result = 0;
+  ErrorType error;
+
+  // Min FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127U, result);
+
+  // Min UINT8.
+  buffer = {ENCODING_TYPE_UINT8, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT8.
+  buffer = {ENCODING_TYPE_UINT8, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffU, result);
+
+  // Min UINT16.
+  buffer = {ENCODING_TYPE_UINT16, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT16.
+  buffer = {ENCODING_TYPE_UINT16, 0xff, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffffU, result);
+
+  // Min UINT32.
+  buffer = {ENCODING_TYPE_UINT32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT32.
+  buffer = {ENCODING_TYPE_UINT32, 0xff, 0xff, 0xff, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffffffffU, result);
+
+  // UINT64 out of range.
+  buffer = {ENCODING_TYPE_UINT64};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_UINT64, error.encoding_type());
+}
+
+TEST(DeserializationTest, uint64_t) {
+  Payload buffer;
+  std::uint64_t result = 0;
+  ErrorType error;
+
+  // Min FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127U, result);
+
+  // Min UINT8.
+  buffer = {ENCODING_TYPE_UINT8, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT8.
+  buffer = {ENCODING_TYPE_UINT8, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffU, result);
+
+  // Min UINT16.
+  buffer = {ENCODING_TYPE_UINT16, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT16.
+  buffer = {ENCODING_TYPE_UINT16, 0xff, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffffU, result);
+
+  // Min UINT32.
+  buffer = {ENCODING_TYPE_UINT32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT32.
+  buffer = {ENCODING_TYPE_UINT32, 0xff, 0xff, 0xff, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffffffffU, result);
+
+  // Min UINT64.
+  buffer = {
+      ENCODING_TYPE_UINT64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0U, result);
+
+  // Max UINT64.
+  buffer = {
+      ENCODING_TYPE_UINT64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0xffffffffffffffffUL, result);
+}
+
+TEST(DeserializationTest, int8_t) {
+  Payload buffer;
+  std::int8_t result = 0;
+  ErrorType error;
+
+  // Min NEGATIVE FIXINT.
+  buffer = {ENCODING_TYPE_NEGATIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-32, result);
+
+  // Max NEGATIVE FIXINT.
+  buffer = {ENCODING_TYPE_NEGATIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-1, result);
+
+  // Min FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0, result);
+
+  // Max FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127, result);
+
+  // Min INT8.
+  buffer = {ENCODING_TYPE_INT8, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-128, result);
+
+  // Max INT8.
+  buffer = {ENCODING_TYPE_INT8, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127, result);
+
+  // INT16 out of range.
+  buffer = {ENCODING_TYPE_INT16};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_INT16, error.encoding_type());
+
+  // INT32 out of range.
+  buffer = {ENCODING_TYPE_INT32};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_INT32, error.encoding_type());
+
+  // INT64 out of range.
+  buffer = {ENCODING_TYPE_INT64};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_INT64, error.encoding_type());
+}
+
+TEST(DeserializationTest, int16_t) {
+  Payload buffer;
+  std::int16_t result = 0;
+  ErrorType error;
+
+  // Min NEGATIVE FIXINT.
+  buffer = {ENCODING_TYPE_NEGATIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-32, result);
+
+  // Max NEGATIVE FIXINT.
+  buffer = {ENCODING_TYPE_NEGATIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-1, result);
+
+  // Min FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0, result);
+
+  // Max FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127, result);
+
+  // Min INT8.
+  buffer = {ENCODING_TYPE_INT8, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-128, result);
+
+  // Max INT8.
+  buffer = {ENCODING_TYPE_INT8, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127, result);
+
+  // Min INT16.
+  buffer = {ENCODING_TYPE_INT16, 0x00, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-32768, result);
+
+  // Max INT16.
+  buffer = {ENCODING_TYPE_INT16, 0xff, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(32767, result);
+
+  // INT32 out of range.
+  buffer = {ENCODING_TYPE_INT32};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_INT32, error.encoding_type());
+
+  // INT64 out of range.
+  buffer = {ENCODING_TYPE_INT64};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_INT64, error.encoding_type());
+}
+
+TEST(DeserializationTest, int32_t) {
+  Payload buffer;
+  std::int32_t result = 0;
+  ErrorType error;
+
+  // Min NEGATIVE FIXINT.
+  buffer = {ENCODING_TYPE_NEGATIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-32, result);
+
+  // Max NEGATIVE FIXINT.
+  buffer = {ENCODING_TYPE_NEGATIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-1, result);
+
+  // Min FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0, result);
+
+  // Max FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127, result);
+
+  // Min INT8.
+  buffer = {ENCODING_TYPE_INT8, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-128, result);
+
+  // Max INT8.
+  buffer = {ENCODING_TYPE_INT8, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127, result);
+
+  // Min INT16.
+  buffer = {ENCODING_TYPE_INT16, 0x00, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-32768, result);
+
+  // Max INT16.
+  buffer = {ENCODING_TYPE_INT16, 0xff, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(32767, result);
+
+  // Min INT32.
+  buffer = {ENCODING_TYPE_INT32, 0x00, 0x00, 0x00, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-2147483648, result);
+
+  // Max INT32.
+  buffer = {ENCODING_TYPE_INT32, 0xff, 0xff, 0xff, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(2147483647, result);
+
+  // INT64 out of range.
+  buffer = {ENCODING_TYPE_INT64};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_INT64, error.encoding_type());
+}
+
+TEST(DeserializationTest, int64_t) {
+  Payload buffer;
+  std::int64_t result = 0;
+  ErrorType error;
+
+  // Min NEGATIVE FIXINT.
+  buffer = {ENCODING_TYPE_NEGATIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-32, result);
+
+  // Max NEGATIVE FIXINT.
+  buffer = {ENCODING_TYPE_NEGATIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-1, result);
+
+  // Min FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0, result);
+
+  // Max FIXINT.
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MAX};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127, result);
+
+  // Min INT8.
+  buffer = {ENCODING_TYPE_INT8, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-128, result);
+
+  // Max INT8.
+  buffer = {ENCODING_TYPE_INT8, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(127, result);
+
+  // Min INT16.
+  buffer = {ENCODING_TYPE_INT16, 0x00, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-32768, result);
+
+  // Max INT16.
+  buffer = {ENCODING_TYPE_INT16, 0xff, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(32767, result);
+
+  // Min INT32.
+  buffer = {ENCODING_TYPE_INT32, 0x00, 0x00, 0x00, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-2147483648, result);
+
+  // Max INT32.
+  buffer = {ENCODING_TYPE_INT32, 0xff, 0xff, 0xff, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(2147483647, result);
+
+  // Min INT64.
+  buffer = {
+      ENCODING_TYPE_INT64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  // Believe it or not, this is actually the correct way to specify the most
+  // negative signed long long.
+  EXPECT_EQ(-9223372036854775807LL - 1, result);
+
+  // Max INT64.
+  buffer = {
+      ENCODING_TYPE_INT64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(9223372036854775807LL, result);
+}
+
+TEST(DeserializationTest, float) {
+  Payload buffer;
+  float result;
+  ErrorType error;
+
+  // FLOAT32.
+  buffer = {ENCODING_TYPE_FLOAT32, kZeroFloatBytes[0], kZeroFloatBytes[1],
+            kZeroFloatBytes[2], kZeroFloatBytes[3]};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(kZeroFloat, result);
+
+  // FLOAT32.
+  buffer = {ENCODING_TYPE_FLOAT32, kOneFloatBytes[0], kOneFloatBytes[1],
+            kOneFloatBytes[2], kOneFloatBytes[3]};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(kOneFloat, result);
+}
+
+TEST(DeserializationTest, double) {
+  Payload buffer;
+  double result;
+  ErrorType error;
+
+  // FLOAT32.
+  buffer = {ENCODING_TYPE_FLOAT32, kZeroFloatBytes[0], kZeroFloatBytes[1],
+            kZeroFloatBytes[2], kZeroFloatBytes[3]};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(kZeroDouble, result);
+
+  // FLOAT64.
+  buffer = {ENCODING_TYPE_FLOAT64, kZeroDoubleBytes[0], kZeroDoubleBytes[1],
+            kZeroDoubleBytes[2],   kZeroDoubleBytes[3], kZeroDoubleBytes[4],
+            kZeroDoubleBytes[5],   kZeroDoubleBytes[6], kZeroDoubleBytes[7]};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(kZeroDouble, result);
+
+  // FLOAT32.
+  buffer = {ENCODING_TYPE_FLOAT32, kOneFloatBytes[0], kOneFloatBytes[1],
+            kOneFloatBytes[2], kOneFloatBytes[3]};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(kOneDouble, result);
+
+  // FLOAT64.
+  buffer = {ENCODING_TYPE_FLOAT64, kOneDoubleBytes[0], kOneDoubleBytes[1],
+            kOneDoubleBytes[2],    kOneDoubleBytes[3], kOneDoubleBytes[4],
+            kOneDoubleBytes[5],    kOneDoubleBytes[6], kOneDoubleBytes[7]};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(kOneDouble, result);
+}
+
+TEST(DeserializationTest, Enum) {
+  Payload buffer;
+  enum Foo { kFoo, kBar, kBaz } result;
+  ErrorType error;
+
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN + 1};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(kBar, result);
+}
+
+TEST(DeserializationTest, EnumClass) {
+  Payload buffer;
+  enum Foo { kFoo, kBar, kBaz } result;
+  ErrorType error;
+
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT_MIN + 2};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(Foo::kBaz, result);
+}
+
+TEST(DeserializationTest, LocalHandle) {
+  Payload buffer;
+  LocalHandle result1;
+  LocalHandle result2;
+  ErrorType error;
+
+  buffer = {ENCODING_TYPE_FIXEXT2, ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 0, 0};
+  error = Deserialize(&result1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0, result1.Get());
+  result1.Release();  // Don't close fd 0.
+
+  std::tuple<LocalHandle&, LocalHandle&> t1(result1, result2);
+  buffer = decltype(buffer)(
+      {ENCODING_TYPE_FIXARRAY_MIN + 2, ENCODING_TYPE_FIXEXT2,
+       ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 0, 0, ENCODING_TYPE_FIXEXT2,
+       ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 1, 0});
+  error = Deserialize(&t1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(0, result1.Get());
+  EXPECT_EQ(1, result2.Get());
+  result1.Release();  // Don't close fd 0.
+  result2.Release();  // Don't close fd 1.
+
+  buffer = {ENCODING_TYPE_FIXEXT2, ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 0xfe,
+            0xff};
+  error = Deserialize(&result1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(-2, result1.Get());
+}
+
+TEST(DeserializationTest, string) {
+  Payload buffer;
+  std::string result = "";
+  ErrorType error;
+
+  // Min FIXSTR.
+  buffer = {ENCODING_TYPE_FIXSTR_MIN};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ("", result);
+
+  // Max FIXSTR.
+  buffer = {ENCODING_TYPE_FIXSTR_MAX};
+  buffer.Append((1 << 5) - 1, 'x');
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(std::string((1 << 5) - 1, 'x'), result);
+
+  // Min STR8.
+  buffer = {ENCODING_TYPE_STR8, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ("", result);
+
+  // Max STR8.
+  buffer = {ENCODING_TYPE_STR8, 0xff};
+  buffer.Append(0xff, 'x');
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(std::string(0xff, 'x'), result);
+
+  // Min STR16.
+  buffer = {ENCODING_TYPE_STR16, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ("", result);
+
+  // Max STR16.
+  buffer = {ENCODING_TYPE_STR16, 0xff, 0xff};
+  buffer.Append(0xffff, 'x');
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(std::string(0xffff, 'x'), result);
+
+  // Min STR32.
+  buffer = {ENCODING_TYPE_STR32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ("", result);
+
+  // Test STR32 with max STR16 + 1 bytes. It's not practical to test max
+  // STR32.
+  buffer = {ENCODING_TYPE_STR32, 0x00, 0x00, 0x01, 0x00};
+  buffer.Append(0x10000, 'x');
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(std::string(0x10000, 'x'), result);
+}
+
+TEST(DeserializationTest, vector) {
+  Payload buffer;
+  std::vector<std::uint8_t, DefaultInitializationAllocator<std::uint8_t>>
+      result;
+  Payload expected;
+  ErrorType error;
+
+  // Min FIXARRAY.
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Max FIXARRAY.
+  buffer = {ENCODING_TYPE_FIXARRAY_MAX};
+  buffer.Append((1 << 4) - 1, 1);
+  error = Deserialize(&result, &buffer);
+  expected = decltype(expected)((1 << 4) - 1, 1);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Min ARRAY16.
+  buffer = {ENCODING_TYPE_ARRAY16, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Max ARRAY16.
+  buffer = {ENCODING_TYPE_ARRAY16, 0xff, 0xff};
+  buffer.Append(0xffff, 1);
+  error = Deserialize(&result, &buffer);
+  expected = decltype(expected)(0xffff, 1);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Min ARRAY32.
+  buffer = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // ARRAY32 with max ARRAY16 + 1. It's not practical to test max ARRAY32.
+  buffer = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x01, 0x00};
+  buffer.Append(0x10000, 1);
+  error = Deserialize(&result, &buffer);
+  expected = decltype(expected)(0x10000, 1);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+}
+
+TEST(DeserializationTest, map) {
+  Payload buffer;
+  std::map<std::uint32_t, std::uint32_t> result;
+  std::map<std::uint32_t, std::uint32_t> expected;
+  ErrorType error;
+
+  // Min FIXMAP.
+  buffer = {ENCODING_TYPE_FIXMAP_MIN};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Size mismatch.
+  buffer = {ENCODING_TYPE_FIXMAP_MIN + 1};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::INSUFFICIENT_BUFFER, error);
+
+  // Max FIXMAP.
+  buffer = {ENCODING_TYPE_FIXMAP_MAX};
+  InsertKeyValue<decltype(result)>(&buffer, (1 << 4) - 1);
+  error = Deserialize(&result, &buffer);
+  expected = MakeMap<decltype(expected)>((1 << 4) - 1);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error) << std::string(error);
+  EXPECT_EQ(expected, result);
+
+  // Min MAP16.
+  buffer = {ENCODING_TYPE_MAP16, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Max MAP16.
+  buffer = {ENCODING_TYPE_MAP16, 0xff, 0xff};
+  InsertKeyValue<decltype(result)>(&buffer, (1 << 16) - 1);
+  error = Deserialize(&result, &buffer);
+  expected = MakeMap<decltype(expected)>((1 << 16) - 1);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Min MAP32.
+  buffer = {ENCODING_TYPE_MAP32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // MAP32 with max MAP16 + 1. It's not practical to test max MAP32.
+  buffer = {ENCODING_TYPE_MAP32, 0x00, 0x00, 0x01, 0x00};
+  InsertKeyValue<decltype(result)>(&buffer, (1 << 16));
+  error = Deserialize(&result, &buffer);
+  expected = MakeMap<decltype(expected)>((1 << 16));
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+}
+
+TEST(DeserializationTest, unordered_map) {
+  Payload buffer;
+  std::unordered_map<std::uint32_t, std::uint32_t> result;
+  std::unordered_map<std::uint32_t, std::uint32_t> expected;
+  ErrorType error;
+
+  // Min FIXMAP.
+  buffer = {ENCODING_TYPE_FIXMAP_MIN};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Size mismatch.
+  buffer = {ENCODING_TYPE_FIXMAP_MIN + 1};
+  error = Deserialize(&result, &buffer);
+  EXPECT_EQ(ErrorCode::INSUFFICIENT_BUFFER, error);
+
+  // Max FIXMAP.
+  buffer = {ENCODING_TYPE_FIXMAP_MAX};
+  InsertKeyValue<decltype(result)>(&buffer, (1 << 4) - 1);
+  error = Deserialize(&result, &buffer);
+  expected = MakeMap<decltype(expected)>((1 << 4) - 1);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Min MAP16.
+  buffer = {ENCODING_TYPE_MAP16, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Max MAP16.
+  buffer = {ENCODING_TYPE_MAP16, 0xff, 0xff};
+  InsertKeyValue<decltype(result)>(&buffer, (1 << 16) - 1);
+  error = Deserialize(&result, &buffer);
+  expected = MakeMap<decltype(expected)>((1 << 16) - 1);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Min MAP32.
+  buffer = {ENCODING_TYPE_MAP32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&result, &buffer);
+  expected = {};
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // MAP32 with max MAP16 + 1. It's not practical to test max MAP32.
+  buffer = {ENCODING_TYPE_MAP32, 0x00, 0x00, 0x01, 0x00};
+  InsertKeyValue<decltype(result)>(&buffer, (1 << 16));
+  error = Deserialize(&result, &buffer);
+  expected = MakeMap<decltype(expected)>((1 << 16));
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+}
+
+TEST(DeserializationTest, array) {
+  Payload buffer;
+  ErrorType error;
+
+  // Min FIXARRAY.
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN};
+  std::array<std::uint8_t, 0> a0;
+  error = Deserialize(&a0, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+
+  // Size mismatch.
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN + 1};
+  error = Deserialize(&a0, &buffer);
+  EXPECT_EQ(ErrorCode::INSUFFICIENT_DESTINATION_SIZE, error);
+
+  // Max FIXARRAY.
+  buffer = {ENCODING_TYPE_FIXARRAY_MAX};
+  buffer.Append((1 << 4) - 1, 'x');
+  std::array<std::uint8_t, (1 << 4) - 1> a1, expected1;
+  for (auto& element : expected1)
+    element = 'x';
+  error = Deserialize(&a1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected1, a1);
+
+  // Min ARRAY16.
+  buffer = {ENCODING_TYPE_ARRAY16, 0x00, 0x00};
+  error = Deserialize(&a0, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+
+  // Max ARRAY16.
+  buffer = {ENCODING_TYPE_ARRAY16, 0xff, 0xff};
+  buffer.Append((1 << 16) - 1, 'x');
+  std::array<std::uint8_t, (1 << 16) - 1> a3, expected3;
+  for (auto& element : expected3)
+    element = 'x';
+  error = Deserialize(&a3, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected3, a3);
+
+  // Min ARRAY32.
+  buffer = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&a0, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+
+  // ARRAY32 with max ARRAY16 + 1. It's not practical to test max ARRAY32.
+  buffer = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x01, 0x00};
+  buffer.Append((1 << 16), 'x');
+  std::array<std::uint8_t, (1 << 16)> a4, expected4;
+  for (auto& element : expected4)
+    element = 'x';
+  error = Deserialize(&a4, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected4, a4);
+}
+
+TEST(DeserializationTest, ArrayWrapper) {
+  Payload buffer;
+  std::vector<std::uint8_t, DefaultInitializationAllocator<std::uint8_t>>
+      result;
+  std::vector<std::uint8_t, DefaultInitializationAllocator<std::uint8_t>>
+      expected;
+  ErrorType error;
+
+  result.reserve(0x10000);
+  ArrayWrapper<std::uint8_t> wrapper(result.data(), result.capacity());
+
+  // Min FIXARRAY.
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN};
+  error = Deserialize(&wrapper, &buffer);
+  expected = {};
+  result.resize(wrapper.size());
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Max FIXARRAY.
+  buffer = {ENCODING_TYPE_FIXARRAY_MAX};
+  buffer.Append((1 << 4) - 1, 1);
+  error = Deserialize(&wrapper, &buffer);
+  expected = decltype(expected)((1 << 4) - 1, 1);
+  result.resize(wrapper.size());
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Min ARRAY16.
+  buffer = {ENCODING_TYPE_ARRAY16, 0x00, 0x00};
+  error = Deserialize(&wrapper, &buffer);
+  expected = {};
+  result.resize(wrapper.size());
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Max ARRAY16.
+  buffer = {ENCODING_TYPE_ARRAY16, 0xff, 0xff};
+  buffer.Append(0xffff, 1);
+  error = Deserialize(&wrapper, &buffer);
+  expected = decltype(expected)(0xffff, 1);
+  result.resize(wrapper.size());
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // Min ARRAY32.
+  buffer = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&wrapper, &buffer);
+  expected = {};
+  result.resize(wrapper.size());
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+
+  // ARRAY32 with max ARRAY16 + 1. It's not practical to test max ARRAY32.
+  buffer = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x01, 0x00};
+  buffer.Append(0x10000, 1);
+  error = Deserialize(&wrapper, &buffer);
+  expected = decltype(expected)(0x10000, 1);
+  result.resize(wrapper.size());
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(expected, result);
+}
+
+TEST(DeserializationTest, pair) {
+  Payload buffer;
+  ErrorType error;
+
+  std::pair<int, int> p1;
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN + 2, 1, 2};
+  error = Deserialize(&p1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(std::make_pair(1, 2), p1);
+}
+
+TEST(DeserializationTest, tuple) {
+  Payload buffer;
+  ErrorType error;
+
+  // Min FIXARRAY.
+  std::tuple<> t1;
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN};
+  error = Deserialize(&t1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(std::make_tuple(), t1);  // Superfluous.
+
+  // Max FIXARRAY.
+  auto t2 = GetNTuple<15, int>(0);
+  buffer = {ENCODING_TYPE_FIXARRAY_MAX};
+  buffer.Append((1 << 4) - 1, 1);
+  error = Deserialize(&t2, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ((GetNTuple<15, int>(1)), t2);
+
+  // Min ARRAY16.
+  // Using t1 above.
+  buffer = {ENCODING_TYPE_ARRAY16, 0x00, 0x00};
+  error = Deserialize(&t1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(std::make_tuple(), t1);
+
+  // ARRAY16 at Max FIXARRAY + 1
+  auto t3 = GetNTuple<(1 << 4), int>(0);
+  buffer = {ENCODING_TYPE_ARRAY16, 0x10, 0x00};
+  buffer.Append((1 << 4), 1);
+  error = Deserialize(&t3, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ((GetNTuple<(1 << 4), int>(1)), t3);
+
+  // Min ARRAY32.
+  // Using t1 from above.
+  buffer = {ENCODING_TYPE_ARRAY32, 0x00, 0x00, 0x00, 0x00};
+  error = Deserialize(&t1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(std::make_tuple(), t1);
+
+  // ARRAY32 at Max FIXARRAY + 1
+  auto t4 = GetNTuple<(1 << 4), int>(0);
+  buffer = {ENCODING_TYPE_ARRAY32, 0x10, 0x00, 0x00, 0x00};
+  buffer.Append((1 << 4), 1);
+  error = Deserialize(&t4, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ((GetNTuple<(1 << 4), int>(1)), t4);
+
+  // Template instantiation depth is an issue for tuples with large numbers of
+  // elements. As these are not expected in practice, the limits of ARRAY16
+  // and ARRAY32 are not tested.
+}
+
+TEST(DeserializationTest, Serializable) {
+  Payload buffer;
+  ErrorType error;
+
+  buffer = decltype(buffer)(
+      {ENCODING_TYPE_FIXARRAY_MIN + 4, 10, ENCODING_TYPE_FLOAT32,
+       kZeroFloatBytes[0], kZeroFloatBytes[1], kZeroFloatBytes[2],
+       kZeroFloatBytes[3], ENCODING_TYPE_FIXSTR_MIN + 5, '1', '2', '3', '4',
+       '5', ENCODING_TYPE_POSITIVE_FIXINT_MIN + 1});
+  TestType t1;
+  error = Deserialize(&t1, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(TestType(10, 0.f, "12345", TestType::Foo::kBar), t1);
+
+  buffer =
+      decltype(buffer)({ENCODING_TYPE_FIXARRAY_MIN + 1, ENCODING_TYPE_FIXEXT2,
+                        ENCODING_EXT_TYPE_FILE_DESCRIPTOR, 0xff, 0xff});
+  TestTemplateType<LocalHandle> tt;
+  error = Deserialize(&tt, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_EQ(TestTemplateType<LocalHandle>(LocalHandle(-1)), tt);
+}
+
+TEST(DeserializationTest, Variant) {
+  Payload buffer;
+  ErrorType error;
+
+  Variant<int, bool, float> v;
+
+  buffer = {ENCODING_TYPE_FIXMAP_MIN + 1, ENCODING_TYPE_NEGATIVE_FIXINT_MAX,
+            ENCODING_TYPE_NIL};
+  error = Deserialize(&v, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  EXPECT_TRUE(v.empty());
+
+  buffer = {ENCODING_TYPE_FIXMAP_MIN + 1, ENCODING_TYPE_POSITIVE_FIXINT_MIN + 0,
+            ENCODING_TYPE_POSITIVE_FIXINT_MIN + 10};
+  error = Deserialize(&v, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  ASSERT_TRUE(v.is<int>());
+  EXPECT_EQ(10, std::get<int>(v));
+
+  buffer = {ENCODING_TYPE_FIXMAP_MIN + 1, ENCODING_TYPE_POSITIVE_FIXINT_MIN + 1,
+            ENCODING_TYPE_TRUE};
+  error = Deserialize(&v, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  ASSERT_TRUE(v.is<bool>());
+  EXPECT_EQ(true, std::get<bool>(v));
+
+  buffer = {ENCODING_TYPE_FIXMAP_MIN + 1, ENCODING_TYPE_POSITIVE_FIXINT_MIN + 1,
+            ENCODING_TYPE_FALSE};
+  error = Deserialize(&v, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  ASSERT_TRUE(v.is<bool>());
+  EXPECT_EQ(false, std::get<bool>(v));
+
+  buffer = {ENCODING_TYPE_FIXMAP_MIN + 1,
+            ENCODING_TYPE_POSITIVE_FIXINT_MIN + 2,
+            ENCODING_TYPE_FLOAT32,
+            kOneFloatBytes[0],
+            kOneFloatBytes[1],
+            kOneFloatBytes[2],
+            kOneFloatBytes[3]};
+  error = Deserialize(&v, &buffer);
+  EXPECT_EQ(ErrorCode::NO_ERROR, error);
+  ASSERT_TRUE(v.is<float>());
+  EXPECT_FLOAT_EQ(1.0, std::get<float>(v));
+
+  // TODO(eieio): Add more deserialization tests for Variant.
+}
+
+TEST(DeserializationTest, ErrorType) {
+  Payload buffer;
+  ErrorType error;
+
+  std::uint8_t u8;
+  buffer = {ENCODING_TYPE_STR8};
+  error = Deserialize(&u8, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  std::uint16_t u16;
+  buffer = {ENCODING_TYPE_STR8};
+  error = Deserialize(&u16, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  std::uint32_t u32;
+  buffer = {ENCODING_TYPE_STR8};
+  error = Deserialize(&u32, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  std::uint64_t u64;
+  buffer = {ENCODING_TYPE_STR8};
+  error = Deserialize(&u64, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  std::int8_t i8;
+  buffer = {ENCODING_TYPE_STR8};
+  error = Deserialize(&i8, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  std::int16_t i16;
+  buffer = {ENCODING_TYPE_STR8};
+  error = Deserialize(&i16, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  std::int32_t i32;
+  buffer = {ENCODING_TYPE_STR8};
+  error = Deserialize(&i32, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  std::int64_t i64;
+  buffer = {ENCODING_TYPE_STR8};
+  error = Deserialize(&i64, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_INT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  std::string s;
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT};
+  error = Deserialize(&s, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_STRING, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_POSITIVE_FIXINT, error.encoding_type());
+
+  std::vector<std::uint8_t> v;
+  buffer = {ENCODING_TYPE_POSITIVE_FIXINT};
+  error = Deserialize(&v, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_ARRAY, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_POSITIVE_FIXINT, error.encoding_type());
+
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN + 1, ENCODING_TYPE_STR8};
+  error = Deserialize(&v, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_ENCODING, error);
+  EXPECT_EQ(ENCODING_CLASS_UINT, error.encoding_class());
+  EXPECT_EQ(ENCODING_TYPE_STR8, error.encoding_type());
+
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN + 2, 0, 1};
+  std::tuple<int> t;
+  error = Deserialize(&t, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_TYPE_SIZE, error);
+
+  buffer = {ENCODING_TYPE_FIXARRAY_MIN + 3, 0, 1, 2};
+  std::pair<int, int> p;
+  error = Deserialize(&p, &buffer);
+  EXPECT_EQ(ErrorCode::UNEXPECTED_TYPE_SIZE, error);
+}
diff --git a/libs/vr/libpdx/service.cpp b/libs/vr/libpdx/service.cpp
new file mode 100644
index 0000000..0053af8
--- /dev/null
+++ b/libs/vr/libpdx/service.cpp
@@ -0,0 +1,680 @@
+#define LOG_TAG "ServiceFramework"
+#include "pdx/service.h"
+
+#include <cutils/log.h>
+#include <fcntl.h>
+#include <utils/misc.h>
+
+#include <algorithm>
+#include <cstdint>
+
+#include <pdx/trace.h>
+#include "errno_guard.h"
+
+#define TRACE 0
+
+namespace android {
+namespace pdx {
+
+std::shared_ptr<Channel> Channel::GetFromMessageInfo(const MessageInfo& info) {
+  return info.channel ? info.channel->shared_from_this()
+                      : std::shared_ptr<Channel>();
+}
+
+Message::Message() : replied_(true) {}
+
+Message::Message(const MessageInfo& info)
+    : service_{Service::GetFromMessageInfo(info)},
+      channel_{Channel::GetFromMessageInfo(info)},
+      info_{info},
+      replied_{IsImpulse()} {
+  auto svc = service_.lock();
+  if (svc)
+    state_ = svc->endpoint()->AllocateMessageState();
+}
+
+// C++11 specifies the move semantics for shared_ptr but not weak_ptr. This
+// means we have to manually implement the desired move semantics for Message.
+Message::Message(Message&& other) { *this = std::move(other); }
+
+Message& Message::operator=(Message&& other) {
+  Destroy();
+  auto base = reinterpret_cast<std::uint8_t*>(&info_);
+  std::fill(&base[0], &base[sizeof(info_)], 0);
+  replied_ = true;
+  std::swap(service_, other.service_);
+  std::swap(channel_, other.channel_);
+  std::swap(info_, other.info_);
+  std::swap(state_, other.state_);
+  std::swap(replied_, other.replied_);
+  return *this;
+}
+
+Message::~Message() { Destroy(); }
+
+void Message::Destroy() {
+  auto svc = service_.lock();
+  if (svc) {
+    if (!replied_) {
+      ALOGE(
+          "ERROR: Service \"%s\" failed to reply to message: op=%d pid=%d "
+          "cid=%d\n",
+          svc->name_.c_str(), info_.op, info_.pid, info_.cid);
+      svc->endpoint()->DefaultHandleMessage(info_);
+    }
+    svc->endpoint()->FreeMessageState(state_);
+  }
+  state_ = nullptr;
+  service_.reset();
+  channel_.reset();
+}
+
+const std::uint8_t* Message::ImpulseBegin() const {
+  return reinterpret_cast<const std::uint8_t*>(info_.impulse);
+}
+
+const std::uint8_t* Message::ImpulseEnd() const {
+  return ImpulseBegin() + (IsImpulse() ? GetSendLength() : 0);
+}
+
+ssize_t Message::ReadVector(const struct iovec* vector, size_t vector_length) {
+  PDX_TRACE_NAME("Message::ReadVector");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    const ssize_t ret =
+        svc->endpoint()->ReadMessageData(this, vector, vector_length);
+    return ReturnCodeOrError(ret);
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+ssize_t Message::Read(void* buffer, size_t length) {
+  PDX_TRACE_NAME("Message::Read");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    const struct iovec vector = {buffer, length};
+    const ssize_t ret = svc->endpoint()->ReadMessageData(this, &vector, 1);
+    return ReturnCodeOrError(ret);
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+ssize_t Message::WriteVector(const struct iovec* vector, size_t vector_length) {
+  PDX_TRACE_NAME("Message::WriteVector");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    const ssize_t ret =
+        svc->endpoint()->WriteMessageData(this, vector, vector_length);
+    return ReturnCodeOrError(ret);
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+ssize_t Message::Write(const void* buffer, size_t length) {
+  PDX_TRACE_NAME("Message::Write");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    const struct iovec vector = {const_cast<void*>(buffer), length};
+    const ssize_t ret = svc->endpoint()->WriteMessageData(this, &vector, 1);
+    return ReturnCodeOrError(ret);
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+FileReference Message::PushFileHandle(const LocalHandle& handle) {
+  PDX_TRACE_NAME("Message::PushFileHandle");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    return ReturnCodeOrError(svc->endpoint()->PushFileHandle(this, handle));
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+FileReference Message::PushFileHandle(const BorrowedHandle& handle) {
+  PDX_TRACE_NAME("Message::PushFileHandle");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    return ReturnCodeOrError(svc->endpoint()->PushFileHandle(this, handle));
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+FileReference Message::PushFileHandle(const RemoteHandle& handle) {
+  PDX_TRACE_NAME("Message::PushFileHandle");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    return ReturnCodeOrError(svc->endpoint()->PushFileHandle(this, handle));
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+ChannelReference Message::PushChannelHandle(const LocalChannelHandle& handle) {
+  PDX_TRACE_NAME("Message::PushChannelHandle");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    return ReturnCodeOrError(svc->endpoint()->PushChannelHandle(this, handle));
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+ChannelReference Message::PushChannelHandle(
+    const BorrowedChannelHandle& handle) {
+  PDX_TRACE_NAME("Message::PushChannelHandle");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    return ReturnCodeOrError(svc->endpoint()->PushChannelHandle(this, handle));
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+ChannelReference Message::PushChannelHandle(const RemoteChannelHandle& handle) {
+  PDX_TRACE_NAME("Message::PushChannelHandle");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    return ReturnCodeOrError(svc->endpoint()->PushChannelHandle(this, handle));
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+bool Message::GetFileHandle(FileReference ref, LocalHandle* handle) {
+  PDX_TRACE_NAME("Message::GetFileHandle");
+  auto svc = service_.lock();
+  if (!svc)
+    return false;
+
+  if (ref >= 0) {
+    ErrnoGuard errno_guard;
+    *handle = svc->endpoint()->GetFileHandle(this, ref);
+    if (!handle->IsValid())
+      return false;
+  } else {
+    *handle = LocalHandle{ref};
+  }
+  return true;
+}
+
+bool Message::GetChannelHandle(ChannelReference ref,
+                               LocalChannelHandle* handle) {
+  PDX_TRACE_NAME("Message::GetChannelHandle");
+  auto svc = service_.lock();
+  if (!svc)
+    return false;
+
+  if (ref >= 0) {
+    ErrnoGuard errno_guard;
+    *handle = svc->endpoint()->GetChannelHandle(this, ref);
+    if (!handle->valid())
+      return false;
+  } else {
+    *handle = LocalChannelHandle{nullptr, ref};
+  }
+  return true;
+}
+
+int Message::Reply(int return_code) {
+  PDX_TRACE_NAME("Message::Reply");
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    const int ret = svc->endpoint()->MessageReply(this, return_code);
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::ReplyFileDescriptor(unsigned int fd) {
+  PDX_TRACE_NAME("Message::ReplyFileDescriptor");
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    const int ret = svc->endpoint()->MessageReplyFd(this, fd);
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::ReplyError(unsigned error) {
+  PDX_TRACE_NAME("Message::ReplyError");
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    const int ret = svc->endpoint()->MessageReply(this, -error);
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::Reply(const LocalHandle& handle) {
+  PDX_TRACE_NAME("Message::ReplyFileHandle");
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    int ret;
+
+    if (handle)
+      ret = svc->endpoint()->MessageReplyFd(this, handle.Get());
+    else
+      ret = svc->endpoint()->MessageReply(this, handle.Get());
+
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::Reply(const BorrowedHandle& handle) {
+  PDX_TRACE_NAME("Message::ReplyFileHandle");
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    int ret;
+
+    if (handle)
+      ret = svc->endpoint()->MessageReplyFd(this, handle.Get());
+    else
+      ret = svc->endpoint()->MessageReply(this, handle.Get());
+
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::Reply(const RemoteHandle& handle) {
+  PDX_TRACE_NAME("Message::ReplyFileHandle");
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    const int ret = svc->endpoint()->MessageReply(this, handle.Get());
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::Reply(const LocalChannelHandle& handle) {
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    const int ret = svc->endpoint()->MessageReplyChannelHandle(this, handle);
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::Reply(const BorrowedChannelHandle& handle) {
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    const int ret = svc->endpoint()->MessageReplyChannelHandle(this, handle);
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::Reply(const RemoteChannelHandle& handle) {
+  auto svc = service_.lock();
+  if (!replied_ && svc) {
+    ErrnoGuard errno_guard;
+    const int ret = svc->endpoint()->MessageReplyChannelHandle(this, handle);
+    replied_ = ret == 0;
+    return ReturnCodeOrError(ret);
+  } else {
+    return -EINVAL;
+  }
+}
+
+int Message::ModifyChannelEvents(int clear_mask, int set_mask) {
+  PDX_TRACE_NAME("Message::ModifyChannelEvents");
+  if (auto svc = service_.lock()) {
+    ErrnoGuard errno_guard;
+    const int ret =
+        svc->endpoint()->ModifyChannelEvents(info_.cid, clear_mask, set_mask);
+    return ReturnCodeOrError(ret);
+  } else {
+    return -ESHUTDOWN;
+  }
+}
+
+Status<RemoteChannelHandle> Message::PushChannel(
+    int flags, const std::shared_ptr<Channel>& channel, int* channel_id) {
+  PDX_TRACE_NAME("Message::PushChannel");
+  if (auto svc = service_.lock()) {
+    return svc->PushChannel(this, flags, channel, channel_id);
+  } else {
+    return ErrorStatus(ESHUTDOWN);
+  }
+}
+
+Status<RemoteChannelHandle> Message::PushChannel(
+    Service* service, int flags, const std::shared_ptr<Channel>& channel,
+    int* channel_id) {
+  PDX_TRACE_NAME("Message::PushChannel");
+  return service->PushChannel(this, flags, channel, channel_id);
+}
+
+Status<int> Message::CheckChannel(ChannelReference ref,
+                                  std::shared_ptr<Channel>* channel) const {
+  PDX_TRACE_NAME("Message::CheckChannel");
+  if (auto svc = service_.lock()) {
+    return svc->CheckChannel(this, ref, channel);
+  } else {
+    return ErrorStatus(ESHUTDOWN);
+  }
+}
+
+Status<int> Message::CheckChannel(const Service* service, ChannelReference ref,
+                                  std::shared_ptr<Channel>* channel) const {
+  PDX_TRACE_NAME("Message::CheckChannel");
+  return service->CheckChannel(this, ref, channel);
+}
+
+pid_t Message::GetProcessId() const { return info_.pid; }
+
+pid_t Message::GetThreadId() const { return info_.tid; }
+
+uid_t Message::GetEffectiveUserId() const { return info_.euid; }
+
+gid_t Message::GetEffectiveGroupId() const { return info_.egid; }
+
+int Message::GetChannelId() const { return info_.cid; }
+
+int Message::GetMessageId() const { return info_.mid; }
+
+int Message::GetOp() const { return info_.op; }
+
+int Message::GetFlags() const { return info_.flags; }
+
+size_t Message::GetSendLength() const { return info_.send_len; }
+
+size_t Message::GetReceiveLength() const { return info_.recv_len; }
+
+size_t Message::GetFileDescriptorCount() const { return info_.fd_count; }
+
+std::shared_ptr<Channel> Message::GetChannel() const { return channel_.lock(); }
+
+void Message::SetChannel(const std::shared_ptr<Channel>& chan) {
+  channel_ = chan;
+
+  if (auto svc = service_.lock())
+    svc->SetChannel(info_.cid, chan);
+}
+
+std::shared_ptr<Service> Message::GetService() const { return service_.lock(); }
+
+const MessageInfo& Message::GetInfo() const { return info_; }
+
+Service::Service(const std::string& name, std::unique_ptr<Endpoint> endpoint)
+    : name_(name), endpoint_{std::move(endpoint)} {
+  if (!endpoint_)
+    return;
+
+  const int ret = endpoint_->SetService(this);
+  ALOGE_IF(ret < 0, "Failed to set service context because: %s",
+           strerror(-ret));
+}
+
+Service::~Service() {
+  if (endpoint_) {
+    const int ret = endpoint_->SetService(nullptr);
+    ALOGE_IF(ret < 0, "Failed to clear service context because: %s",
+             strerror(-ret));
+  }
+}
+
+std::shared_ptr<Service> Service::GetFromMessageInfo(const MessageInfo& info) {
+  return info.service ? info.service->shared_from_this()
+                      : std::shared_ptr<Service>();
+}
+
+bool Service::IsInitialized() const { return endpoint_.get() != nullptr; }
+
+std::shared_ptr<Channel> Service::OnChannelOpen(Message& /*message*/) {
+  return nullptr;
+}
+
+void Service::OnChannelClose(Message& /*message*/,
+                             const std::shared_ptr<Channel>& /*channel*/) {}
+
+int Service::SetChannel(int channel_id,
+                        const std::shared_ptr<Channel>& channel) {
+  PDX_TRACE_NAME("Service::SetChannel");
+  ErrnoGuard errno_guard;
+  std::lock_guard<std::mutex> autolock(channels_mutex_);
+
+  const int ret = endpoint_->SetChannel(channel_id, channel.get());
+  if (ret == -1) {
+    ALOGE("%s::SetChannel: Failed to set channel context: %s\n", name_.c_str(),
+          strerror(errno));
+
+    // It's possible someone mucked with things behind our back by calling the C
+    // API directly. Since we know the channel id isn't valid, make sure we
+    // don't have it in the channels map.
+    if (errno == ENOENT)
+      channels_.erase(channel_id);
+
+    return ReturnCodeOrError(ret);
+  }
+
+  if (channel != nullptr)
+    channels_[channel_id] = channel;
+  else
+    channels_.erase(channel_id);
+
+  return ret;
+}
+
+std::shared_ptr<Channel> Service::GetChannel(int channel_id) const {
+  PDX_TRACE_NAME("Service::GetChannel");
+  std::lock_guard<std::mutex> autolock(channels_mutex_);
+
+  auto search = channels_.find(channel_id);
+  if (search != channels_.end())
+    return search->second;
+  else
+    return nullptr;
+}
+
+int Service::CloseChannel(int channel_id) {
+  PDX_TRACE_NAME("Service::CloseChannel");
+  ErrnoGuard errno_guard;
+  std::lock_guard<std::mutex> autolock(channels_mutex_);
+
+  const int ret = endpoint_->CloseChannel(channel_id);
+
+  // Always erase the map entry, in case someone mucked with things behind our
+  // back using the C API directly.
+  channels_.erase(channel_id);
+
+  return ReturnCodeOrError(ret);
+}
+
+int Service::ModifyChannelEvents(int channel_id, int clear_mask, int set_mask) {
+  PDX_TRACE_NAME("Service::ModifyChannelEvents");
+  return endpoint_->ModifyChannelEvents(channel_id, clear_mask, set_mask);
+}
+
+Status<RemoteChannelHandle> Service::PushChannel(
+    Message* message, int flags, const std::shared_ptr<Channel>& channel,
+    int* channel_id) {
+  PDX_TRACE_NAME("Service::PushChannel");
+  ErrnoGuard errno_guard;
+
+  std::lock_guard<std::mutex> autolock(channels_mutex_);
+
+  int channel_id_temp = -1;
+  Status<RemoteChannelHandle> ret =
+      endpoint_->PushChannel(message, flags, channel.get(), &channel_id_temp);
+  ALOGE_IF(!ret.ok(), "%s::PushChannel: Failed to push channel: %s",
+           name_.c_str(), strerror(ret.error()));
+
+  if (channel && channel_id_temp != -1)
+    channels_[channel_id_temp] = channel;
+  if (channel_id)
+    *channel_id = channel_id_temp;
+
+  return ret;
+}
+
+Status<int> Service::CheckChannel(const Message* message, ChannelReference ref,
+                                  std::shared_ptr<Channel>* channel) const {
+  PDX_TRACE_NAME("Service::CheckChannel");
+  ErrnoGuard errno_guard;
+
+  // Synchronization to maintain consistency between the kernel's channel
+  // context pointer and the userspace channels_ map. Other threads may attempt
+  // to modify the map at the same time, which could cause the channel context
+  // pointer returned by the kernel to be invalid.
+  std::lock_guard<std::mutex> autolock(channels_mutex_);
+
+  Channel* channel_context = nullptr;
+  Status<int> ret = endpoint_->CheckChannel(
+      message, ref, channel ? &channel_context : nullptr);
+  if (ret && channel) {
+    if (channel_context)
+      *channel = channel_context->shared_from_this();
+    else
+      *channel = nullptr;
+  }
+
+  return ret;
+}
+
+std::string Service::DumpState(size_t /*max_length*/) { return ""; }
+
+int Service::HandleMessage(Message& message) {
+  return DefaultHandleMessage(message);
+}
+
+void Service::HandleImpulse(Message& /*impulse*/) {}
+
+bool Service::HandleSystemMessage(Message& message) {
+  const MessageInfo& info = message.GetInfo();
+
+  switch (info.op) {
+    case opcodes::CHANNEL_OPEN: {
+      ALOGD("%s::OnChannelOpen: pid=%d cid=%d\n", name_.c_str(), info.pid,
+            info.cid);
+      message.SetChannel(OnChannelOpen(message));
+      message.Reply(0);
+      return true;
+    }
+
+    case opcodes::CHANNEL_CLOSE: {
+      ALOGD("%s::OnChannelClose: pid=%d cid=%d\n", name_.c_str(), info.pid,
+            info.cid);
+      OnChannelClose(message, Channel::GetFromMessageInfo(info));
+      message.SetChannel(nullptr);
+      message.Reply(0);
+      return true;
+    }
+
+    case opcodes::REPORT_SYSPROP_CHANGE:
+      ALOGD("%s:REPORT_SYSPROP_CHANGE: pid=%d cid=%d\n", name_.c_str(),
+            info.pid, info.cid);
+      OnSysPropChange();
+      android::report_sysprop_change();
+      message.Reply(0);
+      return true;
+
+    case opcodes::DUMP_STATE: {
+      ALOGD("%s:DUMP_STATE: pid=%d cid=%d\n", name_.c_str(), info.pid,
+            info.cid);
+      auto response = DumpState(message.GetReceiveLength());
+      const size_t response_size = response.size() < message.GetReceiveLength()
+                                       ? response.size()
+                                       : message.GetReceiveLength();
+      const ssize_t bytes_written =
+          message.Write(response.data(), response_size);
+      if (bytes_written < static_cast<ssize_t>(response_size))
+        message.ReplyError(EIO);
+      else
+        message.Reply(bytes_written);
+      return true;
+    }
+
+    default:
+      return false;
+  }
+}
+
+int Service::DefaultHandleMessage(Message& message) {
+  const MessageInfo& info = message.GetInfo();
+
+  ALOGD_IF(TRACE, "Service::DefaultHandleMessage: pid=%d cid=%d op=%d\n",
+           info.pid, info.cid, info.op);
+
+  switch (info.op) {
+    case opcodes::CHANNEL_OPEN:
+    case opcodes::CHANNEL_CLOSE:
+    case opcodes::REPORT_SYSPROP_CHANGE:
+    case opcodes::DUMP_STATE:
+      HandleSystemMessage(message);
+      return 0;
+
+    default:
+      return message.ReplyError(ENOTSUP);
+  }
+}
+
+void Service::OnSysPropChange() {}
+
+int Service::ReceiveAndDispatch() {
+  ErrnoGuard errno_guard;
+  Message message;
+  const int ret = endpoint_->MessageReceive(&message);
+  if (ret < 0) {
+    ALOGE("Failed to receive message: %s\n", strerror(errno));
+    return ReturnCodeOrError(ret);
+  }
+
+  std::shared_ptr<Service> service = message.GetService();
+
+  if (!service) {
+    ALOGE("Service::ReceiveAndDispatch: service context is NULL!!!\n");
+    // Don't block the sender indefinitely in this error case.
+    endpoint_->MessageReply(&message, -EINVAL);
+    return -EINVAL;
+  }
+
+  if (message.IsImpulse()) {
+    service->HandleImpulse(message);
+    return 0;
+  } else if (service->HandleSystemMessage(message)) {
+    return 0;
+  } else {
+    return service->HandleMessage(message);
+  }
+}
+
+int Service::Cancel() {
+  ErrnoGuard errno_guard;
+  const int ret = endpoint_->Cancel();
+  return ReturnCodeOrError(ret);
+}
+
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx/service_tests.cpp b/libs/vr/libpdx/service_tests.cpp
new file mode 100644
index 0000000..fc0c8db
--- /dev/null
+++ b/libs/vr/libpdx/service_tests.cpp
@@ -0,0 +1,796 @@
+#include <pdx/service.h>
+
+#include <memory>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <pdx/mock_service_endpoint.h>
+
+using android::pdx::BorrowedChannelHandle;
+using android::pdx::BorrowedHandle;
+using android::pdx::Channel;
+using android::pdx::ChannelReference;
+using android::pdx::ErrorStatus;
+using android::pdx::FileReference;
+using android::pdx::LocalChannelHandle;
+using android::pdx::LocalHandle;
+using android::pdx::Message;
+using android::pdx::MessageInfo;
+using android::pdx::MockEndpoint;
+using android::pdx::RemoteChannelHandle;
+using android::pdx::RemoteHandle;
+using android::pdx::Service;
+using android::pdx::Status;
+
+using testing::A;
+using testing::ByMove;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Matcher;
+using testing::Ref;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::SetErrnoAndReturn;
+using testing::WithArg;
+using testing::WithoutArgs;
+using testing::_;
+
+namespace {
+
+// Helper functions to construct fake void pointers for tests.
+inline void* IntToPtr(intptr_t addr) { return reinterpret_cast<void*>(addr); }
+
+// Helper matchers for working with iovec structures in tests.
+// Simple matcher for one element iovec array:
+// EXPECT_CALL(mock, method(IoVecMatcher(ptr, size)));
+MATCHER_P2(IoVecMatcher, ptr, size, "") {
+  return arg->iov_base == ptr && arg->iov_len == size;
+}
+
+// Matcher for an array of iovecs:
+// EXPECT_CALL(mock,
+//             method(IoVecMatcher(IoVecArray{{ptr1, size1}, {ptr2, size2}})));
+using IoVecArray = std::vector<iovec>;
+MATCHER_P(IoVecMatcher, iovec_array, "") {
+  for (const iovec& item : iovec_array) {
+    if (arg->iov_base != item.iov_base || arg->iov_len != item.iov_len)
+      return false;
+    arg++;
+  }
+  return true;
+}
+
+using IoVecData = std::vector<std::string>;
+MATCHER_P(IoVecDataMatcher, iovec_data, "") {
+  for (const std::string& item : iovec_data) {
+    std::string data{reinterpret_cast<const char*>(arg->iov_base),
+                     arg->iov_len};
+    if (data != item)
+      return false;
+    arg++;
+  }
+  return true;
+}
+
+MATCHER_P(FileHandleMatcher, value, "") { return arg.Get() == value; }
+MATCHER_P(ChannelHandleMatcher, value, "") { return arg.value() == value; }
+
+enum : int {
+  kTestPid = 1,
+  kTestTid,
+  kTestCid,
+  kTestMid,
+  kTestEuid,
+  kTestEgid,
+  kTestOp,
+};
+
+class MockService : public Service {
+ public:
+  using Service::Service;
+  MOCK_METHOD1(OnChannelOpen, std::shared_ptr<Channel>(Message& message));
+  MOCK_METHOD2(OnChannelClose,
+               void(Message& message, const std::shared_ptr<Channel>& channel));
+  MOCK_METHOD1(HandleMessage, int(Message& message));
+  MOCK_METHOD1(HandleImpulse, void(Message& impulse));
+  MOCK_METHOD0(OnSysPropChange, void());
+  MOCK_METHOD1(DumpState, std::string(size_t max_length));
+};
+
+class ServiceTest : public testing::Test {
+ public:
+  ServiceTest() {
+    auto endpoint = std::make_unique<testing::StrictMock<MockEndpoint>>();
+    EXPECT_CALL(*endpoint, SetService(_)).Times(2).WillRepeatedly(Return(0));
+    service_ = std::make_shared<MockService>("MockSvc", std::move(endpoint));
+  }
+
+  MockEndpoint* endpoint() {
+    return static_cast<MockEndpoint*>(service_->endpoint());
+  }
+
+  void SetupMessageInfo(MessageInfo* info, int32_t op, bool impulse = false) {
+    info->pid = kTestPid;
+    info->tid = kTestTid;
+    info->cid = kTestCid;
+    info->mid = impulse ? Message::IMPULSE_MESSAGE_ID : kTestMid;
+    info->euid = kTestEuid;
+    info->egid = kTestEgid;
+    info->op = op;
+    info->flags = 0;
+    info->service = service_.get();
+    info->channel = nullptr;
+    info->send_len = 0;
+    info->recv_len = 0;
+    info->fd_count = 0;
+    memset(info->impulse, 0, sizeof(info->impulse));
+  }
+
+  void SetupMessageInfoAndDefaultExpectations(MessageInfo* info, int32_t op,
+                                              bool impulse = false) {
+    SetupMessageInfo(info, op, impulse);
+    EXPECT_CALL(*endpoint(), AllocateMessageState()).WillOnce(Return(kState));
+    EXPECT_CALL(*endpoint(), FreeMessageState(kState));
+  }
+
+  void ExpectDefaultHandleMessage() {
+    EXPECT_CALL(*endpoint(), DefaultHandleMessage(_));
+  }
+
+  std::shared_ptr<MockService> service_;
+  void* kState = IntToPtr(123456);
+};
+
+class ServiceMessageTest : public ServiceTest {
+ public:
+  ServiceMessageTest() {
+    MessageInfo info;
+    SetupMessageInfoAndDefaultExpectations(&info, kTestOp);
+    message_ = std::make_unique<Message>(info);
+  }
+
+  std::unique_ptr<Message> message_;
+};
+
+}  // anonymous namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// Service class tests
+///////////////////////////////////////////////////////////////////////////////
+
+TEST_F(ServiceTest, IsInitialized) {
+  EXPECT_TRUE(service_->IsInitialized());
+  service_ = std::make_shared<MockService>("MockSvc2", nullptr);
+  EXPECT_FALSE(service_->IsInitialized());
+}
+
+TEST_F(ServiceTest, ConstructMessage) {
+  MessageInfo info;
+  SetupMessageInfo(&info, kTestOp);
+  auto test_channel = std::make_shared<Channel>();
+  info.channel = test_channel.get();
+  EXPECT_CALL(*endpoint(), AllocateMessageState()).WillOnce(Return(kState));
+
+  Message message{info};
+
+  EXPECT_FALSE(message.IsImpulse());
+  EXPECT_EQ(kTestPid, message.GetProcessId());
+  EXPECT_EQ(kTestTid, message.GetThreadId());
+  EXPECT_EQ(kTestCid, message.GetChannelId());
+  EXPECT_EQ(kTestMid, message.GetMessageId());
+  EXPECT_EQ(kTestEuid, message.GetEffectiveUserId());
+  EXPECT_EQ(kTestEgid, message.GetEffectiveGroupId());
+  EXPECT_EQ(kTestOp, message.GetOp());
+  EXPECT_EQ(service_, message.GetService());
+  EXPECT_EQ(test_channel, message.GetChannel());
+  EXPECT_FALSE(message.replied());
+  EXPECT_FALSE(message.IsChannelExpired());
+  EXPECT_FALSE(message.IsServiceExpired());
+  EXPECT_EQ(kState, message.GetState());
+
+  ExpectDefaultHandleMessage();
+  EXPECT_CALL(*endpoint(), FreeMessageState(kState));
+}
+
+TEST_F(ServiceTest, ConstructImpulseMessage) {
+  MessageInfo info;
+  SetupMessageInfo(&info, kTestOp, true);
+  auto test_channel = std::make_shared<Channel>();
+  info.channel = test_channel.get();
+  EXPECT_CALL(*endpoint(), AllocateMessageState()).WillOnce(Return(nullptr));
+
+  Message message{info};
+
+  EXPECT_TRUE(message.IsImpulse());
+  EXPECT_EQ(kTestOp, message.GetOp());
+  EXPECT_EQ(service_, message.GetService());
+  EXPECT_EQ(test_channel, message.GetChannel());
+  EXPECT_TRUE(message.replied());
+  EXPECT_FALSE(message.IsChannelExpired());
+  EXPECT_FALSE(message.IsServiceExpired());
+
+  // DefaultHandleMessage should not be called here.
+  EXPECT_CALL(*endpoint(), FreeMessageState(nullptr));
+}
+
+TEST_F(ServiceTest, HandleMessageChannelOpen) {
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(&info,
+                                         android::pdx::opcodes::CHANNEL_OPEN);
+  Message message{info};
+
+  auto channel = std::make_shared<Channel>();
+  EXPECT_CALL(*service_, OnChannelOpen(Ref(message))).WillOnce(Return(channel));
+  EXPECT_CALL(*endpoint(), SetChannel(kTestCid, channel.get()))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*endpoint(), MessageReply(&message, 0)).WillOnce(Return(0));
+
+  EXPECT_EQ(0, service_->Service::HandleMessage(message));
+}
+
+TEST_F(ServiceTest, HandleMessageChannelClose) {
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(&info,
+                                         android::pdx::opcodes::CHANNEL_CLOSE);
+  auto channel = std::make_shared<Channel>();
+  info.channel = channel.get();
+  Message message{info};
+
+  EXPECT_CALL(*service_, OnChannelClose(Ref(message), channel));
+  EXPECT_CALL(*endpoint(), SetChannel(kTestCid, nullptr)).WillOnce(Return(0));
+  EXPECT_CALL(*endpoint(), MessageReply(&message, 0)).WillOnce(Return(0));
+
+  EXPECT_EQ(0, service_->Service::HandleMessage(message));
+}
+
+TEST_F(ServiceTest, HandleMessageOnSysPropChange) {
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(
+      &info, android::pdx::opcodes::REPORT_SYSPROP_CHANGE);
+  Message message{info};
+
+  EXPECT_CALL(*service_, OnSysPropChange());
+  EXPECT_CALL(*endpoint(), MessageReply(&message, 0)).WillOnce(Return(0));
+
+  EXPECT_EQ(0, service_->Service::HandleMessage(message));
+}
+
+TEST_F(ServiceTest, HandleMessageOnDumpState) {
+  const size_t kRecvBufSize = 1000;
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(&info,
+                                         android::pdx::opcodes::DUMP_STATE);
+  info.recv_len = kRecvBufSize;
+  Message message{info};
+
+  const std::string kReply = "foo";
+  EXPECT_CALL(*service_, DumpState(kRecvBufSize)).WillOnce(Return(kReply));
+  EXPECT_CALL(
+      *endpoint(),
+      WriteMessageData(&message, IoVecDataMatcher(IoVecData{kReply}), 1))
+      .WillOnce(Return(kReply.size()));
+  EXPECT_CALL(*endpoint(), MessageReply(&message, kReply.size()))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(0, service_->Service::HandleMessage(message));
+}
+
+TEST_F(ServiceTest, HandleMessageOnDumpStateTooLarge) {
+  const size_t kRecvBufSize = 3;
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(&info,
+                                         android::pdx::opcodes::DUMP_STATE);
+  info.recv_len = kRecvBufSize;
+  Message message{info};
+
+  const std::string kReply = "0123456789";
+  const std::string kActualReply = kReply.substr(0, kRecvBufSize);
+  EXPECT_CALL(*service_, DumpState(kRecvBufSize)).WillOnce(Return(kReply));
+  EXPECT_CALL(
+      *endpoint(),
+      WriteMessageData(&message, IoVecDataMatcher(IoVecData{kActualReply}), 1))
+      .WillOnce(Return(kActualReply.size()));
+  EXPECT_CALL(*endpoint(), MessageReply(&message, kActualReply.size()))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(0, service_->Service::HandleMessage(message));
+}
+
+TEST_F(ServiceTest, HandleMessageOnDumpStateFail) {
+  const size_t kRecvBufSize = 1000;
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(&info,
+                                         android::pdx::opcodes::DUMP_STATE);
+  info.recv_len = kRecvBufSize;
+  Message message{info};
+
+  const std::string kReply = "foo";
+  EXPECT_CALL(*service_, DumpState(kRecvBufSize)).WillOnce(Return(kReply));
+  EXPECT_CALL(
+      *endpoint(),
+      WriteMessageData(&message, IoVecDataMatcher(IoVecData{kReply}), 1))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*endpoint(), MessageReply(&message, -EIO)).WillOnce(Return(0));
+
+  EXPECT_EQ(0, service_->Service::HandleMessage(message));
+}
+
+TEST_F(ServiceTest, HandleMessageCustom) {
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(&info, kTestOp);
+  Message message{info};
+
+  EXPECT_CALL(*endpoint(), MessageReply(&message, -ENOTSUP))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(0, service_->Service::HandleMessage(message));
+}
+
+TEST_F(ServiceTest, ReplyMessageWithoutService) {
+  MessageInfo info;
+  SetupMessageInfo(&info, kTestOp);
+  EXPECT_CALL(*endpoint(), AllocateMessageState()).WillOnce(Return(nullptr));
+
+  Message message{info};
+
+  EXPECT_FALSE(message.IsServiceExpired());
+  service_.reset();
+  EXPECT_TRUE(message.IsServiceExpired());
+
+  EXPECT_EQ(-EINVAL, message.Reply(12));
+}
+
+TEST_F(ServiceTest, ReceiveAndDispatchMessage) {
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(&info, kTestOp);
+  ExpectDefaultHandleMessage();
+
+  auto on_receive = [&info](Message* message) {
+    *message = Message{info};
+    return 0;
+  };
+  EXPECT_CALL(*endpoint(), MessageReceive(_)).WillOnce(Invoke(on_receive));
+  EXPECT_CALL(*service_, HandleMessage(_)).WillOnce(Return(0));
+
+  EXPECT_EQ(0, service_->ReceiveAndDispatch());
+}
+
+TEST_F(ServiceTest, ReceiveAndDispatchImpulse) {
+  MessageInfo info;
+  SetupMessageInfoAndDefaultExpectations(&info, kTestOp, true);
+
+  auto on_receive = [&info](Message* message) {
+    *message = Message{info};
+    return 0;
+  };
+  EXPECT_CALL(*endpoint(), MessageReceive(_)).WillOnce(Invoke(on_receive));
+  EXPECT_CALL(*service_, HandleImpulse(_));
+
+  EXPECT_EQ(0, service_->ReceiveAndDispatch());
+}
+
+TEST_F(ServiceTest, Cancel) {
+  EXPECT_CALL(*endpoint(), Cancel()).WillOnce(Return(0));
+  EXPECT_EQ(0, service_->Cancel());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Message class tests
+///////////////////////////////////////////////////////////////////////////////
+
+TEST_F(ServiceMessageTest, Reply) {
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), 12))
+      .WillOnce(Return(0));
+  EXPECT_FALSE(message_->replied());
+  EXPECT_EQ(0, message_->Reply(12));
+  EXPECT_TRUE(message_->replied());
+
+  EXPECT_EQ(-EINVAL, message_->Reply(12));  // Already replied.
+}
+
+TEST_F(ServiceMessageTest, ReplyFail) {
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), 12))
+      .WillOnce(SetErrnoAndReturn(EIO, -1));
+  EXPECT_EQ(-EIO, message_->Reply(12));
+
+  ExpectDefaultHandleMessage();
+}
+
+TEST_F(ServiceMessageTest, ReplyError) {
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), -12))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->ReplyError(12));
+}
+
+TEST_F(ServiceMessageTest, ReplyFileDescriptor) {
+  EXPECT_CALL(*endpoint(), MessageReplyFd(message_.get(), 5))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->ReplyFileDescriptor(5));
+}
+
+TEST_F(ServiceMessageTest, ReplyLocalFileHandle) {
+  const int kFakeFd = 12345;
+  LocalHandle handle{kFakeFd};
+  EXPECT_CALL(*endpoint(), MessageReplyFd(message_.get(), kFakeFd))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+  handle.Release();  // Make sure we do not close the fake file descriptor.
+}
+
+TEST_F(ServiceMessageTest, ReplyLocalFileHandleError) {
+  LocalHandle handle{-EINVAL};
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), -EINVAL))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+}
+
+TEST_F(ServiceMessageTest, ReplyBorrowedFileHandle) {
+  const int kFakeFd = 12345;
+  BorrowedHandle handle{kFakeFd};
+  EXPECT_CALL(*endpoint(), MessageReplyFd(message_.get(), kFakeFd))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+}
+
+TEST_F(ServiceMessageTest, ReplyBorrowedFileHandleError) {
+  BorrowedHandle handle{-EACCES};
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), -EACCES))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+}
+
+TEST_F(ServiceMessageTest, ReplyRemoteFileHandle) {
+  RemoteHandle handle{123};
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), handle.Get()))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+}
+
+TEST_F(ServiceMessageTest, ReplyRemoteFileHandleError) {
+  RemoteHandle handle{-EIO};
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), -EIO))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+}
+
+TEST_F(ServiceMessageTest, ReplyLocalChannelHandle) {
+  LocalChannelHandle handle{nullptr, 12345};
+  EXPECT_CALL(*endpoint(), MessageReplyChannelHandle(
+                               message_.get(), A<const LocalChannelHandle&>()))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+}
+
+TEST_F(ServiceMessageTest, ReplyBorrowedChannelHandle) {
+  BorrowedChannelHandle handle{12345};
+  EXPECT_CALL(*endpoint(),
+              MessageReplyChannelHandle(message_.get(),
+                                        A<const BorrowedChannelHandle&>()))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+}
+
+TEST_F(ServiceMessageTest, ReplyRemoteChannelHandle) {
+  RemoteChannelHandle handle{12345};
+  EXPECT_CALL(*endpoint(), MessageReplyChannelHandle(
+                               message_.get(), A<const RemoteChannelHandle&>()))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(handle));
+}
+
+TEST_F(ServiceMessageTest, ReplyStatusInt) {
+  Status<int> status{123};
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), status.get()))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(status));
+}
+
+TEST_F(ServiceMessageTest, ReplyStatusError) {
+  Status<int> status{ErrorStatus{EIO}};
+  EXPECT_CALL(*endpoint(), MessageReply(message_.get(), -status.error()))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->Reply(status));
+}
+
+TEST_F(ServiceMessageTest, Read) {
+  ExpectDefaultHandleMessage();
+  void* const kDataBuffer = IntToPtr(12345);
+  const size_t kDataSize = 100;
+  EXPECT_CALL(
+      *endpoint(),
+      ReadMessageData(message_.get(), IoVecMatcher(kDataBuffer, kDataSize), 1))
+      .WillOnce(Return(50))
+      .WillOnce(SetErrnoAndReturn(EACCES, -1));
+  EXPECT_EQ(50, message_->Read(kDataBuffer, kDataSize));
+  EXPECT_EQ(-EACCES, message_->Read(kDataBuffer, kDataSize));
+}
+
+TEST_F(ServiceMessageTest, ReadVector) {
+  ExpectDefaultHandleMessage();
+  char buffer1[10];
+  char buffer2[20];
+  iovec vec[] = {{buffer1, sizeof(buffer1)}, {buffer2, sizeof(buffer2)}};
+  EXPECT_CALL(*endpoint(),
+              ReadMessageData(
+                  message_.get(),
+                  IoVecMatcher(IoVecArray{std::begin(vec), std::end(vec)}), 2))
+      .WillOnce(Return(30))
+      .WillOnce(Return(15))
+      .WillOnce(SetErrnoAndReturn(EBADF, -1));
+  EXPECT_EQ(30, message_->ReadVector(vec, 2));
+  EXPECT_EQ(15, message_->ReadVector(vec));
+  EXPECT_EQ(-EBADF, message_->ReadVector(vec));
+}
+
+TEST_F(ServiceMessageTest, Write) {
+  ExpectDefaultHandleMessage();
+  void* const kDataBuffer = IntToPtr(12345);
+  const size_t kDataSize = 100;
+  EXPECT_CALL(
+      *endpoint(),
+      WriteMessageData(message_.get(), IoVecMatcher(kDataBuffer, kDataSize), 1))
+      .WillOnce(Return(50))
+      .WillOnce(SetErrnoAndReturn(EBADMSG, -1));
+  EXPECT_EQ(50, message_->Write(kDataBuffer, kDataSize));
+  EXPECT_EQ(-EBADMSG, message_->Write(kDataBuffer, kDataSize));
+}
+
+TEST_F(ServiceMessageTest, WriteVector) {
+  ExpectDefaultHandleMessage();
+  char buffer1[10];
+  char buffer2[20];
+  iovec vec[] = {{buffer1, sizeof(buffer1)}, {buffer2, sizeof(buffer2)}};
+  EXPECT_CALL(*endpoint(),
+              WriteMessageData(
+                  message_.get(),
+                  IoVecMatcher(IoVecArray{std::begin(vec), std::end(vec)}), 2))
+      .WillOnce(Return(30))
+      .WillOnce(Return(15))
+      .WillOnce(SetErrnoAndReturn(EIO, -1));
+  EXPECT_EQ(30, message_->WriteVector(vec, 2));
+  EXPECT_EQ(15, message_->WriteVector(vec));
+  EXPECT_EQ(-EIO, message_->WriteVector(vec, 2));
+}
+
+TEST_F(ServiceMessageTest, PushLocalFileHandle) {
+  ExpectDefaultHandleMessage();
+  const int kFakeFd = 12345;
+  LocalHandle handle{kFakeFd};
+  EXPECT_CALL(*endpoint(),
+              PushFileHandle(message_.get(), Matcher<const LocalHandle&>(
+                                                 FileHandleMatcher(kFakeFd))))
+      .WillOnce(Return(12))
+      .WillOnce(SetErrnoAndReturn(EIO, -1));
+  EXPECT_EQ(12, message_->PushFileHandle(handle));
+  EXPECT_EQ(-EIO, message_->PushFileHandle(handle));
+  handle.Release();  // Make sure we do not close the fake file descriptor.
+}
+
+TEST_F(ServiceMessageTest, PushBorrowedFileHandle) {
+  ExpectDefaultHandleMessage();
+  const int kFakeFd = 12345;
+  BorrowedHandle handle{kFakeFd};
+  EXPECT_CALL(*endpoint(),
+              PushFileHandle(message_.get(), Matcher<const BorrowedHandle&>(
+                                                 FileHandleMatcher(kFakeFd))))
+      .WillOnce(Return(13))
+      .WillOnce(SetErrnoAndReturn(EACCES, -1));
+  EXPECT_EQ(13, message_->PushFileHandle(handle));
+  EXPECT_EQ(-EACCES, message_->PushFileHandle(handle));
+}
+
+TEST_F(ServiceMessageTest, PushRemoteFileHandle) {
+  ExpectDefaultHandleMessage();
+  const int kFakeFd = 12345;
+  RemoteHandle handle{kFakeFd};
+  EXPECT_CALL(*endpoint(),
+              PushFileHandle(message_.get(), Matcher<const RemoteHandle&>(
+                                                 FileHandleMatcher(kFakeFd))))
+      .WillOnce(Return(kFakeFd))
+      .WillOnce(SetErrnoAndReturn(EIO, -1));
+  EXPECT_EQ(kFakeFd, message_->PushFileHandle(handle));
+  EXPECT_EQ(-EIO, message_->PushFileHandle(handle));
+}
+
+TEST_F(ServiceMessageTest, PushLocalChannelHandle) {
+  ExpectDefaultHandleMessage();
+  int32_t kValue = 12345;
+  LocalChannelHandle handle{nullptr, kValue};
+  EXPECT_CALL(*endpoint(), PushChannelHandle(message_.get(),
+                                             Matcher<const LocalChannelHandle&>(
+                                                 ChannelHandleMatcher(kValue))))
+      .WillOnce(Return(7))
+      .WillOnce(SetErrnoAndReturn(EIO, -1));
+  EXPECT_EQ(7, message_->PushChannelHandle(handle));
+  EXPECT_EQ(-EIO, message_->PushChannelHandle(handle));
+}
+
+TEST_F(ServiceMessageTest, PushBorrowedChannelHandle) {
+  ExpectDefaultHandleMessage();
+  int32_t kValue = 12345;
+  BorrowedChannelHandle handle{kValue};
+  EXPECT_CALL(
+      *endpoint(),
+      PushChannelHandle(message_.get(), Matcher<const BorrowedChannelHandle&>(
+                                            ChannelHandleMatcher(kValue))))
+      .WillOnce(Return(8))
+      .WillOnce(SetErrnoAndReturn(EIO, -1));
+  EXPECT_EQ(8, message_->PushChannelHandle(handle));
+  EXPECT_EQ(-EIO, message_->PushChannelHandle(handle));
+}
+
+TEST_F(ServiceMessageTest, PushRemoteChannelHandle) {
+  ExpectDefaultHandleMessage();
+  int32_t kValue = 12345;
+  RemoteChannelHandle handle{kValue};
+  EXPECT_CALL(
+      *endpoint(),
+      PushChannelHandle(message_.get(), Matcher<const RemoteChannelHandle&>(
+                                            ChannelHandleMatcher(kValue))))
+      .WillOnce(Return(kValue))
+      .WillOnce(SetErrnoAndReturn(EIO, -1));
+  EXPECT_EQ(kValue, message_->PushChannelHandle(handle));
+  EXPECT_EQ(-EIO, message_->PushChannelHandle(handle));
+}
+
+TEST_F(ServiceMessageTest, GetFileHandle) {
+  ExpectDefaultHandleMessage();
+  auto make_file_handle = [](FileReference ref) { return LocalHandle{ref}; };
+  EXPECT_CALL(*endpoint(), GetFileHandle(message_.get(), _))
+      .WillOnce(WithArg<1>(Invoke(make_file_handle)));
+  LocalHandle handle;
+  FileReference kRef = 12345;
+  EXPECT_TRUE(message_->GetFileHandle(kRef, &handle));
+  EXPECT_EQ(kRef, handle.Get());
+  handle.Release();  // Make sure we do not close the fake file descriptor.
+}
+
+TEST_F(ServiceMessageTest, GetFileHandleInvalid) {
+  ExpectDefaultHandleMessage();
+  LocalHandle handle;
+  FileReference kRef = -12;
+  EXPECT_TRUE(message_->GetFileHandle(kRef, &handle));
+  EXPECT_EQ(kRef, handle.Get());
+}
+
+TEST_F(ServiceMessageTest, GetFileHandleError) {
+  ExpectDefaultHandleMessage();
+  EXPECT_CALL(*endpoint(), GetFileHandle(message_.get(), _))
+      .WillOnce(WithoutArgs(Invoke([] { return LocalHandle{-EIO}; })));
+  LocalHandle handle;
+  FileReference kRef = 12345;
+  EXPECT_FALSE(message_->GetFileHandle(kRef, &handle));
+  EXPECT_EQ(-EIO, handle.Get());
+}
+
+TEST_F(ServiceMessageTest, GetChannelHandle) {
+  ExpectDefaultHandleMessage();
+  auto make_channel_handle = [](ChannelReference ref) {
+    return LocalChannelHandle{nullptr, ref};
+  };
+  EXPECT_CALL(*endpoint(), GetChannelHandle(message_.get(), _))
+      .WillOnce(WithArg<1>(Invoke(make_channel_handle)));
+  LocalChannelHandle handle;
+  ChannelReference kRef = 12345;
+  EXPECT_TRUE(message_->GetChannelHandle(kRef, &handle));
+  EXPECT_EQ(kRef, handle.value());
+}
+
+TEST_F(ServiceMessageTest, GetChannelHandleInvalid) {
+  ExpectDefaultHandleMessage();
+  LocalChannelHandle handle;
+  ChannelReference kRef = -12;
+  EXPECT_TRUE(message_->GetChannelHandle(kRef, &handle));
+  EXPECT_EQ(-12, handle.value());
+}
+
+TEST_F(ServiceMessageTest, GetChannelHandleError) {
+  ExpectDefaultHandleMessage();
+  EXPECT_CALL(*endpoint(), GetChannelHandle(message_.get(), _))
+      .WillOnce(WithoutArgs(Invoke([] {
+        return LocalChannelHandle{nullptr, -EIO};
+      })));
+  LocalChannelHandle handle;
+  ChannelReference kRef = 12345;
+  EXPECT_FALSE(message_->GetChannelHandle(kRef, &handle));
+  EXPECT_EQ(-EIO, handle.value());
+}
+
+TEST_F(ServiceMessageTest, ModifyChannelEvents) {
+  ExpectDefaultHandleMessage();
+  int kClearMask = 1;
+  int kSetMask = 2;
+  EXPECT_CALL(*endpoint(), ModifyChannelEvents(kTestCid, kClearMask, kSetMask))
+      .WillOnce(Return(0));
+  EXPECT_EQ(0, message_->ModifyChannelEvents(kClearMask, kSetMask));
+}
+
+TEST_F(ServiceMessageTest, PushChannelSameService) {
+  ExpectDefaultHandleMessage();
+  int kFlags = 123;
+  int32_t kValue = 12;
+  EXPECT_CALL(*endpoint(), PushChannel(message_.get(), kFlags, nullptr, _))
+      .WillOnce(DoAll(SetArgPointee<3>(kTestCid),
+                      Return(ByMove(RemoteChannelHandle{kValue}))));
+  int channel_id = -1;
+  auto status = message_->PushChannel(kFlags, nullptr, &channel_id);
+  ASSERT_TRUE(status);
+  EXPECT_EQ(kValue, status.get().value());
+  EXPECT_EQ(kTestCid, channel_id);
+}
+
+TEST_F(ServiceMessageTest, PushChannelFailure) {
+  ExpectDefaultHandleMessage();
+  int kFlags = 123;
+  EXPECT_CALL(*endpoint(), PushChannel(message_.get(), kFlags, nullptr, _))
+      .WillOnce(Return(ByMove(ErrorStatus{EIO})));
+  int channel_id = -1;
+  auto status = message_->PushChannel(kFlags, nullptr, &channel_id);
+  ASSERT_FALSE(status);
+  EXPECT_EQ(EIO, status.error());
+}
+
+TEST_F(ServiceMessageTest, PushChannelDifferentService) {
+  ExpectDefaultHandleMessage();
+  auto endpoint2 = std::make_unique<testing::StrictMock<MockEndpoint>>();
+  EXPECT_CALL(*endpoint2, SetService(_)).Times(2).WillRepeatedly(Return(0));
+  auto service2 =
+      std::make_shared<MockService>("MockSvc2", std::move(endpoint2));
+
+  int kFlags = 123;
+  int32_t kValue = 12;
+  EXPECT_CALL(*static_cast<MockEndpoint*>(service2->endpoint()),
+              PushChannel(message_.get(), kFlags, nullptr, _))
+      .WillOnce(DoAll(SetArgPointee<3>(kTestCid),
+                      Return(ByMove(RemoteChannelHandle{kValue}))));
+  int channel_id = -1;
+  auto status =
+      message_->PushChannel(service2.get(), kFlags, nullptr, &channel_id);
+  ASSERT_TRUE(status);
+  EXPECT_EQ(kValue, status.get().value());
+  EXPECT_EQ(kTestCid, channel_id);
+}
+
+TEST_F(ServiceMessageTest, CheckChannelSameService) {
+  ExpectDefaultHandleMessage();
+
+  auto test_channel = std::make_shared<Channel>();
+  ChannelReference kRef = 123;
+  EXPECT_CALL(*endpoint(), CheckChannel(message_.get(), kRef, _))
+      .WillOnce(DoAll(SetArgPointee<2>(test_channel.get()), Return(kTestCid)));
+  std::shared_ptr<Channel> channel;
+  auto status = message_->CheckChannel(kRef, &channel);
+  ASSERT_TRUE(status);
+  EXPECT_EQ(kTestCid, status.get());
+  EXPECT_EQ(test_channel, channel);
+}
+
+TEST_F(ServiceMessageTest, CheckChannelFailure) {
+  ExpectDefaultHandleMessage();
+  ChannelReference kRef = 123;
+  EXPECT_CALL(*endpoint(), CheckChannel(message_.get(), kRef, _))
+      .WillOnce(Return(ByMove(ErrorStatus{EOPNOTSUPP})));
+  std::shared_ptr<Channel> channel;
+  auto status = message_->CheckChannel(kRef, &channel);
+  ASSERT_FALSE(status);
+  EXPECT_EQ(EOPNOTSUPP, status.error());
+}
+
+TEST_F(ServiceMessageTest, CheckChannelDifferentService) {
+  ExpectDefaultHandleMessage();
+  auto endpoint2 = std::make_unique<testing::StrictMock<MockEndpoint>>();
+  EXPECT_CALL(*endpoint2, SetService(_)).Times(2).WillRepeatedly(Return(0));
+  auto service2 =
+      std::make_shared<MockService>("MockSvc2", std::move(endpoint2));
+
+  auto test_channel = std::make_shared<Channel>();
+  ChannelReference kRef = 123;
+  EXPECT_CALL(*static_cast<MockEndpoint*>(service2->endpoint()),
+              CheckChannel(message_.get(), kRef, _))
+      .WillOnce(DoAll(SetArgPointee<2>(test_channel.get()), Return(kTestCid)));
+  std::shared_ptr<Channel> channel;
+  auto status = message_->CheckChannel(service2.get(), kRef, &channel);
+  ASSERT_TRUE(status);
+  EXPECT_EQ(kTestCid, status.get());
+  EXPECT_EQ(test_channel, channel);
+}
diff --git a/libs/vr/libpdx/status.cpp b/libs/vr/libpdx/status.cpp
new file mode 100644
index 0000000..c275daf
--- /dev/null
+++ b/libs/vr/libpdx/status.cpp
@@ -0,0 +1,15 @@
+#include "pdx/status.h"
+
+#include <pdx/rpc/serialization.h>
+#include <string.h>
+
+namespace android {
+namespace pdx {
+
+std::string ErrorStatus::ErrorToString(int error_code) {
+  char message[1024] = {};
+  return strerror_r(error_code, message, sizeof(message));
+}
+
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx/status_tests.cpp b/libs/vr/libpdx/status_tests.cpp
new file mode 100644
index 0000000..d4e697c
--- /dev/null
+++ b/libs/vr/libpdx/status_tests.cpp
@@ -0,0 +1,125 @@
+#include <pdx/status.h>
+
+#include <gtest/gtest.h>
+
+using android::pdx::ErrorStatus;
+using android::pdx::Status;
+
+TEST(Status, DefaultInit) {
+  Status<int> status;
+  EXPECT_FALSE(status.ok());
+  EXPECT_TRUE(status.empty());
+  EXPECT_EQ(0, status.get());
+  EXPECT_EQ(0, status.error());
+}
+
+TEST(Status, InitalizeSuccess) {
+  Status<int> status_int{0};
+  EXPECT_FALSE(status_int.empty());
+  EXPECT_TRUE(status_int.ok());
+  EXPECT_EQ(0, status_int.get());
+  status_int = Status<int>(3);
+  EXPECT_FALSE(status_int.empty());
+  EXPECT_TRUE(status_int.ok());
+  EXPECT_EQ(3, status_int.get());
+  status_int = Status<int>(-3);
+  EXPECT_FALSE(status_int.empty());
+  EXPECT_TRUE(status_int.ok());
+  EXPECT_EQ(-3, status_int.get());
+
+  Status<std::string> status_str{"foo"};
+  EXPECT_FALSE(status_str.empty());
+  EXPECT_TRUE(status_str.ok());
+  EXPECT_EQ("foo", status_str.get());
+}
+
+TEST(Status, InitalizeError) {
+  Status<int> status_int = ErrorStatus(12);
+  EXPECT_FALSE(status_int.empty());
+  EXPECT_FALSE(status_int.ok());
+  EXPECT_EQ(0, status_int.get());
+  EXPECT_EQ(12, status_int.error());
+
+  Status<std::string> status_str = ErrorStatus(EIO);
+  EXPECT_FALSE(status_str.empty());
+  EXPECT_FALSE(status_str.ok());
+  EXPECT_EQ(EIO, status_str.error());
+}
+
+TEST(Status, ErrorMessage) {
+  Status<int> status = ErrorStatus(EIO);
+  EXPECT_EQ(status.GetErrorMessage(), strerror(EIO));
+
+  status = ErrorStatus(EINVAL);
+  EXPECT_EQ(status.GetErrorMessage(), strerror(EINVAL));
+}
+
+TEST(Status, Copy) {
+  Status<int> status1;
+  Status<int> status2;
+
+  status1 = Status<int>{12};
+  status2 = ErrorStatus(13);
+  EXPECT_FALSE(status1.empty());
+  EXPECT_FALSE(status2.empty());
+  EXPECT_TRUE(status1.ok());
+  EXPECT_FALSE(status2.ok());
+  EXPECT_EQ(12, status1.get());
+  EXPECT_EQ(0, status1.error());
+  EXPECT_EQ(0, status2.get());
+  EXPECT_EQ(13, status2.error());
+
+  status1 = status2;
+  EXPECT_FALSE(status1.empty());
+  EXPECT_FALSE(status2.empty());
+  EXPECT_FALSE(status1.ok());
+  EXPECT_FALSE(status2.ok());
+  EXPECT_EQ(0, status1.get());
+  EXPECT_EQ(13, status1.error());
+  EXPECT_EQ(0, status2.get());
+  EXPECT_EQ(13, status2.error());
+}
+
+TEST(Status, Move) {
+  Status<std::unique_ptr<int>> status1;
+  Status<std::unique_ptr<int>> status2;
+
+  status1 = Status<std::unique_ptr<int>>{std::unique_ptr<int>{new int{11}}};
+  status2 = Status<std::unique_ptr<int>>{std::unique_ptr<int>{new int{12}}};
+  EXPECT_FALSE(status1.empty());
+  EXPECT_FALSE(status2.empty());
+  EXPECT_TRUE(status1.ok());
+  EXPECT_TRUE(status2.ok());
+  EXPECT_EQ(11, *status1.get());
+  EXPECT_EQ(12, *status2.get());
+
+  Status<std::unique_ptr<int>> status3 = std::move(status2);
+  EXPECT_FALSE(status1.empty());
+  EXPECT_TRUE(status2.empty());
+  EXPECT_FALSE(status3.empty());
+  EXPECT_TRUE(status1.ok());
+  EXPECT_FALSE(status2.ok());
+  EXPECT_TRUE(status3.ok());
+  EXPECT_EQ(11, *status1.get());
+  EXPECT_EQ(nullptr, status2.get());
+  EXPECT_EQ(12, *status3.get());
+
+  std::swap(status1, status3);
+  EXPECT_EQ(12, *status1.get());
+  EXPECT_EQ(11, *status3.get());
+
+  status3 = std::move(status1);
+  EXPECT_TRUE(status1.empty());
+  EXPECT_EQ(12, *status3.get());
+}
+
+TEST(Status, Take) {
+  Status<std::unique_ptr<int>> status{std::unique_ptr<int>{new int{123}}};
+  EXPECT_FALSE(status.empty());
+  EXPECT_NE(nullptr, status.get());
+
+  auto data = status.take();
+  EXPECT_TRUE(status.empty());
+  EXPECT_EQ(nullptr, status.get());
+  EXPECT_EQ(123, *data);
+}
diff --git a/libs/vr/libpdx/thread_local_buffer_tests.cpp b/libs/vr/libpdx/thread_local_buffer_tests.cpp
new file mode 100644
index 0000000..c6a7b0b
--- /dev/null
+++ b/libs/vr/libpdx/thread_local_buffer_tests.cpp
@@ -0,0 +1,116 @@
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include <gtest/gtest.h>
+#include <pdx/rpc/message_buffer.h>
+
+namespace android {
+namespace pdx {
+namespace rpc {
+
+class ThreadLocalBufferTest {
+ public:
+  // Returns the unique address of the thread-local buffer. Used to test the
+  // correct behavior of the type-based thread local storage slot mapping
+  // mechanism.
+  template <typename Slot>
+  static std::uintptr_t GetSlotAddress() {
+    return reinterpret_cast<std::uintptr_t>(&MessageBuffer<Slot>::buffer_);
+  }
+
+  // Returns the raw value of the thread local buffer. Used to test the behavior
+  // of backing buffer initialization.
+  template <typename Slot>
+  static std::uintptr_t GetSlotValue() {
+    return reinterpret_cast<std::uintptr_t>(MessageBuffer<Slot>::buffer_);
+  }
+};
+
+}  // namespace rpc
+}  // namespace pdx
+}  // namespace android
+
+using namespace android::pdx::rpc;
+
+namespace {
+
+struct TypeTagA;
+struct TypeTagB;
+
+constexpr std::size_t kSendBufferIndex = 0;
+constexpr std::size_t kReceiveBufferIndex = 1;
+
+using SendSlotA = ThreadLocalSlot<TypeTagA, kSendBufferIndex>;
+using SendSlotB = ThreadLocalSlot<TypeTagB, kSendBufferIndex>;
+using ReceiveSlotA = ThreadLocalSlot<TypeTagA, kReceiveBufferIndex>;
+using ReceiveSlotB = ThreadLocalSlot<TypeTagB, kReceiveBufferIndex>;
+
+}  // anonymous namespace
+
+// Tests that index and type-based thread-local slot addressing works by
+// checking that the slot address is the same when the same index/type
+// combination is used and different when different combinations are used.
+TEST(ThreadLocalBufferTest, TypeSlots) {
+  auto id1 = ThreadLocalBufferTest::GetSlotAddress<SendSlotA>();
+  auto id2 = ThreadLocalBufferTest::GetSlotAddress<ReceiveSlotA>();
+  auto id3 = ThreadLocalBufferTest::GetSlotAddress<SendSlotB>();
+  auto id4 = ThreadLocalBufferTest::GetSlotAddress<ReceiveSlotB>();
+
+  EXPECT_NE(id1, id2);
+  EXPECT_NE(id3, id4);
+  EXPECT_NE(id1, id3);
+  EXPECT_NE(id2, id4);
+
+  auto id1_alias = ThreadLocalBufferTest::GetSlotAddress<SendSlotA>();
+  auto id2_alias = ThreadLocalBufferTest::GetSlotAddress<ReceiveSlotA>();
+  auto id3_alias = ThreadLocalBufferTest::GetSlotAddress<SendSlotB>();
+  auto id4_alias = ThreadLocalBufferTest::GetSlotAddress<ReceiveSlotB>();
+
+  EXPECT_EQ(id1, id1_alias);
+  EXPECT_EQ(id2, id2_alias);
+  EXPECT_EQ(id3, id3_alias);
+  EXPECT_EQ(id4, id4_alias);
+}
+
+// Tests that different threads get different buffers for the same slot address.
+TEST(ThreadLocalBufferTest, ThreadSlots) {
+  auto id1 = ThreadLocalBufferTest::GetSlotAddress<SendBuffer>();
+  std::uintptr_t id2 = 0U;
+
+  std::thread thread([&id2]() mutable {
+    id2 = ThreadLocalBufferTest::GetSlotAddress<SendBuffer>();
+  });
+  thread.join();
+
+  EXPECT_NE(0U, id1);
+  EXPECT_NE(0U, id2);
+  EXPECT_NE(id1, id2);
+}
+
+// Tests that thread-local buffers are allocated at the first buffer request.
+TEST(ThreadLocalBufferTest, InitialValue) {
+  struct TypeTagX;
+  using SendSlotX = ThreadLocalSlot<TypeTagX, kSendBufferIndex>;
+
+  auto value1 = ThreadLocalBufferTest::GetSlotValue<SendSlotX>();
+  auto value2 = ThreadLocalBufferTest::GetSlotValue<SendSlotX>();
+
+  EXPECT_EQ(0U, value1);
+  EXPECT_NE(0U, value2);
+}
+
+// Tests that the underlying buffers are the same for a given index/type pair
+// and different across index/type combinations.
+TEST(ThreadLocalBufferTest, BackingBuffer) {
+  auto& buffer1 = MessageBuffer<SendSlotA>::GetBuffer();
+  auto& buffer2 = MessageBuffer<SendSlotA>::GetBuffer();
+  auto& buffer3 = MessageBuffer<SendSlotB>::GetBuffer();
+  auto& buffer4 = MessageBuffer<SendSlotB>::GetBuffer();
+
+  EXPECT_EQ(buffer1.data(), buffer2.data());
+  EXPECT_EQ(buffer3.data(), buffer4.data());
+  EXPECT_NE(buffer1.data(), buffer3.data());
+  EXPECT_NE(buffer2.data(), buffer4.data());
+}
diff --git a/libs/vr/libpdx/variant_tests.cpp b/libs/vr/libpdx/variant_tests.cpp
new file mode 100644
index 0000000..c30c055
--- /dev/null
+++ b/libs/vr/libpdx/variant_tests.cpp
@@ -0,0 +1,1087 @@
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+#include <gtest/gtest.h>
+#include <pdx/rpc/variant.h>
+
+using namespace android::pdx;
+using namespace android::pdx::rpc;
+
+namespace {
+
+struct BaseType {
+  BaseType(int value) : value(value) {}
+  int value;
+};
+
+struct DerivedType : BaseType {
+  DerivedType(int value) : BaseType{value} {};
+};
+
+template <typename T>
+class TestType {
+ public:
+  TestType(const T& value) : value_(value) {}
+  TestType(T&& value) : value_(std::move(value)) {}
+  TestType(const TestType&) = default;
+  TestType(TestType&&) = default;
+
+  TestType& operator=(const TestType&) = default;
+  TestType& operator=(TestType&&) = default;
+
+  const T& get() const { return value_; }
+  T&& take() { return std::move(value_); }
+
+ private:
+  T value_;
+};
+
+template <typename T>
+class InstrumentType {
+ public:
+  InstrumentType(const T& value) : value_(value) { constructor_count_++; }
+  InstrumentType(T&& value) : value_(std::move(value)) { constructor_count_++; }
+  InstrumentType(const InstrumentType& other) : value_(other.value_) {
+    constructor_count_++;
+  }
+  InstrumentType(InstrumentType&& other) : value_(std::move(other.value_)) {
+    constructor_count_++;
+  }
+  InstrumentType(const TestType<T>& other) : value_(other.get()) {
+    constructor_count_++;
+  }
+  InstrumentType(TestType<T>&& other) : value_(other.take()) {
+    constructor_count_++;
+  }
+  ~InstrumentType() { destructor_count_++; }
+
+  InstrumentType& operator=(const InstrumentType& other) {
+    copy_assignment_count_++;
+    value_ = other.value_;
+    return *this;
+  }
+  InstrumentType& operator=(InstrumentType&& other) {
+    move_assignment_count_++;
+    value_ = std::move(other.value_);
+    return *this;
+  }
+
+  InstrumentType& operator=(const TestType<T>& other) {
+    copy_assignment_count_++;
+    value_ = other.get();
+    return *this;
+  }
+  InstrumentType& operator=(TestType<T>&& other) {
+    move_assignment_count_++;
+    value_ = other.take();
+    return *this;
+  }
+
+  static std::size_t constructor_count() { return constructor_count_; }
+  static std::size_t destructor_count() { return destructor_count_; }
+  static std::size_t move_assignment_count() { return move_assignment_count_; }
+  static std::size_t copy_assignment_count() { return copy_assignment_count_; }
+
+  const T& get() const { return value_; }
+  T&& take() { return std::move(value_); }
+
+  static void clear() {
+    constructor_count_ = 0;
+    destructor_count_ = 0;
+    move_assignment_count_ = 0;
+    copy_assignment_count_ = 0;
+  }
+
+ private:
+  T value_;
+
+  static std::size_t constructor_count_;
+  static std::size_t destructor_count_;
+  static std::size_t move_assignment_count_;
+  static std::size_t copy_assignment_count_;
+};
+
+template <typename T>
+std::size_t InstrumentType<T>::constructor_count_ = 0;
+template <typename T>
+std::size_t InstrumentType<T>::destructor_count_ = 0;
+template <typename T>
+std::size_t InstrumentType<T>::move_assignment_count_ = 0;
+template <typename T>
+std::size_t InstrumentType<T>::copy_assignment_count_ = 0;
+
+}  // anonymous namespace
+
+TEST(Variant, Assignment) {
+  // Assert basic type properties.
+  {
+    Variant<int, bool, float> v;
+    ASSERT_EQ(-1, v.index());
+    ASSERT_FALSE(v.is<int>());
+    ASSERT_FALSE(v.is<bool>());
+    ASSERT_FALSE(v.is<float>());
+  }
+
+  {
+    Variant<int, bool, float> v;
+    v = 10;
+    ASSERT_EQ(0, v.index());
+    ASSERT_TRUE(v.is<int>());
+    ASSERT_FALSE(v.is<bool>());
+    ASSERT_FALSE(v.is<float>());
+    EXPECT_EQ(10, std::get<int>(v));
+  }
+
+  {
+    Variant<int, bool, float> v;
+    v = false;
+    ASSERT_EQ(1, v.index());
+    ASSERT_FALSE(v.is<int>());
+    ASSERT_TRUE(v.is<bool>());
+    ASSERT_FALSE(v.is<float>());
+    EXPECT_EQ(false, std::get<bool>(v));
+  }
+
+  {
+    Variant<int, bool, float> v;
+    v = 1.0f;
+    ASSERT_EQ(2, v.index());
+    ASSERT_FALSE(v.is<int>());
+    ASSERT_FALSE(v.is<bool>());
+    ASSERT_TRUE(v.is<float>());
+    EXPECT_FLOAT_EQ(1.0f, std::get<float>(v));
+  }
+
+  {
+    Variant<int, bool, float> v;
+    // ERROR: More than one type is implicitly convertible from double.
+    // v = 1.0;
+    v = static_cast<float>(1.0);
+  }
+
+  {
+    Variant<int, bool, float> v;
+
+    double x = 1.1;
+    v = static_cast<float>(x);
+    ASSERT_EQ(2, v.index());
+    ASSERT_FALSE(v.is<int>());
+    ASSERT_FALSE(v.is<bool>());
+    ASSERT_TRUE(v.is<float>());
+    EXPECT_FLOAT_EQ(1.1, std::get<float>(v));
+  }
+
+  {
+    Variant<int, std::string> v;
+    ASSERT_EQ(-1, v.index());
+    ASSERT_FALSE(v.is<int>());
+    ASSERT_FALSE(v.is<std::string>());
+  }
+
+  {
+    Variant<int, std::string> v;
+    v = 20;
+    ASSERT_EQ(0, v.index());
+    ASSERT_TRUE(v.is<int>());
+    ASSERT_FALSE(v.is<std::string>());
+    EXPECT_EQ(20, std::get<int>(v));
+  }
+
+  {
+    Variant<int, std::string> v;
+    v = std::string("test");
+    ASSERT_EQ(1, v.index());
+    ASSERT_FALSE(v.is<int>());
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_EQ("test", std::get<std::string>(v));
+  }
+
+  {
+    Variant<int, std::string> v;
+    v = "test";
+    ASSERT_EQ(1, v.index());
+    ASSERT_FALSE(v.is<int>());
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_EQ("test", std::get<std::string>(v));
+  }
+
+  {
+    Variant<const char*> v1;
+    Variant<std::string> v2;
+
+    v1 = "test";
+    ASSERT_TRUE(v1.is<const char*>());
+    v2 = v1;
+    ASSERT_TRUE(v2.is<std::string>());
+    EXPECT_EQ("test", std::get<std::string>(v2));
+  }
+
+  {
+    Variant<int> a(1);
+    Variant<int> b;
+    ASSERT_TRUE(!a.empty());
+    ASSERT_TRUE(b.empty());
+
+    a = b;
+    ASSERT_TRUE(a.empty());
+    ASSERT_TRUE(b.empty());
+  }
+
+  {
+    Variant<int*, char*> v;
+
+    // ERROR: More than one type is implicitly convertible from nullptr.
+    // v = nullptr;
+
+    v = static_cast<int*>(nullptr);
+    EXPECT_TRUE(v.is<int*>());
+
+    v = static_cast<char*>(nullptr);
+    EXPECT_TRUE(v.is<char*>());
+  }
+
+  {
+    Variant<int*, char*> v;
+    int a = 10;
+    char b = 20;
+
+    v = &b;
+    ASSERT_TRUE(v.is<char*>());
+    EXPECT_EQ(&b, std::get<char*>(v));
+    EXPECT_EQ(b, *std::get<char*>(v));
+
+    v = &a;
+    ASSERT_TRUE(v.is<int*>());
+    EXPECT_EQ(&a, std::get<int*>(v));
+    EXPECT_EQ(a, *std::get<int*>(v));
+  }
+
+  {
+    using IntRef = std::reference_wrapper<int>;
+    Variant<IntRef> v;
+    int a = 10;
+
+    v = a;
+    ASSERT_TRUE(v.is<IntRef>());
+    EXPECT_EQ(a, std::get<IntRef>(v));
+
+    a = 20;
+    EXPECT_EQ(a, std::get<IntRef>(v));
+  }
+}
+
+TEST(Variant, MoveAssignment) {
+  {
+    Variant<std::string> v;
+    std::string s = "test";
+    v = std::move(s);
+
+    EXPECT_TRUE(s.empty());
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_EQ("test", std::get<std::string>(v));
+  }
+
+  {
+    Variant<std::string> v("test");
+    std::string s = "fizz";
+    s = std::move(std::get<std::string>(v));
+
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_TRUE(std::get<std::string>(v).empty());
+    EXPECT_EQ("test", s);
+  }
+
+  {
+    Variant<std::string> a("test");
+    Variant<std::string> b;
+
+    b = std::move(a);
+    ASSERT_TRUE(a.is<std::string>());
+    ASSERT_TRUE(b.is<std::string>());
+    EXPECT_TRUE(std::get<std::string>(a).empty());
+    EXPECT_EQ("test", std::get<std::string>(b));
+  }
+
+  {
+    Variant<std::string> a("test");
+    Variant<std::string> b("fizz");
+
+    b = std::move(a);
+    ASSERT_TRUE(a.is<std::string>());
+    ASSERT_TRUE(b.is<std::string>());
+    EXPECT_TRUE(std::get<std::string>(a).empty());
+    EXPECT_EQ("test", std::get<std::string>(b));
+  }
+
+  {
+    Variant<int, std::string> a("test");
+    Variant<int, std::string> b(10);
+
+    b = std::move(a);
+    ASSERT_TRUE(a.is<std::string>());
+    ASSERT_TRUE(b.is<std::string>());
+    EXPECT_TRUE(std::get<std::string>(a).empty());
+    EXPECT_EQ("test", std::get<std::string>(b));
+  }
+
+  {
+    Variant<int, std::string> a(10);
+    Variant<int, std::string> b("test");
+
+    b = std::move(a);
+    ASSERT_TRUE(a.is<int>());
+    ASSERT_TRUE(b.is<int>());
+    EXPECT_EQ(10, std::get<int>(a));
+    EXPECT_EQ(10, std::get<int>(b));
+  }
+}
+
+TEST(Variant, Constructor) {
+  {
+    Variant<int, bool, float> v(true);
+    EXPECT_TRUE(v.is<bool>());
+  }
+
+  {
+    Variant<int, bool, float> v(10);
+    EXPECT_TRUE(v.is<int>());
+  }
+
+  {
+    Variant<int, bool, float> v(10.1f);
+    EXPECT_TRUE(v.is<float>());
+  }
+
+  {
+    Variant<float, std::string> v(10.);
+    EXPECT_TRUE(v.is<float>());
+  }
+
+  {
+    TestType<int> i(1);
+    Variant<int, bool, float> v(i.take());
+    ASSERT_TRUE(v.is<int>());
+    EXPECT_EQ(1, std::get<int>(v));
+  }
+
+  {
+    TestType<bool> b(true);
+    Variant<int, bool, float> v(b.take());
+    ASSERT_TRUE(v.is<bool>());
+    EXPECT_EQ(true, std::get<bool>(v));
+  }
+
+  {
+    Variant<const char*> c("test");
+    Variant<std::string> s(c);
+    ASSERT_TRUE(s.is<std::string>());
+    EXPECT_EQ("test", std::get<std::string>(s));
+  }
+
+  {
+    Variant<int, bool, float> a(true);
+    Variant<int, bool, float> b(a);
+
+    ASSERT_TRUE(b.is<bool>());
+  }
+
+  {
+    using IntRef = std::reference_wrapper<int>;
+    int a = 10;
+    Variant<IntRef> v(a);
+    TestType<IntRef> t(a);
+
+    ASSERT_TRUE(v.is<IntRef>());
+    EXPECT_EQ(a, std::get<IntRef>(v));
+    EXPECT_EQ(a, t.get());
+
+    a = 20;
+    EXPECT_EQ(a, std::get<IntRef>(v));
+    EXPECT_EQ(a, t.get());
+  }
+}
+
+// Verify correct ctor/dtor and assignment behavior used an instrumented type.
+TEST(Variant, CopyMoveConstructAssign) {
+  {
+    InstrumentType<int>::clear();
+
+    // Default construct to empty, no InstrumentType activity.
+    Variant<int, InstrumentType<int>> v;
+    ASSERT_EQ(0u, InstrumentType<int>::constructor_count());
+    ASSERT_EQ(0u, InstrumentType<int>::destructor_count());
+    ASSERT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    ASSERT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from int type, no InstrumentType activity.
+    Variant<int, InstrumentType<int>> v;
+    v = 10;
+    EXPECT_EQ(0u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from int type, no InstrumentType activity.
+    Variant<int, InstrumentType<int>> v(10);
+    EXPECT_EQ(0u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from temporary, temporary ctor/dtor.
+    Variant<int, InstrumentType<int>> v;
+    v = InstrumentType<int>(25);
+    EXPECT_EQ(2u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(1u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from temporary, temporary ctor/dtor.
+    Variant<int, InstrumentType<int>> v(InstrumentType<int>(25));
+    EXPECT_EQ(2u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(1u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from temporary, temporary ctor/dtor.
+    Variant<int, InstrumentType<int>> v(InstrumentType<int>(25));
+
+    // Assign from temporary, temporary ctor/dtor.
+    v = InstrumentType<int>(35);
+    EXPECT_EQ(3u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(2u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(1u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from temporary, temporary ctor/dtor.
+    Variant<int, InstrumentType<int>> v(InstrumentType<int>(25));
+
+    // dtor.
+    v = 10;
+    EXPECT_EQ(2u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(2u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from temporary, temporary ctor/dtor.
+    Variant<int, InstrumentType<int>> v(InstrumentType<int>(25));
+
+    EXPECT_EQ(2u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(1u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+  EXPECT_EQ(2u, InstrumentType<int>::constructor_count());
+  EXPECT_EQ(2u, InstrumentType<int>::destructor_count());
+  EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+  EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from other temporary.
+    Variant<int, InstrumentType<int>> v(TestType<int>(10));
+    EXPECT_EQ(1u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from other temporary.
+    Variant<int, InstrumentType<int>> v(TestType<int>(10));
+    // Assign from other temporary.
+    v = TestType<int>(11);
+    EXPECT_EQ(1u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(1u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from other temporary.
+    Variant<int, InstrumentType<int>> v(TestType<int>(10));
+    // Assign from empty Variant.
+    v = Variant<int, InstrumentType<int>>();
+    EXPECT_EQ(1u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(1u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    TestType<int> other(10);
+    // Construct from other.
+    Variant<int, InstrumentType<int>> v(other);
+
+    EXPECT_EQ(1u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(0u, InstrumentType<int>::copy_assignment_count());
+  }
+
+  {
+    InstrumentType<int>::clear();
+
+    // Construct from other temporary.
+    Variant<int, InstrumentType<int>> v(TestType<int>(0));
+    TestType<int> other(10);
+    // Assign from other.
+    v = other;
+    EXPECT_EQ(1u, InstrumentType<int>::constructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::destructor_count());
+    EXPECT_EQ(0u, InstrumentType<int>::move_assignment_count());
+    EXPECT_EQ(1u, InstrumentType<int>::copy_assignment_count());
+  }
+}
+
+TEST(Variant, MoveConstructor) {
+  {
+    std::unique_ptr<int> pointer = std::make_unique<int>(10);
+    Variant<std::unique_ptr<int>> v(std::move(pointer));
+    ASSERT_TRUE(v.is<std::unique_ptr<int>>());
+    EXPECT_TRUE(std::get<std::unique_ptr<int>>(v) != nullptr);
+    EXPECT_TRUE(pointer == nullptr);
+  }
+
+  {
+    Variant<std::unique_ptr<int>> a(std::make_unique<int>(10));
+    Variant<std::unique_ptr<int>> b(std::move(a));
+
+    ASSERT_TRUE(a.is<std::unique_ptr<int>>());
+    ASSERT_TRUE(b.is<std::unique_ptr<int>>());
+    EXPECT_TRUE(std::get<std::unique_ptr<int>>(a) == nullptr);
+    EXPECT_TRUE(std::get<std::unique_ptr<int>>(b) != nullptr);
+  }
+}
+
+TEST(Variant, IndexOf) {
+  Variant<int, bool, float> v1;
+
+  EXPECT_EQ(0, v1.index_of<int>());
+  EXPECT_EQ(1, v1.index_of<bool>());
+  EXPECT_EQ(2, v1.index_of<float>());
+
+  Variant<int, bool, float, int> v2;
+
+  EXPECT_EQ(0, v2.index_of<int>());
+  EXPECT_EQ(1, v2.index_of<bool>());
+  EXPECT_EQ(2, v2.index_of<float>());
+}
+
+struct Visitor {
+  int int_value = 0;
+  bool bool_value = false;
+  float float_value = 0.0;
+  bool empty_value = false;
+
+  void Visit(int value) { int_value = value; }
+  void Visit(bool value) { bool_value = value; }
+  void Visit(float value) { float_value = value; }
+  void Visit(EmptyVariant) { empty_value = true; }
+};
+
+TEST(Variant, Visit) {
+  {
+    Variant<int, bool, float> v(10);
+    EXPECT_TRUE(v.is<int>());
+
+    Visitor visitor;
+    v.Visit([&visitor](const auto& value) { visitor.Visit(value); });
+    EXPECT_EQ(10, visitor.int_value);
+
+    visitor = {};
+    v = true;
+    v.Visit([&visitor](const auto& value) { visitor.Visit(value); });
+    EXPECT_EQ(true, visitor.bool_value);
+  }
+
+  {
+    Variant<int, bool, float> v;
+    EXPECT_EQ(-1, v.index());
+
+    Visitor visitor;
+    v.Visit([&visitor](const auto& value) { visitor.Visit(value); });
+    EXPECT_TRUE(visitor.empty_value);
+  }
+
+  {
+    Variant<std::string> v("test");
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_FALSE(std::get<std::string>(v).empty());
+
+    v.Visit([](auto&& value) {
+      std::remove_reference_t<decltype(value)> empty;
+      std::swap(empty, value);
+    });
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_TRUE(std::get<std::string>(v).empty());
+  }
+}
+
+TEST(Variant, Become) {
+  {
+    Variant<int, bool, float> v;
+
+    v.Become(0);
+    EXPECT_TRUE(v.is<int>());
+
+    v.Become(1);
+    EXPECT_TRUE(v.is<bool>());
+
+    v.Become(2);
+    EXPECT_TRUE(v.is<float>());
+
+    v.Become(3);
+    EXPECT_TRUE(v.empty());
+
+    v.Become(-1);
+    EXPECT_TRUE(v.empty());
+
+    v.Become(-2);
+    EXPECT_TRUE(v.empty());
+  }
+
+  {
+    Variant<int, bool, float> v;
+
+    v.Become(0, 10);
+    ASSERT_TRUE(v.is<int>());
+    EXPECT_EQ(10, std::get<int>(v));
+
+    v.Become(1, true);
+    ASSERT_TRUE(v.is<bool>());
+    EXPECT_EQ(true, std::get<bool>(v));
+
+    v.Become(2, 2.0f);
+    ASSERT_TRUE(v.is<float>());
+    EXPECT_FLOAT_EQ(2.0f, std::get<float>(v));
+
+    v.Become(3, 10);
+    EXPECT_TRUE(v.empty());
+
+    v.Become(-1, 10);
+    EXPECT_TRUE(v.empty());
+
+    v.Become(-2, 20);
+    EXPECT_TRUE(v.empty());
+  }
+
+  {
+    Variant<std::string> v;
+
+    v.Become(0);
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_TRUE(std::get<std::string>(v).empty());
+  }
+
+  {
+    Variant<std::string> v;
+
+    v.Become(0, "test");
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_EQ("test", std::get<std::string>(v));
+  }
+
+  {
+    Variant<std::string> v("foo");
+
+    v.Become(0, "bar");
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_EQ("foo", std::get<std::string>(v));
+  }
+}
+
+TEST(Variant, Swap) {
+  {
+    Variant<std::string> a;
+    Variant<std::string> b;
+
+    std::swap(a, b);
+    EXPECT_TRUE(a.empty());
+    EXPECT_TRUE(b.empty());
+  }
+
+  {
+    Variant<std::string> a("1");
+    Variant<std::string> b;
+
+    std::swap(a, b);
+    EXPECT_TRUE(!a.empty());
+    EXPECT_TRUE(!b.empty());
+    ASSERT_TRUE(b.is<std::string>());
+    EXPECT_EQ("1", std::get<std::string>(b));
+  }
+
+  {
+    Variant<std::string> a;
+    Variant<std::string> b("1");
+
+    std::swap(a, b);
+    EXPECT_TRUE(!a.empty());
+    EXPECT_TRUE(!b.empty());
+    ASSERT_TRUE(a.is<std::string>());
+    EXPECT_EQ("1", std::get<std::string>(a));
+  }
+
+  {
+    Variant<std::string> a("1");
+    Variant<std::string> b("2");
+
+    std::swap(a, b);
+    ASSERT_TRUE(a.is<std::string>());
+    ASSERT_TRUE(b.is<std::string>());
+    EXPECT_EQ("2", std::get<std::string>(a));
+    EXPECT_EQ("1", std::get<std::string>(b));
+  }
+
+  {
+    Variant<int, std::string> a(10);
+    Variant<int, std::string> b("1");
+
+    std::swap(a, b);
+    ASSERT_TRUE(a.is<std::string>());
+    ASSERT_TRUE(b.is<int>());
+    EXPECT_EQ("1", std::get<std::string>(a));
+    EXPECT_EQ(10, std::get<int>(b));
+  }
+
+  {
+    Variant<int, std::string> a("1");
+    Variant<int, std::string> b(10);
+
+    std::swap(a, b);
+    ASSERT_TRUE(a.is<int>());
+    ASSERT_TRUE(b.is<std::string>());
+    EXPECT_EQ(10, std::get<int>(a));
+    EXPECT_EQ("1", std::get<std::string>(b));
+  }
+}
+
+TEST(Variant, Get) {
+  {
+    Variant<int, bool, float, int> v;
+
+    EXPECT_EQ(nullptr, &std::get<int>(v));
+    EXPECT_EQ(nullptr, &std::get<bool>(v));
+    EXPECT_EQ(nullptr, &std::get<float>(v));
+    EXPECT_EQ(nullptr, &std::get<0>(v));
+    EXPECT_EQ(nullptr, &std::get<1>(v));
+    EXPECT_EQ(nullptr, &std::get<2>(v));
+    EXPECT_EQ(nullptr, &std::get<3>(v));
+  }
+
+  {
+    Variant<int, bool, float, int> v;
+    v = 9;
+    ASSERT_TRUE(v.is<int>())
+        << "Expected type " << v.index_of<int>() << " got type " << v.index();
+    EXPECT_EQ(9, std::get<int>(v));
+    EXPECT_EQ(9, std::get<0>(v));
+
+    std::get<int>(v) = 10;
+    EXPECT_EQ(10, std::get<int>(v));
+    EXPECT_EQ(10, std::get<0>(v));
+
+    std::get<0>(v) = 11;
+    EXPECT_EQ(11, std::get<int>(v));
+    EXPECT_EQ(11, std::get<0>(v));
+
+    std::get<3>(v) = 12;
+    EXPECT_EQ(12, std::get<int>(v));
+    EXPECT_EQ(12, std::get<3>(v));
+  }
+
+  {
+    Variant<int, bool, float, int> v;
+    v = false;
+    ASSERT_TRUE(v.is<bool>())
+        << "Expected type " << v.index_of<bool>() << " got type " << v.index();
+    EXPECT_EQ(false, std::get<bool>(v));
+    EXPECT_EQ(false, std::get<1>(v));
+
+    std::get<bool>(v) = true;
+    EXPECT_EQ(true, std::get<bool>(v));
+    EXPECT_EQ(true, std::get<1>(v));
+
+    std::get<bool>(v) = false;
+    EXPECT_EQ(false, std::get<bool>(v));
+    EXPECT_EQ(false, std::get<1>(v));
+
+    std::get<1>(v) = true;
+    EXPECT_EQ(true, std::get<bool>(v));
+    EXPECT_EQ(true, std::get<1>(v));
+
+    std::get<1>(v) = false;
+    EXPECT_EQ(false, std::get<bool>(v));
+    EXPECT_EQ(false, std::get<1>(v));
+  }
+
+  {
+    Variant<int, bool, float, int> v;
+    v = 1.0f;
+    ASSERT_TRUE(v.is<float>())
+        << "Expected type " << v.index_of<float>() << " got type " << v.index();
+    EXPECT_EQ(2, v.index());
+    EXPECT_FLOAT_EQ(1.0, std::get<float>(v));
+    EXPECT_FLOAT_EQ(1.0, std::get<2>(v));
+
+    std::get<float>(v) = 1.1;
+    EXPECT_FLOAT_EQ(1.1, std::get<float>(v));
+    EXPECT_FLOAT_EQ(1.1, std::get<2>(v));
+
+    std::get<float>(v) = -3.0;
+    EXPECT_FLOAT_EQ(-3.0, std::get<float>(v));
+    EXPECT_FLOAT_EQ(-3.0, std::get<2>(v));
+
+    std::get<2>(v) = 1.1;
+    EXPECT_FLOAT_EQ(1.1, std::get<float>(v));
+    EXPECT_FLOAT_EQ(1.1, std::get<2>(v));
+
+    std::get<2>(v) = -3.0;
+    EXPECT_FLOAT_EQ(-3.0, std::get<float>(v));
+    EXPECT_FLOAT_EQ(-3.0, std::get<2>(v));
+  }
+
+  {
+    Variant<std::unique_ptr<int>> v(std::make_unique<int>(10));
+    std::unique_ptr<int> pointer = std::move(std::get<std::unique_ptr<int>>(v));
+    ASSERT_FALSE(v.empty());
+    EXPECT_TRUE(pointer != nullptr);
+    EXPECT_TRUE(std::get<std::unique_ptr<int>>(v) == nullptr);
+  }
+
+  {
+    Variant<std::string> v("test");
+    std::string s = std::get<std::string>(std::move(v));
+    EXPECT_EQ("test", s);
+  }
+}
+
+TEST(Variant, IfAnyOf) {
+  {
+    Variant<int, float> v(10);
+    ASSERT_TRUE(v.is<int>());
+
+    bool b = false;
+    EXPECT_TRUE(IfAnyOf<int>::Get(&v, &b));
+    EXPECT_TRUE(b);
+
+    float f = 0.0f;
+    EXPECT_TRUE((IfAnyOf<int, float>::Get(&v, &f)));
+    EXPECT_FLOAT_EQ(10.f, f);
+  }
+
+  {
+    const Variant<int, float> v(10);
+    ASSERT_TRUE(v.is<int>());
+
+    bool b = false;
+    EXPECT_TRUE(IfAnyOf<int>::Get(&v, &b));
+    EXPECT_TRUE(b);
+
+    float f = 0.0f;
+    EXPECT_TRUE((IfAnyOf<int, float>::Get(&v, &f)));
+    EXPECT_FLOAT_EQ(10.f, f);
+  }
+
+  {
+    Variant<int, float> v(10);
+    ASSERT_TRUE(v.is<int>());
+
+    bool b = false;
+    EXPECT_TRUE(IfAnyOf<int>::Call(&v, [&b](const auto& value) { b = value; }));
+    EXPECT_TRUE(b);
+
+    float f = 0.0f;
+    EXPECT_TRUE((
+        IfAnyOf<int, float>::Call(&v, [&f](const auto& value) { f = value; })));
+    EXPECT_FLOAT_EQ(10.f, f);
+  }
+
+  {
+    Variant<std::unique_ptr<int>, int> v(std::make_unique<int>(10));
+    ASSERT_TRUE(v.is<std::unique_ptr<int>>());
+    const int* original_v = std::get<std::unique_ptr<int>>(v).get();
+
+    std::unique_ptr<int> u(std::make_unique<int>(20));
+
+    EXPECT_TRUE(IfAnyOf<std::unique_ptr<int>>::Take(&v, &u));
+    ASSERT_TRUE(v.is<std::unique_ptr<int>>());
+    EXPECT_TRUE(std::get<std::unique_ptr<int>>(v) == nullptr);
+    EXPECT_EQ(u.get(), original_v);
+  }
+
+  {
+    Variant<std::unique_ptr<DerivedType>, int> v(
+        std::make_unique<DerivedType>(10));
+    ASSERT_TRUE(v.is<std::unique_ptr<DerivedType>>());
+    const DerivedType* original_v =
+        std::get<std::unique_ptr<DerivedType>>(v).get();
+
+    std::unique_ptr<BaseType> u(std::make_unique<BaseType>(20));
+
+    EXPECT_TRUE(IfAnyOf<std::unique_ptr<DerivedType>>::Take(&v, &u));
+    ASSERT_TRUE(v.is<std::unique_ptr<DerivedType>>());
+    EXPECT_TRUE(std::get<std::unique_ptr<DerivedType>>(v) == nullptr);
+    EXPECT_EQ(u.get(), original_v);
+  }
+
+  {
+    Variant<std::unique_ptr<int>, int> v(std::make_unique<int>(10));
+    ASSERT_TRUE(v.is<std::unique_ptr<int>>());
+    const int* original_v = std::get<std::unique_ptr<int>>(v).get();
+
+    std::unique_ptr<int> u(std::make_unique<int>(20));
+
+    EXPECT_TRUE(IfAnyOf<std::unique_ptr<int>>::Call(
+        &v, [&u](auto&& value) { u = std::move(value); }));
+    ASSERT_TRUE(v.is<std::unique_ptr<int>>());
+    EXPECT_TRUE(std::get<std::unique_ptr<int>>(v) == nullptr);
+    EXPECT_EQ(u.get(), original_v);
+  }
+
+  {
+    Variant<int, bool, float> v(true);
+    ASSERT_TRUE(v.is<bool>());
+
+    float f = 0.f;
+    EXPECT_FALSE((IfAnyOf<int, float>::Get(&v, &f)));
+    EXPECT_FLOAT_EQ(0.f, f);
+  }
+
+  {
+    Variant<std::string, int> v("foo");
+    ASSERT_TRUE(v.is<std::string>());
+
+    std::string s = "bar";
+    EXPECT_TRUE(IfAnyOf<std::string>::Swap(&v, &s));
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_EQ("bar", std::get<std::string>(v));
+    EXPECT_EQ("foo", s);
+  }
+
+  {
+    Variant<std::string, const char*> v(static_cast<const char*>("foo"));
+    ASSERT_TRUE(v.is<const char*>());
+
+    std::string s = "bar";
+    EXPECT_TRUE((IfAnyOf<std::string, const char*>::Take(&v, &s)));
+    ASSERT_TRUE(v.is<const char*>());
+    EXPECT_EQ("foo", std::get<const char*>(v));
+    EXPECT_EQ("foo", s);
+
+    v = std::string("bar");
+    ASSERT_TRUE(v.is<std::string>());
+
+    EXPECT_TRUE((IfAnyOf<std::string, const char*>::Take(&v, &s)));
+    ASSERT_TRUE(v.is<std::string>());
+    EXPECT_EQ("bar", s);
+  }
+
+  {
+    Variant<std::string, const char*> v;
+    ASSERT_TRUE(v.empty());
+
+    std::string s = "bar";
+    EXPECT_FALSE((IfAnyOf<std::string, const char*>::Take(&v, &s)));
+    EXPECT_EQ("bar", s);
+  }
+
+  {
+    Variant<std::string, const char*> v(static_cast<const char*>("test"));
+    ASSERT_TRUE(v.is<const char*>());
+
+    std::string s;
+    EXPECT_FALSE(IfAnyOf<>::Take(&v, &s));
+    EXPECT_TRUE(s.empty());
+  }
+}
+
+TEST(Variant, ConstVolatile) {
+  {
+    Variant<const int> v(10);
+    ASSERT_TRUE(v.is<const int>());
+    EXPECT_EQ(10, std::get<const int>(v));
+  }
+
+  {
+    Variant<const std::string> v("test");
+    ASSERT_TRUE(v.is<const std::string>());
+    EXPECT_EQ("test", std::get<const std::string>(v));
+  }
+
+  {
+    Variant<volatile int, std::string> v(10);
+    ASSERT_TRUE(v.is<volatile int>());
+    EXPECT_EQ(10, std::get<volatile int>(v));
+  }
+}
+
+TEST(Variant, HasType) {
+  EXPECT_TRUE((detail::HasType<int, int, float, bool>::value));
+  EXPECT_FALSE((detail::HasType<char, int, float, bool>::value));
+  EXPECT_FALSE(detail::HasType<>::value);
+
+  EXPECT_TRUE((detail::HasTypeIgnoreRef<int&, int, float, bool>::value));
+  EXPECT_FALSE((detail::HasTypeIgnoreRef<char&, int, float, bool>::value));
+}
+
+TEST(Variant, Set) {
+  EXPECT_TRUE((detail::Set<int, bool, float>::template IsSubset<int, bool,
+                                                                float>::value));
+  EXPECT_TRUE(
+      (detail::Set<int, bool, float>::template IsSubset<bool, float>::value));
+  EXPECT_TRUE((detail::Set<int, bool, float>::template IsSubset<float>::value));
+  EXPECT_TRUE((detail::Set<int, bool, float>::template IsSubset<>::value));
+
+  EXPECT_FALSE(
+      (detail::Set<int, bool, float>::template IsSubset<int, bool, float,
+                                                        char>::value));
+  EXPECT_FALSE((detail::Set<int, bool, float>::template IsSubset<bool, float,
+                                                                 char>::value));
+  EXPECT_FALSE(
+      (detail::Set<int, bool, float>::template IsSubset<float, char>::value));
+  EXPECT_FALSE((detail::Set<int, bool, float>::template IsSubset<char>::value));
+
+  EXPECT_TRUE(detail::Set<>::template IsSubset<>::value);
+  EXPECT_FALSE(detail::Set<>::template IsSubset<int>::value);
+  EXPECT_FALSE((detail::Set<>::template IsSubset<int, float>::value));
+}
diff --git a/libs/vr/libpdx_default_transport/Android.bp b/libs/vr/libpdx_default_transport/Android.bp
new file mode 100644
index 0000000..655adb8
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/Android.bp
@@ -0,0 +1,69 @@
+cc_defaults {
+    name: "pdx_default_transport_compiler_defaults",
+    clang: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+}
+
+cc_defaults {
+    name: "pdx_default_transport_lib_defaults",
+    export_include_dirs: ["private"],
+    whole_static_libs: ["libpdx"],
+}
+
+cc_defaults {
+    name: "pdx_use_transport_servicefs",
+    export_include_dirs: ["private/servicefs"],
+    whole_static_libs: ["libpdx_servicefs", "libservicefs"],
+}
+
+cc_defaults {
+    name: "pdx_use_transport_uds",
+    export_include_dirs: ["private/uds"],
+    whole_static_libs: ["libpdx_uds"],
+}
+
+cc_library_static {
+    name: "libpdx_default_transport",
+    defaults: [
+        "pdx_default_transport_compiler_defaults",
+        "pdx_default_transport_lib_defaults",
+        "pdx_use_transport_uds",
+    ],
+}
+
+cc_binary {
+    name: "servicetool",
+    defaults: ["pdx_default_transport_compiler_defaults"],
+    srcs: [
+        "servicetool.cpp",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    static_libs: [
+        "libpdx_default_transport",
+    ],
+}
+
+// Benchmarks.
+cc_binary {
+    name: "pdx_benchmarks",
+    defaults: ["pdx_default_transport_compiler_defaults"],
+    srcs: [
+        "pdx_benchmarks.cpp",
+    ],
+    shared_libs: [
+        "libchrome",
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+    static_libs: [
+        "libpdx_default_transport",
+    ],
+}
+
diff --git a/libs/vr/libpdx_default_transport/pdx_benchmarks.cpp b/libs/vr/libpdx_default_transport/pdx_benchmarks.cpp
new file mode 100644
index 0000000..de02401
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/pdx_benchmarks.cpp
@@ -0,0 +1,1098 @@
+// Use ALWAYS at the tag level. Control is performed manually during command
+// line processing.
+#define ATRACE_TAG ATRACE_TAG_ALWAYS
+#include <utils/Trace.h>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <errno.h>
+#include <getopt.h>
+#include <pdx/client.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <pdx/default_transport/service_endpoint.h>
+#include <pdx/rpc/buffer_wrapper.h>
+#include <pdx/rpc/default_initialization_allocator.h>
+#include <pdx/rpc/message_buffer.h>
+#include <pdx/rpc/remote_method.h>
+#include <pdx/rpc/serializable.h>
+#include <pdx/service.h>
+#include <sys/prctl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <cstdlib>
+#include <functional>
+#include <future>
+#include <iomanip>
+#include <ios>
+#include <iostream>
+#include <memory>
+#include <numeric>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <vector>
+
+using android::pdx::Channel;
+using android::pdx::ClientBase;
+using android::pdx::Endpoint;
+using android::pdx::Message;
+using android::pdx::Service;
+using android::pdx::ServiceBase;
+using android::pdx::default_transport::ClientChannelFactory;
+using android::pdx::Status;
+using android::pdx::Transaction;
+using android::pdx::rpc::BufferWrapper;
+using android::pdx::rpc::DefaultInitializationAllocator;
+using android::pdx::rpc::MessageBuffer;
+using android::pdx::rpc::DispatchRemoteMethod;
+using android::pdx::rpc::RemoteMethodReturn;
+using android::pdx::rpc::ReplyBuffer;
+using android::pdx::rpc::Void;
+using android::pdx::rpc::WrapBuffer;
+
+namespace {
+
+constexpr size_t kMaxMessageSize = 4096 * 1024;
+
+std::string GetServicePath(const std::string& path, int instance_id) {
+  return path + std::to_string(instance_id);
+}
+
+void SetThreadName(const std::string& name) {
+  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(name.c_str()), 0, 0, 0);
+}
+
+constexpr uint64_t kNanosPerSecond = 1000000000llu;
+
+uint64_t GetClockNs() {
+  timespec t;
+  clock_gettime(CLOCK_MONOTONIC, &t);
+  return kNanosPerSecond * t.tv_sec + t.tv_nsec;
+}
+
+template <typename T>
+ssize_t ssizeof(const T&) {
+  return static_cast<ssize_t>(sizeof(T));
+}
+
+class SchedStats {
+ public:
+  SchedStats() : SchedStats(gettid()) {}
+  SchedStats(pid_t task_id) : task_id_(task_id) {}
+  SchedStats(const SchedStats&) = default;
+  SchedStats& operator=(const SchedStats&) = default;
+
+  void Update() {
+    const std::string stats_path =
+        "/proc/" + std::to_string(task_id_) + "/schedstat";
+
+    std::string line;
+    base::ReadFileToString(base::FilePath{stats_path}, &line);
+    std::vector<std::string> stats = base::SplitString(
+        line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+    CHECK_EQ(3u, stats.size());
+
+    // Calculate the deltas since the last update. Each value is absolute since
+    // the task started.
+    uint64_t current_cpu_time_ns = std::stoull(stats[0]);
+    uint64_t current_wait_ns = std::stoull(stats[1]);
+    uint64_t current_timeslices = std::stoull(stats[2]);
+    cpu_time_ns_ = current_cpu_time_ns - last_cpu_time_ns_;
+    wait_ns_ = current_wait_ns - last_wait_ns_;
+    timeslices_ = current_timeslices - last_timeslices_;
+    last_cpu_time_ns_ = current_cpu_time_ns;
+    last_wait_ns_ = current_wait_ns;
+    last_timeslices_ = current_timeslices;
+  }
+
+  pid_t task_id() const { return task_id_; }
+  uint64_t cpu_time_ns() const { return cpu_time_ns_; }
+  uint64_t wait_ns() const { return wait_ns_; }
+  uint64_t timeslices() const { return timeslices_; }
+
+  double cpu_time_s() const {
+    return static_cast<double>(cpu_time_ns_) / kNanosPerSecond;
+  }
+  double wait_s() const {
+    return static_cast<double>(wait_ns_) / kNanosPerSecond;
+  }
+
+ private:
+  int32_t task_id_;
+  uint64_t cpu_time_ns_ = 0;
+  uint64_t last_cpu_time_ns_ = 0;
+  uint64_t wait_ns_ = 0;
+  uint64_t last_wait_ns_ = 0;
+  uint64_t timeslices_ = 0;
+  uint64_t last_timeslices_ = 0;
+
+  PDX_SERIALIZABLE_MEMBERS(SchedStats, task_id_, cpu_time_ns_, wait_ns_,
+                           timeslices_);
+};
+
+// Opcodes for client/service protocol.
+struct BenchmarkOps {
+  enum : int {
+    Nop,
+    Read,
+    Write,
+    Echo,
+    Stats,
+    WriteVector,
+    EchoVector,
+    Quit,
+  };
+};
+
+struct BenchmarkRPC {
+  PDX_REMOTE_METHOD(Stats, BenchmarkOps::Stats,
+                    std::tuple<uint64_t, uint64_t, SchedStats>(Void));
+  PDX_REMOTE_METHOD(WriteVector, BenchmarkOps::WriteVector,
+                    int(const BufferWrapper<std::vector<uint8_t>> data));
+  PDX_REMOTE_METHOD(EchoVector, BenchmarkOps::EchoVector,
+                    BufferWrapper<std::vector<uint8_t>>(
+                        const BufferWrapper<std::vector<uint8_t>> data));
+};
+
+struct BenchmarkResult {
+  int thread_id = 0;
+  int service_id = 0;
+  double time_delta_s = 0.0;
+  uint64_t bytes_sent = 0;
+  SchedStats sched_stats = {};
+};
+
+// Global command line option values.
+struct Options {
+  bool verbose = false;
+  int threads = 1;
+  int opcode = BenchmarkOps::Read;
+  int blocksize = 1;
+  int count = 1;
+  int instances = 1;
+  int timeout = 1;
+  int warmup = 0;
+} ProgramOptions;
+
+// Command line option names.
+const char kOptionService[] = "service";
+const char kOptionClient[] = "client";
+const char kOptionVerbose[] = "verbose";
+const char kOptionOpcode[] = "op";
+const char kOptionBlocksize[] = "bs";
+const char kOptionCount[] = "count";
+const char kOptionThreads[] = "threads";
+const char kOptionInstances[] = "instances";
+const char kOptionTimeout[] = "timeout";
+const char kOptionTrace[] = "trace";
+const char kOptionWarmup[] = "warmup";
+
+// getopt() long options.
+static option long_options[] = {
+    {kOptionService, required_argument, 0, 0},
+    {kOptionClient, required_argument, 0, 0},
+    {kOptionVerbose, no_argument, 0, 0},
+    {kOptionOpcode, required_argument, 0, 0},
+    {kOptionBlocksize, required_argument, 0, 0},
+    {kOptionCount, required_argument, 0, 0},
+    {kOptionThreads, required_argument, 0, 0},
+    {kOptionInstances, required_argument, 0, 0},
+    {kOptionTimeout, required_argument, 0, 0},
+    {kOptionTrace, no_argument, 0, 0},
+    {kOptionWarmup, required_argument, 0, 0},
+    {0, 0, 0, 0},
+};
+
+// Parses the argument for kOptionOpcode and sets the value of
+// ProgramOptions.opcode.
+void ParseOpcodeOption(const std::string& argument) {
+  if (argument == "read") {
+    ProgramOptions.opcode = BenchmarkOps::Read;
+  } else if (argument == "write") {
+    ProgramOptions.opcode = BenchmarkOps::Write;
+  } else if (argument == "echo") {
+    ProgramOptions.opcode = BenchmarkOps::Echo;
+  } else if (argument == "writevec") {
+    ProgramOptions.opcode = BenchmarkOps::WriteVector;
+  } else if (argument == "echovec") {
+    ProgramOptions.opcode = BenchmarkOps::EchoVector;
+  } else if (argument == "quit") {
+    ProgramOptions.opcode = BenchmarkOps::Quit;
+  } else if (argument == "nop") {
+    ProgramOptions.opcode = BenchmarkOps::Nop;
+  } else if (argument == "stats") {
+    ProgramOptions.opcode = BenchmarkOps::Stats;
+  } else {
+    ProgramOptions.opcode = std::stoi(argument);
+  }
+}
+
+// Implements the service side of the benchmark.
+class BenchmarkService : public ServiceBase<BenchmarkService> {
+ public:
+  std::shared_ptr<Channel> OnChannelOpen(Message& message) override {
+    VLOG(1) << "BenchmarkService::OnChannelCreate: cid="
+            << message.GetChannelId();
+    return nullptr;
+  }
+
+  void OnChannelClose(Message& message,
+                      const std::shared_ptr<Channel>& /*channel*/) override {
+    VLOG(1) << "BenchmarkService::OnChannelClose: cid="
+            << message.GetChannelId();
+  }
+
+  int HandleMessage(Message& message) override {
+    ATRACE_NAME("BenchmarkService::HandleMessage");
+
+    switch (message.GetOp()) {
+      case BenchmarkOps::Nop:
+        VLOG(1) << "BenchmarkService::HandleMessage: op=nop";
+        {
+          ATRACE_NAME("Reply");
+          CHECK(message.Reply(0) == 0);
+        }
+        return 0;
+
+      case BenchmarkOps::Write: {
+        VLOG(1) << "BenchmarkService::HandleMessage: op=write send_length="
+                << message.GetSendLength()
+                << " receive_length=" << message.GetReceiveLength();
+
+        const ssize_t expected_length =
+            static_cast<ssize_t>(message.GetSendLength());
+        const ssize_t actual_length =
+            expected_length > 0
+                ? message.Read(send_buffer.data(), message.GetSendLength())
+                : 0;
+
+        {
+          ATRACE_NAME("Reply");
+          if (actual_length < expected_length)
+            CHECK(message.ReplyError(EIO) == 0);
+          else
+            CHECK(message.Reply(actual_length) == 0);
+        }
+        return 0;
+      }
+
+      case BenchmarkOps::Read: {
+        VLOG(1) << "BenchmarkService::HandleMessage: op=read send_length="
+                << message.GetSendLength()
+                << " receive_length=" << message.GetReceiveLength();
+
+        const ssize_t expected_length =
+            static_cast<ssize_t>(message.GetReceiveLength());
+        const ssize_t actual_length =
+            expected_length > 0
+                ? message.Write(receive_buffer.data(),
+                                message.GetReceiveLength())
+                : 0;
+
+        {
+          ATRACE_NAME("Reply");
+          if (actual_length < expected_length)
+            CHECK(message.ReplyError(EIO) == 0);
+          else
+            CHECK(message.Reply(actual_length) == 0);
+        }
+        return 0;
+      }
+
+      case BenchmarkOps::Echo: {
+        VLOG(1) << "BenchmarkService::HandleMessage: op=echo send_length="
+                << message.GetSendLength()
+                << " receive_length=" << message.GetReceiveLength();
+
+        const ssize_t expected_length =
+            static_cast<ssize_t>(message.GetSendLength());
+        ssize_t actual_length =
+            expected_length > 0
+                ? message.Read(send_buffer.data(), message.GetSendLength())
+                : 0;
+
+        if (actual_length < expected_length) {
+          CHECK(message.ReplyError(EIO) == 0);
+          return 0;
+        }
+
+        actual_length =
+            expected_length > 0
+                ? message.Write(send_buffer.data(), message.GetSendLength())
+                : 0;
+
+        {
+          ATRACE_NAME("Reply");
+          if (actual_length < expected_length)
+            CHECK(message.ReplyError(EIO) == 0);
+          else
+            CHECK(message.Reply(actual_length) == 0);
+        }
+        return 0;
+      }
+
+      case BenchmarkOps::Stats: {
+        VLOG(1) << "BenchmarkService::HandleMessage: op=echo send_length="
+                << message.GetSendLength()
+                << " receive_length=" << message.GetReceiveLength();
+
+        // Snapshot the stats when the message is received.
+        const uint64_t receive_time_ns = GetClockNs();
+        sched_stats_.Update();
+
+        // Use the RPC system to return the results.
+        RemoteMethodReturn<BenchmarkRPC::Stats>(
+            message, BenchmarkRPC::Stats::Return{receive_time_ns, GetClockNs(),
+                                                 sched_stats_});
+        return 0;
+      }
+
+      case BenchmarkOps::WriteVector:
+        VLOG(1) << "BenchmarkService::HandleMessage: op=writevec send_length="
+                << message.GetSendLength()
+                << " receive_length=" << message.GetReceiveLength();
+
+        DispatchRemoteMethod<BenchmarkRPC::WriteVector>(
+            *this, &BenchmarkService::OnWriteVector, message, kMaxMessageSize);
+        return 0;
+
+      case BenchmarkOps::EchoVector:
+        VLOG(1) << "BenchmarkService::HandleMessage: op=echovec send_length="
+                << message.GetSendLength()
+                << " receive_length=" << message.GetReceiveLength();
+
+        DispatchRemoteMethod<BenchmarkRPC::EchoVector>(
+            *this, &BenchmarkService::OnEchoVector, message, kMaxMessageSize);
+        return 0;
+
+      case BenchmarkOps::Quit:
+        Cancel();
+        return -ESHUTDOWN;
+
+      default:
+        VLOG(1) << "BenchmarkService::HandleMessage: default case; op="
+                << message.GetOp();
+        return Service::DefaultHandleMessage(message);
+    }
+  }
+
+  // Updates the scheduler stats from procfs for this thread.
+  void UpdateSchedStats() { sched_stats_.Update(); }
+
+ private:
+  friend BASE;
+
+  BenchmarkService(std::unique_ptr<Endpoint> endpoint)
+      : BASE("BenchmarkService", std::move(endpoint)),
+        send_buffer(kMaxMessageSize),
+        receive_buffer(kMaxMessageSize) {}
+
+  std::vector<uint8_t> send_buffer;
+  std::vector<uint8_t> receive_buffer;
+
+  // Each service thread has its own scheduler stats object.
+  static thread_local SchedStats sched_stats_;
+
+  using BufferType = BufferWrapper<
+      std::vector<uint8_t, DefaultInitializationAllocator<uint8_t>>>;
+
+  int OnWriteVector(Message&, const BufferType& data) { return data.size(); }
+  BufferType OnEchoVector(Message&, BufferType&& data) {
+    return std::move(data);
+  }
+
+  BenchmarkService(const BenchmarkService&) = delete;
+  void operator=(const BenchmarkService&) = delete;
+};
+
+thread_local SchedStats BenchmarkService::sched_stats_;
+
+// Implements the client side of the benchmark.
+class BenchmarkClient : public ClientBase<BenchmarkClient> {
+ public:
+  int Nop() {
+    ATRACE_NAME("BenchmarkClient::Nop");
+    VLOG(1) << "BenchmarkClient::Nop";
+    Transaction transaction{*this};
+    return ReturnStatusOrError(transaction.Send<int>(BenchmarkOps::Nop));
+  }
+
+  int Write(const void* buffer, size_t length) {
+    ATRACE_NAME("BenchmarkClient::Write");
+    VLOG(1) << "BenchmarkClient::Write: buffer=" << buffer
+            << " length=" << length;
+    Transaction transaction{*this};
+    return ReturnStatusOrError(
+        transaction.Send<int>(BenchmarkOps::Write, buffer, length, nullptr, 0));
+    // return write(endpoint_fd(), buffer, length);
+  }
+
+  int Read(void* buffer, size_t length) {
+    ATRACE_NAME("BenchmarkClient::Read");
+    VLOG(1) << "BenchmarkClient::Read: buffer=" << buffer
+            << " length=" << length;
+    Transaction transaction{*this};
+    return ReturnStatusOrError(
+        transaction.Send<int>(BenchmarkOps::Read, nullptr, 0, buffer, length));
+    // return read(endpoint_fd(), buffer, length);
+  }
+
+  int Echo(const void* send_buffer, size_t send_length, void* receive_buffer,
+           size_t receive_length) {
+    ATRACE_NAME("BenchmarkClient::Echo");
+    VLOG(1) << "BenchmarkClient::Echo: send_buffer=" << send_buffer
+            << " send_length=" << send_length
+            << " receive_buffer=" << receive_buffer
+            << " receive_length=" << receive_length;
+    Transaction transaction{*this};
+    return ReturnStatusOrError(
+        transaction.Send<int>(BenchmarkOps::Echo, send_buffer, send_length,
+                              receive_buffer, receive_length));
+  }
+
+  int Stats(std::tuple<uint64_t, uint64_t, SchedStats>* stats_out) {
+    ATRACE_NAME("BenchmarkClient::Stats");
+    VLOG(1) << "BenchmarkClient::Stats";
+
+    auto status = InvokeRemoteMethodInPlace<BenchmarkRPC::Stats>(stats_out);
+    return status ? 0 : -status.error();
+  }
+
+  int WriteVector(const BufferWrapper<std::vector<uint8_t>>& data) {
+    ATRACE_NAME("BenchmarkClient::Stats");
+    VLOG(1) << "BenchmarkClient::Stats";
+
+    auto status = InvokeRemoteMethod<BenchmarkRPC::WriteVector>(data);
+    return ReturnStatusOrError(status);
+  }
+
+  template <typename T>
+  int WriteVector(const BufferWrapper<T>& data) {
+    ATRACE_NAME("BenchmarkClient::WriteVector");
+    VLOG(1) << "BenchmarkClient::WriteVector";
+
+    auto status = InvokeRemoteMethod<BenchmarkRPC::WriteVector>(data);
+    return ReturnStatusOrError(status);
+  }
+
+  template <typename T, typename U>
+  int EchoVector(const BufferWrapper<T>& data, BufferWrapper<U>* data_out) {
+    ATRACE_NAME("BenchmarkClient::EchoVector");
+    VLOG(1) << "BenchmarkClient::EchoVector";
+
+    MessageBuffer<ReplyBuffer>::Reserve(kMaxMessageSize - 1);
+    auto status =
+        InvokeRemoteMethodInPlace<BenchmarkRPC::EchoVector>(data_out, data);
+    return status ? 0 : -status.error();
+  }
+
+  int Quit() {
+    VLOG(1) << "BenchmarkClient::Quit";
+    Transaction transaction{*this};
+    return ReturnStatusOrError(transaction.Send<int>(BenchmarkOps::Echo));
+  }
+
+ private:
+  friend BASE;
+
+  BenchmarkClient(const std::string& service_path)
+      : BASE(ClientChannelFactory::Create(service_path),
+             ProgramOptions.timeout) {}
+
+  BenchmarkClient(const BenchmarkClient&) = delete;
+  void operator=(const BenchmarkClient&) = delete;
+};
+
+// Creates a benchmark service at |path| and dispatches messages.
+int ServiceCommand(const std::string& path) {
+  if (path.empty())
+    return -EINVAL;
+
+  // Start the requested number of dispatch threads.
+  std::vector<std::thread> dispatch_threads;
+  int service_count = ProgramOptions.instances;
+  int service_id_counter = 0;
+  int thread_id_counter = 0;
+  std::atomic<bool> done(false);
+
+  while (service_count--) {
+    std::cerr << "Starting service instance " << service_id_counter
+              << std::endl;
+    auto service = BenchmarkService::Create(
+        android::pdx::default_transport::Endpoint::Create(
+            GetServicePath(path, service_id_counter),
+            android::pdx::default_transport::Endpoint::kDefaultMode,
+            android::pdx::default_transport::Endpoint::kBlocking));
+    if (!service) {
+      std::cerr << "Failed to create service instance!!" << std::endl;
+      done = true;
+      break;
+    }
+
+    int thread_count = ProgramOptions.threads;
+    while (thread_count--) {
+      std::cerr << "Starting dispatch thread " << thread_id_counter
+                << " service " << service_id_counter << std::endl;
+
+      dispatch_threads.emplace_back(
+          [&](const int thread_id, const int service_id,
+              const std::shared_ptr<BenchmarkService>& local_service) {
+            SetThreadName("service" + std::to_string(service_id));
+
+            // Read the inital schedstats for this thread from procfs.
+            local_service->UpdateSchedStats();
+
+            ATRACE_NAME("BenchmarkService::Dispatch");
+            while (!done) {
+              const int ret = local_service->ReceiveAndDispatch();
+              if (ret < 0) {
+                if (ret != -ESHUTDOWN) {
+                  std::cerr << "Error while dispatching message on thread "
+                            << thread_id << " service " << service_id << ": "
+                            << strerror(-ret) << std::endl;
+                } else {
+                  std::cerr << "Quitting thread " << thread_id << " service "
+                            << service_id << std::endl;
+                }
+                done = true;
+                return;
+              }
+            }
+          },
+          thread_id_counter++, service_id_counter, service);
+    }
+
+    service_id_counter++;
+  }
+
+  // Wait for the dispatch threads to exit.
+  for (auto& thread : dispatch_threads) {
+    thread.join();
+  }
+
+  return 0;
+}
+
+int ClientCommand(const std::string& path) {
+  // Start the requested number of client threads.
+  std::vector<std::thread> client_threads;
+  std::vector<std::future<BenchmarkResult>> client_results;
+  int service_count = ProgramOptions.instances;
+  int thread_id_counter = 0;
+  int service_id_counter = 0;
+
+  // Aggregate statistics, updated when worker threads exit.
+  std::atomic<uint64_t> total_bytes(0);
+  std::atomic<uint64_t> total_time_ns(0);
+
+  // Samples for variance calculation.
+  std::vector<uint64_t> latency_samples_ns(
+      ProgramOptions.instances * ProgramOptions.threads * ProgramOptions.count);
+  const size_t samples_per_thread = ProgramOptions.count;
+
+  std::vector<uint8_t> send_buffer(ProgramOptions.blocksize);
+  std::vector<uint8_t> receive_buffer(kMaxMessageSize);
+
+  // Barriers for synchronizing thread start.
+  std::vector<std::future<void>> ready_barrier_futures;
+  std::promise<void> go_barrier_promise;
+  std::future<void> go_barrier_future = go_barrier_promise.get_future();
+
+  // Barrier for synchronizing thread tear down.
+  std::promise<void> done_barrier_promise;
+  std::future<void> done_barrier_future = done_barrier_promise.get_future();
+
+  while (service_count--) {
+    int thread_count = ProgramOptions.threads;
+    while (thread_count--) {
+      std::cerr << "Starting client thread " << thread_id_counter << " service "
+                << service_id_counter << std::endl;
+
+      std::promise<BenchmarkResult> result_promise;
+      client_results.push_back(result_promise.get_future());
+
+      std::promise<void> ready_barrier_promise;
+      ready_barrier_futures.push_back(ready_barrier_promise.get_future());
+
+      client_threads.emplace_back(
+          [&](const int thread_id, const int service_id,
+              std::promise<BenchmarkResult> result, std::promise<void> ready) {
+            SetThreadName("client" + std::to_string(thread_id) + "/" +
+                          std::to_string(service_id));
+
+            ATRACE_NAME("BenchmarkClient::Dispatch");
+
+            auto client =
+                BenchmarkClient::Create(GetServicePath(path, service_id));
+            if (!client) {
+              std::cerr << "Failed to create client for service " << service_id
+                        << std::endl;
+              return -ENOMEM;
+            }
+
+            uint64_t* thread_samples =
+                &latency_samples_ns[samples_per_thread * thread_id];
+
+            // Per-thread statistics.
+            uint64_t bytes_sent = 0;
+            uint64_t time_start_ns;
+            uint64_t time_end_ns;
+            SchedStats sched_stats;
+
+            // Signal ready and wait for go.
+            ready.set_value();
+            go_barrier_future.wait();
+
+            // Warmup the scheduler.
+            int warmup = ProgramOptions.warmup;
+            while (warmup--) {
+              for (int i = 0; i < 1000000; i++)
+                ;
+            }
+
+            sched_stats.Update();
+            time_start_ns = GetClockNs();
+
+            int count = ProgramOptions.count;
+            while (count--) {
+              uint64_t iteration_start_ns = GetClockNs();
+
+              switch (ProgramOptions.opcode) {
+                case BenchmarkOps::Nop: {
+                  const int ret = client->Nop();
+                  if (ret < 0) {
+                    std::cerr << "Failed to send nop: " << strerror(-ret)
+                              << std::endl;
+                    return ret;
+                  } else {
+                    VLOG(1) << "Success";
+                  }
+                  break;
+                }
+
+                case BenchmarkOps::Read: {
+                  const int ret = client->Read(receive_buffer.data(),
+                                               ProgramOptions.blocksize);
+                  if (ret < 0) {
+                    std::cerr << "Failed to read: " << strerror(-ret)
+                              << std::endl;
+                    return ret;
+                  } else if (ret != ProgramOptions.blocksize) {
+                    std::cerr << "Expected ret=" << ProgramOptions.blocksize
+                              << "; actual ret=" << ret << std::endl;
+                    return -EINVAL;
+                  } else {
+                    VLOG(1) << "Success";
+                    bytes_sent += ret;
+                  }
+                  break;
+                }
+
+                case BenchmarkOps::Write: {
+                  const int ret =
+                      client->Write(send_buffer.data(), send_buffer.size());
+                  if (ret < 0) {
+                    std::cerr << "Failed to write: " << strerror(-ret)
+                              << std::endl;
+                    return ret;
+                  } else if (ret != ProgramOptions.blocksize) {
+                    std::cerr << "Expected ret=" << ProgramOptions.blocksize
+                              << "; actual ret=" << ret << std::endl;
+                    return -EINVAL;
+                  } else {
+                    VLOG(1) << "Success";
+                    bytes_sent += ret;
+                  }
+                  break;
+                }
+
+                case BenchmarkOps::Echo: {
+                  const int ret = client->Echo(
+                      send_buffer.data(), send_buffer.size(),
+                      receive_buffer.data(), receive_buffer.size());
+                  if (ret < 0) {
+                    std::cerr << "Failed to echo: " << strerror(-ret)
+                              << std::endl;
+                    return ret;
+                  } else if (ret != ProgramOptions.blocksize) {
+                    std::cerr << "Expected ret=" << ProgramOptions.blocksize
+                              << "; actual ret=" << ret << std::endl;
+                    return -EINVAL;
+                  } else {
+                    VLOG(1) << "Success";
+                    bytes_sent += ret * 2;
+                  }
+                  break;
+                }
+
+                case BenchmarkOps::Stats: {
+                  std::tuple<uint64_t, uint64_t, SchedStats> stats;
+                  const int ret = client->Stats(&stats);
+                  if (ret < 0) {
+                    std::cerr << "Failed to get stats: " << strerror(-ret)
+                              << std::endl;
+                    return ret;
+                  } else {
+                    VLOG(1) << "Success";
+                    std::cerr
+                        << "Round trip: receive_time_ns=" << std::get<0>(stats)
+                        << " reply_time_ns=" << std::get<1>(stats)
+                        << " cpu_time_s=" << std::get<2>(stats).cpu_time_s()
+                        << " wait_s=" << std::get<2>(stats).wait_s()
+                        << std::endl;
+                  }
+                  break;
+                }
+
+                case BenchmarkOps::WriteVector: {
+                  const int ret = client->WriteVector(
+                      WrapBuffer(send_buffer.data(), ProgramOptions.blocksize));
+                  if (ret < 0) {
+                    std::cerr << "Failed to write vector: " << strerror(-ret)
+                              << std::endl;
+                    return ret;
+                  } else {
+                    VLOG(1) << "Success";
+                    bytes_sent += ret;
+                  }
+                  break;
+                }
+
+                case BenchmarkOps::EchoVector: {
+                  thread_local BufferWrapper<std::vector<
+                      uint8_t, DefaultInitializationAllocator<uint8_t>>>
+                      response_buffer;
+                  const int ret = client->EchoVector(
+                      WrapBuffer(send_buffer.data(), ProgramOptions.blocksize),
+                      &response_buffer);
+                  if (ret < 0) {
+                    std::cerr << "Failed to echo vector: " << strerror(-ret)
+                              << std::endl;
+                    return ret;
+                  } else {
+                    VLOG(1) << "Success";
+                    bytes_sent += send_buffer.size() + response_buffer.size();
+                  }
+                  break;
+                }
+
+                case BenchmarkOps::Quit: {
+                  const int ret = client->Quit();
+                  if (ret < 0 && ret != -ESHUTDOWN) {
+                    std::cerr << "Failed to send quit: " << strerror(-ret);
+                    return ret;
+                  } else {
+                    VLOG(1) << "Success";
+                  }
+                  break;
+                }
+
+                default:
+                  std::cerr
+                      << "Invalid client operation: " << ProgramOptions.opcode
+                      << std::endl;
+                  return -EINVAL;
+              }
+
+              uint64_t iteration_end_ns = GetClockNs();
+              uint64_t iteration_delta_ns =
+                  iteration_end_ns - iteration_start_ns;
+              thread_samples[count] = iteration_delta_ns;
+
+              if (iteration_delta_ns > (kNanosPerSecond / 100)) {
+                SchedStats stats = sched_stats;
+                stats.Update();
+                std::cerr << "Thread " << thread_id << " iteration_delta_s="
+                          << (static_cast<double>(iteration_delta_ns) /
+                              kNanosPerSecond)
+                          << " " << stats.cpu_time_s() << " " << stats.wait_s()
+                          << std::endl;
+              }
+            }
+
+            time_end_ns = GetClockNs();
+            sched_stats.Update();
+
+            const double time_delta_s =
+                static_cast<double>(time_end_ns - time_start_ns) /
+                kNanosPerSecond;
+
+            total_bytes += bytes_sent;
+            total_time_ns += time_end_ns - time_start_ns;
+
+            result.set_value(
+                {thread_id, service_id, time_delta_s, bytes_sent, sched_stats});
+            done_barrier_future.wait();
+
+            return 0;
+          },
+          thread_id_counter++, service_id_counter, std::move(result_promise),
+          std::move(ready_barrier_promise));
+    }
+
+    service_id_counter++;
+  }
+
+  // Wait for workers to be ready.
+  std::cerr << "Waiting for workers to be ready..." << std::endl;
+  for (auto& ready : ready_barrier_futures)
+    ready.wait();
+
+  // Signal workers to go.
+  std::cerr << "Kicking off benchmark." << std::endl;
+  go_barrier_promise.set_value();
+
+  // Wait for all the worker threas to finish.
+  for (auto& result : client_results)
+    result.wait();
+
+  // Report worker thread results.
+  for (auto& result : client_results) {
+    BenchmarkResult benchmark_result = result.get();
+    std::cerr << std::fixed << "Thread " << benchmark_result.thread_id
+              << " service " << benchmark_result.service_id << ":" << std::endl;
+    std::cerr << "\t " << benchmark_result.bytes_sent << " bytes in "
+              << benchmark_result.time_delta_s << " seconds ("
+              << std::setprecision(0) << (benchmark_result.bytes_sent / 1024.0 /
+                                          benchmark_result.time_delta_s)
+              << " K/s; " << std::setprecision(3)
+              << (ProgramOptions.count / benchmark_result.time_delta_s)
+              << " txn/s; " << std::setprecision(9)
+              << (benchmark_result.time_delta_s / ProgramOptions.count)
+              << " s/txn)" << std::endl;
+    std::cerr << "\tStats: " << benchmark_result.sched_stats.cpu_time_s() << " "
+              << (benchmark_result.sched_stats.cpu_time_s() /
+                  ProgramOptions.count)
+              << " " << benchmark_result.sched_stats.wait_s() << " "
+              << (benchmark_result.sched_stats.wait_s() / ProgramOptions.count)
+              << " " << benchmark_result.sched_stats.timeslices() << std::endl;
+  }
+
+  // Signal worker threads to exit.
+  done_barrier_promise.set_value();
+
+  // Wait for the worker threads to exit.
+  for (auto& thread : client_threads) {
+    thread.join();
+  }
+
+  // Report aggregate results.
+  const int total_threads = ProgramOptions.threads * ProgramOptions.instances;
+  const int iterations = ProgramOptions.count;
+  const double total_time_s =
+      static_cast<double>(total_time_ns) / kNanosPerSecond;
+  // This is about how much wall time it took to completely transfer all the
+  // paylaods.
+  const double average_time_s = total_time_s / total_threads;
+
+  const uint64_t min_sample_time_ns =
+      *std::min_element(latency_samples_ns.begin(), latency_samples_ns.end());
+  const double min_sample_time_s =
+      static_cast<double>(min_sample_time_ns) / kNanosPerSecond;
+
+  const uint64_t max_sample_time_ns =
+      *std::max_element(latency_samples_ns.begin(), latency_samples_ns.end());
+  const double max_sample_time_s =
+      static_cast<double>(max_sample_time_ns) / kNanosPerSecond;
+
+  const double total_sample_time_s =
+      std::accumulate(latency_samples_ns.begin(), latency_samples_ns.end(), 0.0,
+                      [](double s, uint64_t ns) {
+                        return s + static_cast<double>(ns) / kNanosPerSecond;
+                      });
+  const double average_sample_time_s =
+      total_sample_time_s / latency_samples_ns.size();
+
+  const double sum_of_squared_deviations = std::accumulate(
+      latency_samples_ns.begin(), latency_samples_ns.end(), 0.0,
+      [&](double s, uint64_t ns) {
+        const double delta =
+            static_cast<double>(ns) / kNanosPerSecond - average_sample_time_s;
+        return s + delta * delta;
+      });
+  const double variance = sum_of_squared_deviations / latency_samples_ns.size();
+  const double standard_deviation = std::sqrt(variance);
+
+  const int num_buckets = 200;
+  const uint64_t sample_range_ns = max_sample_time_ns - min_sample_time_ns;
+  const uint64_t ns_per_bucket = sample_range_ns / num_buckets;
+  std::array<uint64_t, num_buckets> sample_buckets = {{0}};
+
+  // Count samples in each bucket range.
+  for (uint64_t sample_ns : latency_samples_ns) {
+    sample_buckets[(sample_ns - min_sample_time_ns) / (ns_per_bucket + 1)] += 1;
+  }
+
+  // Calculate population percentiles.
+  const uint64_t percent_50 =
+      static_cast<uint64_t>(latency_samples_ns.size() * 0.5);
+  const uint64_t percent_90 =
+      static_cast<uint64_t>(latency_samples_ns.size() * 0.9);
+  const uint64_t percent_95 =
+      static_cast<uint64_t>(latency_samples_ns.size() * 0.95);
+  const uint64_t percent_99 =
+      static_cast<uint64_t>(latency_samples_ns.size() * 0.99);
+
+  uint64_t sample_count = 0;
+  double latency_50th_percentile_s, latency_90th_percentile_s,
+      latency_95th_percentile_s, latency_99th_percentile_s;
+  for (int i = 0; i < num_buckets; i++) {
+    // Report the midpoint of the bucket range as the value of the
+    // corresponding
+    // percentile.
+    const double bucket_midpoint_time_s =
+        (ns_per_bucket * i + 0.5 * ns_per_bucket + min_sample_time_ns) /
+        kNanosPerSecond;
+    if (sample_count < percent_50 &&
+        (sample_count + sample_buckets[i]) >= percent_50) {
+      latency_50th_percentile_s = bucket_midpoint_time_s;
+    }
+    if (sample_count < percent_90 &&
+        (sample_count + sample_buckets[i]) >= percent_90) {
+      latency_90th_percentile_s = bucket_midpoint_time_s;
+    }
+    if (sample_count < percent_95 &&
+        (sample_count + sample_buckets[i]) >= percent_95) {
+      latency_95th_percentile_s = bucket_midpoint_time_s;
+    }
+    if (sample_count < percent_99 &&
+        (sample_count + sample_buckets[i]) >= percent_99) {
+      latency_99th_percentile_s = bucket_midpoint_time_s;
+    }
+    sample_count += sample_buckets[i];
+  }
+
+  std::cerr << std::fixed << "Total throughput over " << total_threads
+            << " threads:\n\t " << total_bytes << " bytes in " << average_time_s
+            << " seconds (" << std::setprecision(0)
+            << (total_bytes / 1024.0 / average_time_s) << " K/s; "
+            << std::setprecision(3)
+            << (iterations * total_threads / average_time_s)
+            << std::setprecision(9) << " txn/s; "
+            << (average_time_s / (iterations * total_threads)) << " s/txn)"
+            << std::endl;
+  std::cerr << "Sample statistics: " << std::endl;
+  std::cerr << total_sample_time_s << " s total sample time" << std::endl;
+  std::cerr << average_sample_time_s << " s avg" << std::endl;
+  std::cerr << standard_deviation << " s std dev" << std::endl;
+  std::cerr << min_sample_time_s << " s min" << std::endl;
+  std::cerr << max_sample_time_s << " s max" << std::endl;
+  std::cerr << "Latency percentiles:" << std::endl;
+  std::cerr << "50th: " << latency_50th_percentile_s << " s" << std::endl;
+  std::cerr << "90th: " << latency_90th_percentile_s << " s" << std::endl;
+  std::cerr << "95th: " << latency_95th_percentile_s << " s" << std::endl;
+  std::cerr << "99th: " << latency_99th_percentile_s << " s" << std::endl;
+
+  std::cout << total_time_ns << " " << std::fixed << std::setprecision(9)
+            << average_sample_time_s << " " << std::fixed
+            << std::setprecision(9) << standard_deviation << std::endl;
+  return 0;
+}
+
+int Usage(const std::string& command_name) {
+  // clang-format off
+  std::cout << "Usage: " << command_name << " [options]" << std::endl;
+  std::cout << "\t--verbose                   : Use verbose messages." << std::endl;
+  std::cout << "\t--service <endpoint path>   : Start service at the given path." << std::endl;
+  std::cout << "\t--client <endpoint path>    : Start client to the given path." << std::endl;
+  std::cout << "\t--op <read | write | echo>  : Sepcify client operation mode." << std::endl;
+  std::cout << "\t--bs <block size bytes>     : Sepcify block size to use." << std::endl;
+  std::cout << "\t--count <count>             : Sepcify number of transactions to make." << std::endl;
+  std::cout << "\t--instances <count>         : Specify number of service instances." << std::endl;
+  std::cout << "\t--threads <count>           : Sepcify number of threads per instance." << std::endl;
+  std::cout << "\t--timeout <timeout ms | -1> : Timeout to wait for services." << std::endl;
+  std::cout << "\t--trace                     : Enable systrace logging." << std::endl;
+  std::cout << "\t--warmup <iterations>       : Busy loops before running benchmarks." << std::endl;
+  // clang-format on
+  return -1;
+}
+
+}  // anonymous namespace
+
+int main(int argc, char** argv) {
+  logging::LoggingSettings logging_settings;
+  logging_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+  logging::InitLogging(logging_settings);
+
+  int getopt_code;
+  int option_index;
+  std::string option = "";
+  std::string command = "";
+  std::string command_argument = "";
+  bool tracing_enabled = false;
+
+  // Process command line options.
+  while ((getopt_code =
+              getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+    option = long_options[option_index].name;
+    VLOG(1) << "option=" << option;
+    switch (getopt_code) {
+      case 0:
+        if (option == kOptionVerbose) {
+          ProgramOptions.verbose = true;
+          logging::SetMinLogLevel(-1);
+        } else if (option == kOptionOpcode) {
+          ParseOpcodeOption(optarg);
+        } else if (option == kOptionBlocksize) {
+          ProgramOptions.blocksize = std::stoi(optarg);
+          if (ProgramOptions.blocksize < 0) {
+            std::cerr << "Invalid blocksize argument: "
+                      << ProgramOptions.blocksize << std::endl;
+            return -EINVAL;
+          }
+        } else if (option == kOptionCount) {
+          ProgramOptions.count = std::stoi(optarg);
+          if (ProgramOptions.count < 1) {
+            std::cerr << "Invalid count argument: " << ProgramOptions.count
+                      << std::endl;
+            return -EINVAL;
+          }
+        } else if (option == kOptionThreads) {
+          ProgramOptions.threads = std::stoi(optarg);
+          if (ProgramOptions.threads < 1) {
+            std::cerr << "Invalid threads argument: " << ProgramOptions.threads
+                      << std::endl;
+            return -EINVAL;
+          }
+        } else if (option == kOptionInstances) {
+          ProgramOptions.instances = std::stoi(optarg);
+          if (ProgramOptions.instances < 1) {
+            std::cerr << "Invalid instances argument: "
+                      << ProgramOptions.instances << std::endl;
+            return -EINVAL;
+          }
+        } else if (option == kOptionTimeout) {
+          ProgramOptions.timeout = std::stoi(optarg);
+        } else if (option == kOptionTrace) {
+          tracing_enabled = true;
+        } else if (option == kOptionWarmup) {
+          ProgramOptions.warmup = std::stoi(optarg);
+        } else {
+          command = option;
+          if (optarg)
+            command_argument = optarg;
+        }
+        break;
+    }
+  }
+
+  // Setup ATRACE/systrace based on command line.
+  atrace_setup();
+  atrace_set_tracing_enabled(tracing_enabled);
+
+  VLOG(1) << "command=" << command << " command_argument=" << command_argument;
+
+  if (command == "") {
+    return Usage(argv[0]);
+  } else if (command == kOptionService) {
+    return ServiceCommand(command_argument);
+  } else if (command == kOptionClient) {
+    return ClientCommand(command_argument);
+  } else {
+    return Usage(argv[0]);
+  }
+}
diff --git a/libs/vr/libpdx_default_transport/private/pdx/default_transport/service_utility.h b/libs/vr/libpdx_default_transport/private/pdx/default_transport/service_utility.h
new file mode 100644
index 0000000..22c6b3f
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/pdx/default_transport/service_utility.h
@@ -0,0 +1,45 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_SERVICE_UTILITY_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_SERVICE_UTILITY_H_
+
+#include <pdx/client.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <pdx/service.h>
+#include <pdx/status.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+class ServiceUtility : public ClientBase<ServiceUtility> {
+ public:
+  Status<int> ReloadSystemProperties() {
+    Transaction transaction{*this};
+    return ReturnStatusOrError(
+        transaction.Send<int>(opcodes::REPORT_SYSPROP_CHANGE));
+  }
+
+  static std::string GetRootEndpointPath() {
+    return ClientChannelFactory::GetRootEndpointPath();
+  }
+  static std::string GetEndpointPath(const std::string& endpoint_path) {
+    return ClientChannelFactory::GetEndpointPath(endpoint_path);
+  }
+
+ private:
+  friend BASE;
+
+  ServiceUtility(const std::string& endpoint_path, int* error = nullptr)
+      : BASE(ClientChannelFactory::Create(endpoint_path)) {
+    if (error)
+      *error = Client::error();
+  }
+
+  ServiceUtility(const ServiceUtility&) = delete;
+  void operator=(const ServiceUtility&) = delete;
+};
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_SERVICE_UTILITY_H_
diff --git a/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/channel_manager.h b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/channel_manager.h
new file mode 100644
index 0000000..11163b3
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/channel_manager.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CHANNEL_MANAGER_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CHANNEL_MANAGER_H_
+
+#include <servicefs/channel_manager.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using ChannelManager = ::android::pdx::servicefs::ChannelManager;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CHANNEL_MANAGER_H_
diff --git a/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/client_channel.h b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/client_channel.h
new file mode 100644
index 0000000..d171780
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/client_channel.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CLIENT_CHANNEL_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CLIENT_CHANNEL_H_
+
+#include <servicefs/client_channel.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using ClientChannel = ::android::pdx::servicefs::ClientChannel;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CLIENT_CHANNEL_H_
diff --git a/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/client_channel_factory.h b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/client_channel_factory.h
new file mode 100644
index 0000000..77b5cac
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/client_channel_factory.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CLIENT_CHANNEL_FACTORY_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CLIENT_CHANNEL_FACTORY_H_
+
+#include <servicefs/client_channel_factory.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using ClientChannelFactory = ::android::pdx::servicefs::ClientChannelFactory;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_CLIENT_CHANNEL_FACTORY_H_
diff --git a/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/service_dispatcher.h b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/service_dispatcher.h
new file mode 100644
index 0000000..158871c
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/service_dispatcher.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_SERVICE_DISPATCHER_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_SERVICE_DISPATCHER_H_
+
+#include <servicefs/service_dispatcher.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using ServiceDispatcher = ::android::pdx::servicefs::ServiceDispatcher;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_SERVICE_DISPATCHER_H_
diff --git a/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/service_endpoint.h b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/service_endpoint.h
new file mode 100644
index 0000000..8f413c1
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/servicefs/pdx/default_transport/service_endpoint.h
@@ -0,0 +1,16 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_SERVICE_ENDPOINT_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_SERVICE_ENDPOINT_H_
+
+#include <servicefs/service_endpoint.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using Endpoint = ::android::pdx::servicefs::Endpoint;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_SERVICEFS_PDX_SERVICE_ENDPOINT_H_
diff --git a/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/channel_manager.h b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/channel_manager.h
new file mode 100644
index 0000000..f34636f
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/channel_manager.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CHANNEL_MANAGER_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CHANNEL_MANAGER_H_
+
+#include <uds/channel_manager.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using ChannelManager = ::android::pdx::uds::ChannelManager;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CHANNEL_MANAGER_H_
diff --git a/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/client_channel.h b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/client_channel.h
new file mode 100644
index 0000000..bf632d7
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/client_channel.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CLIENT_CHANNEL_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CLIENT_CHANNEL_H_
+
+#include <uds/client_channel.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using ClientChannel = ::android::pdx::uds::ClientChannel;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CLIENT_CHANNEL_H_
diff --git a/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/client_channel_factory.h b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/client_channel_factory.h
new file mode 100644
index 0000000..e5c4e30
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/client_channel_factory.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CLIENT_CHANNEL_FACTORY_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CLIENT_CHANNEL_FACTORY_H_
+
+#include <uds/client_channel_factory.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using ClientChannelFactory = ::android::pdx::uds::ClientChannelFactory;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_UDS_CLIENT_CHANNEL_FACTORY_H_
diff --git a/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/service_dispatcher.h b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/service_dispatcher.h
new file mode 100644
index 0000000..7cb7a80
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/service_dispatcher.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_UDS_SERVICE_DISPATCHER_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_UDS_SERVICE_DISPATCHER_H_
+
+#include <uds/service_dispatcher.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using ServiceDispatcher = ::android::pdx::uds::ServiceDispatcher;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_UDS_SERVICE_DISPATCHER_H_
diff --git a/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/service_endpoint.h b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/service_endpoint.h
new file mode 100644
index 0000000..1fd6103
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/private/uds/pdx/default_transport/service_endpoint.h
@@ -0,0 +1,16 @@
+#ifndef ANDROID_PDX_DEFAULT_TRANSPORT_UDS_SERVICE_ENDPOINT_H_
+#define ANDROID_PDX_DEFAULT_TRANSPORT_UDS_SERVICE_ENDPOINT_H_
+
+#include <uds/service_endpoint.h>
+
+namespace android {
+namespace pdx {
+namespace default_transport {
+
+using Endpoint = ::android::pdx::uds::Endpoint;
+
+}  // namespace default_transport
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_DEFAULT_TRANSPORT_UDS_PDX_SERVICE_ENDPOINT_H_
diff --git a/libs/vr/libpdx_default_transport/servicetool.cpp b/libs/vr/libpdx_default_transport/servicetool.cpp
new file mode 100644
index 0000000..60eedb3
--- /dev/null
+++ b/libs/vr/libpdx_default_transport/servicetool.cpp
@@ -0,0 +1,244 @@
+#include <errno.h>
+#include <ftw.h>
+#include <getopt.h>
+#include <pdx/client.h>
+#include <pdx/service.h>
+#include <sys/stat.h>
+
+#include <algorithm>
+#include <vector>
+
+#include <pdx/default_transport/client_channel_factory.h>
+
+using android::pdx::default_transport::ClientChannelFactory;
+
+namespace {
+
+constexpr long kClientTimeoutMs = 0;  // Don't wait for non-existent services.
+constexpr int kDumpBufferSize = 2 * 4096;  // Two pages.
+
+class ControlClient : public android::pdx::ClientBase<ControlClient> {
+ public:
+  explicit ControlClient(const std::string& service_path, long timeout_ms);
+
+  void Reload();
+  std::string Dump();
+
+ private:
+  friend BASE;
+
+  ControlClient(const ControlClient&) = delete;
+  void operator=(const ControlClient&) = delete;
+};
+
+bool option_verbose = false;
+
+static struct option long_options[] = {
+    {"reload", required_argument, 0, 0},
+    {"dump", required_argument, 0, 0},
+    {"verbose", no_argument, 0, 0},
+    {0, 0, 0, 0},
+};
+
+#define printf_verbose(fmt, ... /*args*/) \
+  do {                                    \
+    if (option_verbose)                   \
+      printf(fmt, ##__VA_ARGS__);         \
+  } while (0)
+
+void HexDump(const void* pointer, size_t length);
+
+ControlClient::ControlClient(const std::string& service_path, long timeout_ms)
+    : BASE{ClientChannelFactory::Create(service_path), timeout_ms} {}
+
+void ControlClient::Reload() {
+  android::pdx::Transaction trans{*this};
+  auto status = trans.Send<void>(android::pdx::opcodes::REPORT_SYSPROP_CHANGE,
+                                 nullptr, 0, nullptr, 0);
+  if (!status) {
+    fprintf(stderr, "Failed to send reload: %s\n",
+            status.GetErrorMessage().c_str());
+  }
+}
+
+std::string ControlClient::Dump() {
+  android::pdx::Transaction trans{*this};
+  std::vector<char> buffer(kDumpBufferSize);
+  auto status = trans.Send<int>(android::pdx::opcodes::DUMP_STATE, nullptr, 0,
+                                buffer.data(), buffer.size());
+
+  printf_verbose("ControlClient::Dump: ret=%d\n", ReturnStatusOrError(status));
+
+  if (!status) {
+    fprintf(stderr, "Failed to send dump request: %s\n",
+            status.GetErrorMessage().c_str());
+    return "";
+  } else if (status.get() > static_cast<ssize_t>(buffer.capacity())) {
+    fprintf(stderr, "Service returned a larger size than requested: %d\n",
+            status.get());
+    return "";
+  }
+
+  if (option_verbose)
+    HexDump(buffer.data(), status.get());
+
+  return std::string(buffer.data(), status.get());
+}
+
+int Usage(const std::string& command_name) {
+  printf("Usage: %s [options]\n", command_name.c_str());
+  printf("\t--verbose                      : Use verbose messages.\n");
+  printf(
+      "\t--reload <all | service path>  : Ask service(s) to reload system "
+      "properties.\n");
+  printf("\t--dump <all | service path>    : Dump service(s) state.\n");
+  return -1;
+}
+
+typedef int (*CallbackType)(const char* path, const struct stat* sb,
+                            int type_flag, FTW* ftw_buffer);
+
+int ReloadCommandCallback(const char* path, const struct stat* sb,
+                          int type_flag, FTW* ftw_buffer);
+int DumpCommandCallback(const char* path, const struct stat* sb, int type_flag,
+                        FTW* ftw_buffer);
+
+void CallOnAllFiles(CallbackType callback, const std::string& base_path) {
+  const int kMaxDepth = 32;
+  nftw(base_path.c_str(), callback, kMaxDepth, FTW_PHYS);
+}
+
+int ReloadCommand(const std::string& service_path) {
+  printf_verbose("ReloadCommand: service_path=%s\n", service_path.c_str());
+
+  if (service_path == "" || service_path == "all") {
+    CallOnAllFiles(ReloadCommandCallback,
+                   ClientChannelFactory::GetRootEndpointPath());
+    return 0;
+  } else {
+    auto client = ControlClient::Create(service_path, kClientTimeoutMs);
+    if (!client) {
+      fprintf(stderr, "Failed to open service at \"%s\".\n",
+              service_path.c_str());
+      return -1;
+    }
+
+    client->Reload();
+    return 0;
+  }
+}
+
+int DumpCommand(const std::string& service_path) {
+  printf_verbose("DumpCommand: service_path=%s\n", service_path.c_str());
+
+  if (service_path == "" || service_path == "all") {
+    CallOnAllFiles(DumpCommandCallback,
+                   ClientChannelFactory::GetRootEndpointPath());
+    return 0;
+  } else {
+    auto client = ControlClient::Create(service_path, kClientTimeoutMs);
+    if (!client) {
+      fprintf(stderr, "Failed to open service at \"%s\".\n",
+              service_path.c_str());
+      return -1;
+    }
+
+    std::string response = client->Dump();
+    if (!response.empty()) {
+      printf(
+          "--------------------------------------------------------------------"
+          "---\n");
+      printf("%s:\n", service_path.c_str());
+      printf("%s\n", response.c_str());
+    }
+    return 0;
+  }
+}
+
+int ReloadCommandCallback(const char* path, const struct stat*, int type_flag,
+                          FTW*) {
+  if (type_flag == FTW_F)
+    ReloadCommand(path);
+  return 0;
+}
+
+int DumpCommandCallback(const char* path, const struct stat*, int type_flag,
+                        FTW*) {
+  if (type_flag == FTW_F)
+    DumpCommand(path);
+  return 0;
+}
+
+void HexDump(const void* pointer, size_t length) {
+  uintptr_t address = reinterpret_cast<uintptr_t>(pointer);
+
+  for (size_t count = 0; count < length; count += 16, address += 16) {
+    printf("0x%08lx: ", static_cast<unsigned long>(address));
+
+    for (size_t i = 0; i < 16u; i++) {
+      if (i < std::min(length - count, static_cast<size_t>(16))) {
+        printf("%02x ", *reinterpret_cast<const uint8_t*>(address + i));
+      } else {
+        printf("   ");
+      }
+    }
+
+    printf("|");
+
+    for (size_t i = 0; i < 16u; i++) {
+      if (i < std::min(length - count, static_cast<size_t>(16))) {
+        char c = *reinterpret_cast<const char*>(address + i);
+        if (isalnum(c) || c == ' ') {
+          printf("%c", c);
+        } else {
+          printf(".");
+        }
+      } else {
+        printf(" ");
+      }
+    }
+
+    printf("|\n");
+  }
+}
+
+}  // anonymous namespace
+
+int main(int argc, char** argv) {
+  int getopt_code;
+  int option_index;
+  std::string option = "";
+  std::string command = "";
+  std::string command_argument = "";
+
+  // Process command line options.
+  while ((getopt_code =
+              getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+    option = long_options[option_index].name;
+    printf_verbose("option=%s\n", option.c_str());
+    switch (getopt_code) {
+      case 0:
+        if (option == "verbose") {
+          option_verbose = true;
+        } else {
+          command = option;
+          if (optarg)
+            command_argument = optarg;
+        }
+        break;
+    }
+  }
+
+  printf_verbose("command=%s command_argument=%s\n", command.c_str(),
+                 command_argument.c_str());
+
+  if (command == "") {
+    return Usage(argv[0]);
+  } else if (command == "reload") {
+    return ReloadCommand(command_argument);
+  } else if (command == "dump") {
+    return DumpCommand(command_argument);
+  } else {
+    return Usage(argv[0]);
+  }
+}
diff --git a/libs/vr/libpdx_uds/Android.bp b/libs/vr/libpdx_uds/Android.bp
new file mode 100644
index 0000000..9c4a3b0
--- /dev/null
+++ b/libs/vr/libpdx_uds/Android.bp
@@ -0,0 +1,44 @@
+cc_library_static {
+    name: "libpdx_uds",
+    clang: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    export_include_dirs: ["private"],
+    local_include_dirs: ["private"],
+    srcs: [
+        "channel_manager.cpp",
+        "client_channel_factory.cpp",
+        "client_channel.cpp",
+        "ipc_helper.cpp",
+        "service_dispatcher.cpp",
+        "service_endpoint.cpp",
+    ],
+    static_libs: [
+        "libpdx",
+    ],
+}
+
+cc_test {
+    name: "libpdx_uds_tests",
+    clang: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    srcs: [
+        "remote_method_tests.cpp",
+        "service_framework_tests.cpp",
+    ],
+    static_libs: [
+        "libpdx_uds",
+        "libpdx",
+    ],
+    shared_libs: [
+        "liblog",
+        "libutils",
+    ],
+}
diff --git a/libs/vr/libpdx_uds/channel_manager.cpp b/libs/vr/libpdx_uds/channel_manager.cpp
new file mode 100644
index 0000000..387625c
--- /dev/null
+++ b/libs/vr/libpdx_uds/channel_manager.cpp
@@ -0,0 +1,44 @@
+#include <uds/channel_manager.h>
+
+#include <log/log.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+ChannelManager& ChannelManager::Get() {
+  static ChannelManager instance;
+  return instance;
+}
+
+void ChannelManager::CloseHandle(int32_t handle) {
+  std::lock_guard<std::mutex> autolock(mutex_);
+  auto channel = channels_.find(handle);
+  if (channel == channels_.end()) {
+    ALOGE("Invalid channel handle: %d", handle);
+  } else {
+    channels_.erase(channel);
+  }
+}
+
+LocalChannelHandle ChannelManager::CreateHandle(LocalHandle data_fd,
+                                                LocalHandle event_fd) {
+  if (data_fd && event_fd) {
+    std::lock_guard<std::mutex> autolock(mutex_);
+    int32_t handle = data_fd.Get();
+    channels_.emplace(handle,
+                      ChannelData{std::move(data_fd), std::move(event_fd)});
+    return LocalChannelHandle(this, handle);
+  }
+  return LocalChannelHandle(nullptr, -1);
+}
+
+int ChannelManager::GetEventFd(int32_t handle) {
+  std::lock_guard<std::mutex> autolock(mutex_);
+  auto channel = channels_.find(handle);
+  return channel != channels_.end() ? channel->second.event_fd.Get() : -1;
+};
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx_uds/client_channel.cpp b/libs/vr/libpdx_uds/client_channel.cpp
new file mode 100644
index 0000000..3394759
--- /dev/null
+++ b/libs/vr/libpdx_uds/client_channel.cpp
@@ -0,0 +1,304 @@
+#include "uds/client_channel.h"
+
+#include <errno.h>
+#include <log/log.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+
+#include <pdx/client.h>
+#include <pdx/service_endpoint.h>
+#include <uds/ipc_helper.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+namespace {
+
+struct TransactionState {
+  bool GetLocalFileHandle(int index, LocalHandle* handle) {
+    if (index < 0) {
+      handle->Reset(index);
+    } else if (static_cast<size_t>(index) < response.file_descriptors.size()) {
+      *handle = std::move(response.file_descriptors[index]);
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  bool GetLocalChannelHandle(int index, LocalChannelHandle* handle) {
+    if (index < 0) {
+      *handle = LocalChannelHandle{nullptr, index};
+    } else if (static_cast<size_t>(index) < response.channels.size()) {
+      auto& channel_info = response.channels[index];
+      *handle = ChannelManager::Get().CreateHandle(
+          std::move(channel_info.data_fd), std::move(channel_info.event_fd));
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  FileReference PushFileHandle(BorrowedHandle handle) {
+    if (!handle)
+      return handle.Get();
+    request.file_descriptors.push_back(std::move(handle));
+    return request.file_descriptors.size() - 1;
+  }
+
+  ChannelReference PushChannelHandle(BorrowedChannelHandle handle) {
+    if (!handle)
+      return handle.value();
+    ChannelInfo<BorrowedHandle> channel_info;
+    channel_info.data_fd.Reset(handle.value());
+    channel_info.event_fd.Reset(
+        ChannelManager::Get().GetEventFd(handle.value()));
+    request.channels.push_back(std::move(channel_info));
+    return request.channels.size() - 1;
+  }
+
+  RequestHeader<BorrowedHandle> request;
+  ResponseHeader<LocalHandle> response;
+};
+
+Status<void> ReadAndDiscardData(int socket_fd, size_t size) {
+  while (size > 0) {
+    // If there is more data to read in the message than the buffers provided
+    // by the caller, read and discard the extra data from the socket.
+    char buffer[1024];
+    size_t size_to_read = std::min(sizeof(buffer), size);
+    auto status = ReceiveData(socket_fd, buffer, size_to_read);
+    if (!status)
+      return status;
+    size -= size_to_read;
+  }
+  // We still want to return EIO error to the caller in case we had unexpected
+  // data in the socket stream.
+  return ErrorStatus(EIO);
+}
+
+Status<void> SendRequest(int socket_fd, TransactionState* transaction_state,
+                         int opcode, const iovec* send_vector,
+                         size_t send_count, size_t max_recv_len) {
+  size_t send_len = CountVectorSize(send_vector, send_count);
+  InitRequest(&transaction_state->request, opcode, send_len, max_recv_len,
+              false);
+  auto status = SendData(socket_fd, transaction_state->request);
+  if (status && send_len > 0)
+    status = SendDataVector(socket_fd, send_vector, send_count);
+  return status;
+}
+
+Status<void> ReceiveResponse(int socket_fd, TransactionState* transaction_state,
+                             const iovec* receive_vector, size_t receive_count,
+                             size_t max_recv_len) {
+  auto status = ReceiveData(socket_fd, &transaction_state->response);
+  if (!status)
+    return status;
+
+  if (transaction_state->response.recv_len > 0) {
+    std::vector<iovec> read_buffers;
+    size_t size_remaining = 0;
+    if (transaction_state->response.recv_len != max_recv_len) {
+      // If the receive buffer not exactly the size of data available, recreate
+      // the vector list to consume the data exactly since ReceiveDataVector()
+      // validates that the number of bytes received equals the number of bytes
+      // requested.
+      size_remaining = transaction_state->response.recv_len;
+      for (size_t i = 0; i < receive_count && size_remaining > 0; i++) {
+        read_buffers.push_back(receive_vector[i]);
+        iovec& last_vec = read_buffers.back();
+        if (last_vec.iov_len > size_remaining)
+          last_vec.iov_len = size_remaining;
+        size_remaining -= last_vec.iov_len;
+      }
+      receive_vector = read_buffers.data();
+      receive_count = read_buffers.size();
+    }
+    status = ReceiveDataVector(socket_fd, receive_vector, receive_count);
+    if (status && size_remaining > 0)
+      status = ReadAndDiscardData(socket_fd, size_remaining);
+  }
+  return status;
+}
+
+}  // anonymous namespace
+
+ClientChannel::ClientChannel(LocalChannelHandle channel_handle)
+    : channel_handle_{std::move(channel_handle)} {
+  int data_fd = channel_handle_.value();
+  int event_fd =
+      channel_handle_ ? ChannelManager::Get().GetEventFd(data_fd) : -1;
+
+  if (event_fd >= 0) {
+    epoll_fd_.Reset(epoll_create(1));
+    if (epoll_fd_) {
+      epoll_event data_ev;
+      data_ev.events = EPOLLHUP;
+      data_ev.data.fd = data_fd;
+
+      epoll_event event_ev;
+      event_ev.events = EPOLLIN;
+      event_ev.data.fd = event_fd;
+      if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, data_fd, &data_ev) < 0 ||
+          epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, event_fd, &event_ev) < 0) {
+        ALOGE("Failed to add fd to epoll fd because: %s\n", strerror(errno));
+        epoll_fd_.Close();
+      }
+    }
+  }
+}
+
+std::unique_ptr<pdx::ClientChannel> ClientChannel::Create(
+    LocalChannelHandle channel_handle) {
+  return std::unique_ptr<pdx::ClientChannel>{
+      new ClientChannel{std::move(channel_handle)}};
+}
+
+ClientChannel::~ClientChannel() {
+  if (channel_handle_)
+    shutdown(channel_handle_.value(), SHUT_WR);
+}
+
+void* ClientChannel::AllocateTransactionState() { return new TransactionState; }
+
+void ClientChannel::FreeTransactionState(void* state) {
+  delete static_cast<TransactionState*>(state);
+}
+
+Status<void> ClientChannel::SendImpulse(int opcode, const void* buffer,
+                                        size_t length) {
+  Status<void> status;
+  android::pdx::uds::RequestHeader<BorrowedHandle> request;
+  if (length > request.impulse_payload.size() ||
+      (buffer == nullptr && length != 0)) {
+    status.SetError(EINVAL);
+    return status;
+  }
+
+  InitRequest(&request, opcode, length, 0, true);
+  memcpy(request.impulse_payload.data(), buffer, length);
+  return SendData(channel_handle_.value(), request);
+}
+
+Status<int> ClientChannel::SendAndReceive(void* transaction_state, int opcode,
+                                          const iovec* send_vector,
+                                          size_t send_count,
+                                          const iovec* receive_vector,
+                                          size_t receive_count) {
+  Status<int> result;
+  if ((send_vector == nullptr && send_count != 0) ||
+      (receive_vector == nullptr && receive_count != 0)) {
+    result.SetError(EINVAL);
+    return result;
+  }
+
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  size_t max_recv_len = CountVectorSize(receive_vector, receive_count);
+
+  auto status = SendRequest(channel_handle_.value(), state, opcode, send_vector,
+                            send_count, max_recv_len);
+  if (status) {
+    status = ReceiveResponse(channel_handle_.value(), state, receive_vector,
+                             receive_count, max_recv_len);
+  }
+  if (!result.PropagateError(status)) {
+    const int return_code = state->response.ret_code;
+    if (return_code >= 0)
+      result.SetValue(return_code);
+    else
+      result.SetError(-return_code);
+  }
+  return result;
+}
+
+Status<int> ClientChannel::SendWithInt(void* transaction_state, int opcode,
+                                       const iovec* send_vector,
+                                       size_t send_count,
+                                       const iovec* receive_vector,
+                                       size_t receive_count) {
+  return SendAndReceive(transaction_state, opcode, send_vector, send_count,
+                        receive_vector, receive_count);
+}
+
+Status<LocalHandle> ClientChannel::SendWithFileHandle(
+    void* transaction_state, int opcode, const iovec* send_vector,
+    size_t send_count, const iovec* receive_vector, size_t receive_count) {
+  Status<int> int_status =
+      SendAndReceive(transaction_state, opcode, send_vector, send_count,
+                     receive_vector, receive_count);
+  Status<LocalHandle> status;
+  if (status.PropagateError(int_status))
+    return status;
+
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  LocalHandle handle;
+  if (state->GetLocalFileHandle(int_status.get(), &handle)) {
+    status.SetValue(std::move(handle));
+  } else {
+    status.SetError(EINVAL);
+  }
+  return status;
+}
+
+Status<LocalChannelHandle> ClientChannel::SendWithChannelHandle(
+    void* transaction_state, int opcode, const iovec* send_vector,
+    size_t send_count, const iovec* receive_vector, size_t receive_count) {
+  Status<int> int_status =
+      SendAndReceive(transaction_state, opcode, send_vector, send_count,
+                     receive_vector, receive_count);
+  Status<LocalChannelHandle> status;
+  if (status.PropagateError(int_status))
+    return status;
+
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  LocalChannelHandle handle;
+  if (state->GetLocalChannelHandle(int_status.get(), &handle)) {
+    status.SetValue(std::move(handle));
+  } else {
+    status.SetError(EINVAL);
+  }
+  return status;
+}
+
+FileReference ClientChannel::PushFileHandle(void* transaction_state,
+                                            const LocalHandle& handle) {
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  return state->PushFileHandle(handle.Borrow());
+}
+
+FileReference ClientChannel::PushFileHandle(void* transaction_state,
+                                            const BorrowedHandle& handle) {
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  return state->PushFileHandle(handle.Duplicate());
+}
+
+ChannelReference ClientChannel::PushChannelHandle(
+    void* transaction_state, const LocalChannelHandle& handle) {
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  return state->PushChannelHandle(handle.Borrow());
+}
+
+ChannelReference ClientChannel::PushChannelHandle(
+    void* transaction_state, const BorrowedChannelHandle& handle) {
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  return state->PushChannelHandle(handle.Duplicate());
+}
+
+bool ClientChannel::GetFileHandle(void* transaction_state, FileReference ref,
+                                  LocalHandle* handle) const {
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  return state->GetLocalFileHandle(ref, handle);
+}
+
+bool ClientChannel::GetChannelHandle(void* transaction_state,
+                                     ChannelReference ref,
+                                     LocalChannelHandle* handle) const {
+  auto* state = static_cast<TransactionState*>(transaction_state);
+  return state->GetLocalChannelHandle(ref, handle);
+}
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx_uds/client_channel_factory.cpp b/libs/vr/libpdx_uds/client_channel_factory.cpp
new file mode 100644
index 0000000..1879127
--- /dev/null
+++ b/libs/vr/libpdx_uds/client_channel_factory.cpp
@@ -0,0 +1,89 @@
+#include <uds/client_channel_factory.h>
+
+#include <errno.h>
+#include <log/log.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <uds/channel_manager.h>
+#include <uds/client_channel.h>
+#include <uds/ipc_helper.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+std::string ClientChannelFactory::GetRootEndpointPath() {
+  return "/dev/socket/pdx";
+}
+
+std::string ClientChannelFactory::GetEndpointPath(
+    const std::string& endpoint_path) {
+  std::string path;
+  if (!endpoint_path.empty()) {
+    if (endpoint_path.front() == '/')
+      path = endpoint_path;
+    else
+      path = GetRootEndpointPath() + '/' + endpoint_path;
+  }
+  return path;
+}
+
+ClientChannelFactory::ClientChannelFactory(const std::string& endpoint_path)
+    : endpoint_path_{GetEndpointPath(endpoint_path)} {}
+
+std::unique_ptr<pdx::ClientChannelFactory> ClientChannelFactory::Create(
+    const std::string& endpoint_path) {
+  return std::unique_ptr<pdx::ClientChannelFactory>{
+      new ClientChannelFactory{endpoint_path}};
+}
+
+Status<std::unique_ptr<pdx::ClientChannel>> ClientChannelFactory::Connect(
+    int64_t timeout_ms) const {
+  auto status = WaitForEndpoint(endpoint_path_, timeout_ms);
+  if (!status)
+    return ErrorStatus(status.error());
+
+  LocalHandle socket_fd{socket(AF_UNIX, SOCK_STREAM, 0)};
+  if (!socket_fd) {
+    ALOGE("ClientChannelFactory::Connect: socket error %s", strerror(errno));
+    return ErrorStatus(errno);
+  }
+
+  sockaddr_un remote;
+  remote.sun_family = AF_UNIX;
+  strncpy(remote.sun_path, endpoint_path_.c_str(), sizeof(remote.sun_path));
+  remote.sun_path[sizeof(remote.sun_path) - 1] = '\0';
+
+  int ret = RETRY_EINTR(connect(
+      socket_fd.Get(), reinterpret_cast<sockaddr*>(&remote), sizeof(remote)));
+  if (ret == -1) {
+    ALOGE(
+        "ClientChannelFactory::Connect: Failed to initialize connection when "
+        "connecting %s",
+        strerror(errno));
+    return ErrorStatus(errno);
+  }
+
+  RequestHeader<BorrowedHandle> request;
+  InitRequest(&request, opcodes::CHANNEL_OPEN, 0, 0, false);
+  status = SendData(socket_fd.Get(), request);
+  if (!status)
+    return ErrorStatus(status.error());
+  ResponseHeader<LocalHandle> response;
+  status = ReceiveData(socket_fd.Get(), &response);
+  if (!status)
+    return ErrorStatus(status.error());
+  int ref = response.ret_code;
+  if (ref < 0 || static_cast<size_t>(ref) > response.file_descriptors.size())
+    return ErrorStatus(EIO);
+
+  LocalHandle event_fd = std::move(response.file_descriptors[ref]);
+  return ClientChannel::Create(ChannelManager::Get().CreateHandle(
+      std::move(socket_fd), std::move(event_fd)));
+}
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx_uds/ipc_helper.cpp b/libs/vr/libpdx_uds/ipc_helper.cpp
new file mode 100644
index 0000000..dca23ef
--- /dev/null
+++ b/libs/vr/libpdx_uds/ipc_helper.cpp
@@ -0,0 +1,408 @@
+#include "uds/ipc_helper.h"
+
+#include <alloca.h>
+#include <errno.h>
+#include <log/log.h>
+#include <poll.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <algorithm>
+
+#include <pdx/service.h>
+#include <pdx/utility.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+uint32_t kMagicPreamble = 0x7564736d;  // 'udsm'.
+
+struct MessagePreamble {
+  uint32_t magic{0};
+  uint32_t data_size{0};
+  uint32_t fd_count{0};
+};
+
+Status<void> SendPayload::Send(int socket_fd) {
+  return Send(socket_fd, nullptr);
+}
+
+Status<void> SendPayload::Send(int socket_fd, const ucred* cred) {
+  MessagePreamble preamble;
+  preamble.magic = kMagicPreamble;
+  preamble.data_size = buffer_.size();
+  preamble.fd_count = file_handles_.size();
+
+  ssize_t ret =
+      RETRY_EINTR(send(socket_fd, &preamble, sizeof(preamble), MSG_NOSIGNAL));
+  if (ret < 0)
+    return ErrorStatus(errno);
+  if (ret != sizeof(preamble))
+    return ErrorStatus(EIO);
+
+  msghdr msg = {};
+  iovec recv_vect = {buffer_.data(), buffer_.size()};
+  msg.msg_iov = &recv_vect;
+  msg.msg_iovlen = 1;
+
+  if (cred || !file_handles_.empty()) {
+    const size_t fd_bytes = file_handles_.size() * sizeof(int);
+    msg.msg_controllen = (cred ? CMSG_SPACE(sizeof(ucred)) : 0) +
+                         (fd_bytes == 0 ? 0 : CMSG_SPACE(fd_bytes));
+    msg.msg_control = alloca(msg.msg_controllen);
+
+    cmsghdr* control = CMSG_FIRSTHDR(&msg);
+    if (cred) {
+      control->cmsg_level = SOL_SOCKET;
+      control->cmsg_type = SCM_CREDENTIALS;
+      control->cmsg_len = CMSG_LEN(sizeof(ucred));
+      memcpy(CMSG_DATA(control), cred, sizeof(ucred));
+      control = CMSG_NXTHDR(&msg, control);
+    }
+
+    if (fd_bytes) {
+      control->cmsg_level = SOL_SOCKET;
+      control->cmsg_type = SCM_RIGHTS;
+      control->cmsg_len = CMSG_LEN(fd_bytes);
+      memcpy(CMSG_DATA(control), file_handles_.data(), fd_bytes);
+    }
+  }
+
+  ret = RETRY_EINTR(sendmsg(socket_fd, &msg, MSG_NOSIGNAL));
+  if (ret < 0)
+    return ErrorStatus(errno);
+  if (static_cast<size_t>(ret) != buffer_.size())
+    return ErrorStatus(EIO);
+  return {};
+}
+
+// MessageWriter
+void* SendPayload::GetNextWriteBufferSection(size_t size) {
+  return buffer_.grow_by(size);
+}
+
+OutputResourceMapper* SendPayload::GetOutputResourceMapper() { return this; }
+
+// OutputResourceMapper
+FileReference SendPayload::PushFileHandle(const LocalHandle& handle) {
+  if (handle) {
+    const int ref = file_handles_.size();
+    file_handles_.push_back(handle.Get());
+    return ref;
+  } else {
+    return handle.Get();
+  }
+}
+
+FileReference SendPayload::PushFileHandle(const BorrowedHandle& handle) {
+  if (handle) {
+    const int ref = file_handles_.size();
+    file_handles_.push_back(handle.Get());
+    return ref;
+  } else {
+    return handle.Get();
+  }
+}
+
+FileReference SendPayload::PushFileHandle(const RemoteHandle& handle) {
+  return handle.Get();
+}
+
+ChannelReference SendPayload::PushChannelHandle(
+    const LocalChannelHandle& /*handle*/) {
+  return -1;
+}
+ChannelReference SendPayload::PushChannelHandle(
+    const BorrowedChannelHandle& /*handle*/) {
+  return -1;
+}
+ChannelReference SendPayload::PushChannelHandle(
+    const RemoteChannelHandle& /*handle*/) {
+  return -1;
+}
+
+Status<void> ReceivePayload::Receive(int socket_fd) {
+  return Receive(socket_fd, nullptr);
+}
+
+Status<void> ReceivePayload::Receive(int socket_fd, ucred* cred) {
+  MessagePreamble preamble;
+  ssize_t ret =
+      RETRY_EINTR(recv(socket_fd, &preamble, sizeof(preamble), MSG_WAITALL));
+  if (ret < 0)
+    return ErrorStatus(errno);
+  if (ret != sizeof(preamble) || preamble.magic != kMagicPreamble)
+    return ErrorStatus(EIO);
+
+  buffer_.resize(preamble.data_size);
+  file_handles_.clear();
+  read_pos_ = 0;
+
+  msghdr msg = {};
+  iovec recv_vect = {buffer_.data(), buffer_.size()};
+  msg.msg_iov = &recv_vect;
+  msg.msg_iovlen = 1;
+
+  if (cred || preamble.fd_count) {
+    const size_t receive_fd_bytes = preamble.fd_count * sizeof(int);
+    msg.msg_controllen =
+        (cred ? CMSG_SPACE(sizeof(ucred)) : 0) +
+        (receive_fd_bytes == 0 ? 0 : CMSG_SPACE(receive_fd_bytes));
+    msg.msg_control = alloca(msg.msg_controllen);
+  }
+
+  ret = RETRY_EINTR(recvmsg(socket_fd, &msg, MSG_WAITALL));
+  if (ret < 0)
+    return ErrorStatus(errno);
+  if (static_cast<uint32_t>(ret) != preamble.data_size)
+    return ErrorStatus(EIO);
+
+  bool cred_available = false;
+  file_handles_.reserve(preamble.fd_count);
+  cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+  while (cmsg) {
+    if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS &&
+        cred && cmsg->cmsg_len == CMSG_LEN(sizeof(ucred))) {
+      cred_available = true;
+      memcpy(cred, CMSG_DATA(cmsg), sizeof(ucred));
+    } else if (cmsg->cmsg_level == SOL_SOCKET &&
+               cmsg->cmsg_type == SCM_RIGHTS) {
+      socklen_t payload_len = cmsg->cmsg_len - CMSG_LEN(0);
+      const int* fds = reinterpret_cast<const int*>(CMSG_DATA(cmsg));
+      size_t fd_count = payload_len / sizeof(int);
+      std::transform(fds, fds + fd_count, std::back_inserter(file_handles_),
+                     [](int fd) { return LocalHandle{fd}; });
+    }
+    cmsg = CMSG_NXTHDR(&msg, cmsg);
+  }
+
+  if (cred && !cred_available) {
+    return ErrorStatus(EIO);
+  }
+
+  return {};
+}
+
+// MessageReader
+MessageReader::BufferSection ReceivePayload::GetNextReadBufferSection() {
+  return {buffer_.data() + read_pos_, &*buffer_.end()};
+}
+
+void ReceivePayload::ConsumeReadBufferSectionData(const void* new_start) {
+  read_pos_ = PointerDistance(new_start, buffer_.data());
+}
+
+InputResourceMapper* ReceivePayload::GetInputResourceMapper() { return this; }
+
+// InputResourceMapper
+bool ReceivePayload::GetFileHandle(FileReference ref, LocalHandle* handle) {
+  if (ref < 0) {
+    *handle = LocalHandle{ref};
+    return true;
+  }
+  if (static_cast<size_t>(ref) > file_handles_.size())
+    return false;
+  *handle = std::move(file_handles_[ref]);
+  return true;
+}
+
+bool ReceivePayload::GetChannelHandle(ChannelReference /*ref*/,
+                                      LocalChannelHandle* /*handle*/) {
+  return false;
+}
+
+Status<void> SendData(int socket_fd, const void* data, size_t size) {
+  ssize_t size_written = RETRY_EINTR(send(socket_fd, data, size, MSG_NOSIGNAL));
+  if (size_written < 0)
+    return ErrorStatus(errno);
+  if (static_cast<size_t>(size_written) != size)
+    return ErrorStatus(EIO);
+  return {};
+}
+
+Status<void> SendDataVector(int socket_fd, const iovec* data, size_t count) {
+  msghdr msg = {};
+  msg.msg_iov = const_cast<iovec*>(data);
+  msg.msg_iovlen = count;
+  ssize_t size_written = RETRY_EINTR(sendmsg(socket_fd, &msg, MSG_NOSIGNAL));
+  if (size_written < 0)
+    return ErrorStatus(errno);
+  if (static_cast<size_t>(size_written) != CountVectorSize(data, count))
+    return ErrorStatus(EIO);
+  return {};
+}
+
+Status<void> ReceiveData(int socket_fd, void* data, size_t size) {
+  ssize_t size_read = RETRY_EINTR(recv(socket_fd, data, size, MSG_WAITALL));
+  if (size_read < 0)
+    return ErrorStatus(errno);
+  if (static_cast<size_t>(size_read) != size)
+    return ErrorStatus(EIO);
+  return {};
+}
+
+Status<void> ReceiveDataVector(int socket_fd, const iovec* data, size_t count) {
+  msghdr msg = {};
+  msg.msg_iov = const_cast<iovec*>(data);
+  msg.msg_iovlen = count;
+  ssize_t size_read = RETRY_EINTR(recvmsg(socket_fd, &msg, MSG_WAITALL));
+  if (size_read < 0)
+    return ErrorStatus(errno);
+  if (static_cast<size_t>(size_read) != CountVectorSize(data, count))
+    return ErrorStatus(EIO);
+  return {};
+}
+
+size_t CountVectorSize(const iovec* vector, size_t count) {
+  return std::accumulate(
+      vector, vector + count, size_t{0},
+      [](size_t size, const iovec& vec) { return size + vec.iov_len; });
+}
+
+void InitRequest(android::pdx::uds::RequestHeader<BorrowedHandle>* request,
+                 int opcode, uint32_t send_len, uint32_t max_recv_len,
+                 bool is_impulse) {
+  request->op = opcode;
+  request->cred.pid = getpid();
+  request->cred.uid = geteuid();
+  request->cred.gid = getegid();
+  request->send_len = send_len;
+  request->max_recv_len = max_recv_len;
+  request->is_impulse = is_impulse;
+}
+
+Status<void> WaitForEndpoint(const std::string& endpoint_path,
+                             int64_t timeout_ms) {
+  // Endpoint path must be absolute.
+  if (endpoint_path.empty() || endpoint_path.front() != '/')
+    return ErrorStatus(EINVAL);
+
+  // Create inotify fd.
+  LocalHandle fd{inotify_init()};
+  if (!fd)
+    return ErrorStatus(errno);
+
+  // Set the inotify fd to non-blocking.
+  int ret = fcntl(fd.Get(), F_GETFL);
+  fcntl(fd.Get(), F_SETFL, ret | O_NONBLOCK);
+
+  // Setup the pollfd.
+  pollfd pfd = {fd.Get(), POLLIN, 0};
+
+  // Find locations of each path separator.
+  std::vector<size_t> separators{0};  // The path is absolute, so '/' is at #0.
+  size_t pos = endpoint_path.find('/', 1);
+  while (pos != std::string::npos) {
+    separators.push_back(pos);
+    pos = endpoint_path.find('/', pos + 1);
+  }
+  separators.push_back(endpoint_path.size());
+
+  // Walk down the path, checking for existence and waiting if needed.
+  pos = 1;
+  size_t links = 0;
+  std::string current;
+  while (pos < separators.size() && links <= MAXSYMLINKS) {
+    std::string previous = current;
+    current = endpoint_path.substr(0, separators[pos]);
+
+    // Check for existence; proceed to setup a watch if not.
+    if (access(current.c_str(), F_OK) < 0) {
+      if (errno != ENOENT)
+        return ErrorStatus(errno);
+
+      // Extract the name of the path component to wait for.
+      std::string next = current.substr(
+          separators[pos - 1] + 1, separators[pos] - separators[pos - 1] - 1);
+
+      // Add a watch on the last existing directory we reach.
+      int wd = inotify_add_watch(
+          fd.Get(), previous.c_str(),
+          IN_CREATE | IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVED_TO);
+      if (wd < 0) {
+        if (errno != ENOENT)
+          return ErrorStatus(errno);
+        // Restart at the beginning if previous was deleted.
+        links = 0;
+        current.clear();
+        pos = 1;
+        continue;
+      }
+
+      // Make sure current didn't get created before the watch was added.
+      ret = access(current.c_str(), F_OK);
+      if (ret < 0) {
+        if (errno != ENOENT)
+          return ErrorStatus(errno);
+
+        bool exit_poll = false;
+        while (!exit_poll) {
+          // Wait for an event or timeout.
+          ret = poll(&pfd, 1, timeout_ms);
+          if (ret <= 0)
+            return ErrorStatus(ret == 0 ? ETIMEDOUT : errno);
+
+          // Read events.
+          char buffer[sizeof(inotify_event) + NAME_MAX + 1];
+
+          ret = read(fd.Get(), buffer, sizeof(buffer));
+          if (ret < 0) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK)
+              continue;
+            else
+              return ErrorStatus(errno);
+          } else if (static_cast<size_t>(ret) < sizeof(struct inotify_event)) {
+            return ErrorStatus(EIO);
+          }
+
+          auto* event = reinterpret_cast<const inotify_event*>(buffer);
+          auto* end = reinterpret_cast<const inotify_event*>(buffer + ret);
+          while (event < end) {
+            std::string event_for;
+            if (event->len > 0)
+              event_for = event->name;
+
+            if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
+              // See if this is the droid we're looking for.
+              if (next == event_for) {
+                exit_poll = true;
+                break;
+              }
+            } else if (event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) {
+              // Restart at the beginning if our watch dir is deleted.
+              links = 0;
+              current.clear();
+              pos = 0;
+              exit_poll = true;
+              break;
+            }
+
+            event = reinterpret_cast<const inotify_event*>(AdvancePointer(
+                event, sizeof(struct inotify_event) + event->len));
+          }  // while (event < end)
+        }    // while (!exit_poll)
+      }      // Current dir doesn't exist.
+      ret = inotify_rm_watch(fd.Get(), wd);
+      if (ret < 0 && errno != EINVAL)
+        return ErrorStatus(errno);
+    }  // if (access(current.c_str(), F_OK) < 0)
+
+    // Check for symbolic link and update link count.
+    struct stat stat_buf;
+    ret = lstat(current.c_str(), &stat_buf);
+    if (ret < 0 && errno != ENOENT)
+      return ErrorStatus(errno);
+    else if (ret == 0 && S_ISLNK(stat_buf.st_mode))
+      links++;
+    pos++;
+  }  // while (pos < separators.size() && links <= MAXSYMLINKS)
+
+  return {};
+}
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx_uds/private/uds/channel_manager.h b/libs/vr/libpdx_uds/private/uds/channel_manager.h
new file mode 100644
index 0000000..e8d3f6e
--- /dev/null
+++ b/libs/vr/libpdx_uds/private/uds/channel_manager.h
@@ -0,0 +1,38 @@
+#ifndef ANDROID_PDX_UDS_CHANNEL_MANAGER_H_
+#define ANDROID_PDX_UDS_CHANNEL_MANAGER_H_
+
+#include <mutex>
+#include <unordered_map>
+
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+class ChannelManager : public ChannelManagerInterface {
+ public:
+  static ChannelManager& Get();
+
+  LocalChannelHandle CreateHandle(LocalHandle data_fd, LocalHandle event_fd);
+  int GetEventFd(int32_t handle);
+
+ private:
+  struct ChannelData {
+    LocalHandle data_fd;
+    LocalHandle event_fd;
+  };
+  ChannelManager() = default;
+
+  void CloseHandle(int32_t handle) override;
+
+  std::mutex mutex_;
+  std::unordered_map<int32_t, ChannelData> channels_;
+};
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_UDS_CHANNEL_MANAGER_H_
diff --git a/libs/vr/libpdx_uds/private/uds/client_channel.h b/libs/vr/libpdx_uds/private/uds/client_channel.h
new file mode 100644
index 0000000..12a40e7
--- /dev/null
+++ b/libs/vr/libpdx_uds/private/uds/client_channel.h
@@ -0,0 +1,71 @@
+#ifndef ANDROID_PDX_UDS_CLIENT_CHANNEL_H_
+#define ANDROID_PDX_UDS_CLIENT_CHANNEL_H_
+
+#include <pdx/client_channel.h>
+
+#include <uds/channel_manager.h>
+#include <uds/service_endpoint.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+class ClientChannel : public pdx::ClientChannel {
+ public:
+  ~ClientChannel() override;
+
+  static std::unique_ptr<pdx::ClientChannel> Create(
+      LocalChannelHandle channel_handle);
+
+  uint32_t GetIpcTag() const override { return Endpoint::kIpcTag; }
+  int event_fd() const override { return epoll_fd_.Get(); }
+  LocalChannelHandle& GetChannelHandle() override { return channel_handle_; }
+  void* AllocateTransactionState() override;
+  void FreeTransactionState(void* state) override;
+
+  Status<void> SendImpulse(int opcode, const void* buffer,
+                           size_t length) override;
+
+  Status<int> SendWithInt(void* transaction_state, int opcode,
+                          const iovec* send_vector, size_t send_count,
+                          const iovec* receive_vector,
+                          size_t receive_count) override;
+  Status<LocalHandle> SendWithFileHandle(void* transaction_state, int opcode,
+                                         const iovec* send_vector,
+                                         size_t send_count,
+                                         const iovec* receive_vector,
+                                         size_t receive_count) override;
+  Status<LocalChannelHandle> SendWithChannelHandle(
+      void* transaction_state, int opcode, const iovec* send_vector,
+      size_t send_count, const iovec* receive_vector,
+      size_t receive_count) override;
+
+  FileReference PushFileHandle(void* transaction_state,
+                               const LocalHandle& handle) override;
+  FileReference PushFileHandle(void* transaction_state,
+                               const BorrowedHandle& handle) override;
+  ChannelReference PushChannelHandle(void* transaction_state,
+                                     const LocalChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      void* transaction_state, const BorrowedChannelHandle& handle) override;
+  bool GetFileHandle(void* transaction_state, FileReference ref,
+                     LocalHandle* handle) const override;
+  bool GetChannelHandle(void* transaction_state, ChannelReference ref,
+                        LocalChannelHandle* handle) const override;
+
+ private:
+  explicit ClientChannel(LocalChannelHandle channel_handle);
+
+  Status<int> SendAndReceive(void* transaction_state, int opcode,
+                             const iovec* send_vector, size_t send_count,
+                             const iovec* receive_vector, size_t receive_count);
+
+  LocalChannelHandle channel_handle_;
+  LocalHandle epoll_fd_;
+};
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_UDS_CLIENT_CHANNEL_H_
diff --git a/libs/vr/libpdx_uds/private/uds/client_channel_factory.h b/libs/vr/libpdx_uds/private/uds/client_channel_factory.h
new file mode 100644
index 0000000..6f80d31
--- /dev/null
+++ b/libs/vr/libpdx_uds/private/uds/client_channel_factory.h
@@ -0,0 +1,33 @@
+#ifndef ANDROID_PDX_UDS_CLIENT_CHANNEL_FACTORY_H_
+#define ANDROID_PDX_UDS_CLIENT_CHANNEL_FACTORY_H_
+
+#include <string>
+
+#include <pdx/client_channel_factory.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+class ClientChannelFactory : public pdx::ClientChannelFactory {
+ public:
+  static std::unique_ptr<pdx::ClientChannelFactory> Create(
+      const std::string& endpoint_path);
+
+  Status<std::unique_ptr<pdx::ClientChannel>> Connect(
+      int64_t timeout_ms) const override;
+
+  static std::string GetRootEndpointPath();
+  static std::string GetEndpointPath(const std::string& endpoint_path);
+
+ private:
+  explicit ClientChannelFactory(const std::string& endpoint_path);
+
+  std::string endpoint_path_;
+};
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_UDS_CLIENT_CHANNEL_FACTORY_H_
diff --git a/libs/vr/libpdx_uds/private/uds/ipc_helper.h b/libs/vr/libpdx_uds/private/uds/ipc_helper.h
new file mode 100644
index 0000000..00f3490
--- /dev/null
+++ b/libs/vr/libpdx_uds/private/uds/ipc_helper.h
@@ -0,0 +1,164 @@
+#ifndef ANDROID_PDX_UDS_IPC_HELPER_H_
+#define ANDROID_PDX_UDS_IPC_HELPER_H_
+
+#include <sys/socket.h>
+#include <utility>
+#include <vector>
+
+#include <pdx/rpc/serializable.h>
+#include <pdx/rpc/serialization.h>
+#include <pdx/status.h>
+#include <pdx/utility.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+#define RETRY_EINTR(fnc_call)                 \
+  ([&]() -> decltype(fnc_call) {              \
+    decltype(fnc_call) result;                \
+    do {                                      \
+      result = (fnc_call);                    \
+    } while (result == -1 && errno == EINTR); \
+    return result;                            \
+  })()
+
+class SendPayload : public MessageWriter, public OutputResourceMapper {
+ public:
+  Status<void> Send(int socket_fd);
+  Status<void> Send(int socket_fd, const ucred* cred);
+
+  // MessageWriter
+  void* GetNextWriteBufferSection(size_t size) override;
+  OutputResourceMapper* GetOutputResourceMapper() override;
+
+  // OutputResourceMapper
+  FileReference PushFileHandle(const LocalHandle& handle) override;
+  FileReference PushFileHandle(const BorrowedHandle& handle) override;
+  FileReference PushFileHandle(const RemoteHandle& handle) override;
+  ChannelReference PushChannelHandle(const LocalChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      const BorrowedChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      const RemoteChannelHandle& handle) override;
+
+ private:
+  ByteBuffer buffer_;
+  std::vector<int> file_handles_;
+};
+
+class ReceivePayload : public MessageReader, public InputResourceMapper {
+ public:
+  Status<void> Receive(int socket_fd);
+  Status<void> Receive(int socket_fd, ucred* cred);
+
+  // MessageReader
+  BufferSection GetNextReadBufferSection() override;
+  void ConsumeReadBufferSectionData(const void* new_start) override;
+  InputResourceMapper* GetInputResourceMapper() override;
+
+  // InputResourceMapper
+  bool GetFileHandle(FileReference ref, LocalHandle* handle) override;
+  bool GetChannelHandle(ChannelReference ref,
+                        LocalChannelHandle* handle) override;
+
+ private:
+  ByteBuffer buffer_;
+  std::vector<LocalHandle> file_handles_;
+  size_t read_pos_{0};
+};
+
+template <typename FileHandleType>
+class ChannelInfo {
+ public:
+  FileHandleType data_fd;
+  FileHandleType event_fd;
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(ChannelInfo, data_fd, event_fd);
+};
+
+template <typename FileHandleType>
+class RequestHeader {
+ public:
+  int32_t op{0};
+  ucred cred;
+  uint32_t send_len{0};
+  uint32_t max_recv_len{0};
+  std::vector<FileHandleType> file_descriptors;
+  std::vector<ChannelInfo<FileHandleType>> channels;
+  std::array<uint8_t, 32> impulse_payload;
+  bool is_impulse{false};
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(RequestHeader, op, send_len, max_recv_len,
+                           file_descriptors, channels, impulse_payload,
+                           is_impulse);
+};
+
+template <typename FileHandleType>
+class ResponseHeader {
+ public:
+  int32_t ret_code{0};
+  uint32_t recv_len{0};
+  std::vector<FileHandleType> file_descriptors;
+  std::vector<ChannelInfo<FileHandleType>> channels;
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(ResponseHeader, ret_code, recv_len, file_descriptors,
+                           channels);
+};
+
+template <typename T>
+inline Status<void> SendData(int socket_fd, const T& data) {
+  SendPayload payload;
+  rpc::Serialize(data, &payload);
+  return payload.Send(socket_fd);
+}
+
+template <typename FileHandleType>
+inline Status<void> SendData(int socket_fd,
+                             const RequestHeader<FileHandleType>& request) {
+  SendPayload payload;
+  rpc::Serialize(request, &payload);
+  return payload.Send(socket_fd, &request.cred);
+}
+
+Status<void> SendData(int socket_fd, const void* data, size_t size);
+Status<void> SendDataVector(int socket_fd, const iovec* data, size_t count);
+
+template <typename T>
+inline Status<void> ReceiveData(int socket_fd, T* data) {
+  ReceivePayload payload;
+  Status<void> status = payload.Receive(socket_fd);
+  if (status && rpc::Deserialize(data, &payload) != rpc::ErrorCode::NO_ERROR)
+    status.SetError(EIO);
+  return status;
+}
+
+template <typename FileHandleType>
+inline Status<void> ReceiveData(int socket_fd,
+                                RequestHeader<FileHandleType>* request) {
+  ReceivePayload payload;
+  Status<void> status = payload.Receive(socket_fd, &request->cred);
+  if (status && rpc::Deserialize(request, &payload) != rpc::ErrorCode::NO_ERROR)
+    status.SetError(EIO);
+  return status;
+}
+
+Status<void> ReceiveData(int socket_fd, void* data, size_t size);
+Status<void> ReceiveDataVector(int socket_fd, const iovec* data, size_t count);
+
+size_t CountVectorSize(const iovec* data, size_t count);
+void InitRequest(android::pdx::uds::RequestHeader<BorrowedHandle>* request,
+                 int opcode, uint32_t send_len, uint32_t max_recv_len,
+                 bool is_impulse);
+
+Status<void> WaitForEndpoint(const std::string& endpoint_path,
+                             int64_t timeout_ms);
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_UDS_IPC_HELPER_H_
diff --git a/libs/vr/libpdx_uds/private/uds/service_dispatcher.h b/libs/vr/libpdx_uds/private/uds/service_dispatcher.h
new file mode 100644
index 0000000..23af4f4
--- /dev/null
+++ b/libs/vr/libpdx_uds/private/uds/service_dispatcher.h
@@ -0,0 +1,55 @@
+#ifndef ANDROID_PDX_UDS_SERVICE_DISPATCHER_H_
+#define ANDROID_PDX_UDS_SERVICE_DISPATCHER_H_
+
+#include <list>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include <pdx/file_handle.h>
+#include <pdx/service_dispatcher.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+class ServiceDispatcher : public pdx::ServiceDispatcher {
+ public:
+  // Get a new instance of ServiceDispatcher, or return nullptr if init failed.
+  static std::unique_ptr<pdx::ServiceDispatcher> Create();
+
+  ~ServiceDispatcher() override;
+  int AddService(const std::shared_ptr<Service>& service) override;
+  int RemoveService(const std::shared_ptr<Service>& service) override;
+  int ReceiveAndDispatch() override;
+  int ReceiveAndDispatch(int timeout) override;
+  int EnterDispatchLoop() override;
+  void SetCanceled(bool cancel) override;
+  bool IsCanceled() const override;
+
+ private:
+  ServiceDispatcher();
+
+  // Internal thread accounting.
+  int ThreadEnter();
+  void ThreadExit();
+
+  std::mutex mutex_;
+  std::condition_variable condition_;
+  std::atomic<bool> canceled_{false};
+
+  std::list<std::shared_ptr<Service>> services_;
+
+  int thread_count_ = 0;
+  LocalHandle event_fd_;
+  LocalHandle epoll_fd_;
+
+  ServiceDispatcher(const ServiceDispatcher&) = delete;
+  void operator=(const ServiceDispatcher&) = delete;
+};
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_UDS_SERVICE_DISPATCHER_H_
diff --git a/libs/vr/libpdx_uds/private/uds/service_endpoint.h b/libs/vr/libpdx_uds/private/uds/service_endpoint.h
new file mode 100644
index 0000000..0f69400
--- /dev/null
+++ b/libs/vr/libpdx_uds/private/uds/service_endpoint.h
@@ -0,0 +1,144 @@
+#ifndef ANDROID_PDX_UDS_SERVICE_ENDPOINT_H_
+#define ANDROID_PDX_UDS_SERVICE_ENDPOINT_H_
+
+#include <sys/stat.h>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <pdx/service.h>
+#include <pdx/service_endpoint.h>
+#include <uds/service_dispatcher.h>
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+class Endpoint : public pdx::Endpoint {
+ public:
+  enum {
+    kIpcTag = 0x00736674,  // 'uds'
+  };
+
+  // Blocking modes for service endpoint. Controls whether the epoll set is in
+  // blocking mode or not for message receive.
+  enum {
+    kBlocking = true,
+    kNonBlocking = false,
+    kDefaultBlocking = kNonBlocking,
+  };
+
+  enum : mode_t {
+    kDefaultMode = 0,
+  };
+
+  ~Endpoint() override = default;
+
+  uint32_t GetIpcTag() const override { return kIpcTag; }
+  int SetService(Service* service) override;
+  int SetChannel(int channel_id, Channel* channel) override;
+  int CloseChannel(int channel_id) override;
+  int ModifyChannelEvents(int channel_id, int clear_mask,
+                          int set_mask) override;
+  Status<RemoteChannelHandle> PushChannel(Message* message, int flags,
+                                          Channel* channel,
+                                          int* channel_id) override;
+  Status<int> CheckChannel(const Message* message, ChannelReference ref,
+                           Channel** channel) override;
+  int DefaultHandleMessage(const MessageInfo& info) override;
+  int MessageReceive(Message* message) override;
+  int MessageReply(Message* message, int return_code) override;
+  int MessageReplyFd(Message* message, unsigned int push_fd) override;
+  int MessageReplyChannelHandle(Message* message,
+                                const LocalChannelHandle& handle) override;
+  int MessageReplyChannelHandle(Message* message,
+                                const BorrowedChannelHandle& handle) override;
+  int MessageReplyChannelHandle(Message* message,
+                                const RemoteChannelHandle& handle) override;
+  ssize_t ReadMessageData(Message* message, const iovec* vector,
+                          size_t vector_length) override;
+  ssize_t WriteMessageData(Message* message, const iovec* vector,
+                           size_t vector_length) override;
+  FileReference PushFileHandle(Message* message,
+                               const LocalHandle& handle) override;
+  FileReference PushFileHandle(Message* message,
+                               const BorrowedHandle& handle) override;
+  FileReference PushFileHandle(Message* message,
+                               const RemoteHandle& handle) override;
+  ChannelReference PushChannelHandle(Message* message,
+                                     const LocalChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      Message* message, const BorrowedChannelHandle& handle) override;
+  ChannelReference PushChannelHandle(
+      Message* message, const RemoteChannelHandle& handle) override;
+  LocalHandle GetFileHandle(Message* message, FileReference ref) const override;
+  LocalChannelHandle GetChannelHandle(Message* message,
+                                      ChannelReference ref) const override;
+
+  void* AllocateMessageState() override;
+  void FreeMessageState(void* state) override;
+
+  int Cancel() override;
+
+  // Open an endpoint at the given path.
+  // Second parameter is unused for UDS, but we have it here for compatibility
+  // in signature with servicefs::Endpoint::Create().
+  static std::unique_ptr<Endpoint> Create(const std::string& endpoint_path,
+                                          mode_t /*unused_mode*/ = kDefaultMode,
+                                          bool blocking = kDefaultBlocking);
+
+  int epoll_fd() const { return epoll_fd_.Get(); }
+
+ private:
+  struct ChannelData {
+    LocalHandle data_fd;
+    LocalHandle event_fd;
+    uint32_t event_mask{0};
+    Channel* channel_state{nullptr};
+  };
+
+  // This class must be instantiated using Create() static methods above.
+  Endpoint(const std::string& endpoint_path, bool blocking);
+
+  Endpoint(const Endpoint&) = delete;
+  void operator=(const Endpoint&) = delete;
+
+  uint32_t GetNextAvailableMessageId() {
+    return next_message_id_.fetch_add(1, std::memory_order_relaxed);
+  }
+
+  Status<void> AcceptConnection(Message* message);
+  Status<void> ReceiveMessageForChannel(int channel_id, Message* message);
+  Status<void> OnNewChannel(LocalHandle channel_fd);
+  Status<ChannelData*> OnNewChannelLocked(LocalHandle channel_fd,
+                                          Channel* channel_state);
+  int CloseChannelLocked(int channel_id);
+  Status<void> ReenableEpollEvent(int fd);
+  Channel* GetChannelState(int channel_id);
+  int GetChannelSocketFd(int channel_id);
+  int GetChannelEventFd(int channel_id);
+
+  std::string endpoint_path_;
+  bool is_blocking_;
+  LocalHandle socket_fd_;
+  LocalHandle cancel_event_fd_;
+  LocalHandle epoll_fd_;
+
+  mutable std::mutex channel_mutex_;
+  std::map<int, ChannelData> channels_;
+
+  mutable std::mutex service_mutex_;
+  std::condition_variable condition_;
+
+  Service* service_{nullptr};
+  std::atomic<uint32_t> next_message_id_;
+};
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
+
+#endif  // ANDROID_PDX_UDS_PDX_SERVICE_ENDPOINT_H_
diff --git a/libs/vr/libpdx_uds/remote_method_tests.cpp b/libs/vr/libpdx_uds/remote_method_tests.cpp
new file mode 100644
index 0000000..299910c
--- /dev/null
+++ b/libs/vr/libpdx_uds/remote_method_tests.cpp
@@ -0,0 +1,907 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <array>
+#include <cstdint>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <thread>
+
+#include <gtest/gtest.h>
+#include <pdx/channel_handle.h>
+#include <pdx/client.h>
+#include <pdx/rpc/remote_method.h>
+#include <pdx/rpc/serializable.h>
+#include <pdx/service.h>
+#include <uds/client_channel.h>
+#include <uds/client_channel_factory.h>
+#include <uds/service_dispatcher.h>
+#include <uds/service_endpoint.h>
+
+using android::pdx::BorrowedHandle;
+using android::pdx::Channel;
+using android::pdx::ClientBase;
+using android::pdx::LocalChannelHandle;
+using android::pdx::LocalHandle;
+using android::pdx::Message;
+using android::pdx::RemoteChannelHandle;
+using android::pdx::RemoteHandle;
+using android::pdx::ServiceBase;
+using android::pdx::ServiceDispatcher;
+using android::pdx::Status;
+using android::pdx::uds::Endpoint;
+using namespace android::pdx::rpc;
+
+namespace {
+
+// Defines a serializable user type that may be transferred between client and
+// service.
+struct TestType {
+  int a;
+  float b;
+  std::string c;
+
+  TestType() {}
+  TestType(int a, float b, const std::string& c) : a(a), b(b), c(c) {}
+
+  // Make gtest expressions simpler by defining equality operator. This is not
+  // needed for serialization.
+  bool operator==(const TestType& other) const {
+    return a == other.a && b == other.b && c == other.c;
+  }
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(TestType, a, b, c);
+};
+
+struct DerivedTestType : public TestType {
+  DerivedTestType() : TestType() {}
+  DerivedTestType(int a, float b) : TestType(a, b, "constant") {}
+};
+
+// Defines a serializable user type with a LocalHandle member.
+struct TestFdType {
+  int a;
+  LocalHandle fd;
+
+  TestFdType() {}
+  TestFdType(int a, LocalHandle fd) : a(a), fd(std::move(fd)) {}
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(TestFdType, a, fd);
+};
+
+// Defines a serializable user template type with a FileHandle member.
+template <typename FileHandleType>
+struct TestTemplateType {
+  FileHandleType fd;
+
+  TestTemplateType() {}
+  TestTemplateType(FileHandleType fd) : fd(std::move(fd)) {}
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(TestTemplateType<FileHandleType>, fd);
+};
+
+struct BasicStruct {
+  int a;
+  int b;
+  std::string c;
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(BasicStruct, a, b, c);
+};
+
+using BasicStructTraits = SerializableTraits<BasicStruct>;
+
+struct NonSerializableType {
+  int a;
+  int b;
+  std::string c;
+};
+
+struct IncorrectlyDefinedSerializableType {
+  int a;
+  int b;
+
+ private:
+  using SerializableMembers = std::tuple<int, int>;
+};
+
+// Defines the contract between the client and service, including ServiceFS
+// endpoint path, method opcodes, and remote method signatures.
+struct TestInterface final {
+  // Service path.
+  static constexpr char kClientPath[] = "socket_test";
+
+  // Op codes.
+  enum {
+    kOpAdd = 0,
+    kOpFoo,
+    kOpConcatenate,
+    kOpWriteBuffer,
+    kOpStringLength,
+    kOpSendTestType,
+    kOpSendBasicStruct,
+    kOpSendVector,
+    kOpRot13,
+    kOpNoArgs,
+    kOpSendFile,
+    kOpGetFile,
+    kOpGetTestFdType,
+    kOpOpenFiles,
+    kOpReadFile,
+    kOpPushChannel,
+  };
+
+  // Methods.
+  PDX_REMOTE_METHOD(Add, kOpAdd, int(int, int));
+  PDX_REMOTE_METHOD(Foo, kOpFoo, int(int, const std::string&));
+  PDX_REMOTE_METHOD(Concatenate, kOpConcatenate,
+                    std::string(const std::string&, const std::string&));
+  PDX_REMOTE_METHOD(SumVector, kOpWriteBuffer, int(const std::vector<int>&));
+  PDX_REMOTE_METHOD(StringLength, kOpStringLength, int(const std::string&));
+  PDX_REMOTE_METHOD(SendTestType, kOpSendTestType, TestType(const TestType&));
+  PDX_REMOTE_METHOD(SendBasicStruct, kOpSendBasicStruct,
+                    BasicStruct(const BasicStruct&));
+  PDX_REMOTE_METHOD(SendVector, kOpSendVector,
+                    std::string(const std::vector<TestType>&));
+  PDX_REMOTE_METHOD(Rot13, kOpRot13, std::string(const std::string&));
+  PDX_REMOTE_METHOD(NoArgs, kOpNoArgs, int(Void));
+  PDX_REMOTE_METHOD(SendFile, kOpSendFile, int(const LocalHandle& fd));
+  PDX_REMOTE_METHOD(GetFile, kOpGetFile, LocalHandle(const std::string&, int));
+  PDX_REMOTE_METHOD(GetTestFdType, kOpGetTestFdType,
+                    TestFdType(int, const std::string&, int));
+  PDX_REMOTE_METHOD(OpenFiles, kOpOpenFiles,
+                    std::vector<LocalHandle>(
+                        const std::vector<std::pair<std::string, int>>&));
+  PDX_REMOTE_METHOD(ReadFile, kOpReadFile,
+                    std::pair<int, BufferWrapper<std::uint8_t*>>(
+                        const std::string&, int, std::size_t));
+  PDX_REMOTE_METHOD(PushChannel, kOpPushChannel, LocalChannelHandle(Void));
+
+  PDX_REMOTE_API(API, Add, Foo, Concatenate, SumVector, StringLength,
+                 SendTestType, SendVector, Rot13, NoArgs, SendFile, GetFile,
+                 GetTestFdType, OpenFiles, PushChannel);
+};
+
+constexpr char TestInterface::kClientPath[];
+
+// Test client to send messages to the test service.
+class TestClient : public ClientBase<TestClient> {
+ public:
+  int Add(int a, int b) {
+    return ReturnStatusOrError(InvokeRemoteMethod<TestInterface::Add>(a, b));
+  }
+
+  int Foo(int a, const std::string& b) {
+    return ReturnStatusOrError(InvokeRemoteMethod<TestInterface::Foo>(a, b));
+  }
+
+  std::string Concatenate(const std::string& a, const std::string& b) {
+    std::string return_value;
+
+    Status<std::string> status =
+        InvokeRemoteMethod<TestInterface::Concatenate>(a, b);
+    if (!status)
+      return std::string("[Error]");
+    else
+      return status.take();
+  }
+
+  int SumVector(const int* buffer, std::size_t size) {
+    return ReturnStatusOrError(
+        InvokeRemoteMethod<TestInterface::SumVector>(WrapArray(buffer, size)));
+  }
+
+  int SumVector(const std::vector<int>& buffer) {
+    return ReturnStatusOrError(
+        InvokeRemoteMethod<TestInterface::SumVector>(buffer));
+  }
+
+  int StringLength(const char* string, std::size_t size) {
+    return ReturnStatusOrError(InvokeRemoteMethod<TestInterface::StringLength>(
+        WrapString(string, size)));
+  }
+
+  int StringLength(const std::string& string) {
+    return ReturnStatusOrError(
+        InvokeRemoteMethod<TestInterface::StringLength>(string));
+  }
+
+  TestType SendTestType(const TestType& tt) {
+    Status<TestType> status =
+        InvokeRemoteMethod<TestInterface::SendTestType>(tt);
+    if (!status)
+      return TestType(0, 0.0, "[Error]");
+    else
+      return status.take();
+  }
+
+  BasicStruct SendBasicStruct(const BasicStruct& bs) {
+    Status<BasicStruct> status =
+        InvokeRemoteMethod<TestInterface::SendBasicStruct>(bs);
+    if (!status)
+      return BasicStruct{0, 0, "[Error]"};
+    else
+      return status.take();
+  }
+
+  std::string SendVector(const std::vector<TestType>& v) {
+    Status<std::string> status =
+        InvokeRemoteMethod<TestInterface::SendVector>(v);
+    if (!status)
+      return "[Error]";
+    else
+      return status.take();
+  }
+
+  std::string Rot13(const std::string& string) {
+    Status<std::string> status =
+        InvokeRemoteMethod<TestInterface::Rot13>(string);
+    return status ? status.get() : string;
+  }
+
+  int NoArgs() {
+    return ReturnStatusOrError(InvokeRemoteMethod<TestInterface::NoArgs>());
+  }
+
+  int SendFile(const LocalHandle& fd) {
+    return ReturnStatusOrError(InvokeRemoteMethod<TestInterface::SendFile>(fd));
+  }
+
+  LocalHandle GetFile(const std::string& path, int mode) {
+    Status<LocalHandle> status =
+        InvokeRemoteMethod<TestInterface::GetFile>(path, mode);
+    if (!status)
+      return LocalHandle(-status.error());
+    else
+      return status.take();
+  }
+
+  int GetFile(const std::string& path, int mode, LocalHandle* fd_out) {
+    Status<void> status =
+        InvokeRemoteMethodInPlace<TestInterface::GetFile>(fd_out, path, mode);
+    return status ? 0 : -status.error();
+  }
+
+  TestFdType GetTestFdType(int a, const std::string& path, int mode) {
+    Status<TestFdType> status =
+        InvokeRemoteMethod<TestInterface::GetTestFdType>(a, path, mode);
+    if (!status)
+      return {};
+    else
+      return status.take();
+  }
+
+  std::vector<LocalHandle> OpenFiles(
+      const std::vector<std::pair<std::string, int>>& file_specs) {
+    Status<std::vector<LocalHandle>> status =
+        InvokeRemoteMethod<TestInterface::OpenFiles>(file_specs);
+    if (!status)
+      return {};
+    else
+      return status.take();
+  }
+
+  int ReadFile(void* buffer, std::size_t size, const std::string& path,
+               int mode) {
+    auto buffer_wrapper = WrapBuffer(buffer, size);
+    auto return_value = std::make_pair(-1, buffer_wrapper);
+
+    Status<void> status = InvokeRemoteMethodInPlace<TestInterface::ReadFile>(
+        &return_value, path, mode, size);
+    return status ? return_value.first : -status.error();
+  }
+
+  int PushChannel(LocalChannelHandle* fd_out) {
+    auto status = InvokeRemoteMethodInPlace<TestInterface::PushChannel>(fd_out);
+    return status ? 0 : -status.error();
+  }
+
+  int GetFd() const { return event_fd(); }
+
+ private:
+  friend BASE;
+
+  TestClient(LocalChannelHandle channel_handle)
+      : BASE{android::pdx::uds::ClientChannel::Create(
+            std::move(channel_handle))} {}
+  TestClient()
+      : BASE{android::pdx::uds::ClientChannelFactory::Create(
+            TestInterface::kClientPath)} {}
+
+  TestClient(const TestClient&) = delete;
+  void operator=(const TestClient&) = delete;
+};
+
+// Test service that encodes/decodes messages from clients.
+class TestService : public ServiceBase<TestService> {
+ public:
+  int HandleMessage(Message& message) override {
+    switch (message.GetOp()) {
+      case TestInterface::Add::Opcode:
+        DispatchRemoteMethod<TestInterface::Add>(*this, &TestService::OnAdd,
+                                                 message);
+        return 0;
+
+      case TestInterface::Foo::Opcode:
+        DispatchRemoteMethod<TestInterface::Foo>(*this, &TestService::OnFoo,
+                                                 message);
+        return 0;
+
+      case TestInterface::Concatenate::Opcode:
+        DispatchRemoteMethod<TestInterface::Concatenate>(
+            *this, &TestService::OnConcatenate, message);
+        return 0;
+
+      case TestInterface::SumVector::Opcode:
+        DispatchRemoteMethod<TestInterface::SumVector>(
+            *this, &TestService::OnSumVector, message);
+        return 0;
+
+      case TestInterface::StringLength::Opcode:
+        DispatchRemoteMethod<TestInterface::StringLength>(
+            *this, &TestService::OnStringLength, message);
+        return 0;
+
+      case TestInterface::SendTestType::Opcode:
+        DispatchRemoteMethod<TestInterface::SendTestType>(
+            *this, &TestService::OnSendTestType, message);
+        return 0;
+
+      case TestInterface::SendVector::Opcode:
+        DispatchRemoteMethod<TestInterface::SendVector>(
+            *this, &TestService::OnSendVector, message);
+        return 0;
+
+      case TestInterface::Rot13::Opcode:
+        DispatchRemoteMethod<TestInterface::Rot13>(*this, &TestService::OnRot13,
+                                                   message);
+        return 0;
+
+      case TestInterface::NoArgs::Opcode:
+        DispatchRemoteMethod<TestInterface::NoArgs>(
+            *this, &TestService::OnNoArgs, message);
+        return 0;
+
+      case TestInterface::SendFile::Opcode:
+        DispatchRemoteMethod<TestInterface::SendFile>(
+            *this, &TestService::OnSendFile, message);
+        return 0;
+
+      case TestInterface::GetFile::Opcode:
+        DispatchRemoteMethod<TestInterface::GetFile>(
+            *this, &TestService::OnGetFile, message);
+        return 0;
+
+      case TestInterface::GetTestFdType::Opcode:
+        DispatchRemoteMethod<TestInterface::GetTestFdType>(
+            *this, &TestService::OnGetTestFdType, message);
+        return 0;
+
+      case TestInterface::OpenFiles::Opcode:
+        DispatchRemoteMethod<TestInterface::OpenFiles>(
+            *this, &TestService::OnOpenFiles, message);
+        return 0;
+
+      case TestInterface::ReadFile::Opcode:
+        DispatchRemoteMethod<TestInterface::ReadFile>(
+            *this, &TestService::OnReadFile, message);
+        return 0;
+
+      case TestInterface::PushChannel::Opcode:
+        DispatchRemoteMethod<TestInterface::PushChannel>(
+            *this, &TestService::OnPushChannel, message);
+        return 0;
+
+      default:
+        return Service::DefaultHandleMessage(message);
+    }
+  }
+
+ private:
+  friend BASE;
+
+  TestService()
+      : BASE("TestService", Endpoint::Create(TestInterface::kClientPath)) {}
+
+  int OnAdd(Message&, int a, int b) { return a + b; }
+
+  int OnFoo(Message&, int a, const std::string& b) { return a + b.length(); }
+
+  std::string OnConcatenate(Message&, const std::string& a,
+                            const std::string& b) {
+    return a + b;
+  }
+
+  int OnSumVector(Message&, const std::vector<int>& vector) {
+    return std::accumulate(vector.begin(), vector.end(), 0);
+  }
+
+  int OnStringLength(Message&, const std::string& string) {
+    return string.length();
+  }
+
+  TestType OnSendTestType(Message&, const TestType& tt) {
+    return TestType(tt.a + 20, tt.b - 2.0, tt.c + "foo");
+  }
+
+  std::string OnSendVector(Message&, const std::vector<TestType>& v) {
+    std::string return_value = "";
+
+    for (const auto& tt : v)
+      return_value += tt.c;
+
+    return return_value;
+  }
+
+  std::string OnRot13(Message&, const std::string& s) {
+    std::string text = s;
+    std::transform(std::begin(text), std::end(text), std::begin(text),
+                   [](char c) -> char {
+                     if (!std::isalpha(c)) {
+                       return c;
+                     } else {
+                       const char pivot = std::isupper(c) ? 'A' : 'a';
+                       return (c - pivot + 13) % 26 + pivot;
+                     }
+                   });
+    return text;
+  }
+
+  int OnNoArgs(Message&) { return 1; }
+
+  int OnSendFile(Message&, const LocalHandle& fd) { return fd.Get(); }
+
+  LocalHandle OnGetFile(Message& message, const std::string& path, int mode) {
+    LocalHandle fd(path.c_str(), mode);
+    if (!fd)
+      message.ReplyError(errno);
+    return fd;
+  }
+
+  TestFdType OnGetTestFdType(Message& message, int a, const std::string& path,
+                             int mode) {
+    TestFdType return_value(a, LocalHandle(path, mode));
+    if (!return_value.fd)
+      message.ReplyError(errno);
+    return return_value;
+  }
+
+  std::vector<LocalHandle> OnOpenFiles(
+      Message&, const std::vector<std::pair<std::string, int>>& file_specs) {
+    std::vector<LocalHandle> return_value;
+    for (auto& spec : file_specs) {
+      LocalHandle fd(spec.first, spec.second);
+      if (fd)
+        return_value.emplace_back(std::move(fd));
+      else
+        return_value.emplace_back(-errno);
+    }
+    return return_value;
+  }
+
+  std::pair<int, BufferWrapper<std::vector<std::uint8_t>>> OnReadFile(
+      Message& message, const std::string& path, int mode, std::size_t length) {
+    std::pair<int, BufferWrapper<std::vector<std::uint8_t>>> return_value;
+    LocalHandle fd(path, mode);
+    if (!fd) {
+      message.ReplyError(errno);
+      return return_value;
+    }
+
+    return_value.second.reserve(length);
+    const int ret = read(fd.Get(), return_value.second.data(), length);
+    if (ret < 0) {
+      message.ReplyError(errno);
+      return return_value;
+    }
+
+    return_value.second.resize(ret);
+    return_value.first = ret;
+    return return_value;
+  }
+
+  RemoteChannelHandle OnPushChannel(Message& message) {
+    auto status = message.PushChannel(0, nullptr, nullptr);
+    if (!status) {
+      message.ReplyError(status.error());
+      return {};
+    }
+    return status.take();
+  }
+
+  TestService(const TestService&) = delete;
+  void operator=(const TestService&) = delete;
+};
+
+}  // anonymous namespace
+
+// Use a test fixture to ensure proper order of cleanup between clients,
+// services, and the dispatcher. As these objects are cleaned up in the same
+// thread, either the service or client must be destroyed before stopping the
+// dispatcher. The reason for this is that clients send blocking "close"
+// messages to their respective services on destruction. If this happens after
+// stopping the dispatcher the client destructor will get blocked waiting for a
+// reply that will never come. In normal use of the service framework this is
+// never an issue because clients and the dispatcher for the same service are
+// never destructed in the same thread (they live in different processes).
+class RemoteMethodTest : public ::testing::Test {
+ protected:
+  std::unique_ptr<ServiceDispatcher> dispatcher_;
+  std::thread dispatch_thread_;
+
+  void SetUp() override {
+    // Create a dispatcher to handle messages to services.
+    dispatcher_ = android::pdx::uds::ServiceDispatcher::Create();
+    ASSERT_NE(nullptr, dispatcher_);
+
+    // Start the message dispatch loop in a separate thread.
+    dispatch_thread_ = std::thread(
+        std::bind(&ServiceDispatcher::EnterDispatchLoop, dispatcher_.get()));
+  }
+
+  void TearDown() override {
+    if (dispatcher_) {
+      // Cancel the dispatcher and wait for the thread to terminate.
+      // Explicitly
+      // join the thread so that destruction doesn't deallocate the
+      // dispatcher
+      // before the thread finishes.
+      dispatcher_->SetCanceled(true);
+      dispatch_thread_.join();
+    }
+  }
+};
+
+// Test basic operation of TestService/TestClient classes.
+TEST_F(RemoteMethodTest, BasicClientService) {
+  // Create a test service and add it to the dispatcher.
+
+  auto service = TestService::Create();
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create();
+  ASSERT_NE(nullptr, client);
+
+  const int sum = client->Add(10, 25);
+  EXPECT_GE(35, sum);
+
+  const auto cat = client->Concatenate("This is a string", ", that it is.");
+  EXPECT_EQ("This is a string, that it is.", cat);
+
+  const auto length = client->Foo(10, "123");
+  EXPECT_EQ(13, length);
+
+  const std::vector<int> vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+  const int vector_sum = client->SumVector(vector.data(), vector.size());
+  const int vector_sum2 = client->SumVector(vector);
+  EXPECT_EQ(std::accumulate(vector.begin(), vector.end(), 0), vector_sum);
+  EXPECT_EQ(std::accumulate(vector.begin(), vector.end(), 0), vector_sum2);
+
+  const auto string_length1 = client->StringLength("This is a string");
+  EXPECT_EQ(16, string_length1);
+
+  const auto string_length2 = client->StringLength("1234567890");
+  EXPECT_EQ(10, string_length2);
+
+  std::string string = "1234567890";
+  const auto string_length3 =
+      client->StringLength(string.c_str(), string.length());
+  EXPECT_EQ(10, string_length3);
+
+  TestType tt{10, 0.0, "string"};
+  const auto tt_result = client->SendTestType(tt);
+  EXPECT_EQ(TestType(30, -2.0, "stringfoo"), tt_result);
+
+  std::vector<TestType> ttv = {TestType(0, 0.0, "abc"),
+                               TestType(0, 0.0, "123")};
+  const std::string string_result = client->SendVector(ttv);
+  EXPECT_EQ("abc123", string_result);
+
+  const int int_result = client->NoArgs();
+  EXPECT_EQ(1, int_result);
+}
+
+TEST_F(RemoteMethodTest, LocalHandle) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create();
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create();
+  ASSERT_NE(nullptr, client);
+
+  LocalHandle fd("/dev/zero", O_RDONLY);
+  ASSERT_TRUE(fd.IsValid());
+
+  int fd_result = client->SendFile(fd);
+  EXPECT_LE(0, fd_result);
+  EXPECT_NE(fd.Get(), fd_result);
+  fd = LocalHandle(-3);
+  fd_result = client->SendFile(fd);
+  EXPECT_EQ(fd.Get(), fd_result);
+
+  fd = client->GetFile("/dev/zero", O_RDONLY);
+  ASSERT_TRUE(fd.IsValid()) << "Error code: " << fd.Get();
+
+  std::array<uint8_t, 10> buffer;
+  buffer.fill(1);
+  EXPECT_EQ(10, read(fd.Get(), buffer.data(), buffer.size()));
+  EXPECT_EQ(buffer, decltype(buffer){{0}});
+  fd.Close();
+
+  const int error = client->GetFile("/dev/zero", O_RDONLY, &fd);
+  EXPECT_EQ(0, error);
+  EXPECT_TRUE(fd.IsValid());
+
+  buffer.fill(1);
+  EXPECT_EQ(10, read(fd.Get(), buffer.data(), buffer.size()));
+  EXPECT_EQ(buffer, decltype(buffer){{0}});
+
+  /*
+    Seg fault.
+    fd = client->GetFile("/dev/foobar", O_RDONLY);
+    EXPECT_FALSE(fd.IsValid());
+   */
+}
+
+TEST_F(RemoteMethodTest, PushChannel) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create();
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create();
+  ASSERT_NE(nullptr, client);
+
+  // Get a new channel as an fd.
+  LocalChannelHandle channel;
+  const int ret = client->PushChannel(&channel);
+  EXPECT_EQ(0, ret);
+  EXPECT_TRUE(channel.valid());
+
+  // Create a new client from the channel.
+  auto client2 = TestClient::Create(std::move(channel));
+  ASSERT_NE(nullptr, client2);
+
+  // Test that the new channel works.
+  const int sum = client2->Add(10, 25);
+  EXPECT_GE(35, sum);
+}
+
+TEST_F(RemoteMethodTest, AggregateLocalHandle) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create();
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create();
+  ASSERT_NE(nullptr, client);
+
+  TestFdType result = client->GetTestFdType(10, "/dev/zero", O_RDONLY);
+  EXPECT_TRUE(result.fd.IsValid());
+  EXPECT_EQ(10, result.a);
+
+  std::vector<LocalHandle> files =
+      client->OpenFiles({{{"/dev/zero", O_RDONLY},
+                          {"/dev/null", O_WRONLY},
+                          {"/dev/zero", O_RDONLY}}});
+  ASSERT_EQ(3u, files.size());
+  EXPECT_TRUE(files[0].IsValid());
+  EXPECT_TRUE(files[1].IsValid());
+  EXPECT_TRUE(files[2].IsValid());
+}
+
+TEST_F(RemoteMethodTest, BufferWrapper) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create();
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create();
+  ASSERT_NE(nullptr, client);
+
+  const int buffer_size = 20;
+  std::vector<std::uint8_t> buffer(buffer_size, 'x');
+  std::vector<std::uint8_t> expected(buffer_size, 0);
+  int ret =
+      client->ReadFile(buffer.data(), buffer.size(), "/dev/zero", O_RDONLY);
+  EXPECT_EQ(buffer_size, ret);
+  EXPECT_EQ(expected, buffer);
+}
+
+//
+// RemoteMethodFramework: Tests the type-based framework that remote method
+// support is built upon.
+//
+
+// Test logical And template.
+TEST(RemoteMethodFramework, And) {
+  EXPECT_TRUE((And<std::true_type, std::true_type>::value));
+  EXPECT_FALSE((And<std::true_type, std::false_type>::value));
+  EXPECT_FALSE((And<std::false_type, std::true_type>::value));
+  EXPECT_FALSE((And<std::false_type, std::false_type>::value));
+
+  EXPECT_TRUE((And<std::true_type, std::true_type, std::true_type>::value));
+  EXPECT_FALSE((And<std::true_type, std::true_type, std::false_type>::value));
+  EXPECT_FALSE((And<std::true_type, std::false_type, std::true_type>::value));
+  EXPECT_FALSE((And<std::true_type, std::false_type, std::false_type>::value));
+  EXPECT_FALSE((And<std::false_type, std::true_type, std::true_type>::value));
+  EXPECT_FALSE((And<std::false_type, std::true_type, std::false_type>::value));
+  EXPECT_FALSE((And<std::false_type, std::false_type, std::true_type>::value));
+  EXPECT_FALSE((And<std::false_type, std::false_type, std::false_type>::value));
+}
+
+// Test convertible type constraints.
+TEST(RemoteMethodFramework, IsConvertible) {
+  // std::pair.
+  EXPECT_TRUE(
+      (IsConvertible<std::pair<int, float>, std::pair<int, float>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<std::pair<int, float>, std::pair<float, float>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<std::pair<int, float>, std::pair<float, int>>::value));
+
+  // Nested std::pair.
+  EXPECT_TRUE((IsConvertible<std::pair<std::pair<int, float>, float>,
+                             std::pair<std::pair<int, float>, float>>::value));
+  EXPECT_FALSE((IsConvertible<std::pair<std::pair<int, float>, float>,
+                              std::pair<std::pair<float, int>, float>>::value));
+
+  // std::tuple and std::pair.
+  EXPECT_TRUE(
+      (IsConvertible<std::pair<int, float>, std::tuple<int, float>>::value));
+  EXPECT_TRUE(
+      (IsConvertible<std::tuple<int, float>, std::pair<int, float>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<std::pair<float, float>, std::tuple<int, float>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<std::tuple<float, float>, std::pair<int, float>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<std::pair<int, int>, std::tuple<int, float>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<std::tuple<int, int>, std::pair<int, float>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<std::pair<int, int>, std::tuple<int, int, int>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<std::tuple<int, int, int>, std::pair<int, int>>::value));
+
+  // std::vector.
+  EXPECT_TRUE((IsConvertible<std::vector<int>, std::vector<int>>::value));
+  EXPECT_FALSE((IsConvertible<std::vector<int>, std::vector<float>>::value));
+
+  // Nested std::vector.
+  EXPECT_TRUE((IsConvertible<std::vector<std::pair<int, int>>,
+                             std::vector<std::pair<int, int>>>::value));
+  EXPECT_FALSE((IsConvertible<std::vector<std::pair<int, int>>,
+                              std::vector<std::pair<int, float>>>::value));
+  EXPECT_FALSE((IsConvertible<std::vector<std::pair<int, int>>,
+                              std::vector<std::pair<float, int>>>::value));
+  EXPECT_FALSE((IsConvertible<std::vector<std::pair<int, int>>,
+                              std::vector<std::pair<float, float>>>::value));
+
+  // std::vector with nested convertible types.
+  EXPECT_TRUE((IsConvertible<std::vector<StringWrapper<char>>,
+                             std::vector<std::string>>::value));
+
+  // std::map and std::unordered_map.
+  EXPECT_TRUE((IsConvertible<std::map<int, float>,
+                             std::unordered_map<int, float>>::value));
+  EXPECT_FALSE((IsConvertible<std::map<float, float>,
+                              std::unordered_map<int, float>>::value));
+  EXPECT_FALSE((IsConvertible<std::map<float, float>,
+                              std::unordered_map<float, int>>::value));
+  EXPECT_FALSE((IsConvertible<std::map<float, float>,
+                              std::unordered_map<int, int>>::value));
+  EXPECT_TRUE((IsConvertible<std::unordered_map<int, float>,
+                             std::map<int, float>>::value));
+  EXPECT_FALSE((IsConvertible<std::unordered_map<float, float>,
+                              std::map<int, float>>::value));
+  EXPECT_FALSE((IsConvertible<std::unordered_map<float, float>,
+                              std::map<float, int>>::value));
+  EXPECT_FALSE((IsConvertible<std::unordered_map<float, float>,
+                              std::map<int, int>>::value));
+
+  // std::map with nested convertible types.
+  EXPECT_TRUE((IsConvertible<std::map<int, std::string>,
+                             std::map<int, StringWrapper<char>>>::value));
+  EXPECT_TRUE(
+      (IsConvertible<std::map<std::tuple<int, int>, std::string>,
+                     std::map<std::pair<int, int>, std::string>>::value));
+
+  // std::unordered_map with nested convertible types.
+  EXPECT_TRUE(
+      (IsConvertible<std::unordered_map<int, std::string>,
+                     std::unordered_map<int, StringWrapper<char>>>::value));
+  EXPECT_TRUE((IsConvertible<
+               std::unordered_map<std::tuple<int, int>, std::string>,
+               std::unordered_map<std::pair<int, int>, std::string>>::value));
+
+  // std::string.
+  EXPECT_TRUE((IsConvertible<std::string, std::string>::value));
+  EXPECT_FALSE((IsConvertible<std::string, int>::value));
+  EXPECT_FALSE((IsConvertible<int, std::string>::value));
+
+  // Nested std::string.
+  EXPECT_TRUE((IsConvertible<std::pair<std::string, std::string>,
+                             std::pair<std::string, std::string>>::value));
+  EXPECT_FALSE((IsConvertible<std::pair<std::string, std::string>,
+                              std::pair<std::string, int>>::value));
+  EXPECT_FALSE((IsConvertible<std::pair<std::string, std::string>,
+                              std::pair<int, std::string>>::value));
+  EXPECT_FALSE((IsConvertible<std::pair<std::string, std::string>,
+                              std::pair<int, int>>::value));
+
+  // StringWrapper.
+  EXPECT_TRUE((IsConvertible<StringWrapper<char>, StringWrapper<char>>::value));
+  EXPECT_TRUE((IsConvertible<StringWrapper<char>, std::string>::value));
+  EXPECT_TRUE((IsConvertible<std::string, StringWrapper<char>>::value));
+  EXPECT_FALSE((IsConvertible<StringWrapper<char>, int>::value));
+  EXPECT_FALSE(
+      (IsConvertible<StringWrapper<char>, BufferWrapper<char*>>::value));
+
+  // BufferWrapper.
+  EXPECT_TRUE(
+      (IsConvertible<BufferWrapper<char*>, BufferWrapper<char*>>::value));
+  EXPECT_TRUE(
+      (IsConvertible<BufferWrapper<char*>, BufferWrapper<const char*>>::value));
+  EXPECT_FALSE(
+      (IsConvertible<BufferWrapper<char*>, BufferWrapper<int*>>::value));
+  EXPECT_TRUE((IsConvertible<BufferWrapper<char*>,
+                             BufferWrapper<std::vector<char>>>::value));
+
+  // RemoteHandle and BorrowedHandle.
+  EXPECT_TRUE((IsConvertible<LocalHandle, RemoteHandle>::value));
+  EXPECT_TRUE((IsConvertible<LocalHandle, BorrowedHandle>::value));
+
+  // Test rewriting user defined types.
+  EXPECT_TRUE((IsConvertible<TestTemplateType<LocalHandle>,
+                             TestTemplateType<RemoteHandle>>::value));
+  EXPECT_TRUE((IsConvertible<TestTemplateType<LocalHandle>,
+                             TestTemplateType<BorrowedHandle>>::value));
+  EXPECT_FALSE((IsConvertible<TestTemplateType<RemoteHandle>,
+                              TestTemplateType<LocalHandle>>::value));
+  EXPECT_FALSE((IsConvertible<TestTemplateType<BorrowedHandle>,
+                              TestTemplateType<LocalHandle>>::value));
+
+  // TODO(eieio): More thorough testing of convertible types.
+}
+
+TEST(RemoteMethodFramework, SerializableMembers) {
+  EXPECT_TRUE(HasSerializableMembers<TestTemplateType<LocalHandle>>::value);
+  EXPECT_TRUE(HasSerializableMembers<TestTemplateType<RemoteHandle>>::value);
+  EXPECT_TRUE(HasSerializableMembers<TestTemplateType<BorrowedHandle>>::value);
+
+  EXPECT_TRUE(std::is_void<EnableIfHasSerializableMembers<
+                  TestTemplateType<LocalHandle>>>::value);
+  EXPECT_TRUE(std::is_void<EnableIfHasSerializableMembers<
+                  TestTemplateType<RemoteHandle>>>::value);
+  EXPECT_TRUE(std::is_void<EnableIfHasSerializableMembers<
+                  TestTemplateType<BorrowedHandle>>>::value);
+
+  EXPECT_TRUE(HasSerializableMembers<DerivedTestType>::value);
+
+  EXPECT_TRUE(HasSerializableMembers<BasicStruct>::value);
+  EXPECT_TRUE(HasSerializableMembers<TestType>::value);
+  EXPECT_TRUE(HasSerializableMembers<TestTemplateType<LocalHandle>>::value);
+  EXPECT_TRUE(HasSerializableMembers<TestTemplateType<RemoteHandle>>::value);
+  EXPECT_TRUE(HasSerializableMembers<TestTemplateType<BorrowedHandle>>::value);
+  EXPECT_TRUE(HasSerializableMembers<DerivedTestType>::value);
+  EXPECT_FALSE(HasSerializableMembers<NonSerializableType>::value);
+  EXPECT_FALSE(
+      HasSerializableMembers<IncorrectlyDefinedSerializableType>::value);
+}
+
+TEST(RemoteMethodFramework, RemoteAPITypes) {
+  EXPECT_EQ(0u, TestInterface::API::MethodIndex<TestInterface::Add>());
+}
diff --git a/libs/vr/libpdx_uds/service_dispatcher.cpp b/libs/vr/libpdx_uds/service_dispatcher.cpp
new file mode 100644
index 0000000..8a40158
--- /dev/null
+++ b/libs/vr/libpdx_uds/service_dispatcher.cpp
@@ -0,0 +1,205 @@
+#define LOG_TAG "ServiceDispatcher"
+#include "uds/service_dispatcher.h"
+
+#include <errno.h>
+#include <log/log.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+
+#include "pdx/service.h"
+#include "uds/service_endpoint.h"
+
+#define TRACE 0
+
+static const int kMaxEventsPerLoop = 128;
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+std::unique_ptr<pdx::ServiceDispatcher> ServiceDispatcher::Create() {
+  std::unique_ptr<ServiceDispatcher> dispatcher{new ServiceDispatcher()};
+  if (!dispatcher->epoll_fd_ || !dispatcher->event_fd_) {
+    dispatcher.reset();
+  }
+
+  return std::move(dispatcher);
+}
+
+ServiceDispatcher::ServiceDispatcher() {
+  event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
+  if (!event_fd_) {
+    ALOGE("Failed to create event fd because: %s\n", strerror(errno));
+    return;
+  }
+
+  epoll_fd_.Reset(epoll_create(1));  // Size arg is ignored, but must be > 0.
+  if (!epoll_fd_) {
+    ALOGE("Failed to create epoll fd because: %s\n", strerror(errno));
+    return;
+  }
+
+  // Use "this" as a unique pointer to distinguish the event fd from all
+  // the other entries that point to instances of Service.
+  epoll_event event;
+  event.events = EPOLLIN;
+  event.data.ptr = this;
+
+  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, event_fd_.Get(), &event) < 0) {
+    ALOGE("Failed to add event fd to epoll fd because: %s\n", strerror(errno));
+
+    // Close the fds here and signal failure to the factory method.
+    event_fd_.Close();
+    epoll_fd_.Close();
+  }
+}
+
+ServiceDispatcher::~ServiceDispatcher() { SetCanceled(true); }
+
+int ServiceDispatcher::ThreadEnter() {
+  std::lock_guard<std::mutex> autolock(mutex_);
+
+  if (canceled_)
+    return -EBUSY;
+
+  thread_count_++;
+  return 0;
+}
+
+void ServiceDispatcher::ThreadExit() {
+  std::lock_guard<std::mutex> autolock(mutex_);
+  thread_count_--;
+  condition_.notify_one();
+}
+
+int ServiceDispatcher::AddService(const std::shared_ptr<Service>& service) {
+  if (service->endpoint()->GetIpcTag() != Endpoint::kIpcTag)
+    return -EINVAL;
+
+  std::lock_guard<std::mutex> autolock(mutex_);
+
+  auto* endpoint = static_cast<Endpoint*>(service->endpoint());
+  epoll_event event;
+  event.events = EPOLLIN;
+  event.data.ptr = service.get();
+
+  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, endpoint->epoll_fd(), &event) <
+      0) {
+    ALOGE("Failed to add service to dispatcher because: %s\n", strerror(errno));
+    return -errno;
+  }
+
+  services_.push_back(service);
+  return 0;
+}
+
+int ServiceDispatcher::RemoveService(const std::shared_ptr<Service>& service) {
+  if (service->endpoint()->GetIpcTag() != Endpoint::kIpcTag)
+    return -EINVAL;
+
+  std::lock_guard<std::mutex> autolock(mutex_);
+
+  // It's dangerous to remove a service while other threads may be using it.
+  if (thread_count_ > 0)
+    return -EBUSY;
+
+  epoll_event dummy;  // See BUGS in man 2 epoll_ctl.
+
+  auto* endpoint = static_cast<Endpoint*>(service->endpoint());
+  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_DEL, endpoint->epoll_fd(), &dummy) <
+      0) {
+    ALOGE("Failed to remove service from dispatcher because: %s\n",
+          strerror(errno));
+    return -errno;
+  }
+
+  services_.remove(service);
+  return 0;
+}
+
+int ServiceDispatcher::ReceiveAndDispatch() { return ReceiveAndDispatch(-1); }
+
+int ServiceDispatcher::ReceiveAndDispatch(int timeout) {
+  int ret = ThreadEnter();
+  if (ret < 0)
+    return ret;
+
+  epoll_event events[kMaxEventsPerLoop];
+
+  int count = epoll_wait(epoll_fd_.Get(), events, kMaxEventsPerLoop, timeout);
+  if (count <= 0) {
+    ALOGE_IF(count < 0, "Failed to wait for epoll events because: %s\n",
+             strerror(errno));
+    ThreadExit();
+    return count < 0 ? -errno : -ETIMEDOUT;
+  }
+
+  for (int i = 0; i < count; i++) {
+    if (events[i].data.ptr == this) {
+      ThreadExit();
+      return -EBUSY;
+    } else {
+      Service* service = static_cast<Service*>(events[i].data.ptr);
+
+      ALOGI_IF(TRACE, "Dispatching message: fd=%d\n",
+               static_cast<Endpoint*>(service->endpoint())->epoll_fd());
+      service->ReceiveAndDispatch();
+    }
+  }
+
+  ThreadExit();
+  return 0;
+}
+
+int ServiceDispatcher::EnterDispatchLoop() {
+  int ret = ThreadEnter();
+  if (ret < 0)
+    return ret;
+
+  epoll_event events[kMaxEventsPerLoop];
+
+  while (!IsCanceled()) {
+    int count = epoll_wait(epoll_fd_.Get(), events, kMaxEventsPerLoop, -1);
+    if (count < 0 && errno != EINTR) {
+      ALOGE("Failed to wait for epoll events because: %s\n", strerror(errno));
+      ThreadExit();
+      return -errno;
+    }
+
+    for (int i = 0; i < count; i++) {
+      if (events[i].data.ptr == this) {
+        ThreadExit();
+        return -EBUSY;
+      } else {
+        Service* service = static_cast<Service*>(events[i].data.ptr);
+
+        ALOGI_IF(TRACE, "Dispatching message: fd=%d\n",
+                 static_cast<Endpoint*>(service->endpoint())->epoll_fd());
+        service->ReceiveAndDispatch();
+      }
+    }
+  }
+
+  ThreadExit();
+  return 0;
+}
+
+void ServiceDispatcher::SetCanceled(bool cancel) {
+  std::unique_lock<std::mutex> lock(mutex_);
+  canceled_ = cancel;
+
+  if (canceled_ && thread_count_ > 0) {
+    eventfd_write(event_fd_.Get(), 1);  // Signal threads to quit.
+
+    condition_.wait(lock, [this] { return !(canceled_ && thread_count_ > 0); });
+
+    eventfd_t value;
+    eventfd_read(event_fd_.Get(), &value);  // Unsignal.
+  }
+}
+
+bool ServiceDispatcher::IsCanceled() const { return canceled_; }
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx_uds/service_endpoint.cpp b/libs/vr/libpdx_uds/service_endpoint.cpp
new file mode 100644
index 0000000..b6021e8
--- /dev/null
+++ b/libs/vr/libpdx_uds/service_endpoint.cpp
@@ -0,0 +1,632 @@
+#include "uds/service_endpoint.h"
+
+#include <poll.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <algorithm>  // std::min
+
+#include <pdx/service.h>
+#include <uds/channel_manager.h>
+#include <uds/client_channel_factory.h>
+#include <uds/ipc_helper.h>
+
+namespace {
+
+constexpr int kMaxBackLogForSocketListen = 1;
+
+using android::pdx::BorrowedChannelHandle;
+using android::pdx::BorrowedHandle;
+using android::pdx::ChannelReference;
+using android::pdx::FileReference;
+using android::pdx::LocalChannelHandle;
+using android::pdx::LocalHandle;
+using android::pdx::Status;
+using android::pdx::uds::ChannelInfo;
+using android::pdx::uds::ChannelManager;
+
+struct MessageState {
+  bool GetLocalFileHandle(int index, LocalHandle* handle) {
+    if (index < 0) {
+      handle->Reset(index);
+    } else if (static_cast<size_t>(index) < request.file_descriptors.size()) {
+      *handle = std::move(request.file_descriptors[index]);
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  bool GetLocalChannelHandle(int index, LocalChannelHandle* handle) {
+    if (index < 0) {
+      *handle = LocalChannelHandle{nullptr, index};
+    } else if (static_cast<size_t>(index) < request.channels.size()) {
+      auto& channel_info = request.channels[index];
+      *handle = ChannelManager::Get().CreateHandle(
+          std::move(channel_info.data_fd), std::move(channel_info.event_fd));
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  FileReference PushFileHandle(BorrowedHandle handle) {
+    if (!handle)
+      return handle.Get();
+    response.file_descriptors.push_back(std::move(handle));
+    return response.file_descriptors.size() - 1;
+  }
+
+  ChannelReference PushChannelHandle(BorrowedChannelHandle handle) {
+    if (!handle)
+      return handle.value();
+    ChannelInfo<BorrowedHandle> channel_info;
+    channel_info.data_fd.Reset(handle.value());
+    channel_info.event_fd.Reset(
+        ChannelManager::Get().GetEventFd(handle.value()));
+    response.channels.push_back(std::move(channel_info));
+    return response.channels.size() - 1;
+  }
+
+  ChannelReference PushChannelHandle(BorrowedHandle data_fd,
+                                     BorrowedHandle event_fd) {
+    if (!data_fd || !event_fd)
+      return -1;
+    ChannelInfo<BorrowedHandle> channel_info;
+    channel_info.data_fd = std::move(data_fd);
+    channel_info.event_fd = std::move(event_fd);
+    response.channels.push_back(std::move(channel_info));
+    return response.channels.size() - 1;
+  }
+
+  ssize_t WriteData(const iovec* vector, size_t vector_length) {
+    ssize_t size = 0;
+    for (size_t i = 0; i < vector_length; i++) {
+      const auto* data = reinterpret_cast<const uint8_t*>(vector[i].iov_base);
+      response_data.insert(response_data.end(), data, data + vector[i].iov_len);
+      size += vector[i].iov_len;
+    }
+    return size;
+  }
+
+  ssize_t ReadData(const iovec* vector, size_t vector_length) {
+    size_t size_remaining = request_data.size() - request_data_read_pos;
+    ssize_t size = 0;
+    for (size_t i = 0; i < vector_length && size_remaining > 0; i++) {
+      size_t size_to_copy = std::min(size_remaining, vector[i].iov_len);
+      memcpy(vector[i].iov_base, request_data.data() + request_data_read_pos,
+             size_to_copy);
+      size += size_to_copy;
+      request_data_read_pos += size_to_copy;
+      size_remaining -= size_to_copy;
+    }
+    return size;
+  }
+
+  android::pdx::uds::RequestHeader<LocalHandle> request;
+  android::pdx::uds::ResponseHeader<BorrowedHandle> response;
+  std::vector<LocalHandle> sockets_to_close;
+  std::vector<uint8_t> request_data;
+  size_t request_data_read_pos{0};
+  std::vector<uint8_t> response_data;
+};
+
+}  // anonymous namespace
+
+namespace android {
+namespace pdx {
+namespace uds {
+
+Endpoint::Endpoint(const std::string& endpoint_path, bool blocking)
+    : endpoint_path_{ClientChannelFactory::GetEndpointPath(endpoint_path)},
+      is_blocking_{blocking} {
+  LocalHandle fd{socket(AF_UNIX, SOCK_STREAM, 0)};
+  if (!fd) {
+    ALOGE("Endpoint::Endpoint: Failed to create socket: %s", strerror(errno));
+    return;
+  }
+
+  sockaddr_un local;
+  local.sun_family = AF_UNIX;
+  strncpy(local.sun_path, endpoint_path_.c_str(), sizeof(local.sun_path));
+  local.sun_path[sizeof(local.sun_path) - 1] = '\0';
+
+  unlink(local.sun_path);
+  if (bind(fd.Get(), (struct sockaddr*)&local, sizeof(local)) == -1) {
+    ALOGE("Endpoint::Endpoint: bind error: %s", strerror(errno));
+    return;
+  }
+  if (listen(fd.Get(), kMaxBackLogForSocketListen) == -1) {
+    ALOGE("Endpoint::Endpoint: listen error: %s", strerror(errno));
+    return;
+  }
+
+  cancel_event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
+  if (!cancel_event_fd_) {
+    ALOGE("Endpoint::Endpoint: Failed to create event fd: %s\n",
+          strerror(errno));
+    return;
+  }
+
+  epoll_fd_.Reset(epoll_create(1));  // Size arg is ignored, but must be > 0.
+  if (!epoll_fd_) {
+    ALOGE("Endpoint::Endpoint: Failed to create epoll fd: %s\n",
+          strerror(errno));
+    return;
+  }
+
+  // Use "this" as a unique pointer to distinguish the event fd from all
+  // the other entries that point to instances of Service.
+  epoll_event socket_event;
+  socket_event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
+  socket_event.data.fd = fd.Get();
+
+  epoll_event cancel_event;
+  cancel_event.events = EPOLLIN;
+  cancel_event.data.fd = cancel_event_fd_.Get();
+
+  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, fd.Get(), &socket_event) < 0 ||
+      epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, cancel_event_fd_.Get(),
+                &cancel_event) < 0) {
+    ALOGE("Endpoint::Endpoint: Failed to add event fd to epoll fd: %s\n",
+          strerror(errno));
+    cancel_event_fd_.Close();
+    epoll_fd_.Close();
+  } else {
+    socket_fd_ = std::move(fd);
+  }
+}
+
+void* Endpoint::AllocateMessageState() { return new MessageState; }
+
+void Endpoint::FreeMessageState(void* state) {
+  delete static_cast<MessageState*>(state);
+}
+
+Status<void> Endpoint::AcceptConnection(Message* message) {
+  sockaddr_un remote;
+  socklen_t addrlen = sizeof(remote);
+  LocalHandle channel_fd{
+      accept(socket_fd_.Get(), reinterpret_cast<sockaddr*>(&remote), &addrlen)};
+  if (!channel_fd) {
+    ALOGE("Endpoint::AcceptConnection: failed to accept connection: %s",
+          strerror(errno));
+    return ErrorStatus(errno);
+  }
+
+  int optval = 1;
+  if (setsockopt(channel_fd.Get(), SOL_SOCKET, SO_PASSCRED, &optval,
+                 sizeof(optval)) == -1) {
+    ALOGE(
+        "Endpoint::AcceptConnection: Failed to enable the receiving of the "
+        "credentials for channel %d: %s",
+        channel_fd.Get(), strerror(errno));
+    return ErrorStatus(errno);
+  }
+
+  auto status = ReceiveMessageForChannel(channel_fd.Get(), message);
+  if (status)
+    status = OnNewChannel(std::move(channel_fd));
+  return status;
+}
+
+int Endpoint::SetService(Service* service) {
+  service_ = service;
+  return 0;
+}
+
+int Endpoint::SetChannel(int channel_id, Channel* channel) {
+  std::lock_guard<std::mutex> autolock(channel_mutex_);
+  auto channel_data = channels_.find(channel_id);
+  if (channel_data == channels_.end())
+    return -EINVAL;
+  channel_data->second.channel_state = channel;
+  return 0;
+}
+
+Status<void> Endpoint::OnNewChannel(LocalHandle channel_fd) {
+  std::lock_guard<std::mutex> autolock(channel_mutex_);
+  Status<void> status;
+  status.PropagateError(OnNewChannelLocked(std::move(channel_fd), nullptr));
+  return status;
+}
+
+Status<Endpoint::ChannelData*> Endpoint::OnNewChannelLocked(
+    LocalHandle channel_fd, Channel* channel_state) {
+  epoll_event event;
+  event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
+  event.data.fd = channel_fd.Get();
+  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, channel_fd.Get(), &event) < 0) {
+    ALOGE(
+        "Endpoint::OnNewChannelLocked: Failed to add channel to endpoint: %s\n",
+        strerror(errno));
+    return ErrorStatus(errno);
+  }
+  ChannelData channel_data;
+  int channel_id = channel_fd.Get();
+  channel_data.data_fd = std::move(channel_fd);
+  channel_data.event_fd.Reset(eventfd(0, 0));
+  channel_data.channel_state = channel_state;
+  auto pair = channels_.emplace(channel_id, std::move(channel_data));
+  return &pair.first->second;
+}
+
+Status<void> Endpoint::ReenableEpollEvent(int fd) {
+  epoll_event event;
+  event.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
+  event.data.fd = fd;
+  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_MOD, fd, &event) < 0) {
+    ALOGE(
+        "Endpoint::ReenableEpollEvent: Failed to re-enable channel to "
+        "endpoint: %s\n",
+        strerror(errno));
+    return ErrorStatus(errno);
+  }
+  return {};
+}
+
+int Endpoint::CloseChannel(int channel_id) {
+  std::lock_guard<std::mutex> autolock(channel_mutex_);
+  return CloseChannelLocked(channel_id);
+}
+
+int Endpoint::CloseChannelLocked(int channel_id) {
+  auto channel_data = channels_.find(channel_id);
+  if (channel_data == channels_.end())
+    return -EINVAL;
+
+  int ret = 0;
+  epoll_event dummy;  // See BUGS in man 2 epoll_ctl.
+  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_DEL, channel_id, &dummy) < 0) {
+    ret = -errno;
+    ALOGE(
+        "Endpoint::CloseChannelLocked: Failed to remove channel from endpoint: "
+        "%s\n",
+        strerror(errno));
+  }
+
+  channels_.erase(channel_data);
+  return ret;
+}
+
+int Endpoint::ModifyChannelEvents(int channel_id, int clear_mask,
+                                  int set_mask) {
+  std::lock_guard<std::mutex> autolock(channel_mutex_);
+  auto channel_data = channels_.find(channel_id);
+  if (channel_data == channels_.end())
+    return -EINVAL;
+  int old_mask = channel_data->second.event_mask;
+  int new_mask = (old_mask & ~clear_mask) | set_mask;
+
+  // EPOLLHUP shares the same bitmask with POLLHUP.
+  if ((new_mask & POLLHUP) && !(old_mask & POLLHUP)) {
+    CloseChannelLocked(channel_id);
+    return 0;
+  }
+
+  // EPOLLIN shares the same bitmask with POLLIN and EPOLLPRI shares the same
+  // bitmask with POLLPRI
+  eventfd_t value = 1;
+  if (((new_mask & POLLIN) && !(old_mask & POLLIN)) ||
+      ((new_mask & POLLPRI) && !(old_mask & POLLPRI))) {
+    eventfd_write(channel_data->second.event_fd.Get(), value);
+  } else if ((!(new_mask & POLLIN) && (old_mask & POLLIN)) ||
+             (!(new_mask & POLLPRI) && (old_mask & POLLPRI))) {
+    eventfd_read(channel_data->second.event_fd.Get(), &value);
+  }
+
+  channel_data->second.event_mask = new_mask;
+  return 0;
+}
+
+Status<RemoteChannelHandle> Endpoint::PushChannel(Message* message,
+                                                  int /*flags*/,
+                                                  Channel* channel,
+                                                  int* channel_id) {
+  int channel_pair[2] = {};
+  if (socketpair(AF_UNIX, SOCK_STREAM, 0, channel_pair) == -1) {
+    ALOGE("Endpoint::PushChannel: Failed to create a socket pair: %s",
+          strerror(errno));
+    return ErrorStatus(errno);
+  }
+
+  LocalHandle local_socket{channel_pair[0]};
+  LocalHandle remote_socket{channel_pair[1]};
+
+  int optval = 1;
+  if (setsockopt(local_socket.Get(), SOL_SOCKET, SO_PASSCRED, &optval,
+                 sizeof(optval)) == -1) {
+    ALOGE(
+        "Endpoint::PushChannel: Failed to enable the receiving of the "
+        "credentials for channel %d: %s",
+        local_socket.Get(), strerror(errno));
+    return ErrorStatus(errno);
+  }
+
+  std::lock_guard<std::mutex> autolock(channel_mutex_);
+  *channel_id = local_socket.Get();
+  auto channel_data = OnNewChannelLocked(std::move(local_socket), channel);
+  if (!channel_data)
+    return ErrorStatus(channel_data.error());
+
+  // Flags are ignored for now.
+  // TODO(xiaohuit): Implement those.
+
+  auto* state = static_cast<MessageState*>(message->GetState());
+  ChannelReference ref = state->PushChannelHandle(
+      remote_socket.Borrow(), channel_data.get()->event_fd.Borrow());
+  state->sockets_to_close.push_back(std::move(remote_socket));
+  return RemoteChannelHandle{ref};
+}
+
+Status<int> Endpoint::CheckChannel(const Message* /*message*/,
+                                   ChannelReference /*ref*/,
+                                   Channel** /*channel*/) {
+  // TODO(xiaohuit): Implement this.
+  return ErrorStatus(EFAULT);
+}
+
+int Endpoint::DefaultHandleMessage(const MessageInfo& /* info */) {
+  ALOGE(
+      "Endpoint::CheckChannel: Not implemented! Endpoint DefaultHandleMessage "
+      "does nothing!");
+  return 0;
+}
+
+Channel* Endpoint::GetChannelState(int channel_id) {
+  std::lock_guard<std::mutex> autolock(channel_mutex_);
+  auto channel_data = channels_.find(channel_id);
+  return (channel_data != channels_.end()) ? channel_data->second.channel_state
+                                           : nullptr;
+}
+
+int Endpoint::GetChannelSocketFd(int channel_id) {
+  std::lock_guard<std::mutex> autolock(channel_mutex_);
+  auto channel_data = channels_.find(channel_id);
+  return (channel_data != channels_.end()) ? channel_data->second.data_fd.Get()
+                                           : -1;
+}
+
+int Endpoint::GetChannelEventFd(int channel_id) {
+  std::lock_guard<std::mutex> autolock(channel_mutex_);
+  auto channel_data = channels_.find(channel_id);
+  return (channel_data != channels_.end()) ? channel_data->second.event_fd.Get()
+                                           : -1;
+}
+
+Status<void> Endpoint::ReceiveMessageForChannel(int channel_id,
+                                                Message* message) {
+  RequestHeader<LocalHandle> request;
+  auto status = ReceiveData(channel_id, &request);
+  if (!status) {
+    CloseChannel(channel_id);
+    return status;
+  }
+  MessageInfo info;
+  info.pid = request.cred.pid;
+  info.tid = -1;
+  info.cid = channel_id;
+  info.mid = request.is_impulse ? Message::IMPULSE_MESSAGE_ID
+                                : GetNextAvailableMessageId();
+  info.euid = request.cred.uid;
+  info.egid = request.cred.gid;
+  info.op = request.op;
+  info.flags = 0;
+  info.service = service_;
+  info.channel = GetChannelState(channel_id);
+  info.send_len = request.send_len;
+  info.recv_len = request.max_recv_len;
+  info.fd_count = request.file_descriptors.size();
+  static_assert(sizeof(info.impulse) == request.impulse_payload.size(),
+                "Impulse payload sizes must be the same in RequestHeader and "
+                "MessageInfo");
+  memcpy(info.impulse, request.impulse_payload.data(),
+         request.impulse_payload.size());
+  *message = Message{info};
+  auto* state = static_cast<MessageState*>(message->GetState());
+  state->request = std::move(request);
+  if (request.send_len > 0 && !request.is_impulse) {
+    state->request_data.resize(request.send_len);
+    status = ReceiveData(channel_id, state->request_data.data(),
+                         state->request_data.size());
+  }
+
+  if (status && request.is_impulse)
+    status = ReenableEpollEvent(channel_id);
+
+  if (!status)
+    CloseChannel(channel_id);
+
+  return status;
+}
+
+int Endpoint::MessageReceive(Message* message) {
+  {
+    std::unique_lock<std::mutex> lock(service_mutex_);
+    condition_.wait(lock, [this] { return service_ != nullptr; });
+  }
+
+  // One event at a time.
+  epoll_event event;
+  int count = RETRY_EINTR(
+      epoll_wait(epoll_fd_.Get(), &event, 1, is_blocking_ ? -1 : 0));
+  if (count < 0) {
+    ALOGE("Endpoint::MessageReceive: Failed to wait for epoll events: %s\n",
+          strerror(errno));
+    return -errno;
+  } else if (count == 0) {
+    return -ETIMEDOUT;
+  }
+
+  if (event.data.fd == cancel_event_fd_.Get()) {
+    return -ESHUTDOWN;
+  }
+
+  if (event.data.fd == socket_fd_.Get()) {
+    auto status = AcceptConnection(message);
+    if (!status)
+      return -status.error();
+    status = ReenableEpollEvent(socket_fd_.Get());
+    return status ? 0 : -status.error();
+  }
+
+  int channel_id = event.data.fd;
+  if (event.events & EPOLLRDHUP) {
+    MessageInfo info;
+    info.pid = -1;
+    info.tid = -1;
+    info.cid = channel_id;
+    info.mid = GetNextAvailableMessageId();
+    info.euid = -1;
+    info.egid = -1;
+    info.op = opcodes::CHANNEL_CLOSE;
+    info.flags = 0;
+    info.service = service_;
+    info.channel = GetChannelState(channel_id);
+    info.send_len = 0;
+    info.recv_len = 0;
+    info.fd_count = 0;
+    *message = Message{info};
+    return 0;
+  }
+
+  auto status = ReceiveMessageForChannel(channel_id, message);
+  if (!status)
+    return -status.error();
+  return 0;
+}
+
+int Endpoint::MessageReply(Message* message, int return_code) {
+  int channel_socket = GetChannelSocketFd(message->GetChannelId());
+  if (channel_socket < 0)
+    return -EBADF;
+
+  auto* state = static_cast<MessageState*>(message->GetState());
+  switch (message->GetOp()) {
+    case opcodes::CHANNEL_CLOSE:
+      return CloseChannel(channel_socket);
+
+    case opcodes::CHANNEL_OPEN:
+      if (return_code < 0)
+        return CloseChannel(channel_socket);
+      // Reply with the event fd.
+      return_code = state->PushFileHandle(
+          BorrowedHandle{GetChannelEventFd(channel_socket)});
+      state->response_data.clear();  // Just in case...
+      break;
+  }
+
+  state->response.ret_code = return_code;
+  state->response.recv_len = state->response_data.size();
+  auto status = SendData(channel_socket, state->response);
+  if (status && !state->response_data.empty()) {
+    status = SendData(channel_socket, state->response_data.data(),
+                      state->response_data.size());
+  }
+
+  if (status)
+    status = ReenableEpollEvent(channel_socket);
+
+  return status ? 0 : -status.error();
+}
+
+int Endpoint::MessageReplyFd(Message* message, unsigned int push_fd) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  auto ref = state->PushFileHandle(BorrowedHandle{static_cast<int>(push_fd)});
+  return MessageReply(message, ref);
+}
+
+int Endpoint::MessageReplyChannelHandle(Message* message,
+                                        const LocalChannelHandle& handle) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  auto ref = state->PushChannelHandle(handle.Borrow());
+  return MessageReply(message, ref);
+}
+
+int Endpoint::MessageReplyChannelHandle(Message* message,
+                                        const BorrowedChannelHandle& handle) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  auto ref = state->PushChannelHandle(handle.Duplicate());
+  return MessageReply(message, ref);
+}
+
+int Endpoint::MessageReplyChannelHandle(Message* message,
+                                        const RemoteChannelHandle& handle) {
+  return MessageReply(message, handle.value());
+}
+
+ssize_t Endpoint::ReadMessageData(Message* message, const iovec* vector,
+                                  size_t vector_length) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  return state->ReadData(vector, vector_length);
+}
+
+ssize_t Endpoint::WriteMessageData(Message* message, const iovec* vector,
+                                   size_t vector_length) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  return state->WriteData(vector, vector_length);
+}
+
+FileReference Endpoint::PushFileHandle(Message* message,
+                                       const LocalHandle& handle) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  return state->PushFileHandle(handle.Borrow());
+}
+
+FileReference Endpoint::PushFileHandle(Message* message,
+                                       const BorrowedHandle& handle) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  return state->PushFileHandle(handle.Duplicate());
+}
+
+FileReference Endpoint::PushFileHandle(Message* /*message*/,
+                                       const RemoteHandle& handle) {
+  return handle.Get();
+}
+
+ChannelReference Endpoint::PushChannelHandle(Message* message,
+                                             const LocalChannelHandle& handle) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  return state->PushChannelHandle(handle.Borrow());
+}
+
+ChannelReference Endpoint::PushChannelHandle(
+    Message* message, const BorrowedChannelHandle& handle) {
+  auto* state = static_cast<MessageState*>(message->GetState());
+  return state->PushChannelHandle(handle.Duplicate());
+}
+
+ChannelReference Endpoint::PushChannelHandle(
+    Message* /*message*/, const RemoteChannelHandle& handle) {
+  return handle.value();
+}
+
+LocalHandle Endpoint::GetFileHandle(Message* message, FileReference ref) const {
+  LocalHandle handle;
+  auto* state = static_cast<MessageState*>(message->GetState());
+  state->GetLocalFileHandle(ref, &handle);
+  return handle;
+}
+
+LocalChannelHandle Endpoint::GetChannelHandle(Message* message,
+                                              ChannelReference ref) const {
+  LocalChannelHandle handle;
+  auto* state = static_cast<MessageState*>(message->GetState());
+  state->GetLocalChannelHandle(ref, &handle);
+  return handle;
+}
+
+int Endpoint::Cancel() {
+  return (eventfd_write(cancel_event_fd_.Get(), 1) < 0) ? -errno : 0;
+}
+
+std::unique_ptr<Endpoint> Endpoint::Create(const std::string& endpoint_path,
+                                           mode_t /*unused_mode*/,
+                                           bool blocking) {
+  return std::unique_ptr<Endpoint>(new Endpoint(endpoint_path, blocking));
+}
+
+}  // namespace uds
+}  // namespace pdx
+}  // namespace android
diff --git a/libs/vr/libpdx_uds/service_framework_tests.cpp b/libs/vr/libpdx_uds/service_framework_tests.cpp
new file mode 100644
index 0000000..8891600
--- /dev/null
+++ b/libs/vr/libpdx_uds/service_framework_tests.cpp
@@ -0,0 +1,677 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <unistd.h>
+
+#include <array>
+#include <atomic>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <thread>
+
+#include <gtest/gtest.h>
+#include <pdx/channel_handle.h>
+#include <pdx/client.h>
+#include <pdx/file_handle.h>
+#include <pdx/service.h>
+#include <private/android_filesystem_config.h>
+#include <uds/client_channel.h>
+#include <uds/client_channel_factory.h>
+#include <uds/service_dispatcher.h>
+#include <uds/service_endpoint.h>
+
+using android::pdx::BorrowedChannelHandle;
+using android::pdx::Channel;
+using android::pdx::ChannelReference;
+using android::pdx::ClientBase;
+using android::pdx::LocalChannelHandle;
+using android::pdx::LocalHandle;
+using android::pdx::Message;
+using android::pdx::MessageInfo;
+using android::pdx::RemoteChannelHandle;
+using android::pdx::ServiceBase;
+using android::pdx::ServiceDispatcher;
+using android::pdx::Status;
+using android::pdx::Transaction;
+using android::pdx::uds::Endpoint;
+
+namespace {
+
+const size_t kLargeDataSize = 100000;
+
+const char kTestServicePath[] = "socket_test";
+const char kTestService1[] = "1";
+const char kTestService2[] = "2";
+
+enum test_op_codes {
+  TEST_OP_GET_SERVICE_ID,
+  TEST_OP_SET_TEST_CHANNEL,
+  TEST_OP_GET_THIS_CHANNEL_ID,
+  TEST_OP_GET_TEST_CHANNEL_ID,
+  TEST_OP_CHECK_CHANNEL_ID,
+  TEST_OP_CHECK_CHANNEL_OBJECT,
+  TEST_OP_CHECK_CHANNEL_FROM_OTHER_SERVICE,
+  TEST_OP_GET_NEW_CHANNEL,
+  TEST_OP_GET_NEW_CHANNEL_FROM_OTHER_SERVICE,
+  TEST_OP_GET_THIS_PROCESS_ID,
+  TEST_OP_GET_THIS_THREAD_ID,
+  TEST_OP_GET_THIS_EUID,
+  TEST_OP_GET_THIS_EGID,
+  TEST_OP_IMPULSE,
+  TEST_OP_POLLHUP_FROM_SERVICE,
+  TEST_OP_POLLIN_FROM_SERVICE,
+  TEST_OP_SEND_LARGE_DATA_RETURN_SUM,
+};
+
+using ImpulsePayload = std::array<std::uint8_t, sizeof(MessageInfo::impulse)>;
+
+// The test service creates a TestChannel for every client (channel) that
+// connects. This represents the service-side context for each client.
+class TestChannel : public Channel {
+ public:
+  explicit TestChannel(int channel_id) : channel_id_(channel_id) {}
+
+  int channel_id() const { return channel_id_; }
+
+ private:
+  friend class TestService;
+
+  int channel_id_;
+
+  TestChannel(const TestChannel&) = delete;
+  void operator=(const TestChannel&) = delete;
+};
+
+// Test service that creates a TestChannel for each channel and responds to test
+// messages.
+class TestService : public ServiceBase<TestService> {
+ public:
+  std::shared_ptr<Channel> OnChannelOpen(Message& message) override {
+    return std::make_shared<TestChannel>(message.GetChannelId());
+  }
+
+  void OnChannelClose(Message& /*message*/,
+                      const std::shared_ptr<Channel>& channel) override {
+    if (test_channel_ == channel)
+      test_channel_ = nullptr;
+  }
+
+  void HandleImpulse(Message& message) override {
+    switch (message.GetOp()) {
+      case TEST_OP_SET_TEST_CHANNEL:
+        test_channel_ = message.GetChannel<TestChannel>();
+        break;
+
+      case TEST_OP_IMPULSE: {
+        impulse_payload_.fill(0);
+        std::copy(message.ImpulseBegin(), message.ImpulseEnd(),
+                  impulse_payload_.begin());
+        break;
+      }
+
+      case TEST_OP_POLLHUP_FROM_SERVICE: {
+        message.ModifyChannelEvents(0, EPOLLHUP);
+        break;
+      }
+    }
+  }
+
+  int HandleMessage(Message& message) override {
+    switch (message.GetOp()) {
+      case TEST_OP_GET_SERVICE_ID:
+        REPLY_MESSAGE_RETURN(message, service_id_, 0);
+
+      // Set the test channel to the TestChannel for the current channel. Other
+      // messages can use this to perform tests.
+      case TEST_OP_SET_TEST_CHANNEL:
+        test_channel_ = message.GetChannel<TestChannel>();
+        REPLY_MESSAGE_RETURN(message, 0, 0);
+
+      // Return the channel id for the current channel.
+      case TEST_OP_GET_THIS_CHANNEL_ID:
+        REPLY_MESSAGE_RETURN(message, message.GetChannelId(), 0);
+
+      // Return the channel id for the test channel.
+      case TEST_OP_GET_TEST_CHANNEL_ID:
+        if (test_channel_)
+          REPLY_MESSAGE_RETURN(message, test_channel_->channel_id(), 0);
+        else
+          REPLY_ERROR_RETURN(message, ENOENT, 0);
+
+      // Test check channel feature.
+      case TEST_OP_CHECK_CHANNEL_ID: {
+        ChannelReference ref = 0;
+        if (message.Read(&ref, sizeof(ref)) < static_cast<ssize_t>(sizeof(ref)))
+          REPLY_ERROR_RETURN(message, EIO, 0);
+
+        const Status<int> ret = message.CheckChannel<TestChannel>(ref, nullptr);
+        REPLY_MESSAGE_RETURN(message, ret, 0);
+      }
+
+      case TEST_OP_CHECK_CHANNEL_OBJECT: {
+        std::shared_ptr<TestChannel> channel;
+        ChannelReference ref = 0;
+        if (message.Read(&ref, sizeof(ref)) < static_cast<ssize_t>(sizeof(ref)))
+          REPLY_ERROR_RETURN(message, EIO, 0);
+
+        const Status<int> ret =
+            message.CheckChannel<TestChannel>(ref, &channel);
+        if (!ret)
+          REPLY_MESSAGE_RETURN(message, ret, 0);
+
+        if (channel != nullptr)
+          REPLY_MESSAGE_RETURN(message, channel->channel_id(), 0);
+        else
+          REPLY_ERROR_RETURN(message, ENODATA, 0);
+      }
+
+      case TEST_OP_CHECK_CHANNEL_FROM_OTHER_SERVICE: {
+        ChannelReference ref = 0;
+        if (message.Read(&ref, sizeof(ref)) < static_cast<ssize_t>(sizeof(ref)))
+          REPLY_ERROR_RETURN(message, EIO, 0);
+
+        const Status<int> ret = message.CheckChannel<TestChannel>(
+            other_service_.get(), ref, nullptr);
+        REPLY_MESSAGE_RETURN(message, ret, 0);
+      }
+
+      case TEST_OP_GET_NEW_CHANNEL: {
+        auto channel = std::make_shared<TestChannel>(-1);
+        Status<RemoteChannelHandle> channel_handle =
+            message.PushChannel(0, channel, &channel->channel_id_);
+        REPLY_MESSAGE_RETURN(message, channel_handle, 0);
+      }
+
+      case TEST_OP_GET_NEW_CHANNEL_FROM_OTHER_SERVICE: {
+        if (!other_service_)
+          REPLY_ERROR_RETURN(message, EINVAL, 0);
+
+        auto channel = std::make_shared<TestChannel>(-1);
+        Status<RemoteChannelHandle> channel_handle = message.PushChannel(
+            other_service_.get(), 0, channel, &channel->channel_id_);
+        REPLY_MESSAGE_RETURN(message, channel_handle, 0);
+      }
+
+      case TEST_OP_GET_THIS_PROCESS_ID:
+        REPLY_MESSAGE_RETURN(message, message.GetProcessId(), 0);
+
+      case TEST_OP_GET_THIS_THREAD_ID:
+        REPLY_MESSAGE_RETURN(message, message.GetThreadId(), 0);
+
+      case TEST_OP_GET_THIS_EUID:
+        REPLY_MESSAGE_RETURN(message, message.GetEffectiveUserId(), 0);
+
+      case TEST_OP_GET_THIS_EGID:
+        REPLY_MESSAGE_RETURN(message, message.GetEffectiveGroupId(), 0);
+
+      case TEST_OP_POLLIN_FROM_SERVICE:
+        REPLY_MESSAGE_RETURN(message, message.ModifyChannelEvents(0, EPOLLIN),
+                             0);
+
+      case TEST_OP_SEND_LARGE_DATA_RETURN_SUM: {
+        std::array<int, kLargeDataSize> data_array;
+        ssize_t size_to_read = data_array.size() * sizeof(int);
+        ssize_t read = message.Read(data_array.data(), size_to_read);
+        if (read < size_to_read)
+          REPLY_ERROR_RETURN(message, EIO, 0);
+        int sum = std::accumulate(data_array.begin(), data_array.end(), 0);
+        REPLY_MESSAGE_RETURN(message, sum, 0);
+      }
+
+      default:
+        return Service::DefaultHandleMessage(message);
+    }
+  }
+
+  const ImpulsePayload& GetImpulsePayload() const { return impulse_payload_; }
+
+ private:
+  friend BASE;
+
+  std::shared_ptr<TestChannel> test_channel_;
+  std::shared_ptr<TestService> other_service_;
+  int service_id_;
+  ImpulsePayload impulse_payload_;
+
+  static std::atomic<int> service_counter_;
+
+  TestService(const std::string& name,
+              const std::shared_ptr<TestService>& other_service)
+      : TestService(name, other_service, false) {}
+
+  TestService(const std::string& name,
+              const std::shared_ptr<TestService>& other_service, bool blocking)
+      : BASE(std::string("TestService") + name,
+             Endpoint::Create(kTestServicePath + name, blocking)),
+        other_service_(other_service),
+        service_id_(service_counter_++) {}
+
+  explicit TestService(const std::string& name) : TestService(name, nullptr) {}
+
+  TestService(const TestService&) = delete;
+  void operator=(const TestService&) = delete;
+};
+
+std::atomic<int> TestService::service_counter_;
+
+// Test client to send messages to the test service.
+class TestClient : public ClientBase<TestClient> {
+ public:
+  // Requests the service id of the service this channel is connected to.
+  int GetServiceId() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_GET_SERVICE_ID));
+  }
+
+  // Requests the test channel to be set to this client's channel.
+  int SetTestChannel() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_SET_TEST_CHANNEL));
+  }
+
+  // Request the test channel to be set to this client's channel using an async
+  // message.
+  int SetTestChannelAsync() {
+    return ReturnStatusOrError(SendImpulse(TEST_OP_SET_TEST_CHANNEL));
+  }
+
+  // Sends a test async message with payload.
+  int SendAsync(const void* buffer, size_t length) {
+    Transaction trans{*this};
+    return ReturnStatusOrError(SendImpulse(TEST_OP_IMPULSE, buffer, length));
+  }
+
+  // Requests the channel id for this client.
+  int GetThisChannelId() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_GET_THIS_CHANNEL_ID));
+  }
+
+  // Requests the channel id of the test channel.
+  int GetTestChannelId() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_GET_TEST_CHANNEL_ID));
+  }
+
+  // Checks whether the fd |channel_id| is a channel to the test service.
+  // Returns the channel id of the channel.
+  int CheckChannelIdArgument(BorrowedChannelHandle channel) {
+    Transaction trans{*this};
+    ChannelReference ref = trans.PushChannelHandle(channel);
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_CHECK_CHANNEL_ID, &ref,
+                                               sizeof(ref), nullptr, 0));
+  }
+
+  // Checks whether the fd |channel_id| is a channel to the test service.
+  // Returns the channel id of the channel exercising the context pointer.
+  int CheckChannelObjectArgument(BorrowedChannelHandle channel) {
+    Transaction trans{*this};
+    ChannelReference ref = trans.PushChannelHandle(channel);
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_CHECK_CHANNEL_OBJECT,
+                                               &ref, sizeof(ref), nullptr, 0));
+  }
+
+  // Checks whether the fd |channel_fd| is a channel to the other test service.
+  // Returns 0 on success.
+  int CheckChannelFromOtherService(BorrowedChannelHandle channel) {
+    Transaction trans{*this};
+    ChannelReference ref = trans.PushChannelHandle(channel);
+    return ReturnStatusOrError(
+        trans.Send<int>(TEST_OP_CHECK_CHANNEL_FROM_OTHER_SERVICE, &ref,
+                        sizeof(ref), nullptr, 0));
+  }
+
+  // Requests a new channel to the service.
+  std::unique_ptr<TestClient> GetNewChannel() {
+    Transaction trans{*this};
+    auto status = trans.Send<LocalChannelHandle>(TEST_OP_GET_NEW_CHANNEL);
+    if (status)
+      return TestClient::Create(status.take());
+    else
+      return nullptr;
+  }
+
+  // Requests a new channel to the other service.
+  std::unique_ptr<TestClient> GetNewChannelFromOtherService() {
+    Transaction trans{*this};
+    auto status = trans.Send<LocalChannelHandle>(
+        TEST_OP_GET_NEW_CHANNEL_FROM_OTHER_SERVICE);
+    if (status)
+      return TestClient::Create(status.take());
+    else
+      return nullptr;
+  }
+
+  // Requests an id from the message description.
+  pid_t GetThisProcessId() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_GET_THIS_PROCESS_ID));
+  }
+  pid_t GetThisThreadId() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_GET_THIS_THREAD_ID));
+  }
+  uid_t GetThisEffectiveUserId() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_GET_THIS_EUID));
+  }
+  gid_t GetThisEffectiveGroupId() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_GET_THIS_EGID));
+  }
+
+  int SendPollHupEvent() {
+    return ReturnStatusOrError(SendImpulse(TEST_OP_POLLHUP_FROM_SERVICE));
+  }
+
+  int SendPollInEvent() {
+    Transaction trans{*this};
+    return ReturnStatusOrError(trans.Send<int>(TEST_OP_POLLIN_FROM_SERVICE));
+  }
+
+  int SendLargeDataReturnSum(
+      const std::array<int, kLargeDataSize>& data_array) {
+    Transaction trans{*this};
+    return ReturnStatusOrError(
+        trans.Send<int>(TEST_OP_SEND_LARGE_DATA_RETURN_SUM, data_array.data(),
+                        data_array.size() * sizeof(int), nullptr, 0));
+  }
+
+  using ClientBase<TestClient>::event_fd;
+
+  enum : size_t { kMaxPayload = MAX_IMPULSE_LENGTH };
+
+ private:
+  friend BASE;
+
+  explicit TestClient(const std::string& name)
+      : BASE{android::pdx::uds::ClientChannelFactory::Create(kTestServicePath +
+                                                             name)} {}
+
+  explicit TestClient(LocalChannelHandle channel)
+      : BASE{android::pdx::uds::ClientChannel::Create(std::move(channel))} {}
+
+  TestClient(const TestClient&) = delete;
+  void operator=(const TestClient&) = delete;
+};
+
+}  // anonymous namespace
+
+// Use a test fixture to ensure proper order of cleanup between clients,
+// services, and the dispatcher. These objects are cleaned up in the same
+// thread, order is important; either the service or the client must be
+// destroyed before the dispatcher is stopped. The reason for this is that
+// clients send blocking "close" messages to their respective services on
+// destruction. If this happens after the dispatcher is stopped the client
+// destructor will get blocked waiting for a reply that will never come. In
+// normal use of the service framework this is never an issue because clients
+// and the dispatcher for the same service are never destructed in the same
+// thread (they live in different processes).
+class ServiceFrameworkTest : public ::testing::Test {
+ protected:
+  std::unique_ptr<ServiceDispatcher> dispatcher_;
+  std::thread dispatch_thread_;
+
+  void SetUp() override {
+    // Create a dispatcher to handle messages to services.
+    dispatcher_ = android::pdx::uds::ServiceDispatcher::Create();
+    ASSERT_NE(nullptr, dispatcher_);
+
+    // Start the message dispatch loop in a separate thread.
+    dispatch_thread_ = std::thread(
+        std::bind(&ServiceDispatcher::EnterDispatchLoop, dispatcher_.get()));
+  }
+
+  void TearDown() override {
+    if (dispatcher_) {
+      // Cancel the dispatcher and wait for the thread to terminate. Explicitly
+      // join the thread so that destruction doesn't deallocate the dispatcher
+      // before the thread finishes.
+      dispatcher_->SetCanceled(true);
+      dispatch_thread_.join();
+    }
+  }
+};
+
+// Test basic operation of TestService/TestClient classes.
+TEST_F(ServiceFrameworkTest, BasicClientService) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create(kTestService1);
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create(kTestService1);
+  ASSERT_NE(nullptr, client);
+
+  // Get the channel id that will be returned by the next tests.
+  const int channel_id = client->GetThisChannelId();
+  EXPECT_LE(0, channel_id);
+
+  // Check return value before test channel is set.
+  EXPECT_EQ(-ENOENT, client->GetTestChannelId());
+
+  // Set test channel and perform the test again.
+  EXPECT_EQ(0, client->SetTestChannel());
+  EXPECT_EQ(channel_id, client->GetTestChannelId());
+}
+
+// Test impulses.
+TEST_F(ServiceFrameworkTest, Impulse) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create(kTestService1);
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  auto client = TestClient::Create(kTestService1);
+  ASSERT_NE(nullptr, client);
+
+  // Get the channel id that will be returned by the next tests.
+  const int channel_id = client->GetThisChannelId();
+  EXPECT_LE(0, channel_id);
+
+  // Check return value before test channel is set.
+  EXPECT_EQ(-ENOENT, client->GetTestChannelId());
+
+  // Set test channel with an impulse and perform the test again.
+  EXPECT_EQ(0, client->SetTestChannelAsync());
+  EXPECT_EQ(channel_id, client->GetTestChannelId());
+
+  ImpulsePayload expected_payload = {{'a', 'b', 'c'}};
+  EXPECT_EQ(0, client->SendAsync(expected_payload.data(), 3));
+  // Send a synchronous message to make sure the async message is handled before
+  // we check the payload.
+  client->GetThisChannelId();
+  EXPECT_EQ(expected_payload, service->GetImpulsePayload());
+
+  // Impulse payloads are limited to 4 machine words.
+  EXPECT_EQ(
+      0, client->SendAsync(expected_payload.data(), TestClient::kMaxPayload));
+  EXPECT_EQ(-EINVAL, client->SendAsync(expected_payload.data(),
+                                       TestClient::kMaxPayload + 1));
+
+  // Test invalid pointer.
+  const std::uint8_t* invalid_pointer = nullptr;
+  EXPECT_EQ(-EINVAL, client->SendAsync(invalid_pointer, sizeof(int)));
+}
+
+// Test Message::PushChannel/Service::PushChannel API.
+TEST_F(ServiceFrameworkTest, PushChannel) {
+  // Create a test service and add it to the dispatcher.
+  auto other_service = TestService::Create(kTestService1);
+  ASSERT_NE(nullptr, other_service);
+  ASSERT_EQ(0, dispatcher_->AddService(other_service));
+
+  // Create a second test service and add it to the dispatcher.
+  auto service = TestService::Create(kTestService2, other_service);
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to the second test service.
+  auto client1 = TestClient::Create(kTestService2);
+  ASSERT_NE(nullptr, client1);
+
+  // Test the creation of new channels using the push APIs.
+  const int channel_id1 = client1->GetThisChannelId();
+  EXPECT_LE(0, channel_id1);
+
+  auto client2 = client1->GetNewChannel();
+  EXPECT_NE(nullptr, client2);
+  EXPECT_NE(client1->event_fd(), client2->event_fd());
+
+  const int channel_id2 = client2->GetThisChannelId();
+  EXPECT_LE(0, channel_id2);
+  EXPECT_NE(channel_id1, channel_id2);
+
+  auto client3 = client1->GetNewChannelFromOtherService();
+  EXPECT_NE(nullptr, client3);
+  EXPECT_NE(client1->event_fd(), client3->event_fd());
+
+  const int channel_id3 = client3->GetThisChannelId();
+  EXPECT_LE(0, channel_id3);
+
+  // Test which services the channels are connected to.
+  const int service_id1 = client1->GetServiceId();
+  EXPECT_LE(0, service_id1);
+
+  const int service_id2 = client2->GetServiceId();
+  EXPECT_LE(0, service_id2);
+
+  const int service_id3 = client3->GetServiceId();
+  EXPECT_LE(0, service_id3);
+
+  EXPECT_EQ(service_id1, service_id2);
+  EXPECT_NE(service_id1, service_id3);
+}
+
+// Tests process id, thread id, effective user id, and effective group id
+// returned in the message description.
+TEST_F(ServiceFrameworkTest, Ids) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create(kTestService1);
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create(kTestService1);
+  ASSERT_NE(nullptr, client);
+
+  // Pids 0-2 are defined, no user task should have them.
+
+  const pid_t process_id1 = client->GetThisProcessId();
+  EXPECT_LT(2, process_id1);
+
+  pid_t process_id2;
+
+  std::thread thread([&]() {
+    process_id2 = client->GetThisProcessId();
+  });
+  thread.join();
+
+  EXPECT_LT(2, process_id2);
+  EXPECT_EQ(process_id1, process_id2);
+
+  // This test must run as root for the rest of these tests to work.
+  const int euid1 = client->GetThisEffectiveUserId();
+  ASSERT_EQ(0, euid1);
+
+  const int egid1 = client->GetThisEffectiveGroupId();
+  EXPECT_EQ(0, egid1);
+
+  // Set effective uid/gid to system.
+  ASSERT_EQ(0, setegid(AID_SYSTEM));
+  ASSERT_EQ(0, seteuid(AID_SYSTEM));
+
+  const int euid2 = client->GetThisEffectiveUserId();
+  EXPECT_EQ(AID_SYSTEM, euid2);
+
+  const int egid2 = client->GetThisEffectiveGroupId();
+  EXPECT_EQ(AID_SYSTEM, egid2);
+
+  // Set the euid/egid back to root.
+  ASSERT_EQ(0, setegid(0));
+  ASSERT_EQ(0, seteuid(0));
+}
+
+TEST_F(ServiceFrameworkTest, PollIn) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create(kTestService1);
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create(kTestService1);
+  ASSERT_NE(nullptr, client);
+
+  epoll_event event;
+  int count = epoll_wait(client->event_fd(), &event, 1, 0);
+  ASSERT_EQ(0, count);
+
+  client->SendPollInEvent();
+
+  count = epoll_wait(client->event_fd(), &event, 1, -1);
+  ASSERT_EQ(1, count);
+  ASSERT_TRUE((EPOLLIN & event.events) != 0);
+}
+
+TEST_F(ServiceFrameworkTest, PollHup) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create(kTestService1);
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create(kTestService1);
+  ASSERT_NE(nullptr, client);
+
+  epoll_event event;
+  int count = epoll_wait(client->event_fd(), &event, 1, 0);
+  ASSERT_EQ(0, count);
+
+  client->SendPollHupEvent();
+
+  count = epoll_wait(client->event_fd(), &event, 1, -1);
+  ASSERT_EQ(1, count);
+  ASSERT_TRUE((EPOLLHUP & event.events) != 0);
+}
+
+TEST_F(ServiceFrameworkTest, LargeDataSum) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create(kTestService1);
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create(kTestService1);
+  ASSERT_NE(nullptr, client);
+
+  std::array<int, kLargeDataSize> data_array;
+  std::iota(data_array.begin(), data_array.end(), 0);
+  int expected_sum = std::accumulate(data_array.begin(), data_array.end(), 0);
+  int sum = client->SendLargeDataReturnSum(data_array);
+  ASSERT_EQ(expected_sum, sum);
+}
+
+TEST_F(ServiceFrameworkTest, Cancel) {
+  // Create a test service and add it to the dispatcher.
+  auto service = TestService::Create(kTestService1, nullptr, true);
+  ASSERT_NE(nullptr, service);
+  ASSERT_EQ(0, dispatcher_->AddService(service));
+
+  // Create a client to service.
+  auto client = TestClient::Create(kTestService1);
+  ASSERT_NE(nullptr, client);
+
+  auto previous_time = std::chrono::system_clock::now();
+  dispatcher_->ReceiveAndDispatch(100);  // 0.1 seconds should block.
+  auto time = std::chrono::system_clock::now();
+  ASSERT_LE(100, std::chrono::duration_cast<std::chrono::milliseconds>(
+                     time - previous_time)
+                     .count());
+  service->Cancel();
+  // Non-blocking. Return immediately.
+  dispatcher_->ReceiveAndDispatch(-1);
+  dispatcher_->ReceiveAndDispatch(-1);
+}
diff --git a/libs/vr/libperformance/Android.mk b/libs/vr/libperformance/Android.mk
new file mode 100644
index 0000000..aaacb1a
--- /dev/null
+++ b/libs/vr/libperformance/Android.mk
@@ -0,0 +1,43 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	performance_client.cpp \
+	performance_rpc.cpp
+
+includeFiles := \
+	$(LOCAL_PATH)/include
+
+staticLibraries := \
+	libpdx_default_transport \
+
+sharedLibraries := \
+	libbase \
+	libcutils \
+	liblog \
+	libutils
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+LOCAL_CFLAGS := -DLOG_TAG=\"libperformance\"
+LOCAL_CFLAGS += -DTRACE=0
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := libperformance
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/libs/vr/libperformance/include/CPPLINT.cfg b/libs/vr/libperformance/include/CPPLINT.cfg
new file mode 100644
index 0000000..2f8a3c0
--- /dev/null
+++ b/libs/vr/libperformance/include/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-build/header_guard
diff --git a/libs/vr/libperformance/include/dvr/performance_client_api.h b/libs/vr/libperformance/include/dvr/performance_client_api.h
new file mode 100644
index 0000000..2216e38
--- /dev/null
+++ b/libs/vr/libperformance/include/dvr/performance_client_api.h
@@ -0,0 +1,59 @@
+#ifndef ANDROID_DVR_PERFORMANCE_CLIENT_API_H_
+#define ANDROID_DVR_PERFORMANCE_CLIENT_API_H_
+
+#include <stddef.h>
+#include <unistd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// Sets the CPU partition for a task.
+///
+/// Sets the CPU partition for a task to the partition described by a CPU
+/// partition path.
+///
+/// TODO(eieio): Describe supported partitions and rules governing assignment.
+///
+/// @param task_id The task id of task to attach to a partition. When task_id is
+/// 0 the current task id is substituted.
+/// @param partition NULL-terminated ASCII string describing the CPU partition
+/// to attach the task to.
+/// @returns Returns 0 on success or a negative errno error code on error.
+int dvrSetCpuPartition(pid_t task_id, const char* partition);
+
+/// Sets the scheduler class for a task.
+///
+/// Sets the scheduler class for a task to the class described by a semantic
+/// string.
+///
+/// Supported classes for applications are: audio, graphics, normal, and
+/// background. Additional options following a ':' to be supported in the
+/// future.
+///
+/// @param task_id The task id of task to attach to a partition. When task_id is
+/// 0 the current task id is substituted.
+/// @param scheduler_class NULL-terminated ASCII string containing the desired
+/// scheduler class.
+/// @returns Returns 0 on success or a negative errno error code on error.
+int dvrSetSchedulerClass(pid_t task_id, const char* scheduler_class);
+
+/// Gets the CPU partition for a task.
+///
+/// Gets the CPU partition path for a task as a NULL-terminated ASCII string. If
+/// the path is too large to fit in the supplied buffer, -ENOBUFS is returned.
+///
+/// @param task_id The task id of the task to retrieve the partition for. When
+/// task_id is 0 the current task id is substituted.
+/// @param partition Pointer to an ASCII string buffer to store the partition
+/// path.
+/// @param size Size of the string buffer in bytes, including space for the NULL
+/// terminator.
+/// @returns Returns 0 on success or a negative errno error code on error.
+int dvrGetCpuPartition(pid_t task_id, char* partition, size_t size);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // ANDROID_DVR_PERFORMANCE_CLIENT_API_H_
diff --git a/libs/vr/libperformance/include/private/dvr/performance_client.h b/libs/vr/libperformance/include/private/dvr/performance_client.h
new file mode 100644
index 0000000..a61c6b2
--- /dev/null
+++ b/libs/vr/libperformance/include/private/dvr/performance_client.h
@@ -0,0 +1,36 @@
+#ifndef ANDROID_DVR_PERFORMANCE_CLIENT_H_
+#define ANDROID_DVR_PERFORMANCE_CLIENT_H_
+
+#include <sys/types.h>
+
+#include <cstddef>
+#include <string>
+#include <tuple>
+
+#include <pdx/client.h>
+
+namespace android {
+namespace dvr {
+
+class PerformanceClient : public pdx::ClientBase<PerformanceClient> {
+ public:
+  int SetCpuPartition(pid_t task_id, const std::string& partition);
+  int SetCpuPartition(pid_t task_id, const char* partition);
+  int SetSchedulerClass(pid_t task_id, const std::string& scheduler_class);
+  int SetSchedulerClass(pid_t task_id, const char* scheduler_class);
+  int GetCpuPartition(pid_t task_id, std::string* partition_out);
+  int GetCpuPartition(pid_t task_id, char* partition_out, std::size_t size);
+
+ private:
+  friend BASE;
+
+  explicit PerformanceClient(int* error);
+
+  PerformanceClient(const PerformanceClient&) = delete;
+  void operator=(const PerformanceClient&) = delete;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_PERFORMANCE_CLIENT_H_
diff --git a/libs/vr/libperformance/include/private/dvr/performance_rpc.h b/libs/vr/libperformance/include/private/dvr/performance_rpc.h
new file mode 100644
index 0000000..73bdaa7
--- /dev/null
+++ b/libs/vr/libperformance/include/private/dvr/performance_rpc.h
@@ -0,0 +1,37 @@
+#ifndef ANDROID_DVR_PERFORMANCE_RPC_H_
+#define ANDROID_DVR_PERFORMANCE_RPC_H_
+
+#include <sys/types.h>
+
+#include <string>
+
+#include <pdx/rpc/remote_method_type.h>
+
+namespace android {
+namespace dvr {
+
+// Performance Service RPC interface. Defines the endpoint paths, op codes, and
+// method type signatures supported by performanced.
+struct PerformanceRPC {
+  // Service path.
+  static constexpr char kClientPath[] = "system/performance/client";
+
+  // Op codes.
+  enum {
+    kOpSetCpuPartition = 0,
+    kOpSetSchedulerClass,
+    kOpGetCpuPartition,
+  };
+
+  // Methods.
+  PDX_REMOTE_METHOD(SetCpuPartition, kOpSetCpuPartition,
+                    int(pid_t, const std::string&));
+  PDX_REMOTE_METHOD(SetSchedulerClass, kOpSetSchedulerClass,
+                    int(pid_t, const std::string&));
+  PDX_REMOTE_METHOD(GetCpuPartition, kOpGetCpuPartition, std::string(pid_t));
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_PERFORMANCE_RPC_H_
diff --git a/libs/vr/libperformance/performance_client.cpp b/libs/vr/libperformance/performance_client.cpp
new file mode 100644
index 0000000..2124162
--- /dev/null
+++ b/libs/vr/libperformance/performance_client.cpp
@@ -0,0 +1,119 @@
+#include "include/private/dvr/performance_client.h"
+
+#include <sys/types.h>
+
+#include <pdx/default_transport/client_channel_factory.h>
+#include <pdx/rpc/remote_method.h>
+#include <pdx/rpc/string_wrapper.h>
+#include <private/dvr/performance_rpc.h>
+
+using android::pdx::rpc::WrapString;
+
+namespace android {
+namespace dvr {
+
+PerformanceClient::PerformanceClient(int* error)
+    : BASE(pdx::default_transport::ClientChannelFactory::Create(
+          PerformanceRPC::kClientPath)) {
+  if (error)
+    *error = Client::error();
+}
+
+int PerformanceClient::SetCpuPartition(pid_t task_id,
+                                       const std::string& partition) {
+  if (task_id == 0)
+    task_id = gettid();
+
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<PerformanceRPC::SetCpuPartition>(task_id, partition));
+}
+
+int PerformanceClient::SetCpuPartition(pid_t task_id, const char* partition) {
+  if (task_id == 0)
+    task_id = gettid();
+
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<PerformanceRPC::SetCpuPartition>(
+          task_id, WrapString(partition)));
+}
+
+int PerformanceClient::SetSchedulerClass(pid_t task_id,
+                                         const std::string& scheduler_class) {
+  if (task_id == 0)
+    task_id = gettid();
+
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<PerformanceRPC::SetSchedulerClass>(task_id,
+                                                            scheduler_class));
+}
+
+int PerformanceClient::SetSchedulerClass(pid_t task_id,
+                                         const char* scheduler_class) {
+  if (task_id == 0)
+    task_id = gettid();
+
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<PerformanceRPC::SetSchedulerClass>(
+          task_id, WrapString(scheduler_class)));
+}
+
+int PerformanceClient::GetCpuPartition(pid_t task_id,
+                                       std::string* partition_out) {
+  if (partition_out == nullptr)
+    return -EINVAL;
+
+  if (task_id == 0)
+    task_id = gettid();
+
+  auto status = InvokeRemoteMethodInPlace<PerformanceRPC::GetCpuPartition>(
+      partition_out, task_id);
+  return status ? 0 : -status.error();
+}
+
+int PerformanceClient::GetCpuPartition(pid_t task_id, char* partition_out,
+                                       std::size_t size) {
+  if (partition_out == nullptr)
+    return -EINVAL;
+
+  if (task_id == 0)
+    task_id = gettid();
+
+  auto wrapper = WrapString(partition_out, size);
+  auto status = InvokeRemoteMethodInPlace<PerformanceRPC::GetCpuPartition>(
+      &wrapper, task_id);
+  if (!status)
+    return -status.error();
+
+  if (wrapper.size() < size)
+    partition_out[wrapper.size()] = '\0';
+
+  return 0;
+}
+
+}  // namespace dvr
+}  // namespace android
+
+extern "C" int dvrSetCpuPartition(pid_t task_id, const char* partition) {
+  int error;
+  if (auto client = android::dvr::PerformanceClient::Create(&error))
+    return client->SetCpuPartition(task_id, partition);
+  else
+    return error;
+}
+
+extern "C" int dvrSetSchedulerClass(pid_t task_id,
+                                    const char* scheduler_class) {
+  int error;
+  if (auto client = android::dvr::PerformanceClient::Create(&error))
+    return client->SetSchedulerClass(task_id, scheduler_class);
+  else
+    return error;
+}
+
+extern "C" int dvrGetCpuPartition(pid_t task_id, char* partition, size_t size) {
+  int error;
+  if (auto client = android::dvr::PerformanceClient::Create(&error))
+    return client->GetCpuPartition(task_id, partition, size);
+  else
+    return error;
+}
diff --git a/libs/vr/libperformance/performance_rpc.cpp b/libs/vr/libperformance/performance_rpc.cpp
new file mode 100644
index 0000000..9135349
--- /dev/null
+++ b/libs/vr/libperformance/performance_rpc.cpp
@@ -0,0 +1,9 @@
+#include "include/private/dvr/performance_rpc.h"
+
+namespace android {
+namespace dvr {
+
+constexpr char PerformanceRPC::kClientPath[];
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libposepredictor/Android.mk b/libs/vr/libposepredictor/Android.mk
new file mode 100644
index 0000000..030f79c
--- /dev/null
+++ b/libs/vr/libposepredictor/Android.mk
@@ -0,0 +1,51 @@
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+        linear_pose_predictor.cpp \
+
+includeFiles := \
+        $(LOCAL_PATH)/include
+
+staticLibraries := \
+        libdvrcommon \
+        libsensor \
+        libpdx_default_transport \
+
+sharedLibraries := \
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+LOCAL_CFLAGS := -DLOG_TAG=\"libposepredictor\"
+LOCAL_CFLAGS += -DTRACE=0
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := libposepredictor
+include $(BUILD_STATIC_LIBRARY)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := \
+        linear_pose_predictor_tests.cpp \
+
+LOCAL_STATIC_LIBRARIES := libposepredictor $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := pose_predictor_tests
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/vr/libposepredictor/include/private/dvr/linear_pose_predictor.h b/libs/vr/libposepredictor/include/private/dvr/linear_pose_predictor.h
new file mode 100644
index 0000000..1efe938
--- /dev/null
+++ b/libs/vr/libposepredictor/include/private/dvr/linear_pose_predictor.h
@@ -0,0 +1,43 @@
+#ifndef ANDROID_DVR_POSE_PREDICTOR_H_
+#define ANDROID_DVR_POSE_PREDICTOR_H_
+
+#include <private/dvr/pose_predictor.h>
+
+namespace android {
+namespace dvr {
+
+// This class makes a linear prediction using the last two samples we received.
+class LinearPosePredictor : public PosePredictor {
+ public:
+  LinearPosePredictor() = default;
+
+  // Add a new sample.
+  void Add(const Sample& sample, DvrPoseAsync* out_pose) override;
+
+  // Predict using the last two samples.
+  void Predict(int64_t left_time_ns, int64_t right_time_ns,
+               DvrPoseAsync* out_pose) const override;
+
+ private:
+  // The index of the last sample we received.
+  size_t current_index_ = 0;
+
+  // The previous two samples.
+  Sample samples_[2];
+
+  // Experimental
+  bool forward_predict_angular_speed_ = false;
+
+  // Transient variables updated when a sample is added.
+  vec3d velocity_ = vec3d::Zero();
+  vec3d rotational_velocity_ = vec3d::Zero();
+  vec3d rotational_axis_ = vec3d::Zero();
+  double last_angular_speed_ = 0;
+  double angular_speed_ = 0;
+  double angular_accel_ = 0;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_POSE_PREDICTOR_H_
diff --git a/libs/vr/libposepredictor/include/private/dvr/pose_predictor.h b/libs/vr/libposepredictor/include/private/dvr/pose_predictor.h
new file mode 100644
index 0000000..719edbe
--- /dev/null
+++ b/libs/vr/libposepredictor/include/private/dvr/pose_predictor.h
@@ -0,0 +1,47 @@
+#ifndef ANDROID_DVR_LINEAR_POSE_PREDICTOR_H_
+#define ANDROID_DVR_LINEAR_POSE_PREDICTOR_H_
+
+#include <private/dvr/pose_client_internal.h>
+#include <private/dvr/types.h>
+
+namespace android {
+namespace dvr {
+
+// This is an abstract base class for prediction 6dof pose given
+// a set of samples.
+//
+// TODO(okana): Create a framework for testing different subclasses for
+// performance and accuracy.
+class PosePredictor {
+ public:
+  PosePredictor() = default;
+  virtual ~PosePredictor() = default;
+
+  // Encapsulates a pose sample.
+  struct Sample {
+    vec3d position = vec3d::Zero();
+    quatd orientation = quatd::Identity();
+    int64_t time_ns = 0;
+  };
+
+  // Add a pose sample coming from the sensors.
+  // Returns this sample as a dvr pose.
+  //
+  // We will use the returned pose if prediction is not enabled.
+  virtual void Add(const Sample& sample, DvrPoseAsync* out_pose) = 0;
+
+  // Make a pose prediction for the left and right eyes at specific times.
+  virtual void Predict(int64_t left_time_ns, int64_t right_time_ns,
+                       DvrPoseAsync* out_pose) const = 0;
+
+  // Helpers
+  static double NsToSeconds(int64_t time_ns) { return time_ns / 1e9; }
+  static int64_t SecondsToNs(double seconds) {
+    return static_cast<int64_t>(seconds * 1e9);
+  }
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_LINEAR_POSE_PREDICTOR_H_
diff --git a/libs/vr/libposepredictor/linear_pose_predictor.cpp b/libs/vr/libposepredictor/linear_pose_predictor.cpp
new file mode 100644
index 0000000..a2ce2ca
--- /dev/null
+++ b/libs/vr/libposepredictor/linear_pose_predictor.cpp
@@ -0,0 +1,147 @@
+#include <cutils/log.h>
+
+#include <private/dvr/linear_pose_predictor.h>
+
+namespace android {
+namespace dvr {
+
+using AngleAxisd = Eigen::AngleAxis<double>;
+
+void LinearPosePredictor::Add(const Sample& sample, DvrPoseAsync* out_pose) {
+  // If we are receiving a new sample, move the index to the next item.
+  // If the time stamp is the same as the last frame, we will just overwrite
+  // it with the new data.
+  if (sample.time_ns != samples_[current_index_].time_ns) {
+    current_index_ ^= 1;
+  }
+
+  // Save the sample.
+  samples_[current_index_] = sample;
+
+  // The previous sample we received.
+  const auto& previous_sample = samples_[current_index_ ^ 1];
+
+  // Ready to compute velocities.
+  const auto pose_delta_time =
+      NsToSeconds(sample.time_ns - previous_sample.time_ns);
+
+  const double inverse_dt = 1. / pose_delta_time;
+  if (pose_delta_time > 0.0) {
+    velocity_ = (sample.position - previous_sample.position) * inverse_dt;
+  } else {
+    velocity_ = vec3d::Zero();
+  }
+
+  quatd delta_q = sample.orientation.inverse() * previous_sample.orientation;
+  // Check that delta_q.w() == 1, Eigen doesn't respect this convention. If
+  // delta_q.w() == -1, we'll get the opposite velocity.
+  if (delta_q.w() < 0) {
+    delta_q.w() = -delta_q.w();
+    delta_q.vec() = -delta_q.vec();
+  }
+  rotational_velocity_ = -2.0 * delta_q.vec() * inverse_dt;
+
+  // Temporary experiment with acceleration estimate.
+  angular_speed_ = rotational_velocity_.norm();
+  angular_accel_ = 0.0;
+  if (forward_predict_angular_speed_) {
+    angular_accel_ =
+        pose_delta_time > 0.0
+            ? (angular_speed_ - last_angular_speed_) / pose_delta_time
+            : 0.0;
+  }
+  last_angular_speed_ = angular_speed_;
+
+  rotational_axis_ = vec3d(0.0, 1.0, 0.0);
+  if (angular_speed_ > 0.0) {
+    rotational_axis_ = rotational_velocity_ / angular_speed_;
+  }
+
+  out_pose->orientation = {static_cast<float>(sample.orientation.vec().x()),
+                           static_cast<float>(sample.orientation.vec().y()),
+                           static_cast<float>(sample.orientation.vec().z()),
+                           static_cast<float>(sample.orientation.w())};
+
+  out_pose->translation = {static_cast<float>(sample.position.x()),
+                           static_cast<float>(sample.position.y()),
+                           static_cast<float>(sample.position.z()), 0.0f};
+
+  out_pose->right_orientation = {
+      static_cast<float>(sample.orientation.vec().x()),
+      static_cast<float>(sample.orientation.vec().y()),
+      static_cast<float>(sample.orientation.vec().z()),
+      static_cast<float>(sample.orientation.w())};
+
+  out_pose->right_translation = {static_cast<float>(sample.position.x()),
+                                 static_cast<float>(sample.position.y()),
+                                 static_cast<float>(sample.position.z()), 0.0f};
+
+  out_pose->angular_velocity = {static_cast<float>(rotational_velocity_.x()),
+                                static_cast<float>(rotational_velocity_.y()),
+                                static_cast<float>(rotational_velocity_.z()),
+                                0.0f};
+
+  out_pose->velocity = {static_cast<float>(velocity_.x()),
+                        static_cast<float>(velocity_.y()),
+                        static_cast<float>(velocity_.z()), 0.0f};
+  out_pose->timestamp_ns = sample.time_ns;
+  out_pose->flags = DVR_POSE_FLAG_HEAD | DVR_POSE_FLAG_VALID;
+  memset(out_pose->pad, 0, sizeof(out_pose->pad));
+}
+
+void LinearPosePredictor::Predict(int64_t left_time_ns, int64_t right_time_ns,
+                                  DvrPoseAsync* out_pose) const {
+  const auto& sample = samples_[current_index_];
+
+  double dt = NsToSeconds(left_time_ns - sample.time_ns);
+  double r_dt = NsToSeconds(right_time_ns - sample.time_ns);
+
+  // Temporary forward prediction code.
+  auto start_t_head_future = sample.position + velocity_ * dt;
+  auto r_start_t_head_future = sample.position + velocity_ * r_dt;
+  double angle = angular_speed_ * dt;
+  double r_angle = angular_speed_ * r_dt;
+  if (__builtin_expect(forward_predict_angular_speed_, 0)) {
+    angle += 0.5 * angular_accel_ * dt * dt;
+    r_angle += 0.5 * angular_accel_ * r_dt * r_dt;
+  }
+  auto start_q_head_future =
+      sample.orientation * quatd(AngleAxisd(angle, rotational_axis_));
+  auto r_start_q_head_future =
+      sample.orientation * quatd(AngleAxisd(r_angle, rotational_axis_));
+
+  out_pose->orientation = {static_cast<float>(start_q_head_future.x()),
+                           static_cast<float>(start_q_head_future.y()),
+                           static_cast<float>(start_q_head_future.z()),
+                           static_cast<float>(start_q_head_future.w())};
+
+  out_pose->translation = {static_cast<float>(start_t_head_future.x()),
+                           static_cast<float>(start_t_head_future.y()),
+                           static_cast<float>(start_t_head_future.z()), 0.0f};
+
+  out_pose->right_orientation = {static_cast<float>(r_start_q_head_future.x()),
+                                 static_cast<float>(r_start_q_head_future.y()),
+                                 static_cast<float>(r_start_q_head_future.z()),
+                                 static_cast<float>(r_start_q_head_future.w())};
+
+  out_pose->right_translation = {static_cast<float>(r_start_t_head_future.x()),
+                                 static_cast<float>(r_start_t_head_future.y()),
+                                 static_cast<float>(r_start_t_head_future.z()),
+                                 0.0f};
+
+  out_pose->angular_velocity = {static_cast<float>(rotational_velocity_.x()),
+                                static_cast<float>(rotational_velocity_.y()),
+                                static_cast<float>(rotational_velocity_.z()),
+                                0.0f};
+
+  out_pose->velocity = {static_cast<float>(velocity_.x()),
+                        static_cast<float>(velocity_.y()),
+                        static_cast<float>(velocity_.z()), 0.0f};
+
+  out_pose->timestamp_ns = left_time_ns;
+  out_pose->flags = DVR_POSE_FLAG_HEAD | DVR_POSE_FLAG_VALID;
+  memset(out_pose->pad, 0, sizeof(out_pose->pad));
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libposepredictor/linear_pose_predictor_tests.cpp b/libs/vr/libposepredictor/linear_pose_predictor_tests.cpp
new file mode 100644
index 0000000..6c4f58a
--- /dev/null
+++ b/libs/vr/libposepredictor/linear_pose_predictor_tests.cpp
@@ -0,0 +1,185 @@
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include <private/dvr/linear_pose_predictor.h>
+
+namespace android {
+namespace dvr {
+
+namespace {
+
+// For comparing expected and actual.
+constexpr double kAbsErrorTolerance = 1e-5;
+
+// The default rotation axis we will be using.
+const vec3d kRotationAxis = vec3d(1, 4, 3).normalized();
+
+// Linearly interpolate between a and b.
+vec3d lerp(const vec3d& a, const vec3d& b, double t) { return (b - a) * t + a; }
+
+// Linearly interpolate between two angles and return the resulting rotation as
+// a quaternion (around the kRotationAxis).
+quatd qlerp(double angle1, double angle2, double t) {
+  return quatd(
+      Eigen::AngleAxis<double>((angle2 - angle1) * t + angle1, kRotationAxis));
+}
+
+// Compare two positions.
+void TestPosition(const vec3d& expected, const float32x4_t& actual) {
+  for (int i = 0; i < 3; ++i) {
+    EXPECT_NEAR(expected[i], static_cast<double>(actual[i]),
+                kAbsErrorTolerance);
+  }
+}
+
+// Compare two orientations.
+void TestOrientation(const quatd& expected, const float32x4_t& actual) {
+  // abs(expected.dot(actual)) > 1-eps
+  EXPECT_GE(std::abs(vec4d(actual[0], actual[1], actual[2], actual[3])
+                         .dot(expected.coeffs())),
+            0.99);
+}
+}
+
+// Test the extrapolation from two samples.
+TEST(LinearPosePredictorTest, Extrapolation) {
+  LinearPosePredictor predictor;
+
+  // We wil extrapolate linearly from [position|orientation] 1 -> 2.
+  const vec3d position1(0, 0, 0);
+  const vec3d position2(1, 2, 3);
+  const double angle1 = M_PI * 0.3;
+  const double angle2 = M_PI * 0.5;
+  const quatd orientation1(Eigen::AngleAxis<double>(angle1, kRotationAxis));
+  const quatd orientation2(Eigen::AngleAxis<double>(angle2, kRotationAxis));
+  const int64_t t1_ns = 0;           //< First sample time stamp
+  const int64_t t2_ns = 10;          //< The second sample time stamp
+  const int64_t eval_left_ns = 23;   //< The eval time for left
+  const int64_t eval_right_ns = 31;  //< The eval time for right
+  DvrPoseAsync start_pose, end_pose, extrapolated_pose;
+
+  predictor.Add(
+      PosePredictor::Sample{
+          .position = position1, .orientation = orientation1, .time_ns = t1_ns},
+      &start_pose);
+
+  // The start pose is passthough.
+  TestPosition(position1, start_pose.translation);
+  TestPosition(position1, start_pose.right_translation);
+  TestOrientation(orientation1, start_pose.orientation);
+  TestOrientation(orientation1, start_pose.right_orientation);
+  EXPECT_EQ(t1_ns, start_pose.timestamp_ns);
+
+  predictor.Add(
+      PosePredictor::Sample{
+          .position = position2, .orientation = orientation2, .time_ns = t2_ns},
+      &end_pose);
+
+  TestPosition(position2, end_pose.translation);
+  TestPosition(position2, end_pose.right_translation);
+  TestOrientation(orientation2, end_pose.orientation);
+  TestOrientation(orientation2, end_pose.right_orientation);
+  EXPECT_EQ(t2_ns, end_pose.timestamp_ns);
+
+  // Extrapolate from t1 - t2 to eval_[left/right].
+  predictor.Predict(eval_left_ns, eval_right_ns, &extrapolated_pose);
+
+  // The interpolation factors for left and right.
+  const auto left_t =
+      (eval_left_ns - t1_ns) / static_cast<double>(t2_ns - t1_ns);
+  EXPECT_EQ(2.3, left_t);
+
+  const auto right_t =
+      (eval_right_ns - t1_ns) / static_cast<double>(t2_ns - t1_ns);
+  EXPECT_EQ(3.1, right_t);
+
+  TestPosition(lerp(position1, position2, left_t),
+               extrapolated_pose.translation);
+  TestPosition(lerp(position1, position2, right_t),
+               extrapolated_pose.right_translation);
+  TestOrientation(qlerp(angle1, angle2, left_t), extrapolated_pose.orientation);
+  TestOrientation(qlerp(angle1, angle2, right_t),
+                  extrapolated_pose.right_orientation);
+}
+
+// Test three samples, where the last two samples have the same timestamp.
+TEST(LinearPosePredictorTest, DuplicateSamples) {
+  LinearPosePredictor predictor;
+
+  const vec3d position1(0, 0, 0);
+  const vec3d position2(1, 2, 3);
+  const vec3d position3(2, 2, 3);
+  const double angle1 = M_PI * 0.3;
+  const double angle2 = M_PI * 0.5;
+  const double angle3 = M_PI * 0.65;
+  const quatd orientation1(Eigen::AngleAxis<double>(angle1, kRotationAxis));
+  const quatd orientation2(Eigen::AngleAxis<double>(angle2, kRotationAxis));
+  const quatd orientation3(Eigen::AngleAxis<double>(angle3, kRotationAxis));
+  const int64_t t1_ns = 0;
+  const int64_t t2_ns = 10;
+  const int64_t eval_left_ns = 27;
+  const int64_t eval_right_ns = 31;
+  DvrPoseAsync start_pose, end_pose, extrapolated_pose;
+
+  predictor.Add(
+      PosePredictor::Sample{
+          .position = position1, .orientation = orientation1, .time_ns = t1_ns},
+      &start_pose);
+
+  predictor.Add(
+      PosePredictor::Sample{
+          .position = position2, .orientation = orientation2, .time_ns = t2_ns},
+      &end_pose);
+
+  {
+    // Extrapolate from t1 - t2 to eval_[left/right].
+    predictor.Predict(eval_left_ns, eval_right_ns, &extrapolated_pose);
+
+    // The interpolation factors for left and right.
+    const auto left_t =
+        (eval_left_ns - t1_ns) / static_cast<double>(t2_ns - t1_ns);
+    const auto right_t =
+        (eval_right_ns - t1_ns) / static_cast<double>(t2_ns - t1_ns);
+
+    // Test the result.
+    TestPosition(lerp(position1, position2, left_t),
+                 extrapolated_pose.translation);
+    TestPosition(lerp(position1, position2, right_t),
+                 extrapolated_pose.right_translation);
+    TestOrientation(qlerp(angle1, angle2, left_t),
+                    extrapolated_pose.orientation);
+    TestOrientation(qlerp(angle1, angle2, right_t),
+                    extrapolated_pose.right_orientation);
+  }
+
+  // Sending a duplicate sample here.
+  predictor.Add(
+      PosePredictor::Sample{
+          .position = position3, .orientation = orientation3, .time_ns = t2_ns},
+      &end_pose);
+
+  {
+    // Extrapolate from t1 - t2 to eval_[left/right].
+    predictor.Predict(eval_left_ns, eval_right_ns, &extrapolated_pose);
+
+    // The interpolation factors for left and right.
+    const auto left_t =
+        (eval_left_ns - t1_ns) / static_cast<double>(t2_ns - t1_ns);
+    const auto right_t =
+        (eval_right_ns - t1_ns) / static_cast<double>(t2_ns - t1_ns);
+
+    // Test the result.
+    TestPosition(lerp(position1, position3, left_t),
+                 extrapolated_pose.translation);
+    TestPosition(lerp(position1, position3, right_t),
+                 extrapolated_pose.right_translation);
+    TestOrientation(qlerp(angle1, angle3, left_t),
+                    extrapolated_pose.orientation);
+    TestOrientation(qlerp(angle1, angle3, right_t),
+                    extrapolated_pose.right_orientation);
+  }
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libsensor/Android.mk b/libs/vr/libsensor/Android.mk
new file mode 100644
index 0000000..db1514d
--- /dev/null
+++ b/libs/vr/libsensor/Android.mk
@@ -0,0 +1,78 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+sourceFiles := \
+	pose_client.cpp \
+	sensor_client.cpp
+
+includeFiles := \
+	$(LOCAL_PATH)/include
+
+staticLibraries := \
+	libbufferhub \
+	libchrome \
+	libdvrcommon \
+	libpdx_default_transport \
+
+sharedLibraries := \
+	libbase \
+	libcutils \
+	libhardware \
+	liblog \
+	libutils \
+	libevent
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(sourceFiles)
+LOCAL_C_INCLUDES := $(includeFiles)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(includeFiles)
+LOCAL_STATIC_LIBRARIES := $(staticLibraries)
+LOCAL_SHARED_LIBRARIES := $(sharedLibraries)
+LOCAL_MODULE := libsensor
+include $(BUILD_STATIC_LIBRARY)
+
+
+testFiles := \
+  tests/sensor_app_tests.cpp
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := sensor_app_tests
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+  $(testFiles) \
+
+LOCAL_C_INCLUDES := \
+  $(includeFiles) \
+
+LOCAL_SHARED_LIBRARIES := \
+  libEGL \
+  libGLESv1_CM \
+  libGLESv2 \
+  libvulkan \
+  libsync \
+  $(sharedLibraries) \
+
+LOCAL_STATIC_LIBRARIES := \
+  libgmock_main \
+  libgmock \
+  libdisplay \
+  libeds \
+  libsensor \
+  libdvrgraphics \
+  $(staticLibraries) \
+
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/vr/libsensor/include/CPPLINT.cfg b/libs/vr/libsensor/include/CPPLINT.cfg
new file mode 100644
index 0000000..2f8a3c0
--- /dev/null
+++ b/libs/vr/libsensor/include/CPPLINT.cfg
@@ -0,0 +1 @@
+filter=-build/header_guard
diff --git a/libs/vr/libsensor/include/dvr/pose_client.h b/libs/vr/libsensor/include/dvr/pose_client.h
new file mode 100644
index 0000000..ed75f84
--- /dev/null
+++ b/libs/vr/libsensor/include/dvr/pose_client.h
@@ -0,0 +1,205 @@
+#ifndef ANDROID_DVR_POSE_CLIENT_H_
+#define ANDROID_DVR_POSE_CLIENT_H_
+
+#ifdef __ARM_NEON
+#include <arm_neon.h>
+#else
+#ifndef __FLOAT32X4T_86
+#define __FLOAT32X4T_86
+typedef float float32x4_t __attribute__ ((__vector_size__ (16)));
+typedef struct float32x4x4_t { float32x4_t val[4]; };
+#endif
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DvrPose DvrPose;
+
+// Represents the current state provided by the pose service, containing a
+// rotation and translation.
+typedef struct __attribute__((packed, aligned(8))) DvrPoseState {
+  // A quaternion representing the rotation of the HMD in Start Space.
+  struct __attribute__((packed)) {
+    float x, y, z, w;
+  } head_from_start_rotation;
+  // The position of the HMD in Start Space.
+  struct __attribute__((packed)) {
+    float x, y, z;
+  } head_from_start_translation;
+  // Time in nanoseconds for the current pose.
+  uint64_t timestamp_ns;
+  // The rotational velocity of the HMD.
+  struct __attribute__((packed)) {
+    float x, y, z;
+  } sensor_from_start_rotation_velocity;
+} DvrPoseState;
+
+enum {
+  DVR_POSE_FLAG_VALID = (1UL << 0),       // This pose is valid.
+  DVR_POSE_FLAG_HEAD = (1UL << 1),        // This pose is the head.
+  DVR_POSE_FLAG_CONTROLLER = (1UL << 2),  // This pose is a controller.
+};
+
+// Represents an estimated pose, accessed asynchronously through a shared ring
+// buffer. No assumptions should be made about the data in padding space.
+// The size of this struct is 128 bytes.
+typedef struct __attribute__((packed, aligned(16))) DvrPoseAsync {
+  // Left eye head-from-start orientation quaternion x,y,z,w.
+  float32x4_t orientation;
+  // Left eye head-from-start translation x,y,z,pad in meters.
+  float32x4_t translation;
+  // Right eye head-from-start orientation quaternion x,y,z,w.
+  float32x4_t right_orientation;
+  // Right eye head-from-start translation x,y,z,pad in meters.
+  float32x4_t right_translation;
+  // Start-space angular velocity x,y,z,pad in radians per second.
+  float32x4_t angular_velocity;
+  // Start-space positional velocity x,y,z,pad in meters per second.
+  float32x4_t velocity;
+  // Timestamp of when this pose is predicted for, typically halfway through
+  // scanout.
+  int64_t timestamp_ns;
+  // Bitmask of DVR_POSE_FLAG_* constants that apply to this pose.
+  //
+  // If DVR_POSE_FLAG_VALID is not set, the pose is indeterminate.
+  uint64_t flags;
+  // Reserved padding to 128 bytes.
+  uint8_t pad[16];
+} DvrPoseAsync;
+
+// Returned by the async pose ring buffer access API.
+typedef struct DvrPoseRingBufferInfo {
+  // Read-only pointer to the pose ring buffer. The current pose is in this
+  // buffer at element buffer[current_frame & (buffer_size - 1)]. The next
+  // frame's forecasted pose is at element
+  // ((current_frame + 1) & (buffer_size - 1)). And so on. The poses are
+  // predicted for when 50% of the corresponding frame's pixel data is visible
+  // to the user.
+  // The last value returned by dvrPresent is the count for the next frame,
+  // which is the earliest that the application could display something if they
+  // were to render promptly. (TODO(jbates) move this comment to dvrPresent).
+  volatile const DvrPoseAsync* buffer;
+  // Minimum number of accurate forecasted poses including the current frame's
+  // pose. This is the number of poses that are udpated by the pose service.
+  // If the application reads past this count, they will get a stale prediction
+  // from a previous frame. Guaranteed to be at least 2.
+  uint32_t min_future_count;
+  // Number of elements in buffer. At least 8 and greater than min_future_count.
+  // Guaranteed to be a power of two. The total size of the buffer in bytes is:
+  //   total_count * sizeof(DvrPoseAsync)
+  uint32_t total_count;
+} DvrPoseRingBufferInfo;
+
+typedef enum DvrPoseMode {
+  DVR_POSE_MODE_6DOF = 0,
+  DVR_POSE_MODE_3DOF,
+  DVR_POSE_MODE_MOCK_FROZEN,
+  DVR_POSE_MODE_MOCK_HEAD_TURN_SLOW,
+  DVR_POSE_MODE_MOCK_HEAD_TURN_FAST,
+  DVR_POSE_MODE_MOCK_ROTATE_SLOW,
+  DVR_POSE_MODE_MOCK_ROTATE_MEDIUM,
+  DVR_POSE_MODE_MOCK_ROTATE_FAST,
+  DVR_POSE_MODE_MOCK_CIRCLE_STRAFE,
+
+  // Always last.
+  DVR_POSE_MODE_COUNT,
+} DvrPoseMode;
+
+typedef enum DvrControllerId {
+  DVR_CONTROLLER_0 = 0,
+  DVR_CONTROLLER_1 = 1,
+} DvrControllerId;
+
+// Creates a new pose client.
+//
+// @return Pointer to the created pose client, nullptr on failure.
+DvrPose* dvrPoseCreate();
+
+// Destroys a pose client.
+//
+// @param client Pointer to the pose client to be destroyed.
+void dvrPoseDestroy(DvrPose* client);
+
+// Gets the pose for the given vsync count.
+//
+// @param client Pointer to the pose client.
+// @param vsync_count Vsync that this pose should be forward-predicted to.
+//     Typically this is the count returned by dvrGetNextVsyncCount.
+// @param out_pose Struct to store pose state.
+// @return Zero on success, negative error code on failure.
+int dvrPoseGet(DvrPose* client, uint32_t vsync_count, DvrPoseAsync* out_pose);
+
+// Gets the current vsync count.
+uint32_t dvrPoseGetVsyncCount(DvrPose* client);
+
+// Gets the pose for the given controller at the given vsync count.
+//
+// @param client Pointer to the pose client.
+// @param controller_id The controller id.
+// @param vsync_count Vsync that this pose should be forward-predicted to.
+//     Typically this is the count returned by dvrGetNextVsyncCount.
+// @param out_pose Struct to store pose state.
+// @return Zero on success, negative error code on failure.
+int dvrPoseGetController(DvrPose* client, int32_t controller_id,
+                         uint32_t vsync_count, DvrPoseAsync* out_pose);
+
+// Enables/disables logging for the controller fusion.
+//
+// @param client Pointer to the pose client.
+// @param enable True starts logging, False stops.
+// @return Zero on success, negative error code on failure.
+int dvrPoseLogController(DvrPose* client, bool enable);
+
+// DEPRECATED
+// Polls current pose state.
+//
+// @param client Pointer to the pose client.
+// @param state Struct to store polled state.
+// @return Zero on success, negative error code on failure.
+int dvrPosePoll(DvrPose* client, DvrPoseState* state);
+
+// Freezes the pose to the provided state.
+//
+// Future poll operations will return this state until a different state is
+// frozen or dvrPoseSetMode() is called with a different mode. The timestamp is
+// not frozen.
+//
+// @param client Pointer to the pose client.
+// @param frozen_state State pose to be frozen to.
+// @return Zero on success, negative error code on failure.
+int dvrPoseFreeze(DvrPose* client, const DvrPoseState* frozen_state);
+
+// Sets the pose service mode.
+//
+// @param mode The requested pose mode.
+// @return Zero on success, negative error code on failure.
+int dvrPoseSetMode(DvrPose* client, DvrPoseMode mode);
+
+// Gets the pose service mode.
+//
+// @param mode Return value for the current pose mode.
+// @return Zero on success, negative error code on failure.
+int dvrPoseGetMode(DvrPose* client, DvrPoseMode* mode);
+
+// Get access to the shared memory pose ring buffer.
+// A future pose at vsync <current> + <offset> is accessed at index:
+//   index = (<current> + <offset>) % out_buffer_size
+// Where <current> was the last value returned by dvrPresent and
+// <offset> is less than or equal to |out_min_future_count|.
+// |out_buffer| will be set to a pointer to the buffer.
+// |out_fd| will be set to the gralloc buffer file descriptor, which is
+//   required for binding this buffer for GPU use.
+// Returns 0 on success.
+int dvrPoseGetRingBuffer(DvrPose* client, DvrPoseRingBufferInfo* out_info);
+
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // ANDROID_DVR_POSE_CLIENT_H_
diff --git a/libs/vr/libsensor/include/private/dvr/pose-ipc.h b/libs/vr/libsensor/include/private/dvr/pose-ipc.h
new file mode 100644
index 0000000..908030e
--- /dev/null
+++ b/libs/vr/libsensor/include/private/dvr/pose-ipc.h
@@ -0,0 +1,28 @@
+#ifndef ANDROID_DVR_POSE_IPC_H_
+#define ANDROID_DVR_POSE_IPC_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DVR_POSE_SERVICE_BASE "system/pose"
+#define DVR_POSE_SERVICE_CLIENT (DVR_POSE_SERVICE_BASE "/client")
+
+enum {
+  DVR_POSE_POLL = 0,
+  DVR_POSE_FREEZE,
+  DVR_POSE_SET_MODE,
+  DVR_POSE_GET_RING_BUFFER,
+  DVR_POSE_NOTIFY_VSYNC,
+  DVR_POSE_GET_MODE,
+  DVR_POSE_GET_CONTROLLER_RING_BUFFER,
+  DVR_POSE_LOG_CONTROLLER,
+};
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // ANDROID_DVR_POSE_IPC_H_
diff --git a/libs/vr/libsensor/include/private/dvr/pose_client_internal.h b/libs/vr/libsensor/include/private/dvr/pose_client_internal.h
new file mode 100644
index 0000000..66c4c7c
--- /dev/null
+++ b/libs/vr/libsensor/include/private/dvr/pose_client_internal.h
@@ -0,0 +1,43 @@
+#ifndef ANDROID_DVR_POSE_CLIENT_INTERNAL_H_
+#define ANDROID_DVR_POSE_CLIENT_INTERNAL_H_
+
+#include <stdint.h>
+
+#include <dvr/pose_client.h>
+#include <pdx/file_handle.h>
+#include <private/dvr/sensor_constants.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Sensord head pose ring buffer.
+typedef struct __attribute__((packed, aligned(16))) DvrPoseRingBuffer {
+  // Ring buffer always at the beginning of the structure, as consumers may
+  // not have access to this parent structure definition.
+  DvrPoseAsync ring[kPoseAsyncBufferTotalCount];
+  // Current vsync_count (where sensord is writing poses from).
+  uint32_t vsync_count;
+} DvrPoseMetadata;
+
+// Called by displayd to give vsync count info to the pose service.
+// |display_timestamp| Display timestamp is in the middle of scanout.
+// |display_period_ns| Nanos between vsyncs.
+// |right_eye_photon_offset_ns| Nanos to shift the prediction timestamp for
+//    the right eye head pose (relative to the left eye prediction).
+int privateDvrPoseNotifyVsync(DvrPose* client, uint32_t vsync_count,
+                              int64_t display_timestamp,
+                              int64_t display_period_ns,
+                              int64_t right_eye_photon_offset_ns);
+
+// Get file descriptor for access to the shared memory pose buffer. This can be
+// used with GL extensions that support shared memory buffer objects. The caller
+// takes ownership of the returned fd and must close it or pass on ownership.
+int privateDvrPoseGetRingBufferFd(DvrPose* client,
+                                  android::pdx::LocalHandle* fd);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // ANDROID_DVR_POSE_CLIENT_INTERNAL_H_
diff --git a/libs/vr/libsensor/include/private/dvr/sensor-ipc.h b/libs/vr/libsensor/include/private/dvr/sensor-ipc.h
new file mode 100644
index 0000000..7e8b9e5
--- /dev/null
+++ b/libs/vr/libsensor/include/private/dvr/sensor-ipc.h
@@ -0,0 +1,17 @@
+#ifndef ANDROID_DVR_SENSOR_IPC_H_
+#define ANDROID_DVR_SENSOR_IPC_H_
+
+#define DVR_SENSOR_SERVICE_BASE "system/sensors"
+
+#define DVR_SENSOR_SERVICE_CLIENT (DVR_SENSOR_SERVICE_BASE "/client")
+
+/*
+ * Endpoint ops
+ */
+enum {
+  DVR_SENSOR_START = 0,
+  DVR_SENSOR_STOP,
+  DVR_SENSOR_POLL,
+};
+
+#endif  // ANDROID_DVR_SENSOR_IPC_H_
diff --git a/libs/vr/libsensor/include/private/dvr/sensor_client.h b/libs/vr/libsensor/include/private/dvr/sensor_client.h
new file mode 100644
index 0000000..15a9b8f
--- /dev/null
+++ b/libs/vr/libsensor/include/private/dvr/sensor_client.h
@@ -0,0 +1,37 @@
+#ifndef ANDROID_DVR_SENSOR_CLIENT_H_
+#define ANDROID_DVR_SENSOR_CLIENT_H_
+
+#include <hardware/sensors.h>
+#include <pdx/client.h>
+#include <poll.h>
+
+namespace android {
+namespace dvr {
+
+// SensorClient is a remote interface to the sensor service in sensord.
+class SensorClient : public pdx::ClientBase<SensorClient> {
+ public:
+  ~SensorClient();
+
+  int StartSensor();
+  int StopSensor();
+  int Poll(sensors_event_t* events, int max_count);
+
+ private:
+  friend BASE;
+
+  // Set up a channel associated with the sensor of the indicated type.
+  // NOTE(segal): If our hardware ends up with multiple sensors of the same
+  // type, we'll have to change this.
+  explicit SensorClient(int sensor_type);
+
+  int sensor_type_;
+
+  SensorClient(const SensorClient&);
+  SensorClient& operator=(const SensorClient&);
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_SENSOR_CLIENT_H_
diff --git a/libs/vr/libsensor/include/private/dvr/sensor_constants.h b/libs/vr/libsensor/include/private/dvr/sensor_constants.h
new file mode 100644
index 0000000..8fa87b3
--- /dev/null
+++ b/libs/vr/libsensor/include/private/dvr/sensor_constants.h
@@ -0,0 +1,23 @@
+#ifndef ANDROID_DVR_SENSOR_CONSTANTS_H_
+#define ANDROID_DVR_SENSOR_CONSTANTS_H_
+
+namespace android {
+namespace dvr {
+
+// Number of elements in the async pose buffer.
+// Must be power of two.
+// Macro so that shader code can easily include this value.
+#define kPoseAsyncBufferTotalCount 8
+
+// Mask for accessing the current ring buffer array element:
+// index = vsync_count & kPoseAsyncBufferIndexMask
+constexpr uint32_t kPoseAsyncBufferIndexMask = kPoseAsyncBufferTotalCount - 1;
+
+// Number of pose frames including the current frame that are kept updated with
+// pose forecast data. The other poses are left their last known estimates.
+constexpr uint32_t kPoseAsyncBufferMinFutureCount = 4;
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_SENSOR_CONSTANTS_H_
diff --git a/libs/vr/libsensor/pose_client.cpp b/libs/vr/libsensor/pose_client.cpp
new file mode 100644
index 0000000..13e127d
--- /dev/null
+++ b/libs/vr/libsensor/pose_client.cpp
@@ -0,0 +1,341 @@
+#define LOG_TAG "PoseClient"
+#include <dvr/pose_client.h>
+
+#include <stdint.h>
+
+#include <base/logging.h>
+#include <cutils/log.h>
+#include <pdx/client.h>
+#include <pdx/default_transport/client_channel_factory.h>
+#include <pdx/file_handle.h>
+#include <private/dvr/buffer_hub_client.h>
+#include <private/dvr/pose-ipc.h>
+#include <private/dvr/pose_client_internal.h>
+#include <private/dvr/sensor_constants.h>
+
+using android::pdx::LocalHandle;
+using android::pdx::LocalChannelHandle;
+using android::pdx::Status;
+using android::pdx::Transaction;
+
+namespace android {
+namespace dvr {
+
+// PoseClient is a remote interface to the pose service in sensord.
+class PoseClient : public pdx::ClientBase<PoseClient> {
+ public:
+  ~PoseClient() override {}
+
+  // Casts C handle into an instance of this class.
+  static PoseClient* FromC(DvrPose* client) {
+    return reinterpret_cast<PoseClient*>(client);
+  }
+
+  // Polls the pose service for the current state and stores it in *state.
+  // Returns zero on success, a negative error code otherwise.
+  int Poll(DvrPoseState* state) {
+    Transaction trans{*this};
+    Status<int> status =
+        trans.Send<int>(DVR_POSE_POLL, nullptr, 0, state, sizeof(*state));
+    ALOGE_IF(!status, "Pose poll() failed because: %s\n",
+             status.GetErrorMessage().c_str());
+    return ReturnStatusOrError(status);
+  }
+
+  int GetPose(uint32_t vsync_count, DvrPoseAsync* out_pose) {
+    if (!mapped_pose_buffer_) {
+      int ret = GetRingBuffer(nullptr);
+      if (ret < 0)
+        return ret;
+    }
+    *out_pose =
+        mapped_pose_buffer_->ring[vsync_count & kPoseAsyncBufferIndexMask];
+    return 0;
+  }
+
+  uint32_t GetVsyncCount() {
+    if (!mapped_pose_buffer_) {
+      int ret = GetRingBuffer(nullptr);
+      if (ret < 0)
+        return 0;
+    }
+    return mapped_pose_buffer_->vsync_count;
+  }
+
+  int GetControllerPose(int32_t controller_id, uint32_t vsync_count,
+                        DvrPoseAsync* out_pose) {
+    if (controller_id < 0 ||
+        controller_id >= static_cast<int32_t>(arraysize(controllers_))) {
+      return -EINVAL;
+    }
+    if (!controllers_[controller_id].mapped_pose_buffer) {
+      int ret = GetControllerRingBuffer(controller_id);
+      if (ret < 0)
+        return ret;
+    }
+    *out_pose =
+        controllers_[controller_id]
+            .mapped_pose_buffer[vsync_count & kPoseAsyncBufferIndexMask];
+    return 0;
+  }
+
+  int LogController(bool enable) {
+    Transaction trans{*this};
+    Status<int> status = trans.Send<int>(DVR_POSE_LOG_CONTROLLER, &enable,
+                                         sizeof(enable), nullptr, 0);
+    ALOGE_IF(!status, "Pose LogController() failed because: %s",
+             status.GetErrorMessage().c_str());
+    return ReturnStatusOrError(status);
+  }
+
+  // Freezes the pose to the provided state. Future poll operations will return
+  // this state until a different state is frozen or SetMode() is called with a
+  // different mode.
+  // Returns zero on success, a negative error code otherwise.
+  int Freeze(const DvrPoseState& frozen_state) {
+    Transaction trans{*this};
+    Status<int> status = trans.Send<int>(DVR_POSE_FREEZE, &frozen_state,
+                                         sizeof(frozen_state), nullptr, 0);
+    ALOGE_IF(!status, "Pose Freeze() failed because: %s\n",
+             status.GetErrorMessage().c_str());
+    return ReturnStatusOrError(status);
+  }
+
+  // Sets the data mode for the pose service.
+  int SetMode(DvrPoseMode mode) {
+    Transaction trans{*this};
+    Status<int> status =
+        trans.Send<int>(DVR_POSE_SET_MODE, &mode, sizeof(mode), nullptr, 0);
+    ALOGE_IF(!status, "Pose SetPoseMode() failed because: %s",
+             status.GetErrorMessage().c_str());
+    return ReturnStatusOrError(status);
+  }
+
+  // Gets the data mode for the pose service.
+  int GetMode(DvrPoseMode* out_mode) {
+    int mode;
+    Transaction trans{*this};
+    Status<int> status =
+        trans.Send<int>(DVR_POSE_GET_MODE, nullptr, 0, &mode, sizeof(mode));
+    ALOGE_IF(!status, "Pose GetPoseMode() failed because: %s",
+             status.GetErrorMessage().c_str());
+    if (status)
+      *out_mode = DvrPoseMode(mode);
+    return ReturnStatusOrError(status);
+  }
+
+  int GetRingBuffer(DvrPoseRingBufferInfo* out_info) {
+    if (pose_buffer_.get()) {
+      if (out_info) {
+        GetPoseRingBufferInfo(out_info);
+      }
+      return 0;
+    }
+
+    Transaction trans{*this};
+    Status<LocalChannelHandle> status =
+        trans.Send<LocalChannelHandle>(DVR_POSE_GET_RING_BUFFER);
+    if (!status) {
+      ALOGE("Pose GetRingBuffer() failed because: %s",
+            status.GetErrorMessage().c_str());
+      return -status.error();
+    }
+
+    auto buffer = BufferConsumer::Import(status.take());
+    if (!buffer) {
+      ALOGE("Pose failed to import ring buffer");
+      return -EIO;
+    }
+    void* addr = nullptr;
+    int ret = buffer->GetBlobReadOnlyPointer(sizeof(DvrPoseRingBuffer), &addr);
+    if (ret < 0 || !addr) {
+      ALOGE("Pose failed to map ring buffer: ret:%d, addr:%p", ret, addr);
+      return -EIO;
+    }
+    pose_buffer_.swap(buffer);
+    mapped_pose_buffer_ = static_cast<const DvrPoseRingBuffer*>(addr);
+    LOG(INFO) << "Mapped pose data translation "
+              << mapped_pose_buffer_->ring[0].translation[0] << ','
+              << mapped_pose_buffer_->ring[0].translation[1] << ','
+              << mapped_pose_buffer_->ring[0].translation[2] << ", quat "
+              << mapped_pose_buffer_->ring[0].orientation[0] << ','
+              << mapped_pose_buffer_->ring[0].orientation[1] << ','
+              << mapped_pose_buffer_->ring[0].orientation[2] << ','
+              << mapped_pose_buffer_->ring[0].orientation[3];
+    if (out_info) {
+      GetPoseRingBufferInfo(out_info);
+    }
+    return 0;
+  }
+
+  int GetControllerRingBuffer(int32_t controller_id) {
+    if (controller_id < 0 ||
+        controller_id >= static_cast<int32_t>(arraysize(controllers_))) {
+      return -EINVAL;
+    }
+    ControllerClientState& client_state = controllers_[controller_id];
+    if (client_state.pose_buffer.get()) {
+      return 0;
+    }
+
+    Transaction trans{*this};
+    Status<LocalChannelHandle> status = trans.Send<LocalChannelHandle>(
+        DVR_POSE_GET_CONTROLLER_RING_BUFFER, &controller_id,
+        sizeof(controller_id), nullptr, 0);
+    if (!status) {
+      ALOGE("Pose GetControllerRingBuffer() failed because: %s",
+            status.GetErrorMessage().c_str());
+      return -status.error();
+    }
+
+    auto buffer = BufferConsumer::Import(status.take());
+    if (!buffer) {
+      ALOGE("Pose failed to import ring buffer");
+      return -EIO;
+    }
+    constexpr size_t size = kPoseAsyncBufferTotalCount * sizeof(DvrPoseAsync);
+    void* addr = nullptr;
+    int ret = buffer->GetBlobReadOnlyPointer(size, &addr);
+    if (ret < 0 || !addr) {
+      ALOGE("Pose failed to map ring buffer: ret:%d, addr:%p", ret, addr);
+      return -EIO;
+    }
+    client_state.pose_buffer.swap(buffer);
+    client_state.mapped_pose_buffer = static_cast<const DvrPoseAsync*>(addr);
+    LOG(INFO) << "Mapped controller " << controller_id
+              << " pose data translation "
+              << client_state.mapped_pose_buffer[0].translation[0] << ','
+              << client_state.mapped_pose_buffer[0].translation[1] << ','
+              << client_state.mapped_pose_buffer[0].translation[2] << ", quat "
+              << client_state.mapped_pose_buffer[0].orientation[0] << ','
+              << client_state.mapped_pose_buffer[0].orientation[1] << ','
+              << client_state.mapped_pose_buffer[0].orientation[2] << ','
+              << client_state.mapped_pose_buffer[0].orientation[3];
+    return 0;
+  }
+
+  int NotifyVsync(uint32_t vsync_count, int64_t display_timestamp,
+                  int64_t display_period_ns,
+                  int64_t right_eye_photon_offset_ns) {
+    const struct iovec data[] = {
+        {.iov_base = &vsync_count, .iov_len = sizeof(vsync_count)},
+        {.iov_base = &display_timestamp, .iov_len = sizeof(display_timestamp)},
+        {.iov_base = &display_period_ns, .iov_len = sizeof(display_period_ns)},
+        {.iov_base = &right_eye_photon_offset_ns,
+         .iov_len = sizeof(right_eye_photon_offset_ns)},
+    };
+    Transaction trans{*this};
+    Status<int> status =
+        trans.SendVector<int>(DVR_POSE_NOTIFY_VSYNC, data, nullptr);
+    ALOGE_IF(!status, "Pose NotifyVsync() failed because: %s\n",
+             status.GetErrorMessage().c_str());
+    return ReturnStatusOrError(status);
+  }
+
+  int GetRingBufferFd(LocalHandle* fd) {
+    int ret = GetRingBuffer(nullptr);
+    if (ret < 0)
+      return ret;
+    *fd = pose_buffer_->GetBlobFd();
+    return 0;
+  }
+
+ private:
+  friend BASE;
+
+  // Set up a channel to the pose service.
+  PoseClient()
+      : BASE(pdx::default_transport::ClientChannelFactory::Create(
+            DVR_POSE_SERVICE_CLIENT)) {
+    // TODO(eieio): Cache the pose and make timeout 0 so that the API doesn't
+    // block while waiting for the pose service to come back up.
+    EnableAutoReconnect(kInfiniteTimeout);
+  }
+
+  PoseClient(const PoseClient&) = delete;
+  PoseClient& operator=(const PoseClient&) = delete;
+
+  void GetPoseRingBufferInfo(DvrPoseRingBufferInfo* out_info) const {
+    out_info->min_future_count = kPoseAsyncBufferMinFutureCount;
+    out_info->total_count = kPoseAsyncBufferTotalCount;
+    out_info->buffer = mapped_pose_buffer_->ring;
+  }
+
+  std::unique_ptr<BufferConsumer> pose_buffer_;
+  const DvrPoseRingBuffer* mapped_pose_buffer_ = nullptr;
+
+  struct ControllerClientState {
+    std::unique_ptr<BufferConsumer> pose_buffer;
+    const DvrPoseAsync* mapped_pose_buffer = nullptr;
+  };
+  ControllerClientState controllers_[2];
+};
+
+}  // namespace dvr
+}  // namespace android
+
+using android::dvr::PoseClient;
+
+struct DvrPose {};
+
+extern "C" {
+
+DvrPose* dvrPoseCreate() {
+  PoseClient* client = PoseClient::Create().release();
+  return reinterpret_cast<DvrPose*>(client);
+}
+
+void dvrPoseDestroy(DvrPose* client) { delete PoseClient::FromC(client); }
+
+int dvrPoseGet(DvrPose* client, uint32_t vsync_count, DvrPoseAsync* out_pose) {
+  return PoseClient::FromC(client)->GetPose(vsync_count, out_pose);
+}
+
+uint32_t dvrPoseGetVsyncCount(DvrPose* client) {
+  return PoseClient::FromC(client)->GetVsyncCount();
+}
+
+int dvrPoseGetController(DvrPose* client, int32_t controller_id,
+                         uint32_t vsync_count, DvrPoseAsync* out_pose) {
+  return PoseClient::FromC(client)->GetControllerPose(controller_id,
+                                                      vsync_count, out_pose);
+}
+
+int dvrPoseLogController(DvrPose* client, bool enable) {
+  return PoseClient::FromC(client)->LogController(enable);
+}
+
+int dvrPosePoll(DvrPose* client, DvrPoseState* state) {
+  return PoseClient::FromC(client)->Poll(state);
+}
+
+int dvrPoseFreeze(DvrPose* client, const DvrPoseState* frozen_state) {
+  return PoseClient::FromC(client)->Freeze(*frozen_state);
+}
+
+int dvrPoseSetMode(DvrPose* client, DvrPoseMode mode) {
+  return PoseClient::FromC(client)->SetMode(mode);
+}
+
+int dvrPoseGetMode(DvrPose* client, DvrPoseMode* mode) {
+  return PoseClient::FromC(client)->GetMode(mode);
+}
+
+int dvrPoseGetRingBuffer(DvrPose* client, DvrPoseRingBufferInfo* out_info) {
+  return PoseClient::FromC(client)->GetRingBuffer(out_info);
+}
+
+int privateDvrPoseNotifyVsync(DvrPose* client, uint32_t vsync_count,
+                              int64_t display_timestamp,
+                              int64_t display_period_ns,
+                              int64_t right_eye_photon_offset_ns) {
+  return PoseClient::FromC(client)->NotifyVsync(vsync_count, display_timestamp,
+                                                display_period_ns,
+                                                right_eye_photon_offset_ns);
+}
+
+int privateDvrPoseGetRingBufferFd(DvrPose* client, LocalHandle* fd) {
+  return PoseClient::FromC(client)->GetRingBufferFd(fd);
+}
+
+}  // extern "C"
diff --git a/libs/vr/libsensor/sensor_client.cpp b/libs/vr/libsensor/sensor_client.cpp
new file mode 100644
index 0000000..1c240f5
--- /dev/null
+++ b/libs/vr/libsensor/sensor_client.cpp
@@ -0,0 +1,79 @@
+#define LOG_TAG "SensorClient"
+#include <private/dvr/sensor_client.h>
+
+#include <cutils/log.h>
+#include <poll.h>
+
+#include <pdx/default_transport/client_channel_factory.h>
+#include <private/dvr/sensor-ipc.h>
+
+using android::pdx::Transaction;
+
+namespace android {
+namespace dvr {
+
+SensorClient::SensorClient(int sensor_type)
+    : BASE(pdx::default_transport::ClientChannelFactory::Create(
+          DVR_SENSOR_SERVICE_CLIENT)),
+      sensor_type_(sensor_type) {}
+
+SensorClient::~SensorClient() {}
+
+int SensorClient::StartSensor() {
+  Transaction trans{*this};
+  auto status = trans.Send<int>(DVR_SENSOR_START, &sensor_type_,
+                                sizeof(sensor_type_), nullptr, 0);
+  ALOGE_IF(!status, "startSensor() failed because: %s\n",
+           status.GetErrorMessage().c_str());
+  return ReturnStatusOrError(status);
+}
+
+int SensorClient::StopSensor() {
+  Transaction trans{*this};
+  auto status = trans.Send<int>(DVR_SENSOR_STOP);
+  ALOGE_IF(!status, "stopSensor() failed because: %s\n",
+           status.GetErrorMessage().c_str());
+  return ReturnStatusOrError(status);
+}
+
+int SensorClient::Poll(sensors_event_t* events, int max_events) {
+  int num_events = 0;
+  struct iovec rvec[] = {
+      {.iov_base = &num_events, .iov_len = sizeof(int)},
+      {.iov_base = events, .iov_len = max_events * sizeof(sensors_event_t)},
+  };
+  Transaction trans{*this};
+  auto status = trans.SendVector<int>(DVR_SENSOR_POLL, nullptr, rvec);
+  ALOGE_IF(!status, "Sensor poll() failed because: %s\n",
+           status.GetErrorMessage().c_str());
+  return !status ? -status.error() : num_events;
+}
+
+}  // namespace dvr
+}  // namespace android
+
+// Entrypoints to simplify using the library when programmatically dynamicly
+// loading it.
+// Allows us to call this library without linking it, as, for instance,
+// when compiling GVR in Google3.
+// NOTE(segal): It's kind of a hack.
+
+extern "C" uint64_t dvrStartSensor(int type) {
+  android::dvr::SensorClient* service =
+      android::dvr::SensorClient::Create(type).release();
+  service->StartSensor();
+  return (uint64_t)service;
+}
+
+extern "C" void dvrStopSensor(uint64_t service) {
+  android::dvr::SensorClient* iss =
+      reinterpret_cast<android::dvr::SensorClient*>(service);
+  iss->StopSensor();
+  delete iss;
+}
+
+extern "C" int dvrPollSensor(uint64_t service, int max_count,
+                             sensors_event_t* events) {
+  return reinterpret_cast<android::dvr::SensorClient*>(service)->Poll(
+      events, max_count);
+}
diff --git a/libs/vr/libsensor/tests/sensor_app_tests.cpp b/libs/vr/libsensor/tests/sensor_app_tests.cpp
new file mode 100644
index 0000000..8cd6f79
--- /dev/null
+++ b/libs/vr/libsensor/tests/sensor_app_tests.cpp
@@ -0,0 +1,131 @@
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <math.h>
+
+#include <base/logging.h>
+#include <dvr/graphics.h>
+#include <dvr/pose_client.h>
+#include <gtest/gtest.h>
+#include <private/dvr/types.h>
+
+using android::dvr::vec4;
+
+namespace {
+
+vec4 ToVec4(float32x4_t rhs) { return vec4(rhs[0], rhs[1], rhs[2], rhs[3]); }
+
+}
+
+DvrGraphicsContext* CreateContext() {
+  DvrGraphicsContext* context = nullptr;
+  int display_width = 0, display_height = 0;
+  int surface_width = 0, surface_height = 0;
+  float inter_lens_meters = 0.0f;
+  float left_fov[4] = {0.0f};
+  float right_fov[4] = {0.0f};
+  int disable_warp = 0;
+  DvrSurfaceParameter surface_params[] = {
+      DVR_SURFACE_PARAMETER_IN(DISABLE_DISTORTION, disable_warp),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_WIDTH, &display_width),
+      DVR_SURFACE_PARAMETER_OUT(DISPLAY_HEIGHT, &display_height),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_WIDTH, &surface_width),
+      DVR_SURFACE_PARAMETER_OUT(SURFACE_HEIGHT, &surface_height),
+      DVR_SURFACE_PARAMETER_OUT(INTER_LENS_METERS, &inter_lens_meters),
+      DVR_SURFACE_PARAMETER_OUT(LEFT_FOV_LRBT, left_fov),
+      DVR_SURFACE_PARAMETER_OUT(RIGHT_FOV_LRBT, right_fov),
+      DVR_SURFACE_PARAMETER_LIST_END,
+  };
+  dvrGraphicsContextCreate(surface_params, &context);
+  return context;
+}
+
+TEST(SensorAppTests, GetPose) {
+  DvrGraphicsContext* context = CreateContext();
+  ASSERT_NE(nullptr, context);
+  DvrPose* client = dvrPoseCreate();
+  ASSERT_NE(nullptr, client);
+
+  DvrPoseAsync last_pose;
+  uint32_t last_vsync_count = 0;
+  for (int i = 0; i < 10; ++i) {
+    DvrFrameSchedule schedule;
+    dvrGraphicsWaitNextFrame(context, 0, &schedule);
+    DvrPoseAsync pose;
+    int ret = dvrPoseGet(client, schedule.vsync_count, &pose);
+    ASSERT_EQ(0, ret);
+
+    // Check for unit-length quaternion to verify valid pose.
+    vec4 quaternion = ToVec4(pose.orientation);
+    float length = quaternion.norm();
+    EXPECT_GT(0.001, fabs(1.0f - length));
+
+    // Check for different data each frame, but skip first few to allow
+    // startup anomalies.
+    if (i > 0) {
+      if (last_vsync_count == schedule.vsync_count)
+        LOG(ERROR) << "vsync did not increment: " << schedule.vsync_count;
+      if (pose.timestamp_ns == last_pose.timestamp_ns)
+        LOG(ERROR) << "timestamp did not change: " << pose.timestamp_ns;
+      // TODO(jbates) figure out why the bots are not passing this check.
+      // EXPECT_NE(last_vsync_count, schedule.vsync_count);
+      // EXPECT_NE(pose.timestamp_ns, last_pose.timestamp_ns);
+    }
+    last_pose = pose;
+    last_vsync_count = schedule.vsync_count;
+    dvrBeginRenderFrame(context);
+    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+    dvrPresent(context);
+  }
+
+  dvrPoseDestroy(client);
+  dvrGraphicsContextDestroy(context);
+}
+
+TEST(SensorAppTests, PoseRingBuffer) {
+  DvrGraphicsContext* context = CreateContext();
+  ASSERT_NE(nullptr, context);
+  DvrPose* client = dvrPoseCreate();
+  ASSERT_NE(nullptr, client);
+
+  DvrPoseRingBufferInfo info;
+  int ret = dvrPoseGetRingBuffer(client, &info);
+  ASSERT_EQ(0, ret);
+  ASSERT_NE(nullptr, info.buffer);
+  EXPECT_LE(2u, info.min_future_count);
+  EXPECT_LE(8u, info.total_count);
+
+  DvrPoseAsync last_pose;
+  uint32_t last_vsync_count = 0;
+  for (int i = 0; i < 10; ++i) {
+    DvrFrameSchedule schedule;
+    dvrGraphicsWaitNextFrame(context, 0, &schedule);
+    DvrPoseAsync pose;
+    ret = dvrPoseGet(client, schedule.vsync_count, &pose);
+    ASSERT_EQ(0, ret);
+
+    // Check for unit-length quaternion to verify valid pose.
+    vec4 quaternion = ToVec4(pose.orientation);
+    float length = quaternion.norm();
+    EXPECT_GT(0.001, fabs(1.0f - length));
+
+    // Check for different data each frame, but skip first few to allow
+    // startup anomalies.
+    if (i > 0) {
+      if (last_vsync_count == schedule.vsync_count)
+        LOG(ERROR) << "vsync did not increment: " << schedule.vsync_count;
+      if (pose.timestamp_ns == last_pose.timestamp_ns)
+        LOG(ERROR) << "timestamp did not change: " << pose.timestamp_ns;
+      // TODO(jbates) figure out why the bots are not passing this check.
+      // EXPECT_NE(last_vsync_count, schedule.vsync_count);
+      // EXPECT_NE(pose.timestamp_ns, last_pose.timestamp_ns);
+    }
+    last_pose = pose;
+    last_vsync_count = schedule.vsync_count;
+    dvrBeginRenderFrame(context);
+    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+    dvrPresent(context);
+  }
+
+  dvrPoseDestroy(client);
+  dvrGraphicsContextDestroy(context);
+}