Migrate buffers during surface change

Migrate graphic buffers during surface change in order to avoid
BufferQueue handling complexity later on.

Test: Manually using Chrome and google photo app
Bug: 132302078
Bug: 130862880
Change-Id: Ifb348b5d6a8f5a89dcc10a9f0be075057a5d3a6d
diff --git a/media/codec2/hidl/1.0/utils/ClientBlockHelper.cpp b/media/codec2/hidl/1.0/utils/ClientBlockHelper.cpp
index 1786c69..50790bc 100644
--- a/media/codec2/hidl/1.0/utils/ClientBlockHelper.cpp
+++ b/media/codec2/hidl/1.0/utils/ClientBlockHelper.cpp
@@ -82,21 +82,14 @@
 
 template <typename BlockProcessor>
 void forEachBlock(const std::list<std::unique_ptr<C2Work>>& workList,
-                  BlockProcessor process,
-                  bool processInput, bool processOutput) {
+                  BlockProcessor process) {
     for (const std::unique_ptr<C2Work>& work : workList) {
         if (!work) {
             continue;
         }
-        if (processInput) {
-            forEachBlock(work->input, process);
-        }
-        if (processOutput) {
-            for (const std::unique_ptr<C2Worklet>& worklet : work->worklets) {
-                if (worklet) {
-                    forEachBlock(worklet->output,
-                                 process);
-                }
+        for (const std::unique_ptr<C2Worklet>& worklet : work->worklets) {
+            if (worklet) {
+                forEachBlock(worklet->output, process);
             }
         }
     }
@@ -109,8 +102,6 @@
             new B2HGraphicBufferProducer(igbp);
 }
 
-} // unnamed namespace
-
 status_t attachToBufferQueue(const C2ConstGraphicBlock& block,
                              const sp<IGraphicBufferProducer>& igbp,
                              uint32_t generation,
@@ -154,73 +145,221 @@
             _C2BlockFactory::GetGraphicBlockPoolData(block),
             generation, bqId, bqSlot);
 }
+} // unnamed namespace
 
-bool holdBufferQueueBlock(const C2ConstGraphicBlock& block,
-                            const sp<IGraphicBufferProducer>& igbp,
-                            uint64_t bqId,
-                            uint32_t generation) {
-    std::shared_ptr<_C2BlockPoolData> data =
-            _C2BlockFactory::GetGraphicBlockPoolData(block);
-    if (!data) {
-        return false;
-    }
+class OutputBufferQueue::Impl {
+    std::mutex mMutex;
+    sp<IGraphicBufferProducer> mIgbp;
+    uint32_t mGeneration;
+    uint64_t mBqId;
+    std::shared_ptr<int> mOwner;
+    // To migrate existing buffers
+    sp<GraphicBuffer> mBuffers[BufferQueueDefs::NUM_BUFFER_SLOTS]; // find a better way
+    std::weak_ptr<_C2BlockPoolData>
+                    mPoolDatas[BufferQueueDefs::NUM_BUFFER_SLOTS];
 
-    uint32_t oldGeneration;
-    uint64_t oldId;
-    int32_t oldSlot;
-    // If the block is not bufferqueue-based, do nothing.
-    if (!_C2BlockFactory::GetBufferQueueData(
-            data, &oldGeneration, &oldId, &oldSlot) ||
-            (oldId == 0)) {
-        return false;
-    }
+public:
+    Impl(): mGeneration(0), mBqId(0) {}
 
-    // If the block's bqId is the same as the desired bqId, just hold.
-    if ((oldId == bqId) && (oldGeneration == generation)) {
-        LOG(VERBOSE) << "holdBufferQueueBlock -- import without attaching:"
-                     << " bqId " << oldId
-                     << ", bqSlot " << oldSlot
-                     << ", generation " << generation
-                     << ".";
-        _C2BlockFactory::HoldBlockFromBufferQueue(data, getHgbp(igbp));
+    bool configure(const sp<IGraphicBufferProducer>& igbp,
+                   uint32_t generation,
+                   uint64_t bqId) {
+        size_t tryNum = 0;
+        size_t success = 0;
+        sp<GraphicBuffer> buffers[BufferQueueDefs::NUM_BUFFER_SLOTS];
+        std::weak_ptr<_C2BlockPoolData>
+                poolDatas[BufferQueueDefs::NUM_BUFFER_SLOTS];
+        {
+            std::scoped_lock<std::mutex> l(mMutex);
+            if (generation == mGeneration) {
+                return false;
+            }
+            mIgbp = igbp;
+            mGeneration = generation;
+            mBqId = bqId;
+            mOwner = std::make_shared<int>(0);
+            for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; ++i) {
+                if (mBqId == 0 || !mBuffers[i]) {
+                    continue;
+                }
+                std::shared_ptr<_C2BlockPoolData> data = mPoolDatas[i].lock();
+                if (!data ||
+                    !_C2BlockFactory::BeginAttachBlockToBufferQueue(data)) {
+                    continue;
+                }
+                ++tryNum;
+                int bqSlot;
+                mBuffers[i]->setGenerationNumber(generation);
+                status_t result = igbp->attachBuffer(&bqSlot, mBuffers[i]);
+                if (result != OK) {
+                    continue;
+                }
+                bool attach =
+                        _C2BlockFactory::EndAttachBlockToBufferQueue(
+                                data, mOwner, getHgbp(mIgbp),
+                                generation, bqId, bqSlot);
+                if (!attach) {
+                    igbp->cancelBuffer(bqSlot, Fence::NO_FENCE);
+                    continue;
+                }
+                buffers[bqSlot] = mBuffers[i];
+                poolDatas[bqSlot] = data;
+                ++success;
+            }
+            for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; ++i) {
+                mBuffers[i] = buffers[i];
+                mPoolDatas[i] = poolDatas[i];
+            }
+        }
+        ALOGD("remote graphic buffer migration %zu/%zu", success, tryNum);
         return true;
     }
 
-    // Otherwise, attach to the given igbp, which must not be null.
-    if (!igbp) {
+    bool registerBuffer(const C2ConstGraphicBlock& block) {
+        std::shared_ptr<_C2BlockPoolData> data =
+                _C2BlockFactory::GetGraphicBlockPoolData(block);
+        if (!data) {
+            return false;
+        }
+        std::scoped_lock<std::mutex> l(mMutex);
+
+        if (!mIgbp) {
+            return false;
+        }
+
+        uint32_t oldGeneration;
+        uint64_t oldId;
+        int32_t oldSlot;
+        // If the block is not bufferqueue-based, do nothing.
+        if (!_C2BlockFactory::GetBufferQueueData(
+                data, &oldGeneration, &oldId, &oldSlot) || (oldId == 0)) {
+            return false;
+        }
+        // If the block's bqId is the same as the desired bqId, just hold.
+        if ((oldId == mBqId) && (oldGeneration == mGeneration)) {
+            LOG(VERBOSE) << "holdBufferQueueBlock -- import without attaching:"
+                         << " bqId " << oldId
+                         << ", bqSlot " << oldSlot
+                         << ", generation " << mGeneration
+                         << ".";
+            _C2BlockFactory::HoldBlockFromBufferQueue(data, mOwner, getHgbp(mIgbp));
+            mPoolDatas[oldSlot] = data;
+            mBuffers[oldSlot] = createGraphicBuffer(block);
+            mBuffers[oldSlot]->setGenerationNumber(mGeneration);
+            return true;
+        }
+        int32_t d = (int32_t) mGeneration - (int32_t) oldGeneration;
+        LOG(WARNING) << "receiving stale buffer: generation "
+                     << mGeneration << " , diff " << d  << " : slot "
+                     << oldSlot;
         return false;
     }
 
-    int32_t bqSlot;
-    status_t result = attachToBufferQueue(block, igbp, generation, &bqSlot);
+    status_t outputBuffer(
+            const C2ConstGraphicBlock& block,
+            const BnGraphicBufferProducer::QueueBufferInput& input,
+            BnGraphicBufferProducer::QueueBufferOutput* output) {
+        uint32_t generation;
+        uint64_t bqId;
+        int32_t bqSlot;
+        bool display = displayBufferQueueBlock(block);
+        if (!getBufferQueueAssignment(block, &generation, &bqId, &bqSlot) ||
+            bqId == 0) {
+            // Block not from bufferqueue -- it must be attached before queuing.
 
-    if (result != OK) {
-        LOG(ERROR) << "holdBufferQueueBlock -- fail to attach:"
-                   << " target bqId " << bqId
-                   << ", generation " << generation
-                   << ".";
-        return false;
+            mMutex.lock();
+            sp<IGraphicBufferProducer> outputIgbp = mIgbp;
+            uint32_t outputGeneration = mGeneration;
+            mMutex.unlock();
+
+            status_t status = attachToBufferQueue(
+                    block, outputIgbp, outputGeneration, &bqSlot);
+            if (status != OK) {
+                LOG(WARNING) << "outputBuffer -- attaching failed.";
+                return INVALID_OPERATION;
+            }
+
+            status = outputIgbp->queueBuffer(static_cast<int>(bqSlot),
+                                         input, output);
+            if (status != OK) {
+                LOG(ERROR) << "outputBuffer -- queueBuffer() failed "
+                           "on non-bufferqueue-based block. "
+                           "Error = " << status << ".";
+                return status;
+            }
+            return OK;
+        }
+
+        mMutex.lock();
+        sp<IGraphicBufferProducer> outputIgbp = mIgbp;
+        uint32_t outputGeneration = mGeneration;
+        uint64_t outputBqId = mBqId;
+        mMutex.unlock();
+
+        if (!outputIgbp) {
+            LOG(VERBOSE) << "outputBuffer -- output surface is null.";
+            return NO_INIT;
+        }
+
+        if (!display) {
+            LOG(WARNING) << "outputBuffer -- cannot display "
+                         "bufferqueue-based block to the bufferqueue.";
+            return UNKNOWN_ERROR;
+        }
+        if (bqId != outputBqId || generation != outputGeneration) {
+            int32_t diff = (int32_t) outputGeneration - (int32_t) generation;
+            LOG(WARNING) << "outputBuffer -- buffers from old generation to "
+                         << outputGeneration << " , diff: " << diff
+                         << " , slot: " << bqSlot;
+            return DEAD_OBJECT;
+        }
+
+        status_t status = outputIgbp->queueBuffer(static_cast<int>(bqSlot),
+                                              input, output);
+        if (status != OK) {
+            LOG(ERROR) << "outputBuffer -- queueBuffer() failed "
+                       "on bufferqueue-based block. "
+                       "Error = " << status << ".";
+            return status;
+        }
+        return OK;
     }
 
-    LOG(VERBOSE) << "holdBufferQueueBlock -- attached:"
-                 << " bqId " << bqId
-                 << ", bqSlot " << bqSlot
-                 << ", generation " << generation
-                 << ".";
-    _C2BlockFactory::AssignBlockToBufferQueue(
-            data, getHgbp(igbp), generation, bqId, bqSlot, true);
-    return true;
+    Impl *getPtr() {
+        return this;
+    }
+
+    ~Impl() {}
+};
+
+OutputBufferQueue::OutputBufferQueue(): mImpl(new Impl()) {}
+
+OutputBufferQueue::~OutputBufferQueue() {}
+
+bool OutputBufferQueue::configure(const sp<IGraphicBufferProducer>& igbp,
+                                  uint32_t generation,
+                                  uint64_t bqId) {
+    return mImpl && mImpl->configure(igbp, generation, bqId);
 }
 
-void holdBufferQueueBlocks(const std::list<std::unique_ptr<C2Work>>& workList,
-                           const sp<IGraphicBufferProducer>& igbp,
-                           uint64_t bqId,
-                           uint32_t generation,
-                           bool forInput) {
+status_t OutputBufferQueue::outputBuffer(
+    const C2ConstGraphicBlock& block,
+    const BnGraphicBufferProducer::QueueBufferInput& input,
+    BnGraphicBufferProducer::QueueBufferOutput* output) {
+    if (mImpl) {
+        return mImpl->outputBuffer(block, input, output);
+    }
+    return DEAD_OBJECT;
+}
+
+void OutputBufferQueue::holdBufferQueueBlocks(
+        const std::list<std::unique_ptr<C2Work>>& workList) {
+    if (!mImpl) {
+        return;
+    }
     forEachBlock(workList,
-                 std::bind(holdBufferQueueBlock,
-                           std::placeholders::_1, igbp, bqId, generation),
-                 forInput, !forInput);
+                 std::bind(&OutputBufferQueue::Impl::registerBuffer,
+                           mImpl->getPtr(), std::placeholders::_1));
 }
 
 }  // namespace utils
diff --git a/media/codec2/hidl/1.0/utils/Component.cpp b/media/codec2/hidl/1.0/utils/Component.cpp
index 5897dce..a9f20a4 100644
--- a/media/codec2/hidl/1.0/utils/Component.cpp
+++ b/media/codec2/hidl/1.0/utils/Component.cpp
@@ -107,19 +107,22 @@
             WorkBundle workBundle;
 
             sp<Component> strongComponent = mComponent.promote();
+            beginTransferBufferQueueBlocks(c2workItems, true);
             if (!objcpy(&workBundle, c2workItems, strongComponent ?
                     &strongComponent->mBufferPoolSender : nullptr)) {
                 LOG(ERROR) << "Component::Listener::onWorkDone_nb -- "
                            << "received corrupted work items.";
+                endTransferBufferQueueBlocks(c2workItems, false, true);
                 return;
             }
             Return<void> transStatus = listener->onWorkDone(workBundle);
             if (!transStatus.isOk()) {
                 LOG(ERROR) << "Component::Listener::onWorkDone_nb -- "
                            << "transaction failed.";
+                endTransferBufferQueueBlocks(c2workItems, false, true);
                 return;
             }
-            yieldBufferQueueBlocks(c2workItems, true);
+            endTransferBufferQueueBlocks(c2workItems, true, true);
         }
     }
 
@@ -254,13 +257,14 @@
 
     WorkBundle flushedWorkBundle;
     Status res = static_cast<Status>(c2res);
+    beginTransferBufferQueueBlocks(c2flushedWorks, true);
     if (c2res == C2_OK) {
         if (!objcpy(&flushedWorkBundle, c2flushedWorks, &mBufferPoolSender)) {
             res = Status::CORRUPTED;
         }
     }
     _hidl_cb(res, flushedWorkBundle);
-    yieldBufferQueueBlocks(c2flushedWorks, true);
+    endTransferBufferQueueBlocks(c2flushedWorks, true, true);
     return Void();
 }
 
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ClientBlockHelper.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ClientBlockHelper.h
index be429ac..0a2298c 100644
--- a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ClientBlockHelper.h
+++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ClientBlockHelper.h
@@ -28,62 +28,42 @@
 namespace V1_0 {
 namespace utils {
 
-
 // BufferQueue-Based Block Operations
 // ==================================
 
-// Create a GraphicBuffer object from a graphic block and attach it to an
-// IGraphicBufferProducer.
-status_t attachToBufferQueue(const C2ConstGraphicBlock& block,
-                             const sp<IGraphicBufferProducer>& igbp,
-                             uint32_t generation,
-                             int32_t* bqSlot);
+// Manage BufferQueue and graphic blocks for both component and codec.
+// Manage graphic blocks ownership consistently during surface change.
+struct OutputBufferQueue {
 
-// Return false if block does not come from a bufferqueue-based blockpool.
-// Otherwise, extract generation, bqId and bqSlot and return true.
-bool getBufferQueueAssignment(const C2ConstGraphicBlock& block,
-                              uint32_t* generation,
-                              uint64_t* bqId,
-                              int32_t* bqSlot);
+    OutputBufferQueue();
 
-// Assign the given block to a bufferqueue so that when the block is destroyed,
-// cancelBuffer() will be called.
-//
-// If the block does not come from a bufferqueue-based blockpool, this function
-// returns false.
-//
-// If the block already has a bufferqueue assignment that matches the given one,
-// the function returns true.
-//
-// If the block already has a bufferqueue assignment that does not match the
-// given one, the block will be reassigned to the given bufferqueue. This
-// will call attachBuffer() on the given igbp. The function then returns true on
-// success or false on any failure during the operation.
-//
-// Note: This function should be called after detachBuffer() or dequeueBuffer()
-// is called manually.
-bool holdBufferQueueBlock(const C2ConstGraphicBlock& block,
-                          const sp<IGraphicBufferProducer>& igbp,
-                          uint64_t bqId,
-                          uint32_t generation);
+    ~OutputBufferQueue();
 
-// Call holdBufferQueueBlock() on input or output blocks in the given workList.
-// Since the bufferqueue assignment for input and output buffers can be
-// different, this function takes forInput to determine whether the given
-// bufferqueue is for input buffers or output buffers. (The default value of
-// forInput is false.)
-//
-// In the (rare) case that both input and output buffers are bufferqueue-based,
-// this function must be called twice, once for the input buffers and once for
-// the output buffers.
-//
-// Note: This function should be called after WorkBundle has been received from
-// another process.
-void holdBufferQueueBlocks(const std::list<std::unique_ptr<C2Work>>& workList,
-                           const sp<IGraphicBufferProducer>& igbp,
-                           uint64_t bqId,
-                           uint32_t generation,
-                           bool forInput = false);
+    // Configure a new surface to render graphic blocks.
+    // Graphic blocks from older surface will be migrated to new surface.
+    bool configure(const sp<IGraphicBufferProducer>& igbp,
+                   uint32_t generation,
+                   uint64_t bqId);
+
+    // Render a graphic block to current surface.
+    status_t outputBuffer(
+            const C2ConstGraphicBlock& block,
+            const BnGraphicBufferProducer::QueueBufferInput& input,
+            BnGraphicBufferProducer::QueueBufferOutput* output);
+
+    // Call holdBufferQueueBlock() on output blocks in the given workList.
+    // The OutputBufferQueue will take the ownership of output blocks.
+    //
+    // Note: This function should be called after WorkBundle has been received
+    // from another process.
+    void holdBufferQueueBlocks(
+            const std::list<std::unique_ptr<C2Work>>& workList);
+
+private:
+
+    class Impl;
+    std::unique_ptr<Impl> mImpl;
+};
 
 }  // namespace utils
 }  // namespace V1_0
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/types.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/types.h
index 0ddfec5..a9928b3 100644
--- a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/types.h
+++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/types.h
@@ -299,26 +299,46 @@
 // BufferQueue-Based Block Operations
 // ==================================
 
-// Disassociate the given block with its designated bufferqueue so that
-// cancelBuffer() will not be called when the block is destroyed. If the block
-// does not have a designated bufferqueue, the function returns false.
-// Otherwise, it returns true.
+// Call before transferring block to other processes.
 //
-// Note: This function should be called after attachBuffer() or queueBuffer() is
-// called manually.
-bool yieldBufferQueueBlock(const C2ConstGraphicBlock& block);
+// The given block is ready to transfer to other processes. This will guarantee
+// the given block data is not mutated by bufferqueue migration.
+bool beginTransferBufferQueueBlock(const C2ConstGraphicBlock& block);
 
-// Call yieldBufferQueueBlock() on blocks in the given workList. processInput
-// determines whether input blocks are yielded. processOutput works similarly on
-// output blocks. (The default value of processInput is false while the default
-// value of processOutput is true. This implies that in most cases, only output
-// buffers contain bufferqueue-based blocks.)
+// Call beginTransferBufferQueueBlock() on blocks in the given workList.
+// processInput determines whether input blocks are yielded. processOutput
+// works similarly on output blocks. (The default value of processInput is
+// false while the default value of processOutput is true. This implies that in
+// most cases, only output buffers contain bufferqueue-based blocks.)
+void beginTransferBufferQueueBlocks(
+        const std::list<std::unique_ptr<C2Work>>& workList,
+        bool processInput = false,
+        bool processOutput = true);
+
+// Call after transferring block is finished and make sure that
+// beginTransferBufferQueueBlock() is called before.
 //
-// Note: This function should be called after WorkBundle has been successfully
-// sent over the Treble boundary to another process.
-void yieldBufferQueueBlocks(const std::list<std::unique_ptr<C2Work>>& workList,
-                            bool processInput = false,
-                            bool processOutput = true);
+// The transfer of given block is finished. If transfer is successful the given
+// block is not owned by process anymore. Since transfer is finished the given
+// block data is OK to mutate by bufferqueue migration after this call.
+bool endTransferBufferQueueBlock(const C2ConstGraphicBlock& block,
+                                 bool transfer);
+
+// Call endTransferBufferQueueBlock() on blocks in the given workList.
+// processInput determines whether input blocks are yielded. processOutput
+// works similarly on output blocks. (The default value of processInput is
+// false while the default value of processOutput is true. This implies that in
+// most cases, only output buffers contain bufferqueue-based blocks.)
+void endTransferBufferQueueBlocks(
+        const std::list<std::unique_ptr<C2Work>>& workList,
+        bool transfer,
+        bool processInput = false,
+        bool processOutput = true);
+
+// The given block is ready to be rendered. the given block is not owned by
+// process anymore. If migration is in progress, this returns false in order
+// not to render.
+bool displayBufferQueueBlock(const C2ConstGraphicBlock& block);
 
 }  // namespace utils
 }  // namespace V1_0
diff --git a/media/codec2/hidl/1.0/utils/types.cpp b/media/codec2/hidl/1.0/utils/types.cpp
index 3fd2f92..07dbf67 100644
--- a/media/codec2/hidl/1.0/utils/types.cpp
+++ b/media/codec2/hidl/1.0/utils/types.cpp
@@ -737,7 +737,15 @@
                     bufferPoolSender, baseBlocks, baseBlockIndices);
         }
     case _C2BlockPoolData::TYPE_BUFFERQUEUE:
-        // Do the same thing as a NATIVE block.
+        uint32_t gen;
+        uint64_t bqId;
+        int32_t bqSlot;
+        // Update handle if migration happened.
+        if (_C2BlockFactory::GetBufferQueueData(
+                blockPoolData, &gen, &bqId, &bqSlot)) {
+            android::MigrateNativeCodec2GrallocHandle(
+                    const_cast<native_handle_t*>(handle), gen, bqId, bqSlot);
+        }
         return _addBaseBlock(
                 index, handle,
                 baseBlocks, baseBlockIndices);
@@ -1772,20 +1780,53 @@
 
 } // unnamed namespace
 
-bool yieldBufferQueueBlock(const C2ConstGraphicBlock& block) {
+bool beginTransferBufferQueueBlock(const C2ConstGraphicBlock& block) {
     std::shared_ptr<_C2BlockPoolData> data =
             _C2BlockFactory::GetGraphicBlockPoolData(block);
     if (data && _C2BlockFactory::GetBufferQueueData(data)) {
-        _C2BlockFactory::YieldBlockToBufferQueue(data);
+        _C2BlockFactory::BeginTransferBlockToClient(data);
         return true;
     }
     return false;
 }
 
-void yieldBufferQueueBlocks(
+void beginTransferBufferQueueBlocks(
         const std::list<std::unique_ptr<C2Work>>& workList,
         bool processInput, bool processOutput) {
-    forEachBlock(workList, yieldBufferQueueBlock, processInput, processOutput);
+    forEachBlock(workList, beginTransferBufferQueueBlock,
+                 processInput, processOutput);
+}
+
+bool endTransferBufferQueueBlock(
+        const C2ConstGraphicBlock& block,
+        bool transfer) {
+    std::shared_ptr<_C2BlockPoolData> data =
+            _C2BlockFactory::GetGraphicBlockPoolData(block);
+    if (data && _C2BlockFactory::GetBufferQueueData(data)) {
+        _C2BlockFactory::EndTransferBlockToClient(data, transfer);
+        return true;
+    }
+    return false;
+}
+
+void endTransferBufferQueueBlocks(
+        const std::list<std::unique_ptr<C2Work>>& workList,
+        bool transfer,
+        bool processInput, bool processOutput) {
+    forEachBlock(workList,
+                 std::bind(endTransferBufferQueueBlock,
+                           std::placeholders::_1, transfer),
+                 processInput, processOutput);
+}
+
+bool displayBufferQueueBlock(const C2ConstGraphicBlock& block) {
+    std::shared_ptr<_C2BlockPoolData> data =
+            _C2BlockFactory::GetGraphicBlockPoolData(block);
+    if (data && _C2BlockFactory::GetBufferQueueData(data)) {
+        _C2BlockFactory::DisplayBlockToBufferQueue(data);
+        return true;
+    }
+    return false;
 }
 
 }  // namespace utils
diff --git a/media/codec2/hidl/client/client.cpp b/media/codec2/hidl/client/client.cpp
index 6aca4a3..2b417a6 100644
--- a/media/codec2/hidl/client/client.cpp
+++ b/media/codec2/hidl/client/client.cpp
@@ -1080,15 +1080,7 @@
 void Codec2Client::Component::handleOnWorkDone(
         const std::list<std::unique_ptr<C2Work>> &workItems) {
     // Output bufferqueue-based blocks' lifetime management
-    mOutputBufferQueueMutex.lock();
-    sp<IGraphicBufferProducer> igbp = mOutputIgbp;
-    uint64_t bqId = mOutputBqId;
-    uint32_t generation = mOutputGeneration;
-    mOutputBufferQueueMutex.unlock();
-
-    if (igbp) {
-        holdBufferQueueBlocks(workItems, igbp, bqId, generation);
-    }
+    mOutputBufferQueue.holdBufferQueueBlocks(workItems);
 }
 
 c2_status_t Codec2Client::Component::queue(
@@ -1151,15 +1143,7 @@
     }
 
     // Output bufferqueue-based blocks' lifetime management
-    mOutputBufferQueueMutex.lock();
-    sp<IGraphicBufferProducer> igbp = mOutputIgbp;
-    uint64_t bqId = mOutputBqId;
-    uint32_t generation = mOutputGeneration;
-    mOutputBufferQueueMutex.unlock();
-
-    if (igbp) {
-        holdBufferQueueBlocks(*flushedWork, igbp, bqId, generation);
-    }
+    mOutputBufferQueue.holdBufferQueueBlocks(*flushedWork);
 
     return status;
 }
@@ -1239,15 +1223,31 @@
         C2BlockPool::local_id_t blockPoolId,
         const sp<IGraphicBufferProducer>& surface,
         uint32_t generation) {
-    sp<HGraphicBufferProducer2> igbp =
-            surface->getHalInterface<HGraphicBufferProducer2>();
+    uint64_t bqId = 0;
+    sp<IGraphicBufferProducer> nullIgbp;
+    sp<HGraphicBufferProducer2> nullHgbp;
 
-    if (!igbp) {
+    sp<HGraphicBufferProducer2> igbp = surface ?
+            surface->getHalInterface<HGraphicBufferProducer2>() : nullHgbp;
+    if (surface && !igbp) {
         igbp = new B2HGraphicBufferProducer2(surface);
     }
 
+    if (!surface) {
+        mOutputBufferQueue.configure(nullIgbp, generation, 0);
+    } else if (surface->getUniqueId(&bqId) != OK) {
+        LOG(ERROR) << "setOutputSurface -- "
+                   "cannot obtain bufferqueue id.";
+        bqId = 0;
+        mOutputBufferQueue.configure(nullIgbp, generation, 0);
+    } else {
+        mOutputBufferQueue.configure(surface, generation, bqId);
+    }
+    ALOGD("generation remote change %u", generation);
+
     Return<Status> transStatus = mBase->setOutputSurface(
-            static_cast<uint64_t>(blockPoolId), igbp);
+            static_cast<uint64_t>(blockPoolId),
+            bqId == 0 ? nullHgbp : igbp);
     if (!transStatus.isOk()) {
         LOG(ERROR) << "setOutputSurface -- transaction failed.";
         return C2_TRANSACTION_FAILED;
@@ -1256,18 +1256,6 @@
             static_cast<c2_status_t>(static_cast<Status>(transStatus));
     if (status != C2_OK) {
         LOG(DEBUG) << "setOutputSurface -- call failed: " << status << ".";
-    } else {
-        std::lock_guard<std::mutex> lock(mOutputBufferQueueMutex);
-        if (mOutputIgbp != surface) {
-            mOutputIgbp = surface;
-            if (!surface) {
-                mOutputBqId = 0;
-            } else if (surface->getUniqueId(&mOutputBqId) != OK) {
-                LOG(ERROR) << "setOutputSurface -- "
-                              "cannot obtain bufferqueue id.";
-            }
-        }
-        mOutputGeneration = generation;
     }
     return status;
 }
@@ -1276,74 +1264,7 @@
         const C2ConstGraphicBlock& block,
         const QueueBufferInput& input,
         QueueBufferOutput* output) {
-    uint32_t generation;
-    uint64_t bqId;
-    int32_t bqSlot;
-    if (!getBufferQueueAssignment(block, &generation, &bqId, &bqSlot) ||
-            bqId == 0) {
-        // Block not from bufferqueue -- it must be attached before queuing.
-
-        mOutputBufferQueueMutex.lock();
-        sp<IGraphicBufferProducer> outputIgbp = mOutputIgbp;
-        uint32_t outputGeneration = mOutputGeneration;
-        mOutputBufferQueueMutex.unlock();
-
-        status_t status = attachToBufferQueue(block,
-                                               outputIgbp,
-                                               outputGeneration,
-                                               &bqSlot);
-        if (status != OK) {
-            LOG(WARNING) << "queueToOutputSurface -- attaching failed.";
-            return INVALID_OPERATION;
-        }
-
-        status = outputIgbp->queueBuffer(static_cast<int>(bqSlot),
-                                         input, output);
-        if (status != OK) {
-            LOG(ERROR) << "queueToOutputSurface -- queueBuffer() failed "
-                          "on non-bufferqueue-based block. "
-                          "Error = " << status << ".";
-            return status;
-        }
-        return OK;
-    }
-
-    mOutputBufferQueueMutex.lock();
-    sp<IGraphicBufferProducer> outputIgbp = mOutputIgbp;
-    uint64_t outputBqId = mOutputBqId;
-    uint32_t outputGeneration = mOutputGeneration;
-    mOutputBufferQueueMutex.unlock();
-
-    if (!outputIgbp) {
-        LOG(VERBOSE) << "queueToOutputSurface -- output surface is null.";
-        return NO_INIT;
-    }
-
-    if (bqId != outputBqId || generation != outputGeneration) {
-        if (!holdBufferQueueBlock(block, mOutputIgbp, mOutputBqId, mOutputGeneration)) {
-            LOG(ERROR) << "queueToOutputSurface -- migration failed.";
-            return DEAD_OBJECT;
-        }
-        if (!getBufferQueueAssignment(block, &generation, &bqId, &bqSlot)) {
-            LOG(ERROR) << "queueToOutputSurface -- corrupted bufferqueue assignment.";
-            return UNKNOWN_ERROR;
-        }
-    }
-
-    status_t status = outputIgbp->queueBuffer(static_cast<int>(bqSlot),
-                                              input, output);
-    if (status != OK) {
-        LOG(DEBUG) << "queueToOutputSurface -- queueBuffer() failed "
-                      "on bufferqueue-based block. "
-                      "Error = " << status << ".";
-        return status;
-    }
-    if (!yieldBufferQueueBlock(block)) {
-        LOG(DEBUG) << "queueToOutputSurface -- cannot yield "
-                      "bufferqueue-based block to the bufferqueue.";
-        return UNKNOWN_ERROR;
-    }
-    return OK;
+    return mOutputBufferQueue.outputBuffer(block, input, output);
 }
 
 c2_status_t Codec2Client::Component::connectToInputSurface(
diff --git a/media/codec2/hidl/client/include/codec2/hidl/client.h b/media/codec2/hidl/client/include/codec2/hidl/client.h
index 03db515..b8a7fb5 100644
--- a/media/codec2/hidl/client/include/codec2/hidl/client.h
+++ b/media/codec2/hidl/client/include/codec2/hidl/client.h
@@ -369,10 +369,8 @@
     ::android::hardware::media::c2::V1_0::utils::DefaultBufferPoolSender
             mBufferPoolSender;
 
-    std::mutex mOutputBufferQueueMutex;
-    sp<IGraphicBufferProducer> mOutputIgbp;
-    uint64_t mOutputBqId;
-    uint32_t mOutputGeneration;
+    ::android::hardware::media::c2::V1_0::utils::OutputBufferQueue
+            mOutputBufferQueue;
 
     static c2_status_t setDeathListener(
             const std::shared_ptr<Component>& component,
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index eb20b20..8be9a1d 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -1581,6 +1581,7 @@
     if (newSurface) {
         newSurface->setScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
         newSurface->setDequeueTimeout(kDequeueTimeoutNs);
+        newSurface->setMaxDequeuedBufferCount(mOutputSurface.lock()->maxDequeueBuffers);
         producer = newSurface->getIGraphicBufferProducer();
         producer->setGenerationNumber(generation);
     } else {
@@ -1608,7 +1609,6 @@
 
     {
         Mutexed<OutputSurface>::Locked output(mOutputSurface);
-        newSurface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
         output->surface = newSurface;
         output->generation = generation;
     }
diff --git a/media/codec2/vndk/C2AllocatorGralloc.cpp b/media/codec2/vndk/C2AllocatorGralloc.cpp
index e698bf4..286c48a 100644
--- a/media/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/codec2/vndk/C2AllocatorGralloc.cpp
@@ -201,6 +201,22 @@
         return res;
     }
 
+    static bool MigrateNativeHandle(
+            native_handle_t *handle,
+            uint32_t generation, uint64_t igbp_id, uint32_t igbp_slot) {
+        if (handle == nullptr || !isValid(handle)) {
+            return false;
+        }
+        ExtraData *ed = getExtraData(handle);
+        if (!ed) return false;
+        ed->generation = generation;
+        ed->igbp_id_lo = uint32_t(igbp_id & 0xFFFFFFFF);
+        ed->igbp_id_hi = uint32_t(igbp_id >> 32);
+        ed->igbp_slot = igbp_slot;
+        return true;
+    }
+
+
     static native_handle_t* UnwrapNativeHandle(
             const C2Handle *const handle) {
         const ExtraData *xd = getExtraData(handle);
@@ -270,6 +286,13 @@
                                              generation, igbp_id, igbp_slot);
 }
 
+bool MigrateNativeCodec2GrallocHandle(
+        native_handle_t *handle,
+        uint32_t generation, uint64_t igbp_id, uint32_t igbp_slot) {
+    return C2HandleGralloc::MigrateNativeHandle(handle, generation, igbp_id, igbp_slot);
+}
+
+
 class C2AllocationGralloc : public C2GraphicAllocation {
 public:
     virtual ~C2AllocationGralloc() override;
diff --git a/media/codec2/vndk/include/C2AllocatorGralloc.h b/media/codec2/vndk/include/C2AllocatorGralloc.h
index 05d989e..ee7524e 100644
--- a/media/codec2/vndk/include/C2AllocatorGralloc.h
+++ b/media/codec2/vndk/include/C2AllocatorGralloc.h
@@ -45,6 +45,16 @@
         uint32_t generation = 0, uint64_t igbp_id = 0, uint32_t igbp_slot = 0);
 
 /**
+ * When the gralloc handle is migrated to another bufferqueue, update
+ * bufferqueue information.
+ *
+ * @return {@code true} when native_handle is a wrapped codec2 handle.
+ */
+bool MigrateNativeCodec2GrallocHandle(
+        native_handle_t *handle,
+        uint32_t generation, uint64_t igbp_id, uint32_t igbp_slot);
+
+/**
  * \todo Get this from the buffer
  */
 void _UnwrapNativeCodec2GrallocMetadata(
diff --git a/media/codec2/vndk/internal/C2BlockInternal.h b/media/codec2/vndk/internal/C2BlockInternal.h
index 84ce70a..4ae946a 100644
--- a/media/codec2/vndk/internal/C2BlockInternal.h
+++ b/media/codec2/vndk/internal/C2BlockInternal.h
@@ -206,23 +206,19 @@
      *
      *   - GetBufferQueueData(): Returns generation, bqId and bqSlot.
      *   - HoldBlockFromBufferQueue(): Sets "held" status to true.
-     *   - YieldBlockToBufferQueue(): Sets "held" status to false.
-     *   - AssignBlockToBufferQueue(): Sets the bufferqueue assignment and
-     *     "held" status.
+     *   - BeginTransferBlockToClient()/EndTransferBlockToClient():
+     *     Clear "held" status to false if transfer was successful,
+     *     otherwise "held" status remains true.
+     *   - BeginAttachBlockToBufferQueue()/EndAttachBlockToBufferQueue():
+     *     The will keep "held" status true if attach was eligible.
+     *     Otherwise, "held" status is cleared to false. In that case,
+     *     ownership of buffer should be transferred to bufferqueue.
+     *   - DisplayBlockToBufferQueue()
+     *     This will clear "held" status to false.
      *
      * All these functions operate on _C2BlockPoolData, which can be obtained by
      * calling GetGraphicBlockPoolData().
      *
-     * HoldBlockFromBufferQueue() will mark the block as held, while
-     * YieldBlockToBufferQueue() will do the opposite. These two functions do
-     * not modify the bufferqueue assignment, so it is not wrong to call
-     * HoldBlockFromBufferQueue() after YieldBlockToBufferQueue() if it can be
-     * guaranteed that the block is not destroyed during the period between the
-     * two calls.
-     *
-     * AssingBlockToBufferQueue() has a "held" status as an optional argument.
-     * The default value is true.
-     *
      * Maintaining Consistency with IGraphicBufferProducer Operations
      * ==============================================================
      *
@@ -232,16 +228,20 @@
      *     information for _C2BlockPoolData, with "held" status set to true.
      *
      * queueBuffer()
-     *   - After queueBuffer() is called, YieldBlockToBufferQueue() should be
-     *     called.
+     *   - Before queueBuffer() is called, DisplayBlockToBufferQueue() should be
+     *     called to test eligibility. If it's not eligible, do not call
+     *     queueBuffer().
      *
-     * attachBuffer()
-     *   - After attachBuffer() is called, AssignBlockToBufferQueue() should be
-     *     called with "held" status set to true.
+     * attachBuffer() - remote migration only.
+     *   - Local migration on blockpool side will be done automatically by
+     *     blockpool.
+     *   - Before attachBuffer(), BeginAttachBlockToBufferQueue() should be called
+     *     to test eligiblity.
+     *   - After attachBuffer() is called, EndAttachBlockToBufferQueue() should
+     *     be called. This will set "held" status to true. If it returned
+     *     false, cancelBuffer() should be called.
      *
-     * detachBuffer()
-     *   - After detachBuffer() is called, HoldBlockFromBufferQueue() should be
-     *     called.
+     * detachBuffer() - no-op.
      */
 
     /**
@@ -261,43 +261,12 @@
      */
     static
     bool GetBufferQueueData(
-            const std::shared_ptr<_C2BlockPoolData>& poolData,
+            const std::shared_ptr<const _C2BlockPoolData>& poolData,
             uint32_t* generation = nullptr,
             uint64_t* bqId = nullptr,
             int32_t* bqSlot = nullptr);
 
     /**
-     * Set bufferqueue assignment and "held" status to a block created by a
-     * bufferqueue-based blockpool.
-     *
-     * \param poolData blockpool data associated to the block.
-     * \param igbp       \c IGraphicBufferProducer instance from the designated
-     *                   bufferqueue.
-     * \param generation Generation number that the buffer belongs to.
-     * \param bqId       Id of the bufferqueue that will own the buffer (block).
-     * \param bqSlot     Slot number of the buffer.
-     * \param held       Whether the block is held. This "held" status can be
-     *                   changed later by calling YieldBlockToBufferQueue() or
-     *                   HoldBlockFromBufferQueue().
-     *
-     * \return \c true if \p poolData is valid bufferqueue data;
-     *         \c false otherwise.
-     *
-     * Note: \p generation should match the latest generation number set on the
-     * bufferqueue, and \p bqId should match the unique id for the bufferqueue
-     * (obtainable by calling igbp->getUniqueId()).
-     */
-    static
-    bool AssignBlockToBufferQueue(
-            const std::shared_ptr<_C2BlockPoolData>& poolData,
-            const ::android::sp<::android::hardware::graphics::bufferqueue::
-                                V2_0::IGraphicBufferProducer>& igbp,
-            uint32_t generation,
-            uint64_t bqId,
-            int32_t bqSlot,
-            bool held = true);
-
-    /**
      * Hold a block from the designated bufferqueue. This causes the destruction
      * of the block to trigger a call to cancelBuffer().
      *
@@ -305,6 +274,9 @@
      * block. It does not check if that is the case.
      *
      * \param poolData blockpool data associated to the block.
+     * \param owner    block owner from client bufferqueue manager.
+     *                 If this is expired, the block is not owned by client
+     *                 anymore.
      * \param igbp     \c IGraphicBufferProducer instance to be assigned to the
      *                 block. This is not needed when the block is local.
      *
@@ -313,24 +285,96 @@
     static
     bool HoldBlockFromBufferQueue(
             const std::shared_ptr<_C2BlockPoolData>& poolData,
+            const std::shared_ptr<int>& owner,
             const ::android::sp<::android::hardware::graphics::bufferqueue::
                                 V2_0::IGraphicBufferProducer>& igbp = nullptr);
 
     /**
-     * Yield a block to the designated bufferqueue. This causes the destruction
-     * of the block not to trigger a call to cancelBuffer();
+     * Prepare a block to be transferred to other process. This blocks
+     * bufferqueue migration from happening. The block should be in held.
      *
      * This function assumes that \p poolData comes from a bufferqueue-based
      * block. It does not check if that is the case.
      *
      * \param poolData blockpool data associated to the block.
      *
-     * \return The previous held status.
+     * \return true if transfer is eligible, false otherwise.
      */
     static
-    bool YieldBlockToBufferQueue(
+    bool BeginTransferBlockToClient(
             const std::shared_ptr<_C2BlockPoolData>& poolData);
 
+    /**
+     * Called after transferring the specified block is finished. Make sure
+     * that BeginTransferBlockToClient() was called before this call.
+     *
+     * This will unblock bufferqueue migration. If transfer result was
+     * successful, this causes the destruction of the block not to trigger a
+     * call to cancelBuffer().
+     * This function assumes that \p poolData comes from a bufferqueue-based
+     * block. It does not check if that is the case.
+     *
+     * \param poolData blockpool data associated to the block.
+     *
+     * \return true if transfer began before, false otherwise.
+     */
+    static
+    bool EndTransferBlockToClient(
+            const std::shared_ptr<_C2BlockPoolData>& poolData,
+            bool transferred);
+
+    /**
+     * Prepare a block to be migrated to another bufferqueue. This blocks
+     * rendering until migration has been finished.  The block should be in
+     * held.
+     *
+     * This function assumes that \p poolData comes from a bufferqueue-based
+     * block. It does not check if that is the case.
+     *
+     * \param poolData blockpool data associated to the block.
+     *
+     * \return true if migration is eligible, false otherwise.
+     */
+    static
+    bool BeginAttachBlockToBufferQueue(
+            const std::shared_ptr<_C2BlockPoolData>& poolData);
+
+    /**
+     * Called after migration of the specified block is finished. Make sure
+     * that BeginAttachBlockToBufferQueue() was called before this call.
+     *
+     * This will unblock rendering. if redering is tried during migration,
+     * this returns false. In that case, cancelBuffer() should be called.
+     * This function assumes that \p poolData comes from a bufferqueue-based
+     * block. It does not check if that is the case.
+     *
+     * \param poolData blockpool data associated to the block.
+     *
+     * \return true if migration is eligible, false otherwise.
+     */
+    static
+    bool EndAttachBlockToBufferQueue(
+            const std::shared_ptr<_C2BlockPoolData>& poolData,
+            const std::shared_ptr<int>& owner,
+            const ::android::sp<::android::hardware::graphics::bufferqueue::
+                                V2_0::IGraphicBufferProducer>& igbp,
+            uint32_t generation,
+            uint64_t bqId,
+            int32_t bqSlot);
+
+    /**
+     * Indicates a block to be rendered very soon.
+     *
+     * This function assumes that \p poolData comes from a bufferqueue-based
+     * block. It does not check if that is the case.
+     *
+     * \param poolData blockpool data associated to the block.
+     *
+     * \return true if migration is eligible, false otherwise.
+     */
+    static
+    bool DisplayBlockToBufferQueue(
+            const std::shared_ptr<_C2BlockPoolData>& poolData);
 };
 
 #endif // ANDROID_STAGEFRIGHT_C2BLOCK_INTERNAL_H_
diff --git a/media/codec2/vndk/platform/C2BqBuffer.cpp b/media/codec2/vndk/platform/C2BqBuffer.cpp
index 3f40e56..5fa48a8 100644
--- a/media/codec2/vndk/platform/C2BqBuffer.cpp
+++ b/media/codec2/vndk/platform/C2BqBuffer.cpp
@@ -61,8 +61,13 @@
     uint32_t generation;
     uint64_t bqId;
     int32_t bqSlot;
+    bool transfer; // local transfer to remote
+    bool attach; // attach on remote
+    bool display; // display on remote;
+    std::weak_ptr<int> owner;
     sp<HGraphicBufferProducer> igbp;
     std::shared_ptr<C2BufferQueueBlockPool::Impl> localPool;
+    mutable std::mutex lock;
 
     virtual type_t getType() const override {
         return TYPE_BUFFERQUEUE;
@@ -71,7 +76,8 @@
     // Create a remote BlockPoolData.
     C2BufferQueueBlockPoolData(
             uint32_t generation, uint64_t bqId, int32_t bqSlot,
-            const sp<HGraphicBufferProducer>& producer = nullptr);
+            const std::shared_ptr<int> &owner,
+            const sp<HGraphicBufferProducer>& producer);
 
     // Create a local BlockPoolData.
     C2BufferQueueBlockPoolData(
@@ -80,15 +86,19 @@
 
     virtual ~C2BufferQueueBlockPoolData() override;
 
+    int migrate(const sp<HGraphicBufferProducer>& producer,
+                uint32_t toGeneration, uint64_t toBqId,
+                sp<GraphicBuffer> *buffers, uint32_t oldGeneration);
 };
 
 bool _C2BlockFactory::GetBufferQueueData(
-        const std::shared_ptr<_C2BlockPoolData>& data,
+        const std::shared_ptr<const _C2BlockPoolData>& data,
         uint32_t* generation, uint64_t* bqId, int32_t* bqSlot) {
     if (data && data->getType() == _C2BlockPoolData::TYPE_BUFFERQUEUE) {
         if (generation) {
-            const std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
-                    std::static_pointer_cast<C2BufferQueueBlockPoolData>(data);
+            const std::shared_ptr<const C2BufferQueueBlockPoolData> poolData =
+                    std::static_pointer_cast<const C2BufferQueueBlockPoolData>(data);
+            std::scoped_lock<std::mutex> lock(poolData->lock);
             *generation = poolData->generation;
             if (bqId) {
                 *bqId = poolData->bqId;
@@ -102,32 +112,15 @@
     return false;
 }
 
-bool _C2BlockFactory::AssignBlockToBufferQueue(
-        const std::shared_ptr<_C2BlockPoolData>& data,
-        const sp<HGraphicBufferProducer>& igbp,
-        uint32_t generation,
-        uint64_t bqId,
-        int32_t bqSlot,
-        bool held) {
-    if (data && data->getType() == _C2BlockPoolData::TYPE_BUFFERQUEUE) {
-        const std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
-                std::static_pointer_cast<C2BufferQueueBlockPoolData>(data);
-        poolData->igbp = igbp;
-        poolData->generation = generation;
-        poolData->bqId = bqId;
-        poolData->bqSlot = bqSlot;
-        poolData->held = held;
-        return true;
-    }
-    return false;
-}
-
 bool _C2BlockFactory::HoldBlockFromBufferQueue(
         const std::shared_ptr<_C2BlockPoolData>& data,
+        const std::shared_ptr<int>& owner,
         const sp<HGraphicBufferProducer>& igbp) {
     const std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
             std::static_pointer_cast<C2BufferQueueBlockPoolData>(data);
+    std::scoped_lock<std::mutex> lock(poolData->lock);
     if (!poolData->local) {
+        poolData->owner = owner;
         poolData->igbp = igbp;
     }
     if (poolData->held) {
@@ -138,12 +131,86 @@
     return true;
 }
 
-bool _C2BlockFactory::YieldBlockToBufferQueue(
+bool _C2BlockFactory::BeginTransferBlockToClient(
         const std::shared_ptr<_C2BlockPoolData>& data) {
     const std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
             std::static_pointer_cast<C2BufferQueueBlockPoolData>(data);
-    if (!poolData->held) {
+    std::scoped_lock<std::mutex> lock(poolData->lock);
+    poolData->transfer = true;
+    return true;
+}
+
+bool _C2BlockFactory::EndTransferBlockToClient(
+        const std::shared_ptr<_C2BlockPoolData>& data,
+        bool transfer) {
+    const std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
+            std::static_pointer_cast<C2BufferQueueBlockPoolData>(data);
+    std::scoped_lock<std::mutex> lock(poolData->lock);
+    poolData->transfer = false;
+    if (transfer) {
         poolData->held = false;
+    }
+    return true;
+}
+
+bool _C2BlockFactory::BeginAttachBlockToBufferQueue(
+        const std::shared_ptr<_C2BlockPoolData>& data) {
+    const std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
+            std::static_pointer_cast<C2BufferQueueBlockPoolData>(data);
+    std::scoped_lock<std::mutex> lock(poolData->lock);
+    if (poolData->local || poolData->display ||
+        poolData->attach || !poolData->held) {
+        return false;
+    }
+    if (poolData->bqId == 0) {
+        return false;
+    }
+    poolData->attach = true;
+    return true;
+}
+
+// if display was tried during attach, buffer should be retired ASAP.
+bool _C2BlockFactory::EndAttachBlockToBufferQueue(
+        const std::shared_ptr<_C2BlockPoolData>& data,
+        const std::shared_ptr<int>& owner,
+        const sp<HGraphicBufferProducer>& igbp,
+        uint32_t generation,
+        uint64_t bqId,
+        int32_t bqSlot) {
+    const std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
+            std::static_pointer_cast<C2BufferQueueBlockPoolData>(data);
+    std::scoped_lock<std::mutex> lock(poolData->lock);
+    if (poolData->local || !poolData->attach ) {
+        return false;
+    }
+    if (poolData->display) {
+        poolData->attach = false;
+        poolData->held = false;
+        return false;
+    }
+    poolData->attach = false;
+    poolData->held = true;
+    poolData->owner = owner;
+    poolData->igbp = igbp;
+    poolData->generation = generation;
+    poolData->bqId = bqId;
+    poolData->bqSlot = bqSlot;
+    return true;
+}
+
+bool _C2BlockFactory::DisplayBlockToBufferQueue(
+        const std::shared_ptr<_C2BlockPoolData>& data) {
+    const std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
+            std::static_pointer_cast<C2BufferQueueBlockPoolData>(data);
+    std::scoped_lock<std::mutex> lock(poolData->lock);
+    if (poolData->local || poolData->display || !poolData->held) {
+        return false;
+    }
+    if (poolData->bqId == 0) {
+        return false;
+    }
+    poolData->display = true;
+    if (poolData->attach) {
         return false;
     }
     poolData->held = false;
@@ -175,7 +242,9 @@
                 std::shared_ptr<C2BufferQueueBlockPoolData> poolData =
                         std::make_shared<C2BufferQueueBlockPoolData>(generation,
                                                                      bqId,
-                                                                     (int32_t)bqSlot);
+                                                                     (int32_t)bqSlot,
+                                                                     nullptr,
+                                                                     nullptr);
                 block = _C2BlockFactory::CreateGraphicBlock(alloc, poolData);
             } else {
                 block = _C2BlockFactory::CreateGraphicBlock(alloc);
@@ -186,6 +255,78 @@
     return nullptr;
 }
 
+namespace {
+
+int64_t getTimestampNow() {
+    int64_t stamp;
+    struct timespec ts;
+    // TODO: CLOCK_MONOTONIC_COARSE?
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    stamp = ts.tv_nsec / 1000;
+    stamp += (ts.tv_sec * 1000000LL);
+    return stamp;
+}
+
+bool getGenerationNumber(const sp<HGraphicBufferProducer> &producer,
+                         uint32_t *generation) {
+    status_t status{};
+    int slot{};
+    bool bufferNeedsReallocation{};
+    sp<Fence> fence = new Fence();
+
+    using Input = HGraphicBufferProducer::DequeueBufferInput;
+    using Output = HGraphicBufferProducer::DequeueBufferOutput;
+    Return<void> transResult = producer->dequeueBuffer(
+            Input{640, 480, HAL_PIXEL_FORMAT_YCBCR_420_888, 0},
+            [&status, &slot, &bufferNeedsReallocation, &fence]
+            (HStatus hStatus, int32_t hSlot, Output const& hOutput) {
+                slot = static_cast<int>(hSlot);
+                if (!h2b(hStatus, &status) || !h2b(hOutput.fence, &fence)) {
+                    status = ::android::BAD_VALUE;
+                } else {
+                    bufferNeedsReallocation =
+                            hOutput.bufferNeedsReallocation;
+                }
+            });
+    if (!transResult.isOk() || status != android::OK) {
+        return false;
+    }
+    HFenceWrapper hFenceWrapper{};
+    if (!b2h(fence, &hFenceWrapper)) {
+        (void)producer->detachBuffer(static_cast<int32_t>(slot)).isOk();
+        ALOGE("Invalid fence received from dequeueBuffer.");
+        return false;
+    }
+    sp<GraphicBuffer> slotBuffer = new GraphicBuffer();
+    // N.B. This assumes requestBuffer# returns an existing allocation
+    // instead of a new allocation.
+    transResult = producer->requestBuffer(
+            slot,
+            [&status, &slotBuffer, &generation](
+                    HStatus hStatus,
+                    HBuffer const& hBuffer,
+                    uint32_t generationNumber){
+                if (h2b(hStatus, &status) &&
+                        h2b(hBuffer, &slotBuffer) &&
+                        slotBuffer) {
+                    *generation = generationNumber;
+                    slotBuffer->setGenerationNumber(generationNumber);
+                } else {
+                    status = android::BAD_VALUE;
+                }
+            });
+    if (!transResult.isOk()) {
+        return false;
+    } else if (status != android::NO_ERROR) {
+        (void)producer->detachBuffer(static_cast<int32_t>(slot)).isOk();
+        return false;
+    }
+    (void)producer->detachBuffer(static_cast<int32_t>(slot)).isOk();
+    return true;
+}
+
+};
+
 class C2BufferQueueBlockPool::Impl
         : public std::enable_shared_from_this<C2BufferQueueBlockPool::Impl> {
 private:
@@ -227,6 +368,7 @@
                     });
             if (!transResult.isOk() || status != android::OK) {
                 if (transResult.isOk()) {
+                    ++mDqFailure;
                     if (status == android::INVALID_OPERATION ||
                         status == android::TIMED_OUT ||
                         status == android::WOULD_BLOCK) {
@@ -238,6 +380,8 @@
                 ALOGD("cannot dequeue buffer %d", status);
                 return C2_BAD_VALUE;
             }
+            mDqFailure = 0;
+            mLastDqTs = getTimestampNow();
         }
         HFenceWrapper hFenceWrapper{};
         if (!b2h(fence, &hFenceWrapper)) {
@@ -319,6 +463,7 @@
                                 slotBuffer->getGenerationNumber(),
                                 mProducerId, slot,
                                 shared_from_this());
+                mPoolDatas[slot] = poolData;
                 *block = _C2BlockFactory::CreateGraphicBlock(alloc, poolData);
                 return C2_OK;
             }
@@ -331,7 +476,9 @@
 
 public:
     Impl(const std::shared_ptr<C2Allocator> &allocator)
-        : mInit(C2_OK), mProducerId(0), mAllocator(allocator) {
+        : mInit(C2_OK), mProducerId(0), mGeneration(0),
+          mDqFailure(0), mLastDqTs(0), mLastDqLogTs(0),
+          mAllocator(allocator) {
     }
 
     ~Impl() {
@@ -361,6 +508,19 @@
         static int kMaxIgbpRetryDelayUs = 10000;
 
         std::unique_lock<std::mutex> lock(mMutex);
+        if (mLastDqLogTs == 0) {
+            mLastDqLogTs = getTimestampNow();
+        } else {
+            int64_t now = getTimestampNow();
+            if (now >= mLastDqLogTs + 5000000) {
+                if (now >= mLastDqTs + 1000000 || mDqFailure > 5) {
+                    ALOGW("last successful dequeue was %lld us ago, "
+                          "%zu consecutive failures",
+                          (long long)(now - mLastDqTs), mDqFailure);
+                }
+                mLastDqLogTs = now;
+            }
+        }
         if (mProducerId == 0) {
             std::shared_ptr<C2GraphicAllocation> alloc;
             c2_status_t err = mAllocator->newGraphicAllocation(
@@ -386,12 +546,14 @@
     }
 
     void setRenderCallback(const OnRenderCallback &renderCallback) {
-        std::lock_guard<std::mutex> lock(mMutex);
+        std::scoped_lock<std::mutex> lock(mMutex);
         mRenderCallback = renderCallback;
     }
 
     void configureProducer(const sp<HGraphicBufferProducer> &producer) {
         uint64_t producerId = 0;
+        uint32_t generation = 0;
+        bool haveGeneration = false;
         if (producer) {
             Return<uint64_t> transResult = producer->getUniqueId();
             if (!transResult.isOk()) {
@@ -399,9 +561,15 @@
                 return;
             }
             producerId = static_cast<uint64_t>(transResult);
+            // TODO: provide gneration number from parameter.
+            haveGeneration = getGenerationNumber(producer, &generation);
         }
+        int migrated = 0;
         {
-            std::lock_guard<std::mutex> lock(mMutex);
+            sp<GraphicBuffer> buffers[NUM_BUFFER_SLOTS];
+            std::weak_ptr<C2BufferQueueBlockPoolData>
+                    poolDatas[NUM_BUFFER_SLOTS];
+            std::scoped_lock<std::mutex> lock(mMutex);
             bool noInit = false;
             for (int i = 0; i < NUM_BUFFER_SLOTS; ++i) {
                 if (!noInit && mProducer) {
@@ -410,46 +578,88 @@
                     noInit = !transResult.isOk() ||
                              static_cast<HStatus>(transResult) == HStatus::NO_INIT;
                 }
-                mBuffers[i].clear();
             }
-            if (producer) {
+            int32_t oldGeneration = mGeneration;
+            if (producer && haveGeneration) {
                 mProducer = producer;
                 mProducerId = producerId;
+                mGeneration = generation;
             } else {
                 mProducer = nullptr;
                 mProducerId = 0;
+                mGeneration = 0;
+                ALOGW("invalid producer producer(%d), generation(%d)",
+                      (bool)producer, haveGeneration);
             }
+            if (mProducer) { // migrate buffers
+                for (int i = 0; i < NUM_BUFFER_SLOTS; ++i) {
+                    std::shared_ptr<C2BufferQueueBlockPoolData> data =
+                            mPoolDatas[i].lock();
+                    if (data) {
+                        int slot = data->migrate(
+                                mProducer, generation,
+                                producerId, mBuffers, oldGeneration);
+                        if (slot >= 0) {
+                            buffers[slot] = mBuffers[i];
+                            poolDatas[slot] = data;
+                            ++migrated;
+                        }
+                    }
+                }
+            }
+            for (int i = 0; i < NUM_BUFFER_SLOTS; ++i) {
+                mBuffers[i] = buffers[i];
+                mPoolDatas[i] = poolDatas[i];
+            }
+        }
+        if (producer && haveGeneration) {
+            ALOGD("local generation change %u , "
+                  "bqId: %llu migrated buffers # %d",
+                  generation, (unsigned long long)producerId, migrated);
         }
     }
 
 private:
     friend struct C2BufferQueueBlockPoolData;
 
-    void cancel(uint64_t igbp_id, int32_t igbp_slot) {
-        std::lock_guard<std::mutex> lock(mMutex);
-        if (igbp_id == mProducerId && mProducer) {
+    void cancel(uint32_t generation, uint64_t igbp_id, int32_t igbp_slot) {
+        bool cancelled = false;
+        {
+        std::scoped_lock<std::mutex> lock(mMutex);
+        if (generation == mGeneration && igbp_id == mProducerId && mProducer) {
             (void)mProducer->cancelBuffer(igbp_slot, hidl_handle{}).isOk();
+            cancelled = true;
+        }
         }
     }
 
     c2_status_t mInit;
     uint64_t mProducerId;
+    uint32_t mGeneration;
     OnRenderCallback mRenderCallback;
 
+    size_t mDqFailure;
+    int64_t mLastDqTs;
+    int64_t mLastDqLogTs;
+
     const std::shared_ptr<C2Allocator> mAllocator;
 
     std::mutex mMutex;
     sp<HGraphicBufferProducer> mProducer;
+    sp<HGraphicBufferProducer> mSavedProducer;
 
     sp<GraphicBuffer> mBuffers[NUM_BUFFER_SLOTS];
+    std::weak_ptr<C2BufferQueueBlockPoolData> mPoolDatas[NUM_BUFFER_SLOTS];
 };
 
 C2BufferQueueBlockPoolData::C2BufferQueueBlockPoolData(
         uint32_t generation, uint64_t bqId, int32_t bqSlot,
+        const std::shared_ptr<int>& owner,
         const sp<HGraphicBufferProducer>& producer) :
         held(producer && bqId != 0), local(false),
         generation(generation), bqId(bqId), bqSlot(bqSlot),
-        igbp(producer),
+        transfer(false), attach(false), display(false),
+        owner(owner), igbp(producer),
         localPool() {
 }
 
@@ -458,6 +668,7 @@
         const std::shared_ptr<C2BufferQueueBlockPool::Impl>& pool) :
         held(true), local(true),
         generation(generation), bqId(bqId), bqSlot(bqSlot),
+        transfer(false), attach(false), display(false),
         igbp(pool ? pool->mProducer : nullptr),
         localPool(pool) {
 }
@@ -466,12 +677,78 @@
     if (!held || bqId == 0) {
         return;
     }
-    if (local && localPool) {
-        localPool->cancel(bqId, bqSlot);
-    } else if (igbp) {
+    if (local) {
+        if (localPool) {
+            localPool->cancel(generation, bqId, bqSlot);
+        }
+    } else if (igbp && !owner.expired()) {
         igbp->cancelBuffer(bqSlot, hidl_handle{}).isOk();
     }
 }
+int C2BufferQueueBlockPoolData::migrate(
+        const sp<HGraphicBufferProducer>& producer,
+        uint32_t toGeneration, uint64_t toBqId,
+        sp<GraphicBuffer> *buffers, uint32_t oldGeneration) {
+    std::scoped_lock<std::mutex> l(lock);
+    if (!held || bqId == 0) {
+        ALOGV("buffer is not owned");
+        return -1;
+    }
+    if (!local || !localPool) {
+        ALOGV("pool is not local");
+        return -1;
+    }
+    if (bqSlot < 0 || bqSlot >= NUM_BUFFER_SLOTS || !buffers[bqSlot]) {
+        ALOGV("slot is not in effect");
+        return -1;
+    }
+    if (toGeneration == generation && bqId == toBqId) {
+        ALOGV("cannot migrate to same bufferqueue");
+        return -1;
+    }
+    if (oldGeneration != generation) {
+        ALOGV("cannot migrate stale buffer");
+    }
+    if (transfer) {
+        // either transferred or detached.
+        ALOGV("buffer is in transfer");
+        return -1;
+    }
+    sp<GraphicBuffer> const& graphicBuffer = buffers[bqSlot];
+    graphicBuffer->setGenerationNumber(toGeneration);
+
+    HBuffer hBuffer{};
+    uint32_t hGenerationNumber{};
+    if (!b2h(graphicBuffer, &hBuffer, &hGenerationNumber)) {
+        ALOGD("I to O conversion failed");
+        return -1;
+    }
+
+    bool converted{};
+    status_t bStatus{};
+    int slot;
+    int *outSlot = &slot;
+    Return<void> transResult =
+            producer->attachBuffer(hBuffer, hGenerationNumber,
+                    [&converted, &bStatus, outSlot](
+                            HStatus hStatus, int32_t hSlot, bool releaseAll) {
+                        converted = h2b(hStatus, &bStatus);
+                        *outSlot = static_cast<int>(hSlot);
+                        if (converted && releaseAll && bStatus == android::OK) {
+                            bStatus = android::INVALID_OPERATION;
+                        }
+                    });
+    if (!transResult.isOk() || !converted || bStatus != android::OK) {
+        ALOGD("attach failed %d", static_cast<int>(bStatus));
+        return -1;
+    }
+    ALOGV("local migration from gen %u : %u slot %d : %d",
+          generation, toGeneration, bqSlot, slot);
+    generation = toGeneration;
+    bqId = toBqId;
+    bqSlot = slot;
+    return slot;
+}
 
 C2BufferQueueBlockPool::C2BufferQueueBlockPool(
         const std::shared_ptr<C2Allocator> &allocator, const local_id_t localId)