Merge "Use unique_ptr for proto parser to handle destruction." into pi-dev
diff --git a/headers/media_plugin/media/openmax/OMX_VideoExt.h b/headers/media_plugin/media/openmax/OMX_VideoExt.h
index 4b90765..bbf157b 100644
--- a/headers/media_plugin/media/openmax/OMX_VideoExt.h
+++ b/headers/media_plugin/media/openmax/OMX_VideoExt.h
@@ -427,14 +427,15 @@
  * use cases, corresponding to index OMX_IndexParamVideoAndroidImageGrid.
  *
  * OMX_VIDEO_CodingImageHEIC encoders must handle this param type. When this param is set
- * on the component with bEnabled set to true, nGrid* indicates the desired grid config
- * by the client. The component can use this as a heuristic, but is free to choose any
- * suitable grid configs, and the client shall always get the actual from the component
- * after the param is set. Encoder will receive each input image in full, and shall encode
- * it into tiles in row-major, top-row first, left-to-right order, and send each encoded
- * tile in a separate output buffer. All output buffers for the same input buffer shall
- * carry the same timestamp as the input buffer. If the input buffer is marked EOS,
- * the EOS should only appear on the last output buffer for that input buffer.
+ * on the component with bEnabled set to true, nTileWidth, nTileHeight, nGridRows,
+ * nGridCols indicates the desired grid config by the client. The component can use this
+ * as a heuristic, and is free to choose any suitable grid configs. The client shall
+ * always get the actual from the component after the param is set. Encoder will receive
+ * each input image in full, and shall encode it into tiles in row-major, top-row first,
+ * left-to-right order, and send each encoded tile in a separate output buffer. All output
+ * buffers for the same input buffer shall carry the same timestamp as the input buffer.
+ * If the input buffer is marked EOS, the EOS should only appear on the last output buffer
+ * for that input buffer.
  *
  * OMX_VIDEO_CodingHEVC encoders might also receive this param when it's used for image
  * encoding, although in this case the param only serves as a hint. The encoder will
@@ -446,10 +447,10 @@
  *  nSize                      : Size of the structure in bytes
  *  nVersion                   : OMX specification version information
  *  nPortIndex                 : Port that this structure applies to (output port for encoders)
- *  bEnabled                   : Whether grid is enabled. If true, nGrid* specifies the grid
- *                               config; otherwise nGrid* shall be ignored.
- *  nGridWidth                 : Width of each tile.
- *  nGridHeight                : Height of each tile.
+ *  bEnabled                   : Whether grid is enabled. If true, the other parameters
+ *                               specifies the grid config; otherwise they shall be ignored.
+ *  nTileWidth                 : Width of each tile.
+ *  nTileHeight                : Height of each tile.
  *  nGridRows                  : Number of rows in the grid.
  *  nGridCols                  : Number of cols in the grid.
  */
@@ -458,8 +459,8 @@
     OMX_VERSIONTYPE nVersion;
     OMX_U32 nPortIndex;
     OMX_BOOL bEnabled;
-    OMX_U32 nGridWidth;
-    OMX_U32 nGridHeight;
+    OMX_U32 nTileWidth;
+    OMX_U32 nTileHeight;
     OMX_U32 nGridRows;
     OMX_U32 nGridCols;
 } OMX_VIDEO_PARAM_ANDROID_IMAGEGRIDTYPE;
diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp
index e1cc5da..9adac26 100644
--- a/libs/binder/ActivityManager.cpp
+++ b/libs/binder/ActivityManager.cpp
@@ -80,4 +80,20 @@
     }
 }
 
+status_t ActivityManager::linkToDeath(const sp<IBinder::DeathRecipient>& recipient) {
+    sp<IActivityManager> service = getService();
+    if (service != NULL) {
+        return IInterface::asBinder(service)->linkToDeath(recipient);
+    }
+    return INVALID_OPERATION;
+}
+
+status_t ActivityManager::unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient) {
+    sp<IActivityManager> service = getService();
+    if (service != NULL) {
+        return IInterface::asBinder(service)->unlinkToDeath(recipient);
+    }
+    return INVALID_OPERATION;
+}
+
 }; // namespace android
diff --git a/libs/binder/include/binder/ActivityManager.h b/libs/binder/include/binder/ActivityManager.h
index 408c428..397382f 100644
--- a/libs/binder/include/binder/ActivityManager.h
+++ b/libs/binder/include/binder/ActivityManager.h
@@ -51,6 +51,9 @@
                              const String16& callingPackage);
     void unregisterUidObserver(const sp<IUidObserver>& observer);
 
+    status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
+    status_t unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient);
+
 private:
     Mutex mLock;
     sp<IActivityManager> mService;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 61ed976..38f0eb7 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -612,7 +612,25 @@
         uint32_t windowType,
         uint32_t ownerUid)
 {
+    sp<SurfaceControl> s;
+    createSurfaceChecked(name, w, h, format, &s, flags, parent, windowType, ownerUid);
+    return s;
+}
+
+status_t SurfaceComposerClient::createSurfaceChecked(
+        const String8& name,
+        uint32_t w,
+        uint32_t h,
+        PixelFormat format,
+        sp<SurfaceControl>* outSurface,
+        uint32_t flags,
+        SurfaceControl* parent,
+        uint32_t windowType,
+        uint32_t ownerUid)
+{
     sp<SurfaceControl> sur;
+    status_t err = NO_ERROR;
+
     if (mStatus == NO_ERROR) {
         sp<IBinder> handle;
         sp<IBinder> parentHandle;
@@ -621,14 +639,14 @@
         if (parent != nullptr) {
             parentHandle = parent->getHandle();
         }
-        status_t err = mClient->createSurface(name, w, h, format, flags, parentHandle,
+        err = mClient->createSurface(name, w, h, format, flags, parentHandle,
                 windowType, ownerUid, &handle, &gbp);
         ALOGE_IF(err, "SurfaceComposerClient::createSurface error %s", strerror(-err));
         if (err == NO_ERROR) {
-            sur = new SurfaceControl(this, handle, gbp, true /* owned */);
+            *outSurface = new SurfaceControl(this, handle, gbp, true /* owned */);
         }
     }
-    return sur;
+    return err;
 }
 
 status_t SurfaceComposerClient::destroySurface(const sp<IBinder>& sid) {
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 162fe6e..5ce20ad 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -115,6 +115,18 @@
             uint32_t ownerUid = 0 // UID of the task
     );
 
+    status_t createSurfaceChecked(
+            const String8& name,// name of the surface
+            uint32_t w,         // width in pixel
+            uint32_t h,         // height in pixel
+            PixelFormat format, // pixel-format desired
+            sp<SurfaceControl>* outSurface,
+            uint32_t flags = 0, // usage flags
+            SurfaceControl* parent = nullptr, // parent
+            uint32_t windowType = 0, // from WindowManager.java (STATUS_BAR, INPUT_METHOD, etc.)
+            uint32_t ownerUid = 0 // UID of the task
+    );
+
     //! Create a virtual display
     static sp<IBinder> createDisplay(const String8& displayName, bool secure);
 
diff --git a/libs/vr/libbufferhub/buffer_hub-test.cpp b/libs/vr/libbufferhub/buffer_hub-test.cpp
index b7a6099..3ce5c9f 100644
--- a/libs/vr/libbufferhub/buffer_hub-test.cpp
+++ b/libs/vr/libbufferhub/buffer_hub-test.cpp
@@ -25,7 +25,9 @@
 using android::dvr::BufferHubDefs::IsBufferAcquired;
 using android::dvr::BufferHubDefs::IsBufferReleased;
 using android::dvr::BufferProducer;
+using android::pdx::LocalChannelHandle;
 using android::pdx::LocalHandle;
+using android::pdx::Status;
 
 const int kWidth = 640;
 const int kHeight = 480;
@@ -717,3 +719,59 @@
   // Producer should be able to gain no matter what.
   EXPECT_EQ(0, p->GainAsync(&meta, &fence));
 }
+
+TEST_F(LibBufferHubTest, TestDetachBufferFromProducer) {
+  std::unique_ptr<BufferProducer> p = BufferProducer::Create(
+      kWidth, kHeight, kFormat, kUsage, sizeof(uint64_t));
+  std::unique_ptr<BufferConsumer> c =
+      BufferConsumer::Import(p->CreateConsumer());
+  ASSERT_TRUE(p.get() != nullptr);
+  ASSERT_TRUE(c.get() != nullptr);
+
+  DvrNativeBufferMetadata metadata;
+  LocalHandle invalid_fence;
+
+  // Detach in posted state should fail.
+  EXPECT_EQ(0, p->PostAsync(&metadata, invalid_fence));
+  EXPECT_GT(RETRY_EINTR(c->Poll(kPollTimeoutMs)), 0);
+  auto s1 = p->Detach();
+  EXPECT_FALSE(s1);
+
+  // Detach in acquired state should fail.
+  EXPECT_EQ(0, c->AcquireAsync(&metadata, &invalid_fence));
+  s1 = p->Detach();
+  EXPECT_FALSE(s1);
+
+  // Detach in released state should fail.
+  EXPECT_EQ(0, c->ReleaseAsync(&metadata, invalid_fence));
+  EXPECT_GT(RETRY_EINTR(p->Poll(kPollTimeoutMs)), 0);
+  s1 = p->Detach();
+  EXPECT_FALSE(s1);
+
+  // Detach in gained state should succeed.
+  EXPECT_EQ(0, p->GainAsync(&metadata, &invalid_fence));
+  s1 = p->Detach();
+  EXPECT_TRUE(s1);
+
+  LocalChannelHandle detached_buffer = s1.take();
+  EXPECT_TRUE(detached_buffer.valid());
+
+  // Both producer and consumer should have hangup.
+  EXPECT_GT(RETRY_EINTR(p->Poll(kPollTimeoutMs)), 0);
+  auto s2 = p->GetEventMask(POLLHUP);
+  EXPECT_TRUE(s2);
+  EXPECT_EQ(s2.get(), POLLHUP);
+
+  EXPECT_GT(RETRY_EINTR(c->Poll(kPollTimeoutMs)), 0);
+  s2 = p->GetEventMask(POLLHUP);
+  EXPECT_TRUE(s2);
+  EXPECT_EQ(s2.get(), POLLHUP);
+
+  auto s3 = p->CreateConsumer();
+  EXPECT_FALSE(s3);
+  EXPECT_EQ(s3.error(), EOPNOTSUPP);
+
+  s3 = c->CreateConsumer();
+  EXPECT_FALSE(s3);
+  EXPECT_EQ(s3.error(), EOPNOTSUPP);
+}
diff --git a/libs/vr/libbufferhub/buffer_hub_client.cpp b/libs/vr/libbufferhub/buffer_hub_client.cpp
index 6db09a9..13971eb 100644
--- a/libs/vr/libbufferhub/buffer_hub_client.cpp
+++ b/libs/vr/libbufferhub/buffer_hub_client.cpp
@@ -608,5 +608,23 @@
                        : LocalChannelHandle{nullptr, -status.error()});
 }
 
+Status<LocalChannelHandle> BufferProducer::Detach() {
+  uint64_t buffer_state = buffer_state_->load();
+  if (!BufferHubDefs::IsBufferGained(buffer_state)) {
+    // Can only detach a BufferProducer when it's in gained state.
+    ALOGW("BufferProducer::Detach: The buffer (id=%d, state=0x%" PRIx64
+          ") is not in gained state.",
+          id(), buffer_state);
+    return {};
+  }
+
+  Status<LocalChannelHandle> status =
+      InvokeRemoteMethod<BufferHubRPC::ProducerBufferDetach>();
+  ALOGE_IF(!status,
+           "BufferProducer::Detach: Failed to detach buffer (id=%d): %s.", id(),
+           status.GetErrorMessage().c_str());
+  return status;
+}
+
 }  // namespace dvr
 }  // namespace android
diff --git a/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
index c791250..32448a1 100644
--- a/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
+++ b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
@@ -217,6 +217,14 @@
   // succeeded, or a negative errno code if local error check fails.
   int GainAsync(DvrNativeBufferMetadata* out_meta, LocalHandle* out_fence);
 
+  // Detaches a ProducerBuffer from an existing producer/consumer set. Can only
+  // be called when a producer buffer has exclusive access to the buffer (i.e.
+  // in the gain'ed state). On the successful return of the IPC call, a new
+  // LocalChannelHandle representing a detached buffer will be returned and all
+  // existing producer and consumer channels will be closed. Further IPCs
+  // towards those channels will return error.
+  Status<LocalChannelHandle> Detach();
+
  private:
   friend BASE;
 
diff --git a/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h b/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
index c70fffc..fabefd5 100644
--- a/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
+++ b/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
@@ -373,6 +373,10 @@
     kOpConsumerAcquire,
     kOpConsumerRelease,
     kOpConsumerSetIgnore,
+    kOpProducerBufferDetach,
+    kOpConsumerBufferDetach,
+    kOpCreateDetachedBuffer,
+    kOpDetachedBufferPromote,
     kOpCreateProducerQueue,
     kOpCreateConsumerQueue,
     kOpGetQueueInfo,
@@ -400,6 +404,28 @@
   PDX_REMOTE_METHOD(ConsumerRelease, kOpConsumerRelease,
                     void(LocalFence release_fence));
   PDX_REMOTE_METHOD(ConsumerSetIgnore, kOpConsumerSetIgnore, void(bool ignore));
+  PDX_REMOTE_METHOD(ProducerBufferDetach, kOpProducerBufferDetach,
+                    LocalChannelHandle(Void));
+
+  // Detaches a ConsumerBuffer from an existing producer/consumer set. Can only
+  // be called when the consumer is the only consumer and it has exclusive
+  // access to the buffer (i.e. in the acquired'ed state). On the successful
+  // return of the IPC call, a new DetachedBufferChannel handle will be returned
+  // and all existing producer and consumer channels will be closed. Further
+  // IPCs towards those channels will return error.
+  PDX_REMOTE_METHOD(ConsumerBufferDetach, kOpConsumerBufferDetach,
+                    LocalChannelHandle(Void));
+
+  // Creates a standalone DetachedBuffer not associated with any
+  // producer/consumer set.
+  PDX_REMOTE_METHOD(CreateDetachedBuffer, kOpCreateDetachedBuffer,
+                    LocalChannelHandle(Void));
+
+  // Promotes a DetachedBuffer to become a ProducerBuffer. Once promoted the
+  // DetachedBuffer channel will be closed automatically on successful IPC
+  // return. Further IPCs towards this channel will return error.
+  PDX_REMOTE_METHOD(DetachedBufferPromote, kOpDetachedBufferPromote,
+                    LocalChannelHandle(Void));
 
   // Buffer Queue Methods.
   PDX_REMOTE_METHOD(CreateProducerQueue, kOpCreateProducerQueue,
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index b7bf964..47f4e46 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -144,31 +144,31 @@
 
 void Device::onHotplug(hwc2_display_t displayId, Connection connection) {
     if (connection == Connection::Connected) {
-        auto display = getDisplayById(displayId);
-        if (display) {
-            if (display->isConnected()) {
-                ALOGW("Attempt to hotplug connect display %" PRIu64
-                        " , which is already connected.", displayId);
-            } else {
-                display->setConnected(true);
-            }
-        } else {
-            DisplayType displayType;
-            auto intError = mComposer->getDisplayType(displayId,
-                    reinterpret_cast<Hwc2::IComposerClient::DisplayType *>(
-                            &displayType));
-            auto error = static_cast<Error>(intError);
-            if (error != Error::None) {
-                ALOGE("getDisplayType(%" PRIu64 ") failed: %s (%d). "
-                        "Aborting hotplug attempt.",
-                        displayId, to_string(error).c_str(), intError);
-                return;
-            }
-
-            auto newDisplay = std::make_unique<Display>(
-                    *mComposer.get(), mCapabilities, displayId, displayType);
-            mDisplays.emplace(displayId, std::move(newDisplay));
+        // If we get a hotplug connected event for a display we already have,
+        // destroy the display and recreate it. This will force us to requery
+        // the display params and recreate all layers on that display.
+        auto oldDisplay = getDisplayById(displayId);
+        if (oldDisplay != nullptr && oldDisplay->isConnected()) {
+            ALOGI("Hotplug connecting an already connected display."
+                    " Clearing old display state.");
         }
+        mDisplays.erase(displayId);
+
+        DisplayType displayType;
+        auto intError = mComposer->getDisplayType(displayId,
+                reinterpret_cast<Hwc2::IComposerClient::DisplayType *>(
+                        &displayType));
+        auto error = static_cast<Error>(intError);
+        if (error != Error::None) {
+            ALOGE("getDisplayType(%" PRIu64 ") failed: %s (%d). "
+                    "Aborting hotplug attempt.",
+                    displayId, to_string(error).c_str(), intError);
+            return;
+        }
+
+        auto newDisplay = std::make_unique<Display>(
+                *mComposer.get(), mCapabilities, displayId, displayType);
+        mDisplays.emplace(displayId, std::move(newDisplay));
     } else if (connection == Connection::Disconnected) {
         // The display will later be destroyed by a call to
         // destroyDisplay(). For now we just mark it disconnected.
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.cpp b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
index 7a43ea9..6a34981 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.cpp
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
@@ -305,15 +305,24 @@
 
         if (needs.hasToneMapping()) {
             fs << R"__SHADER__(
-                float ToneMapChannel(const float color) {
+                float CalculateY(const vec3 color) {
+                    // BT2020 standard uses the unadjusted KR = 0.2627,
+                    // KB = 0.0593 luminance interpretation for RGB conversion.
+                    return color.r * 0.262700 + color.g * 0.677998 +
+                            color.b * 0.059302;
+                }
+                vec3 ToneMap(const vec3 color) {
                     const float maxLumi = 10000.0;
                     const float maxMasteringLumi = 1000.0;
                     const float maxContentLumi = 1000.0;
                     const float maxInLumi = min(maxMasteringLumi, maxContentLumi);
                     const float maxOutLumi = 500.0;
 
+                    // Calculate Y value in XYZ color space.
+                    float colorY = CalculateY(color);
+
                     // convert to nits first
-                    float nits = color * maxLumi;
+                    float nits = colorY * maxLumi;
 
                     // clamp to max input luminance
                     nits = clamp(nits, 0.0, maxInLumi);
@@ -360,12 +369,8 @@
                     }
 
                     // convert back to [0.0, 1.0]
-                    return nits / maxOutLumi;
-                }
-
-                vec3 ToneMap(const vec3 color) {
-                    return vec3(ToneMapChannel(color.r), ToneMapChannel(color.g),
-                                ToneMapChannel(color.b));
+                    float targetY = nits / maxOutLumi;
+                    return color * (targetY / max(1e-6, colorY));
                 }
             )__SHADER__";
         } else {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 1d7a98f..a6e0a9e 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2120,16 +2120,16 @@
         getBE().mHwc->onHotplug(event.display, displayType, event.connection);
 
         if (event.connection == HWC2::Connection::Connected) {
-            ALOGV("Creating built in display %d", displayType);
-            ALOGW_IF(mBuiltinDisplays[displayType],
-                    "Overwriting display token for display type %d", displayType);
-            mBuiltinDisplays[displayType] = new BBinder();
-            // All non-virtual displays are currently considered secure.
-            DisplayDeviceState info(displayType, true);
-            info.displayName = displayType == DisplayDevice::DISPLAY_PRIMARY ?
-                    "Built-in Screen" : "External Screen";
-            mCurrentState.displays.add(mBuiltinDisplays[displayType], info);
-            mInterceptor.saveDisplayCreation(info);
+            if (!mBuiltinDisplays[displayType].get()) {
+                ALOGV("Creating built in display %d", displayType);
+                mBuiltinDisplays[displayType] = new BBinder();
+                // All non-virtual displays are currently considered secure.
+                DisplayDeviceState info(displayType, true);
+                info.displayName = displayType == DisplayDevice::DISPLAY_PRIMARY ?
+                        "Built-in Screen" : "External Screen";
+                mCurrentState.displays.add(mBuiltinDisplays[displayType], info);
+                mInterceptor.saveDisplayCreation(info);
+            }
         } else {
             ALOGV("Removing built in display %d", displayType);
 
diff --git a/services/vr/bufferhubd/Android.bp b/services/vr/bufferhubd/Android.bp
index 6009a95..6122846 100644
--- a/services/vr/bufferhubd/Android.bp
+++ b/services/vr/bufferhubd/Android.bp
@@ -17,6 +17,7 @@
     "bufferhubd.cpp",
     "consumer_channel.cpp",
     "producer_channel.cpp",
+    "detached_buffer_channel.cpp",
     "consumer_queue_channel.cpp",
     "producer_queue_channel.cpp",
 ]
diff --git a/services/vr/bufferhubd/buffer_hub.cpp b/services/vr/bufferhubd/buffer_hub.cpp
index 1eb4ef9..e4e19c7 100644
--- a/services/vr/bufferhubd/buffer_hub.cpp
+++ b/services/vr/bufferhubd/buffer_hub.cpp
@@ -100,6 +100,41 @@
       stream << std::setw(8) << info.index;
       stream << std::endl;
     }
+
+    if (channel->channel_type() == BufferHubChannel::kDetachedBufferType) {
+      BufferHubChannel::BufferInfo info = channel->GetBufferInfo();
+
+      stream << std::right;
+      stream << std::setw(6) << info.id;
+      stream << " ";
+      stream << std::setw(9) << "N/A";
+      stream << " ";
+      if (info.format == HAL_PIXEL_FORMAT_BLOB) {
+        std::string size = std::to_string(info.width) + " B";
+        stream << std::setw(14) << size;
+      } else {
+        std::string dimensions = std::to_string(info.width) + "x" +
+                                 std::to_string(info.height) + "x" +
+                                 std::to_string(info.layer_count);
+        stream << std::setw(14) << dimensions;
+      }
+      stream << " ";
+      stream << std::setw(6) << info.format;
+      stream << " ";
+      stream << "0x" << std::hex << std::setfill('0');
+      stream << std::setw(8) << info.usage;
+      stream << std::dec << std::setfill(' ');
+      stream << " ";
+      stream << std::setw(9) << "N/A";
+      stream << " ";
+      stream << std::hex << std::setfill(' ');
+      stream << std::setw(18) << "Detached";
+      stream << " ";
+      stream << std::setw(18) << "N/A";
+      stream << " ";
+      stream << std::setw(10) << "N/A";
+      stream << std::endl;
+    }
   }
 
   stream << std::endl;
@@ -215,6 +250,15 @@
           *this, &BufferHubService::OnCreateProducerQueue, message);
       return {};
 
+    case BufferHubRPC::ProducerBufferDetach::Opcode:
+      // In addition to the message handler in the ProducerChannel's
+      // HandleMessage method, we also need to invalid the producer channel (and
+      // all associated consumer channels). Note that this has to be done after
+      // HandleMessage returns to make sure the IPC request has went back to the
+      // client first.
+      SetChannel(channel->channel_id(), nullptr);
+      return {};
+
     default:
       return DefaultHandleMessage(message);
   }
diff --git a/services/vr/bufferhubd/buffer_hub.h b/services/vr/bufferhubd/buffer_hub.h
index b487b0b..e04967a 100644
--- a/services/vr/bufferhubd/buffer_hub.h
+++ b/services/vr/bufferhubd/buffer_hub.h
@@ -23,6 +23,7 @@
   enum ChannelType {
     kProducerType,
     kConsumerType,
+    kDetachedBufferType,
     kProducerQueueType,
     kConsumerQueueType,
   };
diff --git a/services/vr/bufferhubd/detached_buffer_channel.cpp b/services/vr/bufferhubd/detached_buffer_channel.cpp
new file mode 100644
index 0000000..edb2111
--- /dev/null
+++ b/services/vr/bufferhubd/detached_buffer_channel.cpp
@@ -0,0 +1,57 @@
+#include "detached_buffer_channel.h"
+
+using android::pdx::ErrorStatus;
+using android::pdx::Message;
+using android::pdx::RemoteChannelHandle;
+using android::pdx::Status;
+using android::pdx::rpc::DispatchRemoteMethod;
+
+namespace android {
+namespace dvr {
+
+DetachedBufferChannel::DetachedBufferChannel(BufferHubService* service,
+                                             int buffer_id, int channel_id,
+                                             IonBuffer buffer,
+                                             IonBuffer metadata_buffer,
+                                             size_t user_metadata_size)
+    : BufferHubChannel(service, buffer_id, channel_id, kDetachedBufferType),
+      buffer_(std::move(buffer)),
+      metadata_buffer_(std::move(metadata_buffer)),
+      user_metadata_size_(user_metadata_size) {}
+
+BufferHubChannel::BufferInfo DetachedBufferChannel::GetBufferInfo() const {
+  return BufferInfo(buffer_id(), /*consumer_count=*/0, buffer_.width(),
+                    buffer_.height(), buffer_.layer_count(), buffer_.format(),
+                    buffer_.usage(), /*pending_count=*/0, /*state=*/0,
+                    /*signaled_mask=*/0, /*index=*/0);
+}
+
+void DetachedBufferChannel::HandleImpulse(Message& /*message*/) {
+  ATRACE_NAME("DetachedBufferChannel::HandleImpulse");
+}
+
+bool DetachedBufferChannel::HandleMessage(Message& message) {
+  ATRACE_NAME("DetachedBufferChannel::HandleMessage");
+  switch (message.GetOp()) {
+    case BufferHubRPC::DetachedBufferPromote::Opcode:
+      DispatchRemoteMethod<BufferHubRPC::DetachedBufferPromote>(
+          *this, &DetachedBufferChannel::OnPromote, message);
+      return true;
+
+    default:
+      return false;
+  }
+}
+
+Status<RemoteChannelHandle> DetachedBufferChannel::OnPromote(
+    Message& /*message*/) {
+  ATRACE_NAME("DetachedBufferChannel::OnPromote");
+  ALOGD_IF(TRACE, "DetachedBufferChannel::OnPromote: buffer_id=%d",
+           buffer_id());
+
+  // TODO(b/69982239): Implement the logic to promote a detached buffer.
+  return ErrorStatus(ENOSYS);
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/services/vr/bufferhubd/detached_buffer_channel.h b/services/vr/bufferhubd/detached_buffer_channel.h
new file mode 100644
index 0000000..7ce4aed
--- /dev/null
+++ b/services/vr/bufferhubd/detached_buffer_channel.h
@@ -0,0 +1,43 @@
+#ifndef ANDROID_DVR_BUFFERHUBD_DETACHED_BUFFER_CHANNEL_H_
+#define ANDROID_DVR_BUFFERHUBD_DETACHED_BUFFER_CHANNEL_H_
+
+#include "buffer_hub.h"
+
+// #include <pdx/channel_handle.h>
+// #include <pdx/file_handle.h>
+// #include <pdx/rpc/buffer_wrapper.h>
+// #include <private/dvr/ion_buffer.h>
+
+namespace android {
+namespace dvr {
+
+class DetachedBufferChannel : public BufferHubChannel {
+ public:
+  // Creates a detached buffer.
+  DetachedBufferChannel(BufferHubService* service, int buffer_id,
+                        int channel_id, IonBuffer buffer,
+                        IonBuffer metadata_buffer, size_t user_metadata_size);
+
+  size_t user_metadata_size() const { return user_metadata_size_; }
+
+  // Captures buffer info for use by BufferHubService::DumpState().
+  BufferInfo GetBufferInfo() const override;
+
+  bool HandleMessage(pdx::Message& message) override;
+  void HandleImpulse(pdx::Message& message) override;
+
+ private:
+  pdx::Status<pdx::RemoteChannelHandle> OnPromote(pdx::Message& message);
+
+  // Gralloc buffer handles.
+  IonBuffer buffer_;
+  IonBuffer metadata_buffer_;
+
+  // Size of user requested metadata.
+  const size_t user_metadata_size_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_BUFFERHUBD_DETACHED_BUFFER_CHANNEL_H_
diff --git a/services/vr/bufferhubd/producer_channel.cpp b/services/vr/bufferhubd/producer_channel.cpp
index 8160193..e141b91 100644
--- a/services/vr/bufferhubd/producer_channel.cpp
+++ b/services/vr/bufferhubd/producer_channel.cpp
@@ -13,6 +13,7 @@
 
 #include <private/dvr/bufferhub_rpc.h>
 #include "consumer_channel.h"
+#include "detached_buffer_channel.h"
 
 using android::pdx::BorrowedHandle;
 using android::pdx::ErrorStatus;
@@ -131,8 +132,11 @@
            "ProducerChannel::~ProducerChannel: channel_id=%d buffer_id=%d "
            "state=%" PRIx64 ".",
            channel_id(), buffer_id(), buffer_state_->load());
-  for (auto consumer : consumer_channels_)
+  for (auto consumer : consumer_channels_) {
     consumer->OnProducerClosed();
+    service()->SetChannel(consumer->channel_id(), nullptr);
+  }
+  Hangup();
 }
 
 BufferHubChannel::BufferInfo ProducerChannel::GetBufferInfo() const {
@@ -183,6 +187,11 @@
           *this, &ProducerChannel::OnProducerGain, message);
       return true;
 
+    case BufferHubRPC::ProducerBufferDetach::Opcode:
+      DispatchRemoteMethod<BufferHubRPC::ProducerBufferDetach>(
+          *this, &ProducerChannel::OnProducerDetach, message);
+      return true;
+
     default:
       return false;
   }
@@ -337,6 +346,55 @@
   return {std::move(returned_fence_)};
 }
 
+Status<RemoteChannelHandle> ProducerChannel::OnProducerDetach(
+    Message& message) {
+  ATRACE_NAME("ProducerChannel::OnProducerDetach");
+  ALOGD_IF(TRACE, "ProducerChannel::OnProducerDetach: buffer_id=%d",
+           buffer_id());
+
+  uint64_t buffer_state = buffer_state_->load();
+  if (!BufferHubDefs::IsBufferGained(buffer_state)) {
+    // Can only detach a BufferProducer when it's in gained state.
+    ALOGW(
+        "ProducerChannel::OnProducerDetach: The buffer (id=%d, state=0x%" PRIx64
+        ") is not in gained state.",
+        buffer_id(), buffer_state);
+    return {};
+  }
+
+  int channel_id;
+  auto status = message.PushChannel(0, nullptr, &channel_id);
+  if (!status) {
+    ALOGE(
+        "ProducerChannel::OnProducerDetach: Failed to push detached buffer "
+        "channel: %s",
+        status.GetErrorMessage().c_str());
+    return ErrorStatus(ENOMEM);
+  }
+
+  // Make sure we unlock the buffer.
+  if (int ret = metadata_buffer_.Unlock()) {
+    ALOGE("ProducerChannel::OnProducerDetach: Failed to unlock metadata.");
+    return ErrorStatus(-ret);
+  };
+
+  auto channel = std::make_shared<DetachedBufferChannel>(
+      service(), buffer_id(), channel_id, std::move(buffer_),
+      std::move(metadata_buffer_), user_metadata_size_);
+
+  const auto channel_status = service()->SetChannel(channel_id, channel);
+  if (!channel_status) {
+    // Technically, this should never fail, as we just pushed the channel. Note
+    // that LOG_FATAL will be stripped out in non-debug build.
+    LOG_FATAL(
+        "ProducerChannel::OnProducerDetach: Failed to set new detached buffer "
+        "channel: %s.",
+        channel_status.GetErrorMessage().c_str());
+  }
+
+  return status;
+}
+
 Status<LocalFence> ProducerChannel::OnConsumerAcquire(Message& /*message*/) {
   ATRACE_NAME("ProducerChannel::OnConsumerAcquire");
   ALOGD_IF(TRACE, "ProducerChannel::OnConsumerAcquire: buffer_id=%d",
diff --git a/services/vr/bufferhubd/producer_channel.h b/services/vr/bufferhubd/producer_channel.h
index c5dbf0e..de9ff31 100644
--- a/services/vr/bufferhubd/producer_channel.h
+++ b/services/vr/bufferhubd/producer_channel.h
@@ -99,6 +99,7 @@
   pdx::Status<BufferDescription<BorrowedHandle>> OnGetBuffer(Message& message);
   pdx::Status<void> OnProducerPost(Message& message, LocalFence acquire_fence);
   pdx::Status<LocalFence> OnProducerGain(Message& message);
+  pdx::Status<RemoteChannelHandle> OnProducerDetach(Message& message);
 
   ProducerChannel(const ProducerChannel&) = delete;
   void operator=(const ProducerChannel&) = delete;
diff --git a/services/vr/hardware_composer/impl/vr_hwc.cpp b/services/vr/hardware_composer/impl/vr_hwc.cpp
index 313c022..11ffc3f 100644
--- a/services/vr/hardware_composer/impl/vr_hwc.cpp
+++ b/services/vr/hardware_composer/impl/vr_hwc.cpp
@@ -868,7 +868,7 @@
 
   Error status = Error::NONE;
   sp<VrComposerClient> client;
-  if (client_ == nullptr) {
+  if (!client_.promote().get()) {
     client = new VrComposerClient(*this);
   } else {
     ALOGE("Already have a client");