Merge "AudioMixer: Fix aux effect pointer computation"
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 541093a..6da6c13 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -7289,12 +7289,16 @@
         }
     }
 
-    float rate;
-    if (params->findFloat("operating-rate", &rate) && rate > 0) {
-        status_t err = setOperatingRate(rate, mIsVideo);
+    int32_t rateInt = -1;
+    float rateFloat = -1;
+    if (!params->findFloat("operating-rate", &rateFloat)) {
+        params->findInt32("operating-rate", &rateInt);
+        rateFloat = (float) rateInt; // 16MHz (FLINTMAX) is OK for upper bound.
+    }
+    if (rateFloat > 0) {
+        status_t err = setOperatingRate(rateFloat, mIsVideo);
         if (err != OK) {
-            ALOGE("Failed to set parameter 'operating-rate' (err %d)", err);
-            return err;
+            ALOGI("Failed to set parameter 'operating-rate' (err %d)", err);
         }
     }
 
@@ -7319,10 +7323,8 @@
         }
     }
 
-    status_t err = configureTemporalLayers(params, false /* inConfigure */, mOutputFormat);
-    if (err != OK) {
-        err = OK; // ignore failure
-    }
+    // Ignore errors as failure is expected for codecs that aren't video encoders.
+    (void)configureTemporalLayers(params, false /* inConfigure */, mOutputFormat);
 
     return setVendorParameters(params);
 }
diff --git a/media/libstagefright/BufferImpl.cpp b/media/libstagefright/BufferImpl.cpp
index bd2443d..d2eee33 100644
--- a/media/libstagefright/BufferImpl.cpp
+++ b/media/libstagefright/BufferImpl.cpp
@@ -64,10 +64,87 @@
     return ICrypto::kDestinationTypeNativeHandle;
 }
 
+// Codec2Buffer
+
+bool Codec2Buffer::canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const {
+    if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
+        return false;
+    }
+    if (buffer->data().type() != C2BufferData::LINEAR) {
+        return false;
+    }
+    if (buffer->data().linearBlocks().size() == 0u) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    } else if (buffer->data().linearBlocks().size() > 1u) {
+        // We don't know how to copy more than one blocks.
+        return false;
+    }
+    if (buffer->data().linearBlocks()[0].size() > capacity()) {
+        // It won't fit.
+        return false;
+    }
+    return true;
+}
+
+bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
+    // We assume that all canCopyLinear() checks passed.
+    if (buffer->data().linearBlocks().size() == 0u) {
+        setRange(0, 0);
+        return true;
+    }
+    C2ReadView view = buffer->data().linearBlocks()[0].map().get();
+    if (view.error() != C2_OK) {
+        ALOGD("Error while mapping: %d", view.error());
+        return false;
+    }
+    if (view.capacity() > capacity()) {
+        ALOGD("C2ConstLinearBlock lied --- it actually doesn't fit: view(%u) > this(%zu)",
+                view.capacity(), capacity());
+        return false;
+    }
+    memcpy(base(), view.data(), view.capacity());
+    setRange(0, view.capacity());
+    return true;
+}
+
+// LocalLinearBuffer
+
+bool LocalLinearBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+    return canCopyLinear(buffer);
+}
+
+bool LocalLinearBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    return copyLinear(buffer);
+}
+
+// DummyContainerBuffer
+
+DummyContainerBuffer::DummyContainerBuffer(
+        const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer)
+    : Codec2Buffer(format, new ABuffer(nullptr, 1)),
+      mBufferRef(buffer) {
+    setRange(0, buffer ? 1 : 0);
+}
+
+std::shared_ptr<C2Buffer> DummyContainerBuffer::asC2Buffer() {
+    return std::move(mBufferRef);
+}
+
+bool DummyContainerBuffer::canCopy(const std::shared_ptr<C2Buffer> &) const {
+    return !mBufferRef;
+}
+
+bool DummyContainerBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    mBufferRef = buffer;
+    setRange(0, mBufferRef ? 1 : 0);
+    return true;
+}
+
 // LinearBlockBuffer
 
 // static
-sp<LinearBlockBuffer> LinearBlockBuffer::allocate(
+sp<LinearBlockBuffer> LinearBlockBuffer::Allocate(
         const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
     C2WriteView writeView(block->map().get());
     if (writeView.error() != C2_OK) {
@@ -76,15 +153,23 @@
     return new LinearBlockBuffer(format, std::move(writeView), block);
 }
 
-C2ConstLinearBlock LinearBlockBuffer::share() {
-    return mBlock->share(offset(), size(), C2Fence());
+std::shared_ptr<C2Buffer> LinearBlockBuffer::asC2Buffer() {
+    return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
+}
+
+bool LinearBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+    return canCopyLinear(buffer);
+}
+
+bool LinearBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    return copyLinear(buffer);
 }
 
 LinearBlockBuffer::LinearBlockBuffer(
         const sp<AMessage> &format,
         C2WriteView&& writeView,
         const std::shared_ptr<C2LinearBlock> &block)
-    : MediaCodecBuffer(format, new ABuffer(writeView.data(), writeView.size())),
+    : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
       mWriteView(writeView),
       mBlock(block) {
 }
@@ -92,23 +177,34 @@
 // ConstLinearBlockBuffer
 
 // static
-sp<ConstLinearBlockBuffer> ConstLinearBlockBuffer::allocate(
-        const sp<AMessage> &format, const C2ConstLinearBlock &block) {
-    C2ReadView readView(block.map().get());
+sp<ConstLinearBlockBuffer> ConstLinearBlockBuffer::Allocate(
+        const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer) {
+    if (!buffer
+            || buffer->data().type() != C2BufferData::LINEAR
+            || buffer->data().linearBlocks().size() != 1u) {
+        return nullptr;
+    }
+    C2ReadView readView(buffer->data().linearBlocks()[0].map().get());
     if (readView.error() != C2_OK) {
         return nullptr;
     }
-    return new ConstLinearBlockBuffer(format, std::move(readView));
+    return new ConstLinearBlockBuffer(format, std::move(readView), buffer);
 }
 
 ConstLinearBlockBuffer::ConstLinearBlockBuffer(
         const sp<AMessage> &format,
-        C2ReadView&& readView)
-    : MediaCodecBuffer(format, new ABuffer(
+        C2ReadView&& readView,
+        const std::shared_ptr<C2Buffer> &buffer)
+    : Codec2Buffer(format, new ABuffer(
             // NOTE: ABuffer only takes non-const pointer but this data is
             //       supposed to be read-only.
             const_cast<uint8_t *>(readView.data()), readView.capacity())),
-      mReadView(readView) {
+      mReadView(readView),
+      mBufferRef(buffer) {
+}
+
+std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
+    return std::move(mBufferRef);
 }
 
 }  // namespace android
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index 6fba890..449c6aa 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -138,7 +138,7 @@
     virtual bool registerBuffer(
             const std::shared_ptr<C2Buffer> &buffer,
             size_t *index,
-            sp<MediaCodecBuffer> *codecBuffer) = 0;
+            sp<MediaCodecBuffer> *clientBuffer) = 0;
 
     /**
      * Register codec specific data as a buffer to be consistent with
@@ -147,9 +147,7 @@
     virtual bool registerCsd(
             const C2StreamCsdInfo::output * /* csd */,
             size_t * /* index */,
-            sp<MediaCodecBuffer> * /* codecBuffer */) {
-        return false;
-    }
+            sp<MediaCodecBuffer> * /* clientBuffer */) = 0;
 
     /**
      * Release the buffer obtained from registerBuffer() and get the
@@ -181,23 +179,6 @@
 const static size_t kMinBufferArraySize = 16;
 const static size_t kLinearBufferSize = 524288;
 
-template <class T>
-ssize_t findBufferSlot(
-        std::vector<T> *buffers,
-        size_t maxSize,
-        std::function<bool(const T&)> pred) {
-    auto it = std::find_if(buffers->begin(), buffers->end(), pred);
-    if (it == buffers->end()) {
-        if (buffers->size() < maxSize) {
-            buffers->emplace_back();
-            return buffers->size() - 1;
-        } else {
-            return -1;
-        }
-    }
-    return std::distance(buffers->begin(), it);
-}
-
 sp<LinearBlockBuffer> allocateLinearBuffer(
         const std::shared_ptr<C2BlockPool> &pool,
         const sp<AMessage> &format,
@@ -205,41 +186,209 @@
         const C2MemoryUsage &usage) {
     std::shared_ptr<C2LinearBlock> block;
 
-    status_t err = pool->fetchLinearBlock(
-            size,
-            usage,
-            &block);
+    status_t err = pool->fetchLinearBlock(size, usage, &block);
     if (err != OK) {
         return nullptr;
     }
 
-    return LinearBlockBuffer::allocate(format, block);
+    return LinearBlockBuffer::Allocate(format, block);
 }
 
-class Buffer1D : public C2Buffer {
+class BuffersArrayImpl;
+
+/**
+ * Flexible buffer slots implementation.
+ */
+class FlexBuffersImpl {
 public:
-    explicit Buffer1D(C2ConstLinearBlock block) : C2Buffer({ block }) {}
+    FlexBuffersImpl() = default;
+
+    /**
+     * Assign an empty slot for a buffer and return the index. If there's no
+     * empty slot, just add one at the end and return it.
+     *
+     * \param buffer[in]  a new buffer to assign a slot.
+     * \return            index of the assigned slot.
+     */
+    size_t assignSlot(const sp<Codec2Buffer> &buffer) {
+        for (size_t i = 0; i < mBuffers.size(); ++i) {
+            if (mBuffers[i].clientBuffer.promote() == nullptr
+                    && mBuffers[i].compBuffer.expired()) {
+                mBuffers[i].clientBuffer = buffer;
+                return i;
+            }
+        }
+        mBuffers.push_back({ buffer, std::weak_ptr<C2Buffer>() });
+        return mBuffers.size() - 1;
+    }
+
+    /**
+     * Release the slot from the client, and get the C2Buffer object back from
+     * the previously assigned buffer. Note that the slot is not completely free
+     * until the returned C2Buffer object is freed.
+     *
+     * \param buffer[in]  the buffer previously assigned a slot.
+     * \return            C2Buffer object from |buffer|.
+     */
+    std::shared_ptr<C2Buffer> releaseSlot(const sp<MediaCodecBuffer> &buffer) {
+        sp<Codec2Buffer> c2Buffer;
+        size_t index = mBuffers.size();
+        for (size_t i = 0; i < mBuffers.size(); ++i) {
+            if (mBuffers[i].clientBuffer.promote() == buffer) {
+                c2Buffer = mBuffers[i].clientBuffer.promote();
+                index = i;
+                break;
+            }
+        }
+        if (c2Buffer == nullptr) {
+            ALOGD("No matching buffer found");
+            return nullptr;
+        }
+        std::shared_ptr<C2Buffer> result = c2Buffer->asC2Buffer();
+        mBuffers[index].compBuffer = result;
+        return result;
+    }
+
+private:
+    friend class BuffersArrayImpl;
+
+    struct Entry {
+        wp<Codec2Buffer> clientBuffer;
+        std::weak_ptr<C2Buffer> compBuffer;
+    };
+    std::vector<Entry> mBuffers;
 };
 
-class Buffer2D : public C2Buffer {
+/**
+ * Static buffer slots implementation based on a fixed-size array.
+ */
+class BuffersArrayImpl {
 public:
-    explicit Buffer2D(C2ConstGraphicBlock block) : C2Buffer({ block }) {}
+    /**
+     * Initialize buffer array from the original |impl|. The buffers known by
+     * the client is preserved, and the empty slots are populated so that the
+     * array size is at least |minSize|.
+     *
+     * \param impl[in]      FlexBuffersImpl object used so far.
+     * \param minSize[in]   minimum size of the buffer array.
+     * \param allocate[in]  function to allocate a client buffer for an empty slot.
+     */
+    void initialize(
+            const FlexBuffersImpl &impl,
+            size_t minSize,
+            std::function<sp<Codec2Buffer>()> allocate) {
+        for (size_t i = 0; i < impl.mBuffers.size(); ++i) {
+            sp<Codec2Buffer> clientBuffer = impl.mBuffers[i].clientBuffer.promote();
+            bool ownedByClient = (clientBuffer != nullptr);
+            if (!ownedByClient) {
+                clientBuffer = allocate();
+            }
+            mBuffers.push_back({ clientBuffer, impl.mBuffers[i].compBuffer, ownedByClient });
+        }
+        for (size_t i = impl.mBuffers.size(); i < minSize; ++i) {
+            mBuffers.push_back({ allocate(), std::weak_ptr<C2Buffer>(), false });
+        }
+    }
+
+    /**
+     * Grab a buffer from the underlying array which matches the criteria.
+     *
+     * \param index[out]    index of the slot.
+     * \param buffer[out]   the matching buffer.
+     * \param match[in]     a function to test whether the buffer matches the
+     *                      criteria or not.
+     * \return OK           if successful,
+     *         NO_MEMORY    if there's no available slot meets the criteria.
+     */
+    status_t grabBuffer(
+            size_t *index,
+            sp<Codec2Buffer> *buffer,
+            std::function<bool(const sp<Codec2Buffer> &)> match =
+                [](const sp<Codec2Buffer> &) { return true; }) {
+        for (size_t i = 0; i < mBuffers.size(); ++i) {
+            if (!mBuffers[i].ownedByClient && mBuffers[i].compBuffer.expired()
+                    && match(mBuffers[i].clientBuffer)) {
+                mBuffers[i].ownedByClient = true;
+                *buffer = mBuffers[i].clientBuffer;
+                (*buffer)->meta()->clear();
+                (*buffer)->setRange(0, (*buffer)->capacity());
+                *index = i;
+                return OK;
+            }
+        }
+        return NO_MEMORY;
+    }
+
+    /**
+     * Return the buffer from the client, and get the C2Buffer object back from
+     * the buffer. Note that the slot is not completely free until the returned
+     * C2Buffer object is freed.
+     *
+     * \param buffer[in]  the buffer previously grabbed.
+     * \return            C2Buffer object from |buffer|.
+     */
+    std::shared_ptr<C2Buffer> returnBuffer(const sp<MediaCodecBuffer> &buffer) {
+        sp<Codec2Buffer> c2Buffer;
+        size_t index = mBuffers.size();
+        for (size_t i = 0; i < mBuffers.size(); ++i) {
+            if (mBuffers[i].clientBuffer == buffer) {
+                if (!mBuffers[i].ownedByClient) {
+                    ALOGD("Client returned a buffer it does not own according to our record: %zu", i);
+                }
+                c2Buffer = mBuffers[i].clientBuffer;
+                mBuffers[i].ownedByClient = false;
+                index = i;
+                break;
+            }
+        }
+        if (c2Buffer == nullptr) {
+            ALOGD("No matching buffer found");
+            return nullptr;
+        }
+        std::shared_ptr<C2Buffer> result = c2Buffer->asC2Buffer();
+        mBuffers[index].compBuffer = result;
+        return result;
+    }
+
+    /**
+     * Populate |array| with the underlying buffer array.
+     *
+     * \param array[out]  an array to be filled with the underlying buffer array.
+     */
+    void getArray(Vector<sp<MediaCodecBuffer>> *array) const {
+        array->clear();
+        for (const Entry &entry : mBuffers) {
+            array->push(entry.clientBuffer);
+        }
+    }
+
+    /**
+     * The client abandoned all known buffers, so reclaim the ownership.
+     */
+    void flush() {
+        for (Entry &entry : mBuffers) {
+            entry.ownedByClient = false;
+        }
+    }
+
+private:
+    struct Entry {
+        const sp<Codec2Buffer> clientBuffer;
+        std::weak_ptr<C2Buffer> compBuffer;
+        bool ownedByClient;
+    };
+    std::vector<Entry> mBuffers;
 };
 
 class InputBuffersArray : public CCodecBufferChannel::InputBuffers {
 public:
     InputBuffersArray() = default;
 
-    void add(
-            size_t index,
-            const sp<LinearBlockBuffer> &clientBuffer,
-            bool available) {
-        if (mBufferArray.size() <= index) {
-            // TODO: make this more efficient
-            mBufferArray.resize(index + 1);
-        }
-        mBufferArray[index].clientBuffer = clientBuffer;
-        mBufferArray[index].available = available;
+    void initialize(
+            const FlexBuffersImpl &impl,
+            size_t minSize,
+            std::function<sp<Codec2Buffer>()> allocate) {
+        mImpl.initialize(impl, minSize, allocate);
     }
 
     bool isArrayMode() const final { return true; }
@@ -249,52 +398,30 @@
     }
 
     void getArray(Vector<sp<MediaCodecBuffer>> *array) const final {
-        array->clear();
-        for (const Entry &entry : mBufferArray) {
-            array->push(entry.clientBuffer);
-        }
+        mImpl.getArray(array);
     }
 
     bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
-        for (size_t i = 0; i < mBufferArray.size(); ++i) {
-            if (mBufferArray[i].available && mBufferArray[i].compBuffer.expired()) {
-                mBufferArray[i].available = false;
-                *index = i;
-                *buffer = mBufferArray[i].clientBuffer;
-                (*buffer)->meta()->clear();
-                (*buffer)->setRange(0, (*buffer)->capacity());
-                return true;
-            }
+        sp<Codec2Buffer> c2Buffer;
+        status_t err = mImpl.grabBuffer(index, &c2Buffer);
+        if (err == OK) {
+            c2Buffer->setFormat(mFormat);
+            *buffer = c2Buffer;
+            return true;
         }
         return false;
     }
 
     std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
-        for (size_t i = 0; i < mBufferArray.size(); ++i) {
-            if (!mBufferArray[i].available && mBufferArray[i].clientBuffer == buffer) {
-                std::shared_ptr<C2Buffer> buffer = std::make_shared<Buffer1D>(mBufferArray[i].clientBuffer->share());
-                mBufferArray[i].available = true;
-                mBufferArray[i].compBuffer = buffer;
-                return buffer;
-            }
-        }
-        return nullptr;
+        return mImpl.returnBuffer(buffer);
     }
 
     void flush() override {
-        for (size_t i = 0; i < mBufferArray.size(); ++i) {
-            mBufferArray[i].available = true;
-        }
+        mImpl.flush();
     }
 
 private:
-    struct Entry {
-        sp<LinearBlockBuffer> clientBuffer;
-        std::weak_ptr<C2Buffer> compBuffer;
-        bool available;
-    };
-
-    std::vector<Entry> mBufferArray;
+    BuffersArrayImpl mImpl;
 };
 
 class LinearInputBuffers : public CCodecBufferChannel::InputBuffers {
@@ -302,68 +429,42 @@
     using CCodecBufferChannel::InputBuffers::InputBuffers;
 
     bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
-        *buffer = nullptr;
-        ssize_t ret = findBufferSlot<wp<LinearBlockBuffer>>(
-                &mBuffers, kMinBufferArraySize,
-                [] (const auto &elem) { return elem.promote() == nullptr; });
-        if (ret < 0) {
-            return false;
-        }
-        // TODO: proper max input size and usage
+        // TODO: proper max input size
         // TODO: read usage from intf
         C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
         sp<LinearBlockBuffer> newBuffer = allocateLinearBuffer(mPool, mFormat, kLinearBufferSize, usage);
         if (newBuffer == nullptr) {
             return false;
         }
-        mBuffers[ret] = newBuffer;
-        *index = ret;
+        *index = mImpl.assignSlot(newBuffer);
         *buffer = newBuffer;
         return true;
     }
 
     std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
-        auto it = std::find(mBuffers.begin(), mBuffers.end(), buffer);
-        if (it == mBuffers.end()) {
-            return nullptr;
-        }
-        sp<LinearBlockBuffer> codecBuffer = it->promote();
-        // We got sp<> reference from the caller so this should never happen..
-        CHECK(codecBuffer != nullptr);
-        return std::make_shared<Buffer1D>(codecBuffer->share());
+        return mImpl.releaseSlot(buffer);
     }
 
     void flush() override {
+        // This is no-op by default unless we're in array mode where we need to keep
+        // track of the flushed work.
     }
 
     std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
         std::unique_ptr<InputBuffersArray> array(new InputBuffersArray);
         array->setFormat(mFormat);
-        // TODO
-        const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
-        mBuffers.resize(size);
-        for (size_t i = 0; i < size; ++i) {
-            sp<LinearBlockBuffer> clientBuffer = mBuffers[i].promote();
-            bool available = false;
-            if (clientBuffer == nullptr) {
-                // TODO: proper max input size
-                // TODO: read usage from intf
-                C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-                clientBuffer = allocateLinearBuffer(mPool, mFormat, kLinearBufferSize, usage);
-                available = true;
-            }
-            array->add(
-                    i,
-                    clientBuffer,
-                    available);
-        }
+        array->initialize(
+                mImpl,
+                kMinBufferArraySize,
+                [pool = mPool, format = mFormat] () -> sp<Codec2Buffer> {
+                    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+                    return allocateLinearBuffer(pool, format, kLinearBufferSize, usage);
+                });
         return std::move(array);
     }
 
 private:
-    // Buffers we passed to the client. The index of a buffer matches what
-    // was passed in BufferCallback::onInputBufferAvailable().
-    std::vector<wp<LinearBlockBuffer>> mBuffers;
+    FlexBuffersImpl mImpl;
 };
 
 class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
@@ -396,18 +497,11 @@
 public:
     using CCodecBufferChannel::OutputBuffers::OutputBuffers;
 
-    void add(
-            size_t index,
-            const sp<MediaCodecBuffer> &clientBuffer,
-            const std::shared_ptr<C2Buffer> &compBuffer,
-            bool available) {
-        if (mBufferArray.size() <= index) {
-            // TODO: make this more efficient
-            mBufferArray.resize(index + 1);
-        }
-        mBufferArray[index].clientBuffer = clientBuffer;
-        mBufferArray[index].compBuffer = compBuffer;
-        mBufferArray[index].available = available;
+    void initialize(
+            const FlexBuffersImpl &impl,
+            size_t minSize,
+            std::function<sp<Codec2Buffer>()> allocate) {
+        mImpl.initialize(impl, minSize, allocate);
     }
 
     bool isArrayMode() const final { return true; }
@@ -419,125 +513,69 @@
     bool registerBuffer(
             const std::shared_ptr<C2Buffer> &buffer,
             size_t *index,
-            sp<MediaCodecBuffer> *codecBuffer) final {
-        for (size_t i = 0; i < mBufferArray.size(); ++i) {
-            if (mBufferArray[i].available && copy(buffer, mBufferArray[i].clientBuffer)) {
-                *index = i;
-                *codecBuffer = mBufferArray[i].clientBuffer;
-                (*codecBuffer)->setFormat(mFormat);
-                (*codecBuffer)->meta()->clear();
-                mBufferArray[i].compBuffer = buffer;
-                mBufferArray[i].available = false;
-                return true;
-            }
+            sp<MediaCodecBuffer> *clientBuffer) final {
+        sp<Codec2Buffer> c2Buffer;
+        status_t err = mImpl.grabBuffer(
+                index,
+                &c2Buffer,
+                [buffer](const sp<Codec2Buffer> &clientBuffer) {
+                    return clientBuffer->canCopy(buffer);
+                });
+        if (err != OK) {
+            return false;
         }
-        return false;
+        if (!c2Buffer->copy(buffer)) {
+            return false;
+        }
+        c2Buffer->setFormat(mFormat);
+        *clientBuffer = c2Buffer;
+        return true;
     }
 
     bool registerCsd(
             const C2StreamCsdInfo::output *csd,
             size_t *index,
-            sp<MediaCodecBuffer> *codecBuffer) final {
-        for (size_t i = 0; i < mBufferArray.size(); ++i) {
-            if (mBufferArray[i].available
-                    && mBufferArray[i].clientBuffer->capacity() >= csd->flexCount()) {
-                // TODO: proper format update
-                sp<ABuffer> csdBuffer = ABuffer::CreateAsCopy(csd->m.value, csd->flexCount());
-                mFormat = mFormat->dup();
-                mFormat->setBuffer("csd-0", csdBuffer);
-
-                memcpy(mBufferArray[i].clientBuffer->base(), csd->m.value, csd->flexCount());
-                mBufferArray[i].clientBuffer->setRange(0, csd->flexCount());
-                *index = i;
-                *codecBuffer = mBufferArray[i].clientBuffer;
-                (*codecBuffer)->setFormat(mFormat);
-                (*codecBuffer)->meta()->clear();
-                mBufferArray[i].available = false;
-                return true;
-            }
+            sp<MediaCodecBuffer> *clientBuffer) final {
+        sp<Codec2Buffer> c2Buffer;
+        status_t err = mImpl.grabBuffer(
+                index,
+                &c2Buffer,
+                [csd](const sp<Codec2Buffer> &clientBuffer) {
+                    return clientBuffer->base() != nullptr
+                            && clientBuffer->capacity() >= csd->flexCount();
+                });
+        if (err != OK) {
+            return false;
         }
-        return false;
+        // TODO: proper format update
+        sp<ABuffer> csdBuffer = ABuffer::CreateAsCopy(csd->m.value, csd->flexCount());
+        mFormat = mFormat->dup();
+        mFormat->setBuffer("csd-0", csdBuffer);
+
+        memcpy(c2Buffer->base(), csd->m.value, csd->flexCount());
+        c2Buffer->setRange(0, csd->flexCount());
+        c2Buffer->setFormat(mFormat);
+        *clientBuffer = c2Buffer;
+        return true;
     }
 
     std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) final {
-        for (size_t i = 0; i < mBufferArray.size(); ++i) {
-            if (!mBufferArray[i].available && mBufferArray[i].clientBuffer == buffer) {
-                mBufferArray[i].available = true;
-                return std::move(mBufferArray[i].compBuffer);
-            }
-        }
-        return nullptr;
+        return mImpl.returnBuffer(buffer);
     }
 
-    void flush(
-            const std::list<std::unique_ptr<C2Work>> &flushedWork) override {
+    void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) override {
         (void) flushedWork;
-        for (size_t i = 0; i < mBufferArray.size(); ++i) {
-            mBufferArray[i].available = true;
-            mBufferArray[i].compBuffer.reset();
-        }
+        mImpl.flush();
     }
 
-    virtual bool copy(
-            const std::shared_ptr<C2Buffer> &buffer,
-            const sp<MediaCodecBuffer> &clientBuffer) = 0;
-
     void getArray(Vector<sp<MediaCodecBuffer>> *array) const final {
-        array->clear();
-        for (const Entry &entry : mBufferArray) {
-            array->push(entry.clientBuffer);
-        }
+        mImpl.getArray(array);
     }
 
 private:
-    struct Entry {
-        sp<MediaCodecBuffer> clientBuffer;
-        std::shared_ptr<C2Buffer> compBuffer;
-        bool available;
-    };
-
-    std::vector<Entry> mBufferArray;
+    BuffersArrayImpl mImpl;
 };
 
-class LinearOutputBuffersArray : public OutputBuffersArray {
-public:
-    using OutputBuffersArray::OutputBuffersArray;
-
-    bool copy(
-            const std::shared_ptr<C2Buffer> &buffer,
-            const sp<MediaCodecBuffer> &clientBuffer) final {
-        if (!buffer) {
-            clientBuffer->setRange(0u, 0u);
-            return true;
-        }
-        C2ReadView view = buffer->data().linearBlocks().front().map().get();
-        if (clientBuffer->capacity() < view.capacity()) {
-            ALOGV("view.capacity() = %u", view.capacity());
-            return false;
-        }
-        clientBuffer->setRange(0u, view.capacity());
-        memcpy(clientBuffer->data(), view.data(), view.capacity());
-        return true;
-    }
-};
-
-class GraphicOutputBuffersArray : public OutputBuffersArray {
-public:
-    using OutputBuffersArray::OutputBuffersArray;
-
-    bool copy(
-            const std::shared_ptr<C2Buffer> &buffer,
-            const sp<MediaCodecBuffer> &clientBuffer) final {
-        if (!buffer) {
-            clientBuffer->setRange(0u, 0u);
-            return true;
-        }
-        clientBuffer->setRange(0u, 1u);
-        return true;
-    }
-};
-
-// Flexible in a sense that it does not have fixed array size.
 class FlexOutputBuffers : public CCodecBufferChannel::OutputBuffers {
 public:
     using CCodecBufferChannel::OutputBuffers::OutputBuffers;
@@ -545,56 +583,31 @@
     bool registerBuffer(
             const std::shared_ptr<C2Buffer> &buffer,
             size_t *index,
-            sp<MediaCodecBuffer> *codecBuffer) override {
-        *codecBuffer = nullptr;
-        ssize_t ret = findBufferSlot<BufferInfo>(
-                &mBuffers,
-                std::numeric_limits<size_t>::max(),
-                [] (const auto &elem) { return elem.clientBuffer.promote() == nullptr; });
-        if (ret < 0) {
-            return false;
-        }
-        sp<MediaCodecBuffer> newBuffer = convert(buffer);
-        mBuffers[ret] = { newBuffer, buffer };
-        *index = ret;
-        *codecBuffer = newBuffer;
+            sp<MediaCodecBuffer> *clientBuffer) override {
+        sp<Codec2Buffer> newBuffer = wrap(buffer);
+        *index = mImpl.assignSlot(newBuffer);
+        *clientBuffer = newBuffer;
         return true;
     }
 
     bool registerCsd(
             const C2StreamCsdInfo::output *csd,
             size_t *index,
-            sp<MediaCodecBuffer> *codecBuffer) final {
-        *codecBuffer = nullptr;
-        ssize_t ret = findBufferSlot<BufferInfo>(
-                &mBuffers,
-                std::numeric_limits<size_t>::max(),
-                [] (const auto &elem) { return elem.clientBuffer.promote() == nullptr; });
-        if (ret < 0) {
-            return false;
-        }
+            sp<MediaCodecBuffer> *clientBuffer) final {
         // TODO: proper format update
         sp<ABuffer> csdBuffer = ABuffer::CreateAsCopy(csd->m.value, csd->flexCount());
         mFormat = mFormat->dup();
         mFormat->setBuffer("csd-0", csdBuffer);
-        sp<MediaCodecBuffer> newBuffer = new MediaCodecBuffer(mFormat, csdBuffer);
-        mBuffers[ret] = { newBuffer, nullptr };
-        *index = ret;
-        *codecBuffer = newBuffer;
+
+        sp<Codec2Buffer> newBuffer = new LocalLinearBuffer(mFormat, csdBuffer);
+        *index = mImpl.assignSlot(newBuffer);
+        *clientBuffer = newBuffer;
         return true;
     }
 
     std::shared_ptr<C2Buffer> releaseBuffer(
             const sp<MediaCodecBuffer> &buffer) override {
-        auto it = std::find_if(
-                mBuffers.begin(), mBuffers.end(),
-                [buffer] (const auto &elem) {
-                    return elem.clientBuffer.promote() == buffer;
-                });
-        if (it == mBuffers.end()) {
-            return nullptr;
-        }
-        return std::move(it->bufferRef);
+        return mImpl.releaseSlot(buffer);
     }
 
     void flush(
@@ -604,27 +617,31 @@
         // track of the flushed work.
     }
 
-    virtual sp<MediaCodecBuffer> convert(const std::shared_ptr<C2Buffer> &buffer) = 0;
+    std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
+        std::unique_ptr<OutputBuffersArray> array(new OutputBuffersArray);
+        array->setFormat(mFormat);
+        array->initialize(
+                mImpl,
+                kMinBufferArraySize,
+                [this]() { return allocateArrayBuffer(); });
+        return std::move(array);
+    }
 
-protected:
-    struct BufferInfo {
-        // wp<> of MediaCodecBuffer for MediaCodec.
-        wp<MediaCodecBuffer> clientBuffer;
-        // Buffer reference to hold until clientBuffer is valid.
-        std::shared_ptr<C2Buffer> bufferRef;
-    };
-    // Buffers we passed to the client. The index of a buffer matches what
-    // was passed in BufferCallback::onInputBufferAvailable().
-    std::vector<BufferInfo> mBuffers;
+    virtual sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) = 0;
+
+    virtual sp<Codec2Buffer> allocateArrayBuffer() = 0;
+
+private:
+    FlexBuffersImpl mImpl;
 };
 
 class LinearOutputBuffers : public FlexOutputBuffers {
 public:
     using FlexOutputBuffers::FlexOutputBuffers;
 
-    virtual sp<MediaCodecBuffer> convert(const std::shared_ptr<C2Buffer> &buffer) override {
+    sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) override {
         if (buffer == nullptr) {
-            return new MediaCodecBuffer(mFormat, new ABuffer(nullptr, 0));
+            return new DummyContainerBuffer(mFormat, buffer);
         }
         if (buffer->data().type() != C2BufferData::LINEAR) {
             // We expect linear output buffers from the component.
@@ -634,28 +651,12 @@
             // We expect one and only one linear block from the component.
             return nullptr;
         }
-        return ConstLinearBlockBuffer::allocate(mFormat, buffer->data().linearBlocks().front());
+        return ConstLinearBlockBuffer::Allocate(mFormat, buffer);
     }
 
-    std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
-        std::unique_ptr<OutputBuffersArray> array(new LinearOutputBuffersArray);
-        array->setFormat(mFormat);
-
-        const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
-        mBuffers.resize(size);
-        for (size_t i = 0; i < size; ++i) {
-            sp<MediaCodecBuffer> clientBuffer = mBuffers[i].clientBuffer.promote();
-            std::shared_ptr<C2Buffer> compBuffer = mBuffers[i].bufferRef;
-            bool available = false;
-            if (clientBuffer == nullptr) {
-                // TODO: proper max input size
-                clientBuffer = new MediaCodecBuffer(mFormat, new ABuffer(kLinearBufferSize));
-                available = true;
-                compBuffer.reset();
-            }
-            array->add(i, clientBuffer, compBuffer, available);
-        }
-        return std::move(array);
+    sp<Codec2Buffer> allocateArrayBuffer() override {
+        // TODO: proper max output size
+        return new LocalLinearBuffer(mFormat, new ABuffer(kLinearBufferSize));
     }
 };
 
@@ -663,29 +664,12 @@
 public:
     using FlexOutputBuffers::FlexOutputBuffers;
 
-    sp<MediaCodecBuffer> convert(const std::shared_ptr<C2Buffer> &buffer) override {
-        return new MediaCodecBuffer(
-                mFormat, buffer ? new ABuffer(nullptr, 1) : new ABuffer(nullptr, 0));
+    sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) override {
+        return new DummyContainerBuffer(mFormat, buffer);
     }
 
-    std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
-        std::unique_ptr<OutputBuffersArray> array(new GraphicOutputBuffersArray);
-        array->setFormat(mFormat);
-
-        const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
-        mBuffers.resize(size);
-        for (size_t i = 0; i < size; ++i) {
-            sp<MediaCodecBuffer> clientBuffer = mBuffers[i].clientBuffer.promote();
-            std::shared_ptr<C2Buffer> compBuffer = mBuffers[i].bufferRef;
-            bool available = false;
-            if (clientBuffer == nullptr) {
-                clientBuffer = new MediaCodecBuffer(mFormat, new ABuffer(nullptr, 1));
-                available = true;
-                compBuffer.reset();
-            }
-            array->add(i, clientBuffer, compBuffer, available);
-        }
-        return std::move(array);
+    sp<Codec2Buffer> allocateArrayBuffer() override {
+        return new DummyContainerBuffer(mFormat);
     }
 };
 
diff --git a/media/libstagefright/codec2/SimpleC2Component.cpp b/media/libstagefright/codec2/SimpleC2Component.cpp
index 01e9e43..aaefd31 100644
--- a/media/libstagefright/codec2/SimpleC2Component.cpp
+++ b/media/libstagefright/codec2/SimpleC2Component.cpp
@@ -366,26 +366,6 @@
     }
 }
 
-namespace {
-
-class GraphicBuffer : public C2Buffer {
-public:
-    GraphicBuffer(
-            const std::shared_ptr<C2GraphicBlock> &block,
-            const C2Rect &crop)
-        : C2Buffer({ block->share(crop, ::android::C2Fence()) }) {}
-};
-
-
-class LinearBuffer : public C2Buffer {
-public:
-    LinearBuffer(
-            const std::shared_ptr<C2LinearBlock> &block, size_t offset, size_t size)
-        : C2Buffer({ block->share(offset, size, ::android::C2Fence()) }) {}
-};
-
-}  // namespace
-
 std::shared_ptr<C2Buffer> SimpleC2Component::createLinearBuffer(
         const std::shared_ptr<C2LinearBlock> &block) {
     return createLinearBuffer(block, block->offset(), block->size());
@@ -393,7 +373,7 @@
 
 std::shared_ptr<C2Buffer> SimpleC2Component::createLinearBuffer(
         const std::shared_ptr<C2LinearBlock> &block, size_t offset, size_t size) {
-    return std::make_shared<LinearBuffer>(block, offset, size);
+    return C2Buffer::CreateLinearBuffer(block->share(offset, size, ::android::C2Fence()));
 }
 
 std::shared_ptr<C2Buffer> SimpleC2Component::createGraphicBuffer(
@@ -402,9 +382,8 @@
 }
 
 std::shared_ptr<C2Buffer> SimpleC2Component::createGraphicBuffer(
-        const std::shared_ptr<C2GraphicBlock> &block,
-        const C2Rect &crop) {
-    return std::make_shared<GraphicBuffer>(block, crop);
+        const std::shared_ptr<C2GraphicBlock> &block, const C2Rect &crop) {
+    return C2Buffer::CreateGraphicBuffer(block->share(crop, ::android::C2Fence()));
 }
 
 } // namespace android
diff --git a/media/libstagefright/include/Codec2Buffer.h b/media/libstagefright/include/Codec2Buffer.h
index 6d85e83..9766b41 100644
--- a/media/libstagefright/include/Codec2Buffer.h
+++ b/media/libstagefright/include/Codec2Buffer.h
@@ -24,17 +24,100 @@
 
 namespace android {
 
+class Codec2Buffer : public MediaCodecBuffer {
+public:
+    using MediaCodecBuffer::MediaCodecBuffer;
+    ~Codec2Buffer() override = default;
+
+    /**
+     * \return  C2Buffer object represents this buffer.
+     */
+    virtual std::shared_ptr<C2Buffer> asC2Buffer() = 0;
+
+    /**
+     * Test if we can copy the content of |buffer| into this object.
+     *
+     * \param   buffer  C2Buffer object to copy.
+     * \return  true    if the content of buffer can be copied over to this buffer
+     *          false   otherwise.
+     */
+    virtual bool canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+        (void)buffer;
+        return false;
+    }
+
+    /**
+     * Copy the content of |buffer| into this object. This method assumes that
+     * canCopy() check already passed.
+     *
+     * \param   buffer  C2Buffer object to copy.
+     * \return  true    if successful
+     *          false   otherwise.
+     */
+    virtual bool copy(const std::shared_ptr<C2Buffer> &buffer) {
+        (void)buffer;
+        return false;
+    }
+
+protected:
+    /**
+     * canCopy() implementation for linear buffers.
+     */
+    bool canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const;
+
+    /**
+     * copy() implementation for linear buffers.
+     */
+    bool copyLinear(const std::shared_ptr<C2Buffer> &buffer);
+};
+
+/**
+ * MediaCodecBuffer implementation on top of local linear buffer. This cannot
+ * cross process boundary so asC2Buffer() returns only nullptr.
+ */
+class LocalLinearBuffer : public Codec2Buffer {
+public:
+    using Codec2Buffer::Codec2Buffer;
+
+    std::shared_ptr<C2Buffer> asC2Buffer() override { return nullptr; }
+    bool canCopy(const std::shared_ptr<C2Buffer> &buffer) const override;
+    bool copy(const std::shared_ptr<C2Buffer> &buffer) override;
+};
+
+/**
+ * MediaCodecBuffer implementation to be used only as a dummy wrapper around a
+ * C2Buffer object.
+ */
+class DummyContainerBuffer : public Codec2Buffer {
+public:
+    DummyContainerBuffer(
+            const sp<AMessage> &format,
+            const std::shared_ptr<C2Buffer> &buffer = nullptr);
+
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+    bool canCopy(const std::shared_ptr<C2Buffer> &buffer) const override;
+    bool copy(const std::shared_ptr<C2Buffer> &buffer) override;
+
+private:
+    std::shared_ptr<C2Buffer> mBufferRef;
+};
+
 /**
  * MediaCodecBuffer implementation wraps around C2LinearBlock.
  */
-class LinearBlockBuffer : public MediaCodecBuffer {
+class LinearBlockBuffer : public Codec2Buffer {
 public:
-    static sp<LinearBlockBuffer> allocate(
+    /**
+     * Allocate a new LinearBufferBlock wrapping around C2LinearBlock object.
+     */
+    static sp<LinearBlockBuffer> Allocate(
             const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block);
 
     virtual ~LinearBlockBuffer() = default;
 
-    C2ConstLinearBlock share();
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+    bool canCopy(const std::shared_ptr<C2Buffer> &buffer) const override;
+    bool copy(const std::shared_ptr<C2Buffer> &buffer) override;
 
 private:
     LinearBlockBuffer(
@@ -50,20 +133,27 @@
 /**
  * MediaCodecBuffer implementation wraps around C2ConstLinearBlock.
  */
-class ConstLinearBlockBuffer : public MediaCodecBuffer {
+class ConstLinearBlockBuffer : public Codec2Buffer {
 public:
-    static sp<ConstLinearBlockBuffer> allocate(
-            const sp<AMessage> &format, const C2ConstLinearBlock &block);
+    /**
+     * Allocate a new ConstLinearBlockBuffer wrapping around C2Buffer object.
+     */
+    static sp<ConstLinearBlockBuffer> Allocate(
+            const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer);
 
     virtual ~ConstLinearBlockBuffer() = default;
 
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+
 private:
     ConstLinearBlockBuffer(
             const sp<AMessage> &format,
-            C2ReadView &&readView);
+            C2ReadView &&readView,
+            const std::shared_ptr<C2Buffer> &buffer);
     ConstLinearBlockBuffer() = delete;
 
     C2ReadView mReadView;
+    std::shared_ptr<C2Buffer> mBufferRef;
 };
 
 }  // namespace android
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index 9538c3d..63c0697 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -37,7 +37,7 @@
     //               not to expose other methods to the controller whose connection wasn't accepted.
     //               But this would be enough for now because it's the same as existing
     //               MediaBrowser and MediaBrowserService.
-    void connect(String callingPackage, IMediaSession2Callback callback);
+    void connect(IMediaSession2Callback caller, String callingPackage);
     void release(IMediaSession2Callback caller);
 
     void setVolumeTo(IMediaSession2Callback caller, int value, int flags);
@@ -52,21 +52,24 @@
     void sendCustomCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args,
             in ResultReceiver receiver);
 
-    void prepareFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extra);
-    void prepareFromSearch(IMediaSession2Callback caller, String query, in Bundle extra);
-    void prepareFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extra);
-    void playFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extra);
-    void playFromSearch(IMediaSession2Callback caller, String query, in Bundle extra);
-    void playFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extra);
+    void prepareFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
+    void prepareFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
+    void prepareFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
+    void playFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
+    void playFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
+    void playFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
+    void setRating(IMediaSession2Callback caller, String mediaId, in Bundle rating);
 
-   //////////////////////////////////////////////////////////////////////////////////////////////
-    // Get library service specific
     //////////////////////////////////////////////////////////////////////////////////////////////
-    void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints);
-    void getItem(IMediaSession2Callback callback, String mediaId);
-    void getChildren(IMediaSession2Callback callback, String parentId, int page, int pageSize,
+    // library service specific
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    void getBrowserRoot(IMediaSession2Callback caller, in Bundle rootHints);
+    void getItem(IMediaSession2Callback caller, String mediaId);
+    void getChildren(IMediaSession2Callback caller, String parentId, int page, int pageSize,
             in Bundle extras);
-    void search(IMediaSession2Callback callback, String query, in Bundle extras);
-    void getSearchResult(IMediaSession2Callback callback, String query, int page, int pageSize,
+    void search(IMediaSession2Callback caller, String query, in Bundle extras);
+    void getSearchResult(IMediaSession2Callback caller, String query, int page, int pageSize,
             in Bundle extras);
+    void subscribe(IMediaSession2Callback caller, String parentId, in Bundle extras);
+    void unsubscribe(IMediaSession2Callback caller, String parentId);
 }
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
index b3aa59c..9a0be7a 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -36,7 +36,7 @@
 
     // TODO(jaewan): Handle when the playlist becomes too huge.
     void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState,
-            in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist, int ratingType,
+            in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist,
             in PendingIntent sessionActivity);
     void onDisconnected();
 
@@ -49,8 +49,10 @@
     //////////////////////////////////////////////////////////////////////////////////////////////
     void onGetRootResult(in Bundle rootHints, String rootMediaId, in Bundle rootExtra);
     void onItemLoaded(String mediaId, in Bundle result);
-    void onChildrenLoaded(String parentId, int page, int pageSize, in Bundle extras,
-            in List<Bundle> result);
-    void onSearchResultLoaded(String query, int page, int pageSize, in Bundle extras,
-            in List<Bundle> result);
+    void onChildrenChanged(String rootMediaId, int childCount, in Bundle extras);
+    void onChildrenLoaded(String parentId, int page, int pageSize, in List<Bundle> result,
+            in Bundle extras);
+    void onSearchResultChanged(String query, int itemCount, in Bundle extras);
+    void onSearchResultLoaded(String query, int page, int pageSize, in List<Bundle> result,
+            in Bundle extras);
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
index 76da42b..c095187 100644
--- a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -20,7 +20,6 @@
 import android.media.MediaBrowser2;
 import android.media.MediaBrowser2.BrowserCallback;
 import android.media.MediaItem2;
-import android.media.MediaSession2.CommandButton;
 import android.media.SessionToken2;
 import android.media.update.MediaBrowser2Provider;
 import android.os.Bundle;
@@ -64,12 +63,36 @@
 
     @Override
     public void subscribe_impl(String parentId, Bundle extras) {
-        // TODO(jaewan): Implement
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.subscribe(getControllerStub(), parentId, extras);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
-    public void unsubscribe_impl(String parentId, Bundle extras) {
-        // TODO(jaewan): Implement
+    public void unsubscribe_impl(String parentId) {
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.unsubscribe(getControllerStub(), parentId);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
@@ -170,17 +193,29 @@
         });
     }
 
-    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle extras,
-            List<MediaItem2> result) {
+    public void onChildrenLoaded(String parentId, int page, int pageSize, List<MediaItem2> result,
+            Bundle extras) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onChildrenLoaded(parentId, page, pageSize, extras, result);
+            mCallback.onChildrenLoaded(parentId, page, pageSize, result, extras);
         });
     }
 
-    public void onSearchResultLoaded(String query, int page, int pageSize, Bundle extras,
-            List<MediaItem2> result) {
+    public void onSearchResultChanged(String query, int itemCount, Bundle extras) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onSearchResultLoaded(query, page, pageSize, extras, result);
+            mCallback.onSearchResultChanged(query, itemCount, extras);
+        });
+    }
+
+    public void onSearchResultLoaded(String query, int page, int pageSize, List<MediaItem2> result,
+            Bundle extras) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onSearchResultLoaded(query, page, pageSize, result, extras);
+        });
+    }
+
+    public void onChildrenChanged(final String parentId, int childCount, final Bundle extras) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onChildrenChanged(parentId, childCount, extras);
         });
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 5af4240..77db355 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -75,8 +75,6 @@
     @GuardedBy("mLock")
     private PlaybackInfo mPlaybackInfo;
     @GuardedBy("mLock")
-    private int mRatingType;
-    @GuardedBy("mLock")
     private PendingIntent mSessionActivity;
     @GuardedBy("mLock")
     private CommandGroup mCommandGroup;
@@ -164,7 +162,7 @@
 
     private void connectToSession(IMediaSession2 sessionBinder) {
         try {
-            sessionBinder.connect(mContext.getPackageName(), mSessionCallbackStub);
+            sessionBinder.connect(mSessionCallbackStub, mContext.getPackageName());
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to call connection request. Framework will retry"
                     + " automatically");
@@ -286,11 +284,6 @@
     }
 
     @Override
-    public int getRatingType_impl() {
-        return mRatingType;
-    }
-
-    @Override
     public void setVolumeTo_impl(int value, int flags) {
         // TODO(hdmoon): sanity check
         final IMediaSession2 binder = mSessionBinder;
@@ -403,9 +396,26 @@
             // TODO(jaewan): Handle.
         }
     }
+
     @Override
-    public void setRating_impl(Rating2 rating) {
-        // TODO(jaewan): Implement
+    public void setRating_impl(String mediaId, Rating2 rating) {
+        if (mediaId == null) {
+            throw new IllegalArgumentException("mediaId shouldn't be null");
+        }
+        if (rating == null) {
+            throw new IllegalArgumentException("rating shouldn't be null");
+        }
+
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                binder.setRating(mSessionCallbackStub, mediaId, rating.toBundle());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            // TODO(jaewan): Handle.
+        }
     }
 
     @Override
@@ -563,7 +573,7 @@
     // Should be used without a lock to prevent potential deadlock.
     void onConnectedNotLocked(IMediaSession2 sessionBinder,
             final CommandGroup commandGroup, final PlaybackState2 state, final PlaybackInfo info,
-            final PlaylistParams params, final List<MediaItem2> playlist, final int ratingType,
+            final PlaylistParams params, final List<MediaItem2> playlist,
             final PendingIntent sessionActivity) {
         if (DEBUG) {
             Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
@@ -591,7 +601,6 @@
                 mPlaybackInfo = info;
                 mPlaylistParams = params;
                 mPlaylist = playlist;
-                mRatingType = ratingType;
                 mSessionActivity = sessionActivity;
                 mSessionBinder = sessionBinder;
                 try {
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
index 52db74e..b9d2fa4 100644
--- a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -26,12 +26,12 @@
 import android.media.MediaPlayerInterface;
 import android.media.MediaSession2;
 import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.SessionCallback;
 import android.media.MediaSessionService2;
 import android.media.SessionToken2;
 import android.media.VolumeProvider2;
 import android.media.update.MediaLibraryService2Provider;
 import android.os.Bundle;
+import android.text.TextUtils;
 
 import com.android.media.MediaSession2Impl.BuilderBaseImpl;
 
@@ -68,10 +68,9 @@
             implements MediaLibrarySessionProvider {
         public MediaLibrarySessionImpl(Context context,
                 MediaPlayerInterface player, String id, VolumeProvider2 volumeProvider,
-                int ratingType, PendingIntent sessionActivity, Executor callbackExecutor,
+                PendingIntent sessionActivity, Executor callbackExecutor,
                 MediaLibrarySessionCallback callback) {
-            super(context, player, id, volumeProvider, ratingType, sessionActivity,
-                    callbackExecutor, callback);
+            super(context, player, id, volumeProvider, sessionActivity, callbackExecutor, callback);
             // Don't put any extra initialization here. Here's the reason.
             // System service will recognize this session inside of the super constructor and would
             // connect to this session assuming that initialization is finished. However, if any
@@ -96,13 +95,36 @@
 
         @Override
         public void notifyChildrenChanged_impl(ControllerInfo controller, String parentId,
-                Bundle options) {
-            // TODO(jaewan): Implements
+                int childCount, Bundle extras) {
+            if (controller == null) {
+                throw new IllegalArgumentException("controller shouldn't be null");
+            }
+            if (parentId == null) {
+                throw new IllegalArgumentException("parentId shouldn't be null");
+            }
+            getSessionStub().notifyChildrenChangedNotLocked(controller, parentId, childCount,
+                    extras);
         }
 
         @Override
-        public void notifyChildrenChanged_impl(String parentId, Bundle options) {
-            // TODO(jaewan): Implements
+        public void notifyChildrenChanged_impl(String parentId, int childCount, Bundle extras) {
+            if (parentId == null) {
+                throw new IllegalArgumentException("parentId shouldn't be null");
+            }
+            getSessionStub().notifyChildrenChangedNotLocked(parentId, childCount, extras);
+        }
+
+        @Override
+        public void notifySearchResultChanged_impl(ControllerInfo controller, String query,
+                int itemCount, Bundle extras) {
+            ensureCallingThread();
+            if (controller == null) {
+                throw new IllegalArgumentException("controller shouldn't be null");
+            }
+            if (TextUtils.isEmpty(query)) {
+                throw new IllegalArgumentException("query shouldn't be empty");
+            }
+            getSessionStub().notifySearchResultChanged(controller, query, itemCount, extras);
         }
     }
 
@@ -117,7 +139,7 @@
 
         @Override
         public MediaLibrarySession build_impl() {
-            return new MediaLibrarySessionImpl(mContext, mPlayer, mId, mVolumeProvider, mRatingType,
+            return new MediaLibrarySessionImpl(mContext, mPlayer, mId, mVolumeProvider,
                     mSessionActivity, mCallbackExecutor, mCallback).getInstance();
         }
     }
@@ -148,4 +170,4 @@
             return mExtras;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
index 852029a..e174d91 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
@@ -125,7 +125,7 @@
     @Override
     public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
             Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
-            playlist, int ratingType, PendingIntent sessionActivity) {
+            playlist, PendingIntent sessionActivity) {
         final MediaController2Impl controller = mController.get();
         if (controller == null) {
             if (DEBUG) {
@@ -146,7 +146,7 @@
                 PlaybackState2.fromBundle(context, playbackState),
                 PlaybackInfoImpl.fromBundle(context, playbackInfo),
                 PlaylistParams.fromBundle(context, playlistParams),
-                list, ratingType, sessionActivity);
+                list, sessionActivity);
     }
 
     @Override
@@ -244,8 +244,8 @@
     }
 
     @Override
-    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle extras,
-            List<Bundle> itemBundleList) throws RuntimeException {
+    public void onChildrenLoaded(String parentId, int page, int pageSize,
+            List<Bundle> itemBundleList, Bundle extras) throws RuntimeException {
         final MediaBrowser2Impl browser;
         try {
             browser = getBrowser();
@@ -265,12 +265,29 @@
                 result.add(MediaItem2.fromBundle(browser.getContext(), bundle));
             }
         }
-        browser.onChildrenLoaded(parentId, page, pageSize, extras, result);
+        browser.onChildrenLoaded(parentId, page, pageSize, result, extras);
     }
 
     @Override
-    public void onSearchResultLoaded(String query, int page, int pageSize, Bundle extras,
-            List<Bundle> itemBundleList) throws RuntimeException {
+    public void onSearchResultChanged(String query, int itemCount, Bundle extras)
+            throws RuntimeException {
+        final MediaBrowser2Impl browser;
+        try {
+            browser = getBrowser();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (browser == null) {
+            // TODO(jaewan): Revisit here. Could be a bug
+            return;
+        }
+        browser.onSearchResultChanged(query, itemCount, extras);
+    }
+
+    @Override
+    public void onSearchResultLoaded(String query, int page, int pageSize,
+            List<Bundle> itemBundleList, Bundle extras) throws RuntimeException {
         final MediaBrowser2Impl browser;
         try {
             browser = getBrowser();
@@ -290,6 +307,22 @@
                 result.add(MediaItem2.fromBundle(browser.getContext(), bundle));
             }
         }
-        browser.onSearchResultLoaded(query, page, pageSize, extras, result);
+        browser.onSearchResultLoaded(query, page, pageSize, result, extras);
+    }
+
+    @Override
+    public void onChildrenChanged(String parentId, int childCount, Bundle extras) {
+        final MediaBrowser2Impl browser;
+        try {
+            browser = getBrowser();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (browser == null) {
+            // TODO(jaewan): Revisit here. Could be a bug
+            return;
+        }
+        browser.onChildrenChanged(parentId, childCount, extras);
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 4a9a729..a32ea0a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -38,7 +38,7 @@
 import android.media.MediaLibraryService2;
 import android.media.MediaMetadata2;
 import android.media.MediaPlayerInterface;
-import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.MediaPlayerInterface.EventCallback;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Builder;
 import android.media.MediaSession2.Command;
@@ -62,6 +62,7 @@
 import android.os.ResultReceiver;
 import android.support.annotation.GuardedBy;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -84,8 +85,7 @@
     private final MediaSession2Stub mSessionStub;
     private final SessionToken2 mSessionToken;
     private final AudioManager mAudioManager;
-    private final List<PlaybackListenerHolder> mListeners = new ArrayList<>();
-    private final int mRatingType;
+    private final ArrayMap<EventCallback, Executor> mCallbacks = new ArrayMap<>();
     private final PendingIntent mSessionActivity;
 
     // mPlayer is set to null when the session is closed, and we shouldn't throw an exception
@@ -111,7 +111,7 @@
     @GuardedBy("mLock")
     private PlaybackInfo mPlaybackInfo;
     @GuardedBy("mLock")
-    private MyPlaybackListener mListener;
+    private MyEventCallback mEventCallback;
 
     /**
      * Can be only called by the {@link Builder#build()}.
@@ -119,13 +119,13 @@
      * @param context
      * @param player
      * @param id
-     * @param callback
      * @param volumeProvider
-     * @param ratingType
      * @param sessionActivity
+     * @param callbackExecutor
+     * @param callback
      */
     public MediaSession2Impl(Context context, MediaPlayerInterface player, String id,
-            VolumeProvider2 volumeProvider, int ratingType, PendingIntent sessionActivity,
+            VolumeProvider2 volumeProvider, PendingIntent sessionActivity,
             Executor callbackExecutor, SessionCallback callback) {
         // TODO(jaewan): Keep other params.
         mInstance = createInstance();
@@ -136,7 +136,6 @@
         mId = id;
         mCallback = callback;
         mCallbackExecutor = callbackExecutor;
-        mRatingType = ratingType;
         mSessionActivity = sessionActivity;
         mSessionStub = new MediaSession2Stub(this);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -225,19 +224,20 @@
     }
 
     private void setPlayer(MediaPlayerInterface player, VolumeProvider2 volumeProvider) {
-        PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
+        final PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
         synchronized (mLock) {
-            if (mPlayer != null && mListener != null) {
+            if (mPlayer != null && mEventCallback != null) {
                 // This might not work for a poorly implemented player.
-                mPlayer.removePlaybackListener(mListener);
+                mPlayer.unregisterEventCallback(mEventCallback);
             }
             mPlayer = player;
-            mListener = new MyPlaybackListener(this, player);
-            player.addPlaybackListener(mCallbackExecutor, mListener);
+            mEventCallback = new MyEventCallback(this, player);
+            player.registerEventCallback(mCallbackExecutor, mEventCallback);
             mVolumeProvider = volumeProvider;
             mPlaybackInfo = info;
         }
         mSessionStub.notifyPlaybackInfoChanged(info);
+        notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
     }
 
     private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -293,7 +293,7 @@
         synchronized (mLock) {
             if (mPlayer != null) {
                 // close can be called multiple times
-                mPlayer.removePlaybackListener(mListener);
+                mPlayer.unregisterEventCallback(mEventCallback);
                 mPlayer = null;
             }
         }
@@ -522,32 +522,31 @@
     }
 
     @Override
-    public void addPlaybackListener_impl(Executor executor, PlaybackListener listener) {
+    public void registerPlayerEventCallback_impl(Executor executor, EventCallback callback) {
         if (executor == null) {
             throw new IllegalArgumentException("executor shouldn't be null");
         }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener shouldn't be null");
+        if (callback == null) {
+            throw new IllegalArgumentException("callback shouldn't be null");
         }
         ensureCallingThread();
-        if (PlaybackListenerHolder.contains(mListeners, listener)) {
-            Log.w(TAG, "listener is already added. Ignoring.");
+        if (mCallbacks.get(callback) != null) {
+            Log.w(TAG, "callback is already added. Ignoring.");
             return;
         }
-        mListeners.add(new PlaybackListenerHolder(executor, listener));
-        executor.execute(() -> listener.onPlaybackChanged(getInstance().getPlaybackState()));
+        mCallbacks.put(callback, executor);
+        // TODO(jaewan): Double check if we need this.
+        final PlaybackState2 state = getInstance().getPlaybackState();
+        executor.execute(() -> callback.onPlaybackStateChanged(state));
     }
 
     @Override
-    public void removePlaybackListener_impl(PlaybackListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener shouldn't be null");
+    public void unregisterPlayerEventCallback_impl(EventCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback shouldn't be null");
         }
         ensureCallingThread();
-        int idx = PlaybackListenerHolder.indexOf(mListeners, listener);
-        if (idx >= 0) {
-            mListeners.remove(idx);
-        }
+        mCallbacks.remove(callback);
     }
 
     @Override
@@ -580,7 +579,7 @@
     //               1. Allow calls from random threads for all methods.
     //               2. Allow calls from random threads for all methods, except for the
     //                  {@link #setPlayer()}.
-    private void ensureCallingThread() {
+    void ensureCallingThread() {
         // TODO(jaewan): Uncomment or remove
         /*
         if (mHandler.getLooper() != Looper.myLooper()) {
@@ -588,19 +587,35 @@
         }*/
     }
 
-    private void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
-        List<PlaybackListenerHolder> listeners = new ArrayList<>();
+    private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
+        ArrayMap<EventCallback, Executor> callbacks = new ArrayMap<>();
         synchronized (mLock) {
-            listeners.addAll(mListeners);
+            callbacks.putAll(mCallbacks);
         }
-        // Notify to listeners added directly to this session
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).postPlaybackChange(state);
+        // Notify to callbacks added directly to this session
+        for (int i = 0; i < callbacks.size(); i++) {
+            final EventCallback callback = callbacks.keyAt(i);
+            final Executor executor = callbacks.valueAt(i);
+            executor.execute(() -> callback.onPlaybackStateChanged(state));
         }
         // Notify to controllers as well.
         mSessionStub.notifyPlaybackStateChangedNotLocked(state);
     }
 
+    private void notifyErrorNotLocked(String mediaId, int what, int extra) {
+        ArrayMap<EventCallback, Executor> callbacks = new ArrayMap<>();
+        synchronized (mLock) {
+            callbacks.putAll(mCallbacks);
+        }
+        // Notify to callbacks added directly to this session
+        for (int i = 0; i < callbacks.size(); i++) {
+            final EventCallback callback = callbacks.keyAt(i);
+            final Executor executor = callbacks.valueAt(i);
+            executor.execute(() -> callback.onError(mediaId, what, extra));
+        }
+        // TODO(jaewan): Notify to controllers as well.
+    }
+
     Context getContext() {
         return mContext;
     }
@@ -621,6 +636,10 @@
         return mCallback;
     }
 
+    MediaSession2Stub getSessionStub() {
+        return mSessionStub;
+    }
+
     VolumeProvider2 getVolumeProvider() {
         return mVolumeProvider;
     }
@@ -631,33 +650,47 @@
         }
     }
 
-    int getRatingType() {
-        return mRatingType;
-    }
-
     PendingIntent getSessionActivity() {
         return mSessionActivity;
     }
 
-    private static class MyPlaybackListener implements MediaPlayerInterface.PlaybackListener {
+    private static class MyEventCallback implements EventCallback {
         private final WeakReference<MediaSession2Impl> mSession;
         private final MediaPlayerInterface mPlayer;
 
-        private MyPlaybackListener(MediaSession2Impl session, MediaPlayerInterface player) {
+        private MyEventCallback(MediaSession2Impl session, MediaPlayerInterface player) {
             mSession = new WeakReference<>(session);
             mPlayer = player;
         }
 
         @Override
-        public void onPlaybackChanged(PlaybackState2 state) {
+        public void onPlaybackStateChanged(PlaybackState2 state) {
             MediaSession2Impl session = mSession.get();
             if (mPlayer != session.mInstance.getPlayer()) {
                 Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
                         new IllegalStateException());
                 return;
             }
+            if (DEBUG) {
+                Log.d(TAG, "onPlaybackStateChanged from player, state=" + state);
+            }
             session.notifyPlaybackStateChangedNotLocked(state);
         }
+
+        @Override
+        public void onError(String mediaId, int what, int extra) {
+            MediaSession2Impl session = mSession.get();
+            if (mPlayer != session.mInstance.getPlayer()) {
+                Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
+                        new IllegalStateException());
+                return;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "onError from player, mediaId=" + mediaId + ", what=" + what
+                        + ", extra=" + extra);
+            }
+            session.notifyErrorNotLocked(mediaId, what, extra);
+        }
     }
 
     public static final class CommandImpl implements CommandProvider {
@@ -1167,7 +1200,6 @@
         Executor mCallbackExecutor;
         C mCallback;
         VolumeProvider2 mVolumeProvider;
-        int mRatingType;
         PendingIntent mSessionActivity;
 
         /**
@@ -1196,10 +1228,6 @@
             mVolumeProvider = volumeProvider;
         }
 
-        public void setRatingType_impl(int type) {
-            mRatingType = type;
-        }
-
         public void setSessionActivity_impl(PendingIntent pi) {
             mSessionActivity = pi;
         }
@@ -1239,7 +1267,7 @@
                 mCallback = new SessionCallback(mContext);
             }
 
-            return new MediaSession2Impl(mContext, mPlayer, mId, mVolumeProvider, mRatingType,
+            return new MediaSession2Impl(mContext, mPlayer, mId, mVolumeProvider,
                     mSessionActivity, mCallbackExecutor, mCallback).getInstance();
         }
     }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index e61934f..914f85e 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -28,6 +28,7 @@
 import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSession2.PlaylistParams;
 import android.media.PlaybackState2;
+import android.media.Rating2;
 import android.media.VolumeProvider2;
 import android.net.Uri;
 import android.os.Binder;
@@ -47,6 +48,8 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
 
 public class MediaSession2Stub extends IMediaSession2.Stub {
 
@@ -63,6 +66,8 @@
 
     @GuardedBy("mLock")
     private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private final ArrayMap<ControllerInfo, Set<String>> mSubscriptions = new ArrayMap<>();
 
     public MediaSession2Stub(MediaSession2Impl session) {
         mSession = new WeakReference<>(session);
@@ -76,11 +81,11 @@
             mControllers.clear();
         }
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback callbackBinder =
+            IMediaSession2Callback controllerBinder =
                     ((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
             try {
                 // Should be used without a lock hold to prevent potential deadlock.
-                callbackBinder.onDisconnected();
+                controllerBinder.onDisconnected();
             } catch (RemoteException e) {
                 // Controller is gone. Should be fine because we're destroying.
             }
@@ -117,12 +122,12 @@
     //////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
-    public void connect(String callingPackage, final IMediaSession2Callback callback)
+    public void connect(final IMediaSession2Callback caller, String callingPackage)
             throws RuntimeException {
         final MediaSession2Impl sessionImpl = getSession();
         final Context context = sessionImpl.getContext();
         final ControllerInfo request = new ControllerInfo(context,
-                Binder.getCallingUid(), Binder.getCallingPid(), callingPackage, callback);
+                Binder.getCallingUid(), Binder.getCallingPid(), callingPackage, caller);
         sessionImpl.getCallbackExecutor().execute(() -> {
             final MediaSession2Impl session = mSession.get();
             if (session == null) {
@@ -155,11 +160,10 @@
                 // TODO(jaewan): Should we protect getting playback state?
                 final PlaybackState2 state = session.getInstance().getPlaybackState();
                 final Bundle playbackStateBundle = (state != null) ? state.toBundle() : null;
-                final Bundle playbackInfoBundle =
-                        ((MediaController2Impl.PlaybackInfoImpl) session.getPlaybackInfo().getProvider()).toBundle();
+                final Bundle playbackInfoBundle = ((MediaController2Impl.PlaybackInfoImpl)
+                        session.getPlaybackInfo().getProvider()).toBundle();
                 final PlaylistParams params = session.getInstance().getPlaylistParams();
                 final Bundle paramsBundle = (params != null) ? params.toBundle() : null;
-                final int ratingType = session.getRatingType();
                 final PendingIntent sessionActivity = session.getSessionActivity();
                 final List<MediaItem2> playlist = session.getInstance().getPlaylist();
                 final List<Bundle> playlistBundle = new ArrayList<>();
@@ -182,9 +186,9 @@
                     return;
                 }
                 try {
-                    callback.onConnected(MediaSession2Stub.this,
+                    caller.onConnected(MediaSession2Stub.this,
                             allowedCommands.toBundle(), playbackStateBundle, playbackInfoBundle,
-                            paramsBundle, playlistBundle, ratingType, sessionActivity);
+                            paramsBundle, playlistBundle, sessionActivity);
                 } catch (RemoteException e) {
                     // Controller may be died prematurely.
                     // TODO(jaewan): Handle here.
@@ -194,7 +198,7 @@
                     Log.d(TAG, "Rejecting connection, request=" + request);
                 }
                 try {
-                    callback.onDisconnected();
+                    caller.onDisconnected();
                 } catch (RemoteException e) {
                     // Controller may be died prematurely.
                     // Not an issue because we'll ignore it anyway.
@@ -210,6 +214,7 @@
             if (DEBUG) {
                 Log.d(TAG, "releasing " + controllerInfo);
             }
+            mSubscriptions.remove(controllerInfo);
         }
     }
 
@@ -389,7 +394,7 @@
 
     @Override
     public void prepareFromUri(final IMediaSession2Callback caller, final Uri uri,
-            final Bundle extra) {
+            final Bundle extras) {
         final MediaSession2Impl sessionImpl = getSession();
         final ControllerInfo controller = getController(caller);
         if (controller == null) {
@@ -403,13 +408,13 @@
             if (session == null) {
                 return;
             }
-            session.getCallback().onPrepareFromUri(controller, uri, extra);
+            session.getCallback().onPrepareFromUri(controller, uri, extras);
         });
     }
 
     @Override
     public void prepareFromSearch(final IMediaSession2Callback caller, final String query,
-            final Bundle extra) {
+            final Bundle extras) {
         final MediaSession2Impl sessionImpl = getSession();
         final ControllerInfo controller = getController(caller);
         if (controller == null) {
@@ -423,13 +428,13 @@
             if (session == null) {
                 return;
             }
-            session.getCallback().onPrepareFromSearch(controller, query, extra);
+            session.getCallback().onPrepareFromSearch(controller, query, extras);
         });
     }
 
     @Override
     public void prepareFromMediaId(final IMediaSession2Callback caller, final String mediaId,
-            final Bundle extra) {
+            final Bundle extras) {
         final MediaSession2Impl sessionImpl = getSession();
         final ControllerInfo controller = getController(caller);
         if (controller == null) {
@@ -443,13 +448,13 @@
             if (session == null) {
                 return;
             }
-            session.getCallback().onPrepareFromMediaId(controller, mediaId, extra);
+            session.getCallback().onPrepareFromMediaId(controller, mediaId, extras);
         });
     }
 
     @Override
     public void playFromUri(final IMediaSession2Callback caller, final Uri uri,
-            final Bundle extra) {
+            final Bundle extras) {
         final MediaSession2Impl sessionImpl = getSession();
         final ControllerInfo controller = getController(caller);
         if (controller == null) {
@@ -463,13 +468,13 @@
             if (session == null) {
                 return;
             }
-            session.getCallback().onPlayFromUri(controller, uri, extra);
+            session.getCallback().onPlayFromUri(controller, uri, extras);
         });
     }
 
     @Override
     public void playFromSearch(final IMediaSession2Callback caller, final String query,
-            final Bundle extra) {
+            final Bundle extras) {
         final MediaSession2Impl sessionImpl = getSession();
         final ControllerInfo controller = getController(caller);
         if (controller == null) {
@@ -483,13 +488,13 @@
             if (session == null) {
                 return;
             }
-            session.getCallback().onPlayFromSearch(controller, query, extra);
+            session.getCallback().onPlayFromSearch(controller, query, extras);
         });
     }
 
     @Override
     public void playFromMediaId(final IMediaSession2Callback caller, final String mediaId,
-            final Bundle extra) {
+            final Bundle extras) {
         final MediaSession2Impl sessionImpl = getSession();
         final ControllerInfo controller = getController(caller);
         if (controller == null) {
@@ -503,7 +508,28 @@
             if (session == null) {
                 return;
             }
-            session.getCallback().onPlayFromMediaId(controller, mediaId, extra);
+            session.getCallback().onPlayFromMediaId(controller, mediaId, extras);
+        });
+    }
+
+    @Override
+    public void setRating(final IMediaSession2Callback caller, final String mediaId,
+            final Bundle ratingBundle) {
+        final MediaSession2Impl sessionImpl = getSession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            Rating2 rating = Rating2Impl.fromBundle(session.getContext(), ratingBundle);
+            session.getCallback().onSetRating(controller, mediaId, rating);
         });
     }
 
@@ -621,7 +647,7 @@
 
             try {
                 controllerImpl.getControllerBinder().onChildrenLoaded(
-                        parentId, page, pageSize, extras, bundleList);
+                        parentId, page, pageSize, bundleList, extras);
             } catch (RemoteException e) {
                 // Controller may be died prematurely.
                 // TODO(jaewan): Handle this.
@@ -704,7 +730,7 @@
 
             try {
                 controllerImpl.getControllerBinder().onSearchResultLoaded(
-                        query, page, pageSize, extras, bundleList);
+                        query, page, pageSize, bundleList, extras);
             } catch (RemoteException e) {
                 // Controller may be died prematurely.
                 // TODO(jaewan): Handle this.
@@ -712,6 +738,56 @@
         });
     }
 
+    @Override
+    public void subscribe(final IMediaSession2Callback caller, final String parentId,
+            final Bundle option) {
+        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "subscribe() from a browser that hasn't connected. Ignore");
+            }
+            return;
+        }
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaLibrarySessionImpl session = getLibrarySession();
+            if (session == null) {
+                return;
+            }
+            session.getCallback().onSubscribed(controller, parentId, option);
+            synchronized (mLock) {
+                Set<String> subscription = mSubscriptions.get(controller);
+                if (subscription == null) {
+                    subscription = new HashSet<>();
+                    mSubscriptions.put(controller, subscription);
+                }
+                subscription.add(parentId);
+            }
+        });
+    }
+
+    @Override
+    public void unsubscribe(final IMediaSession2Callback caller, final String parentId) {
+        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "unsubscribe() from a browser that hasn't connected. Ignore");
+            }
+            return;
+        }
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaLibrarySessionImpl session = getLibrarySession();
+            if (session == null) {
+                return;
+            }
+            session.getCallback().onUnsubscribed(controller, parentId);
+            synchronized (mLock) {
+                mSubscriptions.remove(controller);
+            }
+        });
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////////////
     // APIs for MediaSession2Impl
     //////////////////////////////////////////////////////////////////////////////////////////////
@@ -731,11 +807,11 @@
     public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback callbackBinder =
+            IMediaSession2Callback controllerBinder =
                     ControllerInfoImpl.from(list.get(i)).getControllerBinder();
             try {
                 final Bundle bundle = state != null ? state.toBundle() : null;
-                callbackBinder.onPlaybackStateChanged(bundle);
+                controllerBinder.onPlaybackStateChanged(bundle);
             } catch (RemoteException e) {
                 Log.w(TAG, "Controller is gone", e);
                 // TODO(jaewan): What to do when the controller is gone?
@@ -746,7 +822,7 @@
     public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
         // TODO(jaewan): It's OK to be called while it's connecting, but not OK if the connection
         //               is rejected. Handle the case.
-        IMediaSession2Callback callbackBinder =
+        IMediaSession2Callback controllerBinder =
                 ControllerInfoImpl.from(controller).getControllerBinder();
         try {
             List<Bundle> layoutBundles = new ArrayList<>();
@@ -756,7 +832,7 @@
                     layoutBundles.add(bundle);
                 }
             }
-            callbackBinder.onCustomLayoutChanged(layoutBundles);
+            controllerBinder.onCustomLayoutChanged(layoutBundles);
         } catch (RemoteException e) {
             Log.w(TAG, "Controller is gone", e);
             // TODO(jaewan): What to do when the controller is gone?
@@ -778,10 +854,10 @@
         }
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback callbackBinder =
+            IMediaSession2Callback controllerBinder =
                     ControllerInfoImpl.from(list.get(i)).getControllerBinder();
             try {
-                callbackBinder.onPlaylistChanged(bundleList);
+                controllerBinder.onPlaylistChanged(bundleList);
             } catch (RemoteException e) {
                 Log.w(TAG, "Controller is gone", e);
                 // TODO(jaewan): What to do when the controller is gone?
@@ -792,10 +868,10 @@
     public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback callbackBinder =
+            IMediaSession2Callback controllerBinder =
                     ControllerInfoImpl.from(list.get(i)).getControllerBinder();
             try {
-                callbackBinder.onPlaylistParamsChanged(params.toBundle());
+                controllerBinder.onPlaylistParamsChanged(params.toBundle());
             } catch (RemoteException e) {
                 Log.w(TAG, "Controller is gone", e);
                 // TODO(jaewan): What to do when the controller is gone?
@@ -806,11 +882,11 @@
     public void notifyPlaybackInfoChanged(MediaController2.PlaybackInfo playbackInfo) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback callbackBinder =
+            IMediaSession2Callback controllerBinder =
                     ControllerInfoImpl.from(list.get(i)).getControllerBinder();
             try {
-                callbackBinder.onPlaybackInfoChanged(
-                        ((MediaController2Impl.PlaybackInfoImpl) playbackInfo.getProvider()).toBundle());
+                controllerBinder.onPlaybackInfoChanged(((MediaController2Impl.PlaybackInfoImpl)
+                        playbackInfo.getProvider()).toBundle());
             } catch (RemoteException e) {
                 Log.w(TAG, "Controller is gone", e);
                 // TODO(jaewan): What to do when the controller is gone?
@@ -827,9 +903,9 @@
         if (command == null) {
             throw new IllegalArgumentException("command shouldn't be null");
         }
-        final IMediaSession2Callback callbackBinder =
+        final IMediaSession2Callback controllerBinder =
                 ControllerInfoImpl.from(controller).getControllerBinder();
-        if (getController(callbackBinder) == null) {
+        if (getController(controllerBinder) == null) {
             throw new IllegalArgumentException("Controller is gone");
         }
         sendCustomCommandInternal(controller, command, args, receiver);
@@ -847,14 +923,64 @@
 
     private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
             ResultReceiver receiver) {
-        final IMediaSession2Callback callbackBinder =
+        final IMediaSession2Callback controllerBinder =
                 ControllerInfoImpl.from(controller).getControllerBinder();
         try {
             Bundle commandBundle = command.toBundle();
-            callbackBinder.sendCustomCommand(commandBundle, args, receiver);
+            controllerBinder.sendCustomCommand(commandBundle, args, receiver);
         } catch (RemoteException e) {
             Log.w(TAG, "Controller is gone", e);
             // TODO(jaewan): What to do when the controller is gone?
         }
     }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // APIs for MediaLibrarySessionImpl
+    //////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void notifySearchResultChanged(ControllerInfo controller, String query, int itemCount,
+            Bundle extras) {
+        final IMediaSession2Callback callbackBinder =
+                ControllerInfoImpl.from(controller).getControllerBinder();
+        try {
+            callbackBinder.onSearchResultChanged(query, itemCount, extras);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Controller is gone", e);
+            // TODO(jaewan): What to do when the controller is gone?
+        }
+    }
+
+    public void notifyChildrenChangedNotLocked(ControllerInfo controller, String parentId,
+            int childCount, Bundle extras) {
+        // TODO(jaewan): Handle when controller is disconnected and no longer valid.
+        //               Note: Commands may be sent while onConnected() is running. Should we also
+        //                     consider it as error?
+        notifyChildrenChangedInternalNotLocked(controller, parentId, childCount, extras);
+    }
+
+    public void notifyChildrenChangedNotLocked(String parentId, int childCount, Bundle extras) {
+        final List<ControllerInfo> controllers = getControllers();
+        for (int i = 0; i < controllers.size(); i++) {
+            notifyChildrenChangedInternalNotLocked(controllers.get(i), parentId, childCount,
+                    extras);
+        }
+    }
+
+    public void notifyChildrenChangedInternalNotLocked(final ControllerInfo controller,
+            String parentId, int childCount, Bundle extras) {
+        // Ensure subscription
+        synchronized (mLock) {
+            Set<String> subscriptions = mSubscriptions.get(controller);
+            if (subscriptions == null || !subscriptions.contains(parentId)) {
+                return;
+            }
+        }
+        final IMediaSession2Callback callbackBinder =
+                ControllerInfoImpl.from(controller).getControllerBinder();
+        try {
+            callbackBinder.onChildrenChanged(parentId, childCount, extras);
+        } catch (RemoteException e) {
+            // TODO(jaewan): Handle controller removed?
+        }
+    }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 8773df4..aa5ac84 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -22,7 +22,7 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
-import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.MediaPlayerInterface.EventCallback;
 import android.media.MediaSession2;
 import android.media.MediaSessionService2;
 import android.media.MediaSessionService2.MediaNotification;
@@ -42,7 +42,7 @@
     private static final boolean DEBUG = true; // TODO(jaewan): Change this.
 
     private final MediaSessionService2 mInstance;
-    private final PlaybackListener mListener = new SessionServicePlaybackListener();
+    private final EventCallback mCallback = new SessionServiceEventCallback();
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
@@ -94,7 +94,7 @@
                     + ", but got " + mSession);
         }
         // TODO(jaewan): Uncomment here.
-        // mSession.addPlaybackListener(mListener, mSession.getExecutor());
+        // mSession.registerPlayerEventCallback(mCallback, mSession.getExecutor());
     }
 
     @TokenType int getSessionType() {
@@ -135,9 +135,9 @@
                 mediaNotification.getNotification());
     }
 
-    private class SessionServicePlaybackListener implements PlaybackListener {
+    private class SessionServiceEventCallback implements EventCallback {
         @Override
-        public void onPlaybackChanged(PlaybackState2 state) {
+        public void onPlaybackStateChanged(PlaybackState2 state) {
             if (state == null) {
                 Log.w(TAG, "Ignoring null playback state");
                 return;
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java b/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
deleted file mode 100644
index 4241f85..0000000
--- a/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media;
-
-import android.media.MediaPlayerInterface.PlaybackListener;
-import android.media.PlaybackState2;
-import android.os.Handler;
-import android.support.annotation.NonNull;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Holds {@link PlaybackListener} with the {@link Handler}.
- */
-public class PlaybackListenerHolder {
-    public final Executor executor;
-    public final PlaybackListener listener;
-
-    public PlaybackListenerHolder(Executor executor, @NonNull PlaybackListener listener) {
-        this.executor = executor;
-        this.listener = listener;
-    }
-
-    public void postPlaybackChange(final PlaybackState2 state) {
-        executor.execute(() -> listener.onPlaybackChanged(state));
-    }
-
-    /**
-     * Returns {@code true} if the given list contains a {@link PlaybackListenerHolder} that holds
-     * the given listener.
-     *
-     * @param list list to check
-     * @param listener listener to check
-     * @return {@code true} if the given list contains listener. {@code false} otherwise.
-     */
-    public static <Holder extends PlaybackListenerHolder> boolean contains(
-            @NonNull List<Holder> list, PlaybackListener listener) {
-        return indexOf(list, listener) >= 0;
-    }
-
-    /**
-     * Returns the index of the {@link PlaybackListenerHolder} that contains the given listener.
-     *
-     * @param list list to check
-     * @param listener listener to check
-     * @return {@code index} of item if the given list contains listener. {@code -1} otherwise.
-     */
-    public static <Holder extends PlaybackListenerHolder> int indexOf(
-            @NonNull List<Holder> list, PlaybackListener listener) {
-        for (int i = 0; i < list.size(); i++) {
-            if (list.get(i).listener == listener) {
-                return i;
-            }
-        }
-        return -1;
-    }
-}
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java b/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
index 5eb1129..ee8d6d7 100644
--- a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
@@ -30,7 +30,6 @@
     private static final String KEY_BUFFERED_POSITION =
             "android.media.playbackstate2.buffered_position";
     private static final String KEY_SPEED = "android.media.playbackstate2.speed";
-    private static final String KEY_ERROR_MESSAGE = "android.media.playbackstate2.error_message";
     private static final String KEY_UPDATE_TIME = "android.media.playbackstate2.update_time";
     private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id";
 
@@ -42,11 +41,9 @@
     private final float mSpeed;
     private final long mBufferedPosition;
     private final long mActiveItemId;
-    private final CharSequence mErrorMessage;
 
     public PlaybackState2Impl(Context context, PlaybackState2 instance, int state, long position,
-            long updateTime, float speed, long bufferedPosition, long activeItemId,
-            CharSequence error) {
+            long updateTime, float speed, long bufferedPosition, long activeItemId) {
         mContext = context;
         mInstance = instance;
         mState = state;
@@ -55,7 +52,6 @@
         mUpdateTime = updateTime;
         mBufferedPosition = bufferedPosition;
         mActiveItemId = activeItemId;
-        mErrorMessage = error;
     }
 
     @Override
@@ -67,7 +63,6 @@
         bob.append(", speed=").append(mSpeed);
         bob.append(", updated=").append(mUpdateTime);
         bob.append(", active item id=").append(mActiveItemId);
-        bob.append(", error=").append(mErrorMessage);
         bob.append("}");
         return bob.toString();
     }
@@ -93,11 +88,6 @@
     }
 
     @Override
-    public CharSequence getErrorMessage_impl() {
-        return mErrorMessage;
-    }
-
-    @Override
     public long getLastPositionUpdateTime_impl() {
         return mUpdateTime;
     }
@@ -116,7 +106,6 @@
         bundle.putFloat(KEY_SPEED, mSpeed);
         bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition);
         bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId);
-        bundle.putCharSequence(KEY_ERROR_MESSAGE, mErrorMessage);
         return bundle;
     }
 
@@ -129,18 +118,15 @@
                 || !bundle.containsKey(KEY_UPDATE_TIME)
                 || !bundle.containsKey(KEY_SPEED)
                 || !bundle.containsKey(KEY_BUFFERED_POSITION)
-                || !bundle.containsKey(KEY_ACTIVE_ITEM_ID)
-                || !bundle.containsKey(KEY_ERROR_MESSAGE)) {
+                || !bundle.containsKey(KEY_ACTIVE_ITEM_ID)) {
             return null;
         }
-
         return new PlaybackState2(context,
                 bundle.getInt(KEY_STATE),
                 bundle.getLong(KEY_POSITION),
                 bundle.getLong(KEY_UPDATE_TIME),
                 bundle.getFloat(KEY_SPEED),
                 bundle.getLong(KEY_BUFFERED_POSITION),
-                bundle.getLong(KEY_ACTIVE_ITEM_ID),
-                bundle.getCharSequence(KEY_ERROR_MESSAGE));
+                bundle.getLong(KEY_ACTIVE_ITEM_ID));
     }
 }
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 46812e7..994824d 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -44,7 +44,6 @@
 import android.media.MediaSessionService2.MediaNotification;
 import android.media.PlaybackState2;
 import android.media.Rating2;
-import android.media.SessionPlayer2;
 import android.media.SessionToken2;
 import android.media.VolumeProvider2;
 import android.media.update.MediaBrowser2Provider;
@@ -60,7 +59,6 @@
 import android.media.update.MediaSessionService2Provider;
 import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
 import android.media.update.PlaybackState2Provider;
-import android.media.update.SessionPlayer2Provider;
 import android.media.update.SessionToken2Provider;
 import android.media.update.StaticProvider;
 import android.media.update.VideoView2Provider;
@@ -230,12 +228,6 @@
     }
 
     @Override
-    public SessionPlayer2Provider createSessionPlayer2(Context context, SessionPlayer2 instance) {
-        // TODO(jaewan): Implement this
-        return null;
-    }
-
-    @Override
     public MediaItem2Provider createMediaItem2(Context context, MediaItem2 instance,
             String mediaId, DataSourceDesc dsd, MediaMetadata2 metadata, int flags) {
         return new MediaItem2Impl(context, instance, mediaId, dsd, metadata, flags);
@@ -302,9 +294,9 @@
     @Override
     public PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance,
             int state, long position, long updateTime, float speed, long bufferedPosition,
-            long activeItemId, CharSequence error) {
+            long activeItemId) {
         return new PlaybackState2Impl(context, instance, state, position, updateTime, speed,
-                bufferedPosition, activeItemId, error);
+                bufferedPosition, activeItemId);
     }
 
     @Override
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index ecb19c1..a8ce18b 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -92,12 +92,8 @@
     private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
 
     private Pair<Executor, VideoView2.OnCustomActionListener> mCustomActionListenerRecord;
-    private Pair<Executor, VideoView2.OnPreparedListener> mPreparedListenerRecord;
-    private Pair<Executor, VideoView2.OnCompletionListener> mCompletionListenerRecord;
-    private Pair<Executor, VideoView2.OnErrorListener> mErrorListenerRecord;
-    private Pair<Executor, VideoView2.OnInfoListener> mInfoListenerRecord;
-    private Pair<Executor, VideoView2.OnViewTypeChangedListener> mViewTypeChangedListenerRecord;
-    private Pair<Executor, VideoView2.OnFullScreenRequestListener> mFullScreenRequestListenerRecord;
+    private VideoView2.OnViewTypeChangedListener mViewTypeChangedListener;
+    private VideoView2.OnFullScreenRequestListener mFullScreenRequestListener;
 
     private VideoViewInterface mCurrentView;
     private VideoTextureView mTextureView;
@@ -363,35 +359,13 @@
     }
 
     @Override
-    public void setOnPreparedListener_impl(Executor executor, VideoView2.OnPreparedListener l) {
-        mPreparedListenerRecord = new Pair<>(executor, l);
+    public void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l) {
+        mViewTypeChangedListener = l;
     }
 
     @Override
-    public void setOnCompletionListener_impl(Executor executor, VideoView2.OnCompletionListener l) {
-        mCompletionListenerRecord = new Pair<>(executor, l);
-    }
-
-    @Override
-    public void setOnErrorListener_impl(Executor executor, VideoView2.OnErrorListener l) {
-        mErrorListenerRecord = new Pair<>(executor, l);
-    }
-
-    @Override
-    public void setOnInfoListener_impl(Executor executor, VideoView2.OnInfoListener l) {
-        mInfoListenerRecord = new Pair<>(executor, l);
-    }
-
-    @Override
-    public void setOnViewTypeChangedListener_impl(Executor executor,
-            VideoView2.OnViewTypeChangedListener l) {
-        mViewTypeChangedListenerRecord = new Pair<>(executor, l);
-    }
-
-    @Override
-    public void setFullScreenRequestListener_impl(Executor executor,
-            VideoView2.OnFullScreenRequestListener l) {
-        mFullScreenRequestListenerRecord = new Pair<>(executor, l);
+    public void setFullScreenRequestListener_impl(VideoView2.OnFullScreenRequestListener l) {
+        mFullScreenRequestListener = l;
     }
 
     @Override
@@ -490,10 +464,8 @@
             Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
         }
         mCurrentView = view;
-        if (mViewTypeChangedListenerRecord != null) {
-            mViewTypeChangedListenerRecord.first.execute(() ->
-                    mViewTypeChangedListenerRecord.second.onViewTypeChanged(
-                            mInstance, view.getViewType()));
+        if (mViewTypeChangedListener != null) {
+            mViewTypeChangedListener.onViewTypeChanged(mInstance, view.getViewType());
         }
         if (needToStart()) {
             mMediaController.getTransportControls().play();
@@ -845,10 +817,6 @@
             updatePlaybackState();
             extractSubtitleTracks();
 
-            if (mPreparedListenerRecord != null) {
-                mPreparedListenerRecord.first.execute(() ->
-                        mPreparedListenerRecord.second.onPrepared(mInstance));
-            }
             if (mMediaControlView != null) {
                 mMediaControlView.setEnabled(true);
             }
@@ -909,10 +877,6 @@
                     mTargetState = STATE_PLAYBACK_COMPLETED;
                     updatePlaybackState();
 
-                    if (mCompletionListenerRecord != null) {
-                        mCompletionListenerRecord.first.execute(() ->
-                                mCompletionListenerRecord.second.onCompletion(mInstance));
-                    }
                     if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
                         mAudioManager.abandonAudioFocus(null);
                     }
@@ -922,11 +886,6 @@
     private MediaPlayer.OnInfoListener mInfoListener =
             new MediaPlayer.OnInfoListener() {
                 public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                    if (mInfoListenerRecord != null) {
-                        mInfoListenerRecord.first.execute(() ->
-                                mInfoListenerRecord.second.onInfo(mInstance, what, extra));
-                    }
-
                     if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
                         extractSubtitleTracks();
                     }
@@ -947,14 +906,6 @@
                     if (mMediaControlView != null) {
                         mMediaControlView.setVisibility(View.GONE);
                     }
-
-                    /* If an error handler has been supplied, use it and finish. */
-                    if (mErrorListenerRecord != null) {
-                        mErrorListenerRecord.first.execute(() ->
-                            mErrorListenerRecord.second.onError(
-                                    mInstance, frameworkErr, implErr));
-                    }
-
                     return true;
                 }
             };
@@ -990,13 +941,10 @@
                         mInstance.setSubtitleEnabled(false);
                         break;
                     case MediaControlView2.COMMAND_SET_FULLSCREEN:
-                        if (mFullScreenRequestListenerRecord != null) {
-                            mFullScreenRequestListenerRecord.first.execute(() ->
-                                    mFullScreenRequestListenerRecord.second.onFullScreenRequest(
-                                            mInstance,
-                                            args.getBoolean(
-                                                    MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN))
-                                    );
+                        if (mFullScreenRequestListener != null) {
+                            mFullScreenRequestListener.onFullScreenRequest(
+                                    mInstance,
+                                    args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
                         }
                         break;
                 }
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
index b60fde3..7e93232 100644
--- a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -21,16 +21,21 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
 
 import android.annotation.Nullable;
 import android.content.Context;
 import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaLibraryService2.MediaLibrarySession;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSession2.PlaylistParams;
+import android.media.TestServiceRegistry.SessionCallbackProxy;
 import android.os.Bundle;
 import android.os.ResultReceiver;
+import android.os.Process;
 import android.support.annotation.CallSuper;
 import android.support.annotation.NonNull;
 import android.support.test.filters.SmallTest;
@@ -68,8 +73,12 @@
         // Browser specific callbacks
         default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
         default void onItemLoaded(String mediaId, MediaItem2 result) {}
-        default void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
-                List<MediaItem2> result) {}
+        default void onChildrenChanged(String parentId, int childCount, Bundle extras) {}
+        default void onChildrenLoaded(String parentId, int page, int pageSize,
+                List<MediaItem2> result, Bundle extras) {}
+        default void onSearchResultChanged(String query, int itemCount, Bundle extras) {}
+        default void onSearchResultLoaded(String query, int page, int pageSize,
+                List<MediaItem2> result, Bundle extras) {}
     }
 
     @Test
@@ -83,7 +92,7 @@
             public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
                 assertTrue(TestUtils.equals(param, rootHints));
                 assertEquals(MockMediaLibraryService2.ROOT_ID, rootMediaId);
-                assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRA, rootExtra));
+                assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRAS, rootExtra));
                 latch.countDown();
             }
         };
@@ -141,23 +150,22 @@
         final String parentId = MockMediaLibraryService2.PARENT_ID;
         final int page = 4;
         final int pageSize = 10;
-        final Bundle options = new Bundle();
-        options.putString(TAG, TAG);
+        final Bundle extras = new Bundle();
+        extras.putString(TAG, TAG);
 
         final CountDownLatch latch = new CountDownLatch(1);
         final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
             @Override
             public void onChildrenLoaded(String parentIdOut, int pageOut, int pageSizeOut,
-                    Bundle optionsOut, List<MediaItem2> result) {
+                    List<MediaItem2> result, Bundle extrasOut) {
                 assertEquals(parentId, parentIdOut);
                 assertEquals(page, pageOut);
                 assertEquals(pageSize, pageSizeOut);
-                assertTrue(TestUtils.equals(options, optionsOut));
+                assertTrue(TestUtils.equals(extras, extrasOut));
                 assertNotNull(result);
 
                 int fromIndex = (page - 1) * pageSize;
-                int toIndex = Math.min(page * pageSize,
-                        MockMediaLibraryService2.GET_CHILDREN_RESULT.size());
+                int toIndex = Math.min(page * pageSize, MockMediaLibraryService2.CHILDREN_COUNT);
 
                 // Compare the given results with originals.
                 for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
@@ -173,7 +181,7 @@
 
         final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
         MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.getChildren(parentId, page, pageSize, options);
+        browser.getChildren(parentId, page, pageSize, extras);
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
     }
 
@@ -185,7 +193,7 @@
         final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
             @Override
             public void onChildrenLoaded(String parentIdOut, int pageOut, int pageSizeOut,
-                    Bundle optionsOut, List<MediaItem2> result) {
+                    List<MediaItem2> result, Bundle extrasOut) {
                 assertNotNull(result);
                 assertEquals(0, result.size());
                 latch.countDown();
@@ -206,7 +214,7 @@
         final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
             @Override
             public void onChildrenLoaded(String parentIdOut, int pageOut, int pageSizeOut,
-                    Bundle optionsOut, List<MediaItem2> result) {
+                    List<MediaItem2> result, Bundle extrasOut) {
                 assertNull(result);
                 latch.countDown();
             }
@@ -218,6 +226,224 @@
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
     }
 
+    @Test
+    public void testSearch() throws InterruptedException {
+        final String query = MockMediaLibraryService2.SEARCH_QUERY;
+        final int page = 4;
+        final int pageSize = 10;
+        final Bundle extras = new Bundle();
+        extras.putString(TAG, TAG);
+
+        final CountDownLatch latchForSearch = new CountDownLatch(1);
+        final CountDownLatch latchForGetSearchResult = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+            @Override
+            public void onSearchResultChanged(String queryOut, int itemCount, Bundle extrasOut) {
+                assertEquals(query, queryOut);
+                assertTrue(TestUtils.equals(extras, extrasOut));
+                assertEquals(MockMediaLibraryService2.SEARCH_RESULT_COUNT, itemCount);
+                latchForSearch.countDown();
+            }
+
+            @Override
+            public void onSearchResultLoaded(String queryOut, int pageOut, int pageSizeOut,
+                    List<MediaItem2> result, Bundle extrasOut) {
+                assertEquals(query, queryOut);
+                assertEquals(page, pageOut);
+                assertEquals(pageSize, pageSizeOut);
+                assertTrue(TestUtils.equals(extras, extrasOut));
+                assertNotNull(result);
+
+                int fromIndex = (page - 1) * pageSize;
+                int toIndex = Math.min(
+                        page * pageSize, MockMediaLibraryService2.SEARCH_RESULT_COUNT);
+
+                // Compare the given results with originals.
+                for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
+                    int relativeIndex = originalIndex - fromIndex;
+                    assertEquals(
+                            MockMediaLibraryService2.SEARCH_RESULT.get(originalIndex).getMediaId(),
+                            result.get(relativeIndex).getMediaId());
+                }
+                latchForGetSearchResult.countDown();
+            }
+        };
+
+        // Request the search.
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
+        browser.search(query, extras);
+        assertTrue(latchForSearch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+
+        // Get the search result.
+        browser.getSearchResult(query, page, pageSize, extras);
+        assertTrue(latchForGetSearchResult.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testSearchTakesTime() throws InterruptedException {
+        final String query = MockMediaLibraryService2.SEARCH_QUERY_TAKES_TIME;
+        final Bundle extras = new Bundle();
+        extras.putString(TAG, TAG);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+            @Override
+            public void onSearchResultChanged(String queryOut, int itemCount, Bundle extrasOut) {
+                assertEquals(query, queryOut);
+                assertTrue(TestUtils.equals(extras, extrasOut));
+                assertEquals(MockMediaLibraryService2.SEARCH_RESULT_COUNT, itemCount);
+                latch.countDown();
+            }
+        };
+
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
+        browser.search(query, extras);
+        assertTrue(latch.await(
+                MockMediaLibraryService2.SEARCH_TIME_IN_MS + WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testSearchEmptyResult() throws InterruptedException {
+        final String query = MockMediaLibraryService2.SEARCH_QUERY_EMPTY_RESULT;
+        final Bundle extras = new Bundle();
+        extras.putString(TAG, TAG);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+            @Override
+            public void onSearchResultChanged(String queryOut, int itemCount, Bundle extrasOut) {
+                assertEquals(query, queryOut);
+                assertTrue(TestUtils.equals(extras, extrasOut));
+                assertEquals(0, itemCount);
+                latch.countDown();
+            }
+        };
+
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
+        browser.search(query, extras);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testSubscribe() throws InterruptedException {
+        final String testParentId = "testSubscribeId";
+        final Bundle testExtras = new Bundle();
+        testExtras.putString(testParentId, testParentId);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final SessionCallbackProxy callbackProxy = new SessionCallbackProxy(mContext) {
+            @Override
+            public void onSubscribed(ControllerInfo info, String parentId, Bundle extras) {
+                if (Process.myUid() == info.getUid()) {
+                    assertEquals(testParentId, parentId);
+                    assertTrue(TestUtils.equals(testExtras, extras));
+                    latch.countDown();
+                }
+            }
+        };
+        TestServiceRegistry.getInstance().setSessionCallbackProxy(callbackProxy);
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token);
+        browser.subscribe(testParentId, testExtras);
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testUnsubscribe() throws InterruptedException {
+        final String testParentId = "testUnsubscribeId";
+        final CountDownLatch latch = new CountDownLatch(1);
+        final SessionCallbackProxy callbackProxy = new SessionCallbackProxy(mContext) {
+            @Override
+            public void onUnsubscribed(ControllerInfo info, String parentId) {
+                if (Process.myUid() == info.getUid()) {
+                    assertEquals(testParentId, parentId);
+                    latch.countDown();
+                }
+            }
+        };
+        TestServiceRegistry.getInstance().setSessionCallbackProxy(callbackProxy);
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token);
+        browser.unsubscribe(testParentId);
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testBrowserCallback_notifyChildrenChanged() throws InterruptedException {
+        // TODO(jaewan): Add test for the notifyChildrenChanged itself.
+        final String testParentId1 = "testBrowserCallback_notifyChildrenChanged_unexpectedParent";
+        final String testParentId2 = "testBrowserCallback_notifyChildrenChanged";
+        final int testChildrenCount = 101;
+        final Bundle testExtras = new Bundle();
+        testExtras.putString(testParentId1, testParentId1);
+
+        final CountDownLatch latch = new CountDownLatch(3);
+        final SessionCallbackProxy sessionCallbackProxy = new SessionCallbackProxy(mContext) {
+            @Override
+            public CommandGroup onConnect(ControllerInfo controller) {
+                final MockMediaLibraryService2 service = (MockMediaLibraryService2)
+                        TestServiceRegistry.getInstance().getServiceInstance();
+                final MediaLibrarySession session = (MediaLibrarySession) service.getSession();
+                // Shouldn't trigger onChildrenChanged() for the browser, because it hasn't
+                // subscribed.
+                session.notifyChildrenChanged(testParentId1, testChildrenCount, null);
+                session.notifyChildrenChanged(controller, testParentId1, testChildrenCount, null);
+                return super.onConnect(controller);
+            }
+
+            @Override
+            public void onSubscribed(ControllerInfo info, String parentId, Bundle extras) {
+                if (Process.myUid() == info.getUid()) {
+                    final MediaLibrarySession session =  (MediaLibrarySession) mSession;
+                    session.notifyChildrenChanged(testParentId2, testChildrenCount, null);
+                    session.notifyChildrenChanged(info, testParentId2, testChildrenCount,
+                            testExtras);
+                }
+            }
+        };
+        final TestBrowserCallbackInterface controllerCallbackProxy =
+                new TestBrowserCallbackInterface() {
+                    @Override
+                    public void onChildrenChanged(String parentId, int childCount,
+                            Bundle extras) {
+                        switch ((int) latch.getCount()) {
+                            case 3:
+                                assertEquals(testParentId2, parentId);
+                                assertEquals(testChildrenCount, childCount);
+                                assertNull(extras);
+                                latch.countDown();
+                                break;
+                            case 2:
+                                assertEquals(testParentId2, parentId);
+                                assertEquals(testChildrenCount, childCount);
+                                assertTrue(TestUtils.equals(testExtras, extras));
+                                latch.countDown();
+                                break;
+                            default:
+                                // Unexpected call.
+                                fail();
+                        }
+                    }
+                };
+        TestServiceRegistry.getInstance().setSessionCallbackProxy(sessionCallbackProxy);
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        final MediaBrowser2 browser = (MediaBrowser2) createController(
+                token, true, controllerCallbackProxy);
+        final MockMediaLibraryService2 service =
+                (MockMediaLibraryService2) TestServiceRegistry.getInstance().getServiceInstance();
+        if (mSession != null) {
+            mSession.close();
+        }
+        mSession = service.getSession();
+        assertTrue(mSession instanceof MediaLibrarySession);
+        browser.subscribe(testParentId2, null);
+        // This ensures that onChildrenChanged() is only called for the expected reasons.
+        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
     public static class TestBrowserCallback extends BrowserCallback
             implements WaitForConnectionInterface {
         private final TestControllerCallbackInterface mCallbackProxy;
@@ -287,12 +513,40 @@
         }
 
         @Override
-        public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
-                List<MediaItem2> result) {
-            super.onChildrenLoaded(parentId, page, pageSize, options, result);
+        public void onChildrenLoaded(String parentId, int page, int pageSize,
+                List<MediaItem2> result, Bundle extras) {
+            super.onChildrenLoaded(parentId, page, pageSize, result, extras);
             if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
                 ((TestBrowserCallbackInterface) mCallbackProxy)
-                        .onChildrenLoaded(parentId, page, pageSize, options, result);
+                        .onChildrenLoaded(parentId, page, pageSize, result, extras);
+            }
+        }
+
+        @Override
+        public void onSearchResultChanged(String query, int itemCount, Bundle extras) {
+            super.onSearchResultChanged(query, itemCount, extras);
+            if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
+                ((TestBrowserCallbackInterface) mCallbackProxy)
+                        .onSearchResultChanged(query, itemCount, extras);
+            }
+        }
+
+        @Override
+        public void onSearchResultLoaded(String query, int page, int pageSize,
+                List<MediaItem2> result, Bundle extras) {
+            super.onSearchResultLoaded(query, page, pageSize, result, extras);
+            if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
+                ((TestBrowserCallbackInterface) mCallbackProxy)
+                        .onSearchResultLoaded(query, page, pageSize, result, extras);
+            }
+        }
+
+        @Override
+        public void onChildrenChanged(String parentId, int childCount, Bundle extras) {
+            super.onChildrenChanged(parentId, childCount, extras);
+            if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
+                ((TestBrowserCallbackInterface) mCallbackProxy)
+                        .onChildrenChanged(parentId, childCount, extras);
             }
         }
 
@@ -329,4 +583,4 @@
             return mCallback;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index ab63b8e..e162f1d 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -19,7 +19,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.MediaPlayerInterface.EventCallback;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
@@ -60,7 +60,6 @@
 @FlakyTest
 public class MediaController2Test extends MediaSession2TestBase {
     private static final String TAG = "MediaController2Test";
-    private static final int DEFAULT_RATING_TYPE = Rating2.RATING_5_STARS;
 
     PendingIntent mIntent;
     MediaSession2 mSession;
@@ -78,7 +77,6 @@
         mPlayer = new MockPlayer(1);
         mSession = new MediaSession2.Builder(mContext, mPlayer)
                 .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
-                .setRatingType(DEFAULT_RATING_TYPE)
                 .setSessionActivity(mIntent)
                 .setId(TAG).build();
         mController = createController(mSession.getToken());
@@ -210,11 +208,6 @@
     }
 
     @Test
-    public void testGetRatingType() throws InterruptedException {
-        assertEquals(DEFAULT_RATING_TYPE, mController.getRatingType());
-    }
-
-    @Test
     public void testGetSessionActivity() throws InterruptedException {
         PendingIntent sessionActivity = mController.getSessionActivity();
         assertEquals(mContext.getPackageName(), sessionActivity.getCreatorPackage());
@@ -389,7 +382,7 @@
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, query);
                 assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();;
+                latch.countDown();
             }
         };
         try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
@@ -413,7 +406,7 @@
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, uri);
                 assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();;
+                latch.countDown();
             }
         };
         try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
@@ -437,7 +430,7 @@
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, id);
                 assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();;
+                latch.countDown();
             }
         };
         try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
@@ -458,11 +451,12 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onPrepareFromSearch(ControllerInfo controller, String query, Bundle extras) {
+            public void onPrepareFromSearch(ControllerInfo controller, String query,
+                    Bundle extras) {
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, query);
                 assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();;
+                latch.countDown();
             }
         };
         try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
@@ -486,7 +480,7 @@
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, uri);
                 assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();;
+                latch.countDown();
             }
         };
         try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
@@ -510,7 +504,7 @@
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, id);
                 assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();;
+                latch.countDown();
             }
         };
         try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
@@ -523,6 +517,34 @@
     }
 
     @Test
+    public void testSetRating() throws InterruptedException {
+        final int ratingType = Rating2.RATING_5_STARS;
+        final float ratingValue = 3.5f;
+        final Rating2 rating = Rating2.newStarRating(mContext, ratingType, ratingValue);
+        final String mediaId = "media_id";
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final SessionCallback callback = new SessionCallback(mContext) {
+            @Override
+            public void onSetRating(ControllerInfo controller, String mediaIdOut,
+                    Rating2 ratingOut) {
+                assertEquals(mContext.getPackageName(), controller.getPackageName());
+                assertEquals(mediaId, mediaIdOut);
+                assertEquals(rating, ratingOut);
+                latch.countDown();
+            }
+        };
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+                .setSessionCallback(sHandlerExecutor, callback)
+                .setId("testSetRating").build()) {
+            MediaController2 controller = createController(session.getToken());
+            controller.setRating(mediaId, rating);
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        }
+    }
+
+    @Test
     public void testIsConnected() throws InterruptedException {
         assertTrue(mController.isConnected());
         sHandler.postAndSync(()->{
@@ -635,7 +657,7 @@
                 if (Process.myUid() == controller.getUid()) {
                     assertEquals(mContext.getPackageName(), controller.getPackageName());
                     assertFalse(controller.isTrusted());
-                    latch.countDown();;
+                    latch.countDown();
                 }
                 return super.onConnect(controller);
             }
@@ -653,7 +675,7 @@
         // TODO(jaewan): Add equivalent tests again
         /*
         final CountDownLatch latch = new CountDownLatch(1);
-        mController.addPlaybackListener((state) -> {
+        mController.registerPlayerEventCallback((state) -> {
             assertNotNull(state);
             assertEquals(PlaybackState.STATE_REWINDING, state.getState());
             latch.countDown();
@@ -752,16 +774,19 @@
 
     private void testNoInteraction() throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
-        final PlaybackListener playbackListener = (state) -> {
-            fail("Controller shouldn't be notified about change in session after the close.");
-            latch.countDown();
+        final EventCallback callback = new EventCallback() {
+            @Override
+            public void onPlaybackStateChanged(PlaybackState2 state) {
+                fail("Controller shouldn't be notified about change in session after the close.");
+                latch.countDown();
+            }
         };
         // TODO(jaewan): Add equivalent tests again
         /*
-        mController.addPlaybackListener(playbackListener, sHandler);
+        mController.registerPlayerEventCallback(playbackListener, sHandler);
         mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
         assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        mController.removePlaybackListener(playbackListener);
+        mController.unregisterPlayerEventCallback(playbackListener);
         */
     }
 
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index f5ac6aa..9de4ce7 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -28,9 +28,8 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
-import android.media.AudioManager;
 import android.media.MediaController2.PlaybackInfo;
-import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.MediaPlayerInterface.EventCallback;
 import android.media.MediaSession2.Builder;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
@@ -49,7 +48,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -253,51 +251,67 @@
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
     }
 
-    // TODO(jaewan): Re-enable test..
-    @Ignore
     @Test
-    public void testPlaybackStateChangedListener() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(2);
+    public void testRegisterEventCallback() throws InterruptedException {
+        final int testWhat = 1001;
         final MockPlayer player = new MockPlayer(0);
-        final PlaybackListener listener = (state) -> {
-            assertEquals(sHandler.getLooper(), Looper.myLooper());
-            assertNotNull(state);
-            switch ((int) latch.getCount()) {
-                case 2:
-                    assertEquals(PlaybackState2.STATE_PLAYING, state.getState());
-                    break;
-                case 1:
-                    assertEquals(PlaybackState2.STATE_PAUSED, state.getState());
-                    break;
-                case 0:
-                    fail();
+        final CountDownLatch playbackLatch = new CountDownLatch(3);
+        final CountDownLatch errorLatch = new CountDownLatch(1);
+        final EventCallback callback = new EventCallback() {
+            @Override
+            public void onPlaybackStateChanged(PlaybackState2 state) {
+                assertEquals(sHandler.getLooper(), Looper.myLooper());
+                switch ((int) playbackLatch.getCount()) {
+                    case 3:
+                        assertNull(state);
+                        break;
+                    case 2:
+                        assertNotNull(state);
+                        assertEquals(PlaybackState2.STATE_PLAYING, state.getState());
+                        break;
+                    case 1:
+                        assertNotNull(state);
+                        assertEquals(PlaybackState2.STATE_PAUSED, state.getState());
+                        break;
+                    case 0:
+                        fail();
+                }
+                playbackLatch.countDown();
             }
-            latch.countDown();
+
+            @Override
+            public void onError(String mediaId, int what, int extra) {
+                assertEquals(testWhat, what);
+                errorLatch.countDown();
+            }
         };
         player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PLAYING));
-        sHandler.postAndSync(() -> {
-            mSession.addPlaybackListener(sHandlerExecutor, listener);
-            // When the player is set, listeners will be notified about the player's current state.
-            mSession.setPlayer(player);
-        });
+        // EventCallback will be notified with the mPlayer's playback state (null)
+        mSession.registerPlayerEventCallback(sHandlerExecutor, callback);
+        // When the player is set, EventCallback will be notified about the new player's state.
+        mSession.setPlayer(player);
+        // When the player is set, EventCallback will be notified about the new player's state.
         player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertTrue(playbackLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        player.notifyError(testWhat);
+        assertTrue(errorLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
     public void testBadPlayer() throws InterruptedException {
         // TODO(jaewan): Add equivalent tests again
-        final CountDownLatch latch = new CountDownLatch(3); // expected call + 1
+        final CountDownLatch latch = new CountDownLatch(4); // expected call + 1
         final BadPlayer player = new BadPlayer(0);
-        sHandler.postAndSync(() -> {
-            mSession.addPlaybackListener(sHandlerExecutor, (state) -> {
+        mSession.registerPlayerEventCallback(sHandlerExecutor, new EventCallback() {
+            @Override
+            public void onPlaybackStateChanged(PlaybackState2 state) {
                 // This will be called for every setPlayer() calls, but no more.
                 assertNull(state);
                 latch.countDown();
-            });
-            mSession.setPlayer(player);
-            mSession.setPlayer(mPlayer);
+            }
         });
+        mSession.setPlayer(player);
+        mSession.setPlayer(mPlayer);
         player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
         assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
     }
@@ -308,7 +322,7 @@
         }
 
         @Override
-        public void removePlaybackListener(@NonNull PlaybackListener listener) {
+        public void unregisterEventCallback(@NonNull EventCallback listener) {
             // No-op. This bad player will keep push notification to the listener that is previously
             // registered by session.setPlayer().
         }
@@ -461,7 +475,8 @@
         }
 
         @Override
-        public boolean onCommandRequest(ControllerInfo controllerInfo, MediaSession2.Command command) {
+        public boolean onCommandRequest(ControllerInfo controllerInfo,
+                MediaSession2.Command command) {
             assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
             assertEquals(Process.myUid(), controllerInfo.getUid());
             assertFalse(controllerInfo.isTrusted());
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
index f5abfff..7106561 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -118,7 +118,7 @@
      */
     public PlaybackState2 createPlaybackState(int state) {
         return new PlaybackState2(mContext, state, 0, 0, 1.0f,
-                0, 0, null);
+                0, 0);
     }
 
     final MediaController2 createController(SessionToken2 token) throws InterruptedException {
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
index 5fabebc..e1cce25 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -26,7 +26,6 @@
 import android.media.TestServiceRegistry.SessionCallbackProxy;
 import android.media.TestUtils.SyncHandler;
 import android.os.Bundle;
-import android.os.Process;
 import android.util.Log;
 
 import java.io.FileDescriptor;
@@ -34,6 +33,8 @@
 import java.util.List;
 
 import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 import javax.annotation.concurrent.GuardedBy;
 
@@ -45,23 +46,32 @@
     public static final String ID = "TestLibrary";
 
     public static final String ROOT_ID = "rootId";
-    public static final Bundle EXTRA = new Bundle();
+    public static final Bundle EXTRAS = new Bundle();
 
     public static final String MEDIA_ID_GET_ITEM = "media_id_get_item";
 
     public static final String PARENT_ID = "parent_id";
     public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
     public static final String PARENT_ID_ERROR = "parent_id_error";
-    public static final List<MediaItem2> GET_CHILDREN_RESULT = new ArrayList<>();
 
-    private static final int CHILDREN_COUNT = 100;
+    public static final List<MediaItem2> GET_CHILDREN_RESULT = new ArrayList<>();
+    public static final int CHILDREN_COUNT = 100;
+
+    public static final String SEARCH_QUERY = "search_query";
+    public static final String SEARCH_QUERY_TAKES_TIME = "search_query_takes_time";
+    public static final int SEARCH_TIME_IN_MS = 5000;
+    public static final String SEARCH_QUERY_EMPTY_RESULT = "search_query_empty_result";
+
+    public static final List<MediaItem2> SEARCH_RESULT = new ArrayList<>();
+    public static final int SEARCH_RESULT_COUNT = 50;
+
     private static final DataSourceDesc DATA_SOURCE_DESC =
             new DataSourceDesc.Builder().setDataSource(new FileDescriptor()).build();
 
     private static final String TAG = "MockMediaLibrarySvc2";
 
     static {
-        EXTRA.putString(ROOT_ID, ROOT_ID);
+        EXTRAS.putString(ROOT_ID, ROOT_ID);
     }
     @GuardedBy("MockMediaLibraryService2.class")
     private static SessionToken2 sToken;
@@ -71,9 +81,15 @@
     public MockMediaLibraryService2() {
         super();
         GET_CHILDREN_RESULT.clear();
-        String mediaIdPrefix = "media_id_";
+        String getChildrenMediaIdPrefix = "get_children_media_id_";
         for (int i = 0; i < CHILDREN_COUNT; i++) {
-            GET_CHILDREN_RESULT.add(createMediaItem(mediaIdPrefix + i));
+            GET_CHILDREN_RESULT.add(createMediaItem(getChildrenMediaIdPrefix + i));
+        }
+
+        SEARCH_RESULT.clear();
+        String getSearchResultMediaIdPrefix = "get_search_result_media_id_";
+        for (int i = 0; i < SEARCH_RESULT_COUNT; i++) {
+            SEARCH_RESULT.add(createMediaItem(getSearchResultMediaIdPrefix + i));
         }
     }
 
@@ -133,7 +149,7 @@
 
         @Override
         public LibraryRoot onGetRoot(ControllerInfo controller, Bundle rootHints) {
-            return new LibraryRoot(MockMediaLibraryService2.this, ROOT_ID, EXTRA);
+            return new LibraryRoot(MockMediaLibraryService2.this, ROOT_ID, EXTRAS);
         }
 
         @Override
@@ -147,7 +163,7 @@
 
         @Override
         public List<MediaItem2> onLoadChildren(ControllerInfo controller, String parentId, int page,
-                int pageSize, Bundle options) {
+                int pageSize, Bundle extras) {
             if (PARENT_ID.equals(parentId)) {
                 return getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize);
             } else if (PARENT_ID_ERROR.equals(parentId)) {
@@ -156,6 +172,47 @@
             // Includes the case of PARENT_ID_NO_CHILDREN.
             return new ArrayList<>();
         }
+
+        @Override
+        public void onSearch(ControllerInfo controllerInfo, String query, Bundle extras) {
+            if (SEARCH_QUERY.equals(query)) {
+                mSession.notifySearchResultChanged(controllerInfo, query, SEARCH_RESULT_COUNT,
+                        extras);
+            } else if (SEARCH_QUERY_TAKES_TIME.equals(query)) {
+                // Searching takes some time. Notify after 5 seconds.
+                Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
+                    @Override
+                    public void run() {
+                        mSession.notifySearchResultChanged(
+                                controllerInfo, query, SEARCH_RESULT_COUNT, extras);
+                    }
+                }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS);
+            } else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
+                mSession.notifySearchResultChanged(controllerInfo, query, 0, extras);
+            } else {
+                // TODO: For the error case, how should we notify the browser?
+            }
+        }
+
+        @Override
+        public List<MediaItem2> onLoadSearchResult(ControllerInfo controllerInfo,
+                String query, int page, int pageSize, Bundle extras) {
+            if (SEARCH_QUERY.equals(query)) {
+                return getPaginatedResult(SEARCH_RESULT, page, pageSize);
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void onSubscribed(ControllerInfo controller, String parentId, Bundle extras) {
+            mCallbackProxy.onSubscribed(controller, parentId, extras);
+        }
+
+        @Override
+        public void onUnsubscribed(ControllerInfo controller, String parentId) {
+            mCallbackProxy.onUnsubscribed(controller, parentId);
+        }
     }
 
     private List<MediaItem2> getPaginatedResult(List<MediaItem2> items, int page, int pageSize) {
@@ -191,4 +248,4 @@
                         .build(),
                 0 /* Flags */);
     }
-}
\ No newline at end of file
+}
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
index 1faf0f4..ae31ce6 100644
--- a/packages/MediaComponents/test/src/android/media/MockPlayer.java
+++ b/packages/MediaComponents/test/src/android/media/MockPlayer.java
@@ -19,6 +19,7 @@
 import android.media.MediaSession2.PlaylistParams;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.util.ArrayMap;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -46,7 +47,7 @@
     public boolean mSetPlaylistCalled;
     public boolean mSetPlaylistParamsCalled;
 
-    public List<PlaybackListenerHolder> mListeners = new ArrayList<>();
+    public ArrayMap<EventCallback, Executor> mCallbacks = new ArrayMap<>();
     public List<MediaItem2> mPlaylist;
     public PlaylistParams mPlaylistParams;
 
@@ -146,23 +147,30 @@
     }
 
     @Override
-    public void addPlaybackListener(@NonNull Executor executor,
-            @NonNull PlaybackListener listener) {
-        mListeners.add(new PlaybackListenerHolder(executor, listener));
+    public void registerEventCallback(@NonNull Executor executor,
+            @NonNull EventCallback callback) {
+        mCallbacks.put(callback, executor);
     }
 
     @Override
-    public void removePlaybackListener(@NonNull PlaybackListener listener) {
-        int index = PlaybackListenerHolder.indexOf(mListeners, listener);
-        if (index >= 0) {
-            mListeners.remove(index);
-        }
+    public void unregisterEventCallback(@NonNull EventCallback callback) {
+        mCallbacks.remove(callback);
     }
 
     public void notifyPlaybackState(final PlaybackState2 state) {
         mLastPlaybackState = state;
-        for (int i = 0; i < mListeners.size(); i++) {
-            mListeners.get(i).postPlaybackChange(state);
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            final EventCallback callback = mCallbacks.keyAt(i);
+            final Executor executor = mCallbacks.valueAt(i);
+            executor.execute(() -> callback.onPlaybackStateChanged(state));
+        }
+    }
+
+    public void notifyError(int what) {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            final EventCallback callback = mCallbacks.keyAt(i);
+            final Executor executor = mCallbacks.valueAt(i);
+            executor.execute(() -> callback.onError(null, what, 0));
         }
     }
 
diff --git a/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java b/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
deleted file mode 100644
index 0f1644c..0000000
--- a/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.media.MediaPlayerInterface.PlaybackListener;
-import android.os.Handler;
-import android.support.annotation.NonNull;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Holds {@link PlaybackListener} with the {@link Handler}.
- */
-public class PlaybackListenerHolder {
-    public final Executor executor;
-    public final PlaybackListener listener;
-
-    public PlaybackListenerHolder(Executor executor, @NonNull PlaybackListener listener) {
-        this.executor = executor;
-        this.listener = listener;
-    }
-
-    public void postPlaybackChange(final PlaybackState2 state) {
-        executor.execute(() -> listener.onPlaybackChanged(state));
-    }
-
-    /**
-     * Returns {@code true} if the given list contains a {@link PlaybackListenerHolder} that holds
-     * the given listener.
-     *
-     * @param list list to check
-     * @param listener listener to check
-     * @return {@code true} if the given list contains listener. {@code false} otherwise.
-     */
-    public static <Holder extends PlaybackListenerHolder> boolean contains(
-            @NonNull List<Holder> list, PlaybackListener listener) {
-        return indexOf(list, listener) >= 0;
-    }
-
-    /**
-     * Returns the index of the {@link PlaybackListenerHolder} that contains the given listener.
-     *
-     * @param list list to check
-     * @param listener listener to check
-     * @return {@code index} of item if the given list contains listener. {@code -1} otherwise.
-     */
-    public static <Holder extends PlaybackListenerHolder> int indexOf(
-            @NonNull List<Holder> list, PlaybackListener listener) {
-        for (int i = 0; i < list.size(); i++) {
-            if (list.get(i).listener == listener) {
-                return i;
-            }
-        }
-        return -1;
-    }
-}
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
index 3800c28..a18ad8e 100644
--- a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
+++ b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
@@ -22,6 +22,7 @@
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.TestUtils.SyncHandler;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Process;
 import android.support.annotation.GuardedBy;
@@ -73,6 +74,9 @@
          * Called when enclosing service is destroyed.
          */
         public void onServiceDestroyed() { }
+
+        public void onSubscribed(ControllerInfo info, String parentId, Bundle extra) { }
+        public void onUnsubscribed(ControllerInfo info, String parentId) { }
     }
 
     @GuardedBy("TestServiceRegistry.class")
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index a3ce1f6..9a30f71 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1099,26 +1099,17 @@
     if (status != NO_ERROR) {
         return status;
     }
+    if (output == AUDIO_IO_HANDLE_NONE) {
+        return BAD_VALUE;
+    }
     ALOG_ASSERT(stream != AUDIO_STREAM_PATCH, "attempt to change AUDIO_STREAM_PATCH volume");
 
     AutoMutex lock(mLock);
-    Vector<VolumeInterface *> volumeInterfaces;
-    if (output != AUDIO_IO_HANDLE_NONE) {
-        VolumeInterface *volumeInterface = getVolumeInterface_l(output);
-        if (volumeInterface == NULL) {
-            return BAD_VALUE;
-        }
-        volumeInterfaces.add(volumeInterface);
+    VolumeInterface *volumeInterface = getVolumeInterface_l(output);
+    if (volumeInterface == NULL) {
+        return BAD_VALUE;
     }
-
-    mStreamTypes[stream].volume = value;
-
-    if (volumeInterfaces.size() == 0) {
-        volumeInterfaces = getAllVolumeInterfaces_l();
-    }
-    for (size_t i = 0; i < volumeInterfaces.size(); i++) {
-        volumeInterfaces[i]->setStreamVolume(stream, value);
-    }
+    volumeInterface->setStreamVolume(stream, value);
 
     return NO_ERROR;
 }
@@ -1157,21 +1148,17 @@
     if (status != NO_ERROR) {
         return 0.0f;
     }
-
-    AutoMutex lock(mLock);
-    float volume;
-    if (output != AUDIO_IO_HANDLE_NONE) {
-        VolumeInterface *volumeInterface = getVolumeInterface_l(output);
-        if (volumeInterface != NULL) {
-            volume = volumeInterface->streamVolume(stream);
-        } else {
-            volume = 0.0f;
-        }
-    } else {
-        volume = streamVolume_l(stream);
+    if (output == AUDIO_IO_HANDLE_NONE) {
+        return 0.0f;
     }
 
-    return volume;
+    AutoMutex lock(mLock);
+    VolumeInterface *volumeInterface = getVolumeInterface_l(output);
+    if (volumeInterface == NULL) {
+        return 0.0f;
+    }
+
+    return volumeInterface->streamVolume(stream);
 }
 
 bool AudioFlinger::streamMute(audio_stream_type_t stream) const
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 7c38bcc..ebd1b18 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -616,9 +616,6 @@
               // no range check, AudioFlinger::mLock held
               bool streamMute_l(audio_stream_type_t stream) const
                                 { return mStreamTypes[stream].mute; }
-              // no range check, doesn't check per-thread stream volume, AudioFlinger::mLock held
-              float streamVolume_l(audio_stream_type_t stream) const
-                                { return mStreamTypes[stream].volume; }
               void ioConfigChanged(audio_io_config_event event,
                                    const sp<AudioIoDescriptor>& ioDesc,
                                    pid_t pid = 0);
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index cae296e..3134323 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1703,11 +1703,14 @@
     readOutputParameters_l();
 
     // ++ operator does not compile
-    for (audio_stream_type_t stream = AUDIO_STREAM_MIN; stream < AUDIO_STREAM_CNT;
+    for (audio_stream_type_t stream = AUDIO_STREAM_MIN; stream < AUDIO_STREAM_FOR_POLICY_CNT;
             stream = (audio_stream_type_t) (stream + 1)) {
-        mStreamTypes[stream].volume = mAudioFlinger->streamVolume_l(stream);
+        mStreamTypes[stream].volume = 0.0f;
         mStreamTypes[stream].mute = mAudioFlinger->streamMute_l(stream);
     }
+    // Audio patch volume is always max
+    mStreamTypes[AUDIO_STREAM_PATCH].volume = 1.0f;
+    mStreamTypes[AUDIO_STREAM_PATCH].mute = false;
 }
 
 AudioFlinger::PlaybackThread::~PlaybackThread()
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index e8416d4..57d9371 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -3791,9 +3791,11 @@
 
 // ---
 
-void AudioPolicyManager::addOutput(audio_io_handle_t output, const sp<SwAudioOutputDescriptor>& outputDesc)
+void AudioPolicyManager::addOutput(audio_io_handle_t output,
+                                   const sp<SwAudioOutputDescriptor>& outputDesc)
 {
     mOutputs.add(output, outputDesc);
+    applyStreamVolumes(outputDesc, AUDIO_DEVICE_NONE, 0 /* delayMs */, true /* force */);
     updateMono(output); // update mono status when adding to output list
     selectOutputForMusicEffects();
     nextAudioPortGeneration();
@@ -3805,7 +3807,8 @@
     selectOutputForMusicEffects();
 }
 
-void AudioPolicyManager::addInput(audio_io_handle_t input, const sp<AudioInputDescriptor>& inputDesc)
+void AudioPolicyManager::addInput(audio_io_handle_t input,
+                                  const sp<AudioInputDescriptor>& inputDesc)
 {
     mInputs.add(input, inputDesc);
     nextAudioPortGeneration();
@@ -3955,9 +3958,6 @@
                         // outputs used by dynamic policy mixes
                         audio_io_handle_t duplicatedOutput = AUDIO_IO_HANDLE_NONE;
 
-                        // set initial stream volume for device
-                        applyStreamVolumes(desc, device, 0, true);
-
                         //TODO: configure audio effect output stage here
 
                         // open a duplicating output thread for the new output and the primary output
@@ -3968,7 +3968,6 @@
                         if (status == NO_ERROR) {
                             // add duplicated output descriptor
                             addOutput(duplicatedOutput, dupOutputDesc);
-                            applyStreamVolumes(dupOutputDesc, device, 0, true);
                         } else {
                             ALOGW("checkOutputsForDevice() could not open dup output for %d and %d",
                                     mPrimaryOutput->mIoHandle, output);
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 89ca6bb..1df6b6a 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -67,6 +67,7 @@
 #include "api1/Camera2Client.h"
 #include "api2/CameraDeviceClient.h"
 #include "utils/CameraTraces.h"
+#include "utils/TagMonitor.h"
 
 namespace {
     const char* kPermissionServiceName = "permission";
@@ -1364,7 +1365,7 @@
         LOG_ALWAYS_FATAL_IF(client.get() == nullptr, "%s: CameraService in invalid state",
                 __FUNCTION__);
 
-        err = client->initialize(mCameraProviderManager);
+        err = client->initialize(mCameraProviderManager, mMonitorTags);
         if (err != OK) {
             ALOGE("%s: Could not initialize client from HAL.", __FUNCTION__);
             // Errors could be from the HAL module open call or from AppOpsManager
@@ -1733,8 +1734,6 @@
 }
 
 bool CameraService::evictClientIdByRemote(const wp<IBinder>& remote) {
-    const int callingPid = getCallingPid();
-    const int servicePid = getpid();
     bool ret = false;
     {
         // Acquire mServiceLock and prevent other clients from connecting
@@ -1750,8 +1749,7 @@
                 mActiveClientManager.remove(i);
                 continue;
             }
-            if (remote == clientSp->getRemote() && (callingPid == servicePid ||
-                    callingPid == clientSp->getClientPid())) {
+            if (remote == clientSp->getRemote()) {
                 mActiveClientManager.remove(i);
                 evicted.push_back(clientSp);
 
@@ -2643,6 +2641,16 @@
         dprintf(fd, "CameraStates in use, may be deadlocked\n");
     }
 
+    int argSize = args.size();
+    for (int i = 0; i < argSize; i++) {
+        if (args[i] == TagMonitor::kMonitorOption) {
+            if (i + 1 < argSize) {
+                mMonitorTags = String8(args[i + 1]);
+            }
+            break;
+        }
+    }
+
     for (auto& state : mCameraStates) {
         String8 cameraId = state.first;
 
@@ -2770,7 +2778,7 @@
       * While tempting to promote the wp<IBinder> into a sp, it's actually not supported by the
       * binder driver
       */
-
+    // PID here is approximate and can be wrong.
     logClientDied(getCallingPid(), String8("Binder died unexpectedly"));
 
     // check torch client
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 81048e6..cbfc50b 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -204,7 +204,8 @@
 
     class BasicClient : public virtual RefBase {
     public:
-        virtual status_t       initialize(sp<CameraProviderManager> manager) = 0;
+        virtual status_t       initialize(sp<CameraProviderManager> manager,
+                const String8& monitorTags) = 0;
         virtual binder::Status disconnect();
 
         // because we can't virtually inherit IInterface, which breaks
@@ -606,6 +607,9 @@
     RingBuffer<String8> mEventLog;
     Mutex mLogLock;
 
+    // The last monitored tags set by client
+    String8 mMonitorTags;
+
     // Currently allowed user IDs
     std::set<userid_t> mAllowedUsers;
 
diff --git a/services/camera/libcameraservice/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index 0a82cb9..3578bba 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -69,8 +69,8 @@
     mLegacyMode = legacyMode;
 }
 
-status_t Camera2Client::initialize(sp<CameraProviderManager> manager) {
-    return initializeImpl(manager);
+status_t Camera2Client::initialize(sp<CameraProviderManager> manager, const String8& monitorTags) {
+    return initializeImpl(manager, monitorTags);
 }
 
 bool Camera2Client::isZslEnabledInStillTemplate() {
@@ -88,13 +88,13 @@
 }
 
 template<typename TProviderPtr>
-status_t Camera2Client::initializeImpl(TProviderPtr providerPtr)
+status_t Camera2Client::initializeImpl(TProviderPtr providerPtr, const String8& monitorTags)
 {
     ATRACE_CALL();
     ALOGV("%s: Initializing client for camera %d", __FUNCTION__, mCameraId);
     status_t res;
 
-    res = Camera2ClientBase::initialize(providerPtr);
+    res = Camera2ClientBase::initialize(providerPtr, monitorTags);
     if (res != OK) {
         return res;
     }
diff --git a/services/camera/libcameraservice/api1/Camera2Client.h b/services/camera/libcameraservice/api1/Camera2Client.h
index 1ebf4b0..44929c3 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.h
+++ b/services/camera/libcameraservice/api1/Camera2Client.h
@@ -101,7 +101,8 @@
 
     virtual ~Camera2Client();
 
-    virtual status_t initialize(sp<CameraProviderManager> manager) override;
+    virtual status_t initialize(sp<CameraProviderManager> manager,
+            const String8& monitorTags) override;
 
     virtual status_t dump(int fd, const Vector<String16>& args);
 
@@ -224,7 +225,7 @@
     status_t overrideVideoSnapshotSize(Parameters &params);
 
     template<typename TProviderPtr>
-    status_t initializeImpl(TProviderPtr providerPtr);
+    status_t initializeImpl(TProviderPtr providerPtr, const String8& monitorTags);
 
     bool isZslEnabledInStillTemplate();
 };
diff --git a/services/camera/libcameraservice/api1/CameraClient.cpp b/services/camera/libcameraservice/api1/CameraClient.cpp
index 8c6cd3d..f6d27ab 100644
--- a/services/camera/libcameraservice/api1/CameraClient.cpp
+++ b/services/camera/libcameraservice/api1/CameraClient.cpp
@@ -62,7 +62,8 @@
     LOG1("CameraClient::CameraClient X (pid %d, id %d)", callingPid, cameraId);
 }
 
-status_t CameraClient::initialize(sp<CameraProviderManager> manager) {
+status_t CameraClient::initialize(sp<CameraProviderManager> manager,
+        const String8& /*monitorTags*/) {
     int callingPid = getCallingPid();
     status_t res;
 
diff --git a/services/camera/libcameraservice/api1/CameraClient.h b/services/camera/libcameraservice/api1/CameraClient.h
index 7f93fef..1910536 100644
--- a/services/camera/libcameraservice/api1/CameraClient.h
+++ b/services/camera/libcameraservice/api1/CameraClient.h
@@ -72,7 +72,8 @@
             bool legacyMode = false);
     ~CameraClient();
 
-    virtual status_t initialize(sp<CameraProviderManager> manager) override;
+    virtual status_t initialize(sp<CameraProviderManager> manager,
+            const String8& monitorTags) override;
 
     virtual status_t dump(int fd, const Vector<String16>& args);
 
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index a7cc3c3..efe3ca1 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -88,16 +88,17 @@
     ALOGI("CameraDeviceClient %s: Opened", cameraId.string());
 }
 
-status_t CameraDeviceClient::initialize(sp<CameraProviderManager> manager) {
-    return initializeImpl(manager);
+status_t CameraDeviceClient::initialize(sp<CameraProviderManager> manager,
+        const String8& monitorTags) {
+    return initializeImpl(manager, monitorTags);
 }
 
 template<typename TProviderPtr>
-status_t CameraDeviceClient::initializeImpl(TProviderPtr providerPtr) {
+status_t CameraDeviceClient::initializeImpl(TProviderPtr providerPtr, const String8& monitorTags) {
     ATRACE_CALL();
     status_t res;
 
-    res = Camera2ClientBase::initialize(providerPtr);
+    res = Camera2ClientBase::initialize(providerPtr, monitorTags);
     if (res != OK) {
         return res;
     }
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.h b/services/camera/libcameraservice/api2/CameraDeviceClient.h
index 4e049d8..5aaf5aa 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.h
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.h
@@ -157,7 +157,8 @@
             int servicePid);
     virtual ~CameraDeviceClient();
 
-    virtual status_t      initialize(sp<CameraProviderManager> manager) override;
+    virtual status_t      initialize(sp<CameraProviderManager> manager,
+            const String8& monitorTags) override;
 
     virtual status_t      dump(int fd, const Vector<String16>& args);
 
@@ -225,7 +226,7 @@
     std::vector<int32_t> mSupportedPhysicalRequestKeys;
 
     template<typename TProviderPtr>
-    status_t      initializeImpl(TProviderPtr providerPtr);
+    status_t      initializeImpl(TProviderPtr providerPtr, const String8& monitorTags);
 
     /** Utility members */
     binder::Status checkPidStatus(const char* checkLocation);
diff --git a/services/camera/libcameraservice/common/Camera2ClientBase.cpp b/services/camera/libcameraservice/common/Camera2ClientBase.cpp
index db26027..459e45d 100644
--- a/services/camera/libcameraservice/common/Camera2ClientBase.cpp
+++ b/services/camera/libcameraservice/common/Camera2ClientBase.cpp
@@ -80,13 +80,15 @@
 }
 
 template <typename TClientBase>
-status_t Camera2ClientBase<TClientBase>::initialize(sp<CameraProviderManager> manager) {
-    return initializeImpl(manager);
+status_t Camera2ClientBase<TClientBase>::initialize(sp<CameraProviderManager> manager,
+        const String8& monitorTags) {
+    return initializeImpl(manager, monitorTags);
 }
 
 template <typename TClientBase>
 template <typename TProviderPtr>
-status_t Camera2ClientBase<TClientBase>::initializeImpl(TProviderPtr providerPtr) {
+status_t Camera2ClientBase<TClientBase>::initializeImpl(TProviderPtr providerPtr,
+        const String8& monitorTags) {
     ATRACE_CALL();
     ALOGV("%s: Initializing client for camera %s", __FUNCTION__,
           TClientBase::mCameraIdStr.string());
@@ -104,7 +106,7 @@
         return NO_INIT;
     }
 
-    res = mDevice->initialize(providerPtr);
+    res = mDevice->initialize(providerPtr, monitorTags);
     if (res != OK) {
         ALOGE("%s: Camera %s: unable to initialize device: %s (%d)",
                 __FUNCTION__, TClientBase::mCameraIdStr.string(), strerror(-res), res);
diff --git a/services/camera/libcameraservice/common/Camera2ClientBase.h b/services/camera/libcameraservice/common/Camera2ClientBase.h
index edeae5b..e74fbdf 100644
--- a/services/camera/libcameraservice/common/Camera2ClientBase.h
+++ b/services/camera/libcameraservice/common/Camera2ClientBase.h
@@ -56,7 +56,7 @@
                       int servicePid);
     virtual ~Camera2ClientBase();
 
-    virtual status_t      initialize(sp<CameraProviderManager> manager);
+    virtual status_t      initialize(sp<CameraProviderManager> manager, const String8& monitorTags);
     virtual status_t      dumpClient(int fd, const Vector<String16>& args);
 
     /**
@@ -145,7 +145,7 @@
 
 private:
     template<typename TProviderPtr>
-    status_t              initializeImpl(TProviderPtr providerPtr);
+    status_t              initializeImpl(TProviderPtr providerPtr, const String8& monitorTags);
 };
 
 }; // namespace android
diff --git a/services/camera/libcameraservice/common/CameraDeviceBase.h b/services/camera/libcameraservice/common/CameraDeviceBase.h
index ad83c3d..0ba7403 100644
--- a/services/camera/libcameraservice/common/CameraDeviceBase.h
+++ b/services/camera/libcameraservice/common/CameraDeviceBase.h
@@ -60,7 +60,7 @@
      */
     virtual metadata_vendor_id_t getVendorTagId() const = 0;
 
-    virtual status_t initialize(sp<CameraProviderManager> manager) = 0;
+    virtual status_t initialize(sp<CameraProviderManager> manager, const String8& monitorTags) = 0;
     virtual status_t disconnect() = 0;
 
     virtual status_t dump(int fd, const Vector<String16> &args) = 0;
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 04c2c5b..42bcb2d 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -96,7 +96,7 @@
     return mId;
 }
 
-status_t Camera3Device::initialize(sp<CameraProviderManager> manager) {
+status_t Camera3Device::initialize(sp<CameraProviderManager> manager, const String8& monitorTags) {
     ATRACE_CALL();
     Mutex::Autolock il(mInterfaceLock);
     Mutex::Autolock l(mLock);
@@ -169,6 +169,10 @@
     mInterface = new HalInterface(session, queue);
     std::string providerType;
     mVendorTagId = manager->getProviderTagIdLocked(mId.string());
+    mTagMonitor.initialize(mVendorTagId);
+    if (!monitorTags.isEmpty()) {
+        mTagMonitor.parseTagsToMonitor(String8(monitorTags));
+    }
 
     return initializeCommonLocked();
 }
@@ -192,8 +196,6 @@
     /** Create buffer manager */
     mBufferManager = new Camera3BufferManager();
 
-    mTagMonitor.initialize(mVendorTagId);
-
     Vector<int32_t> sessionParamKeys;
     camera_metadata_entry_t sessionKeysEntry = mDeviceInfo.find(
             ANDROID_REQUEST_AVAILABLE_SESSION_KEYS);
@@ -582,13 +584,12 @@
     bool dumpTemplates = false;
 
     String16 templatesOption("-t");
-    String16 monitorOption("-m");
     int n = args.size();
     for (int i = 0; i < n; i++) {
         if (args[i] == templatesOption) {
             dumpTemplates = true;
         }
-        if (args[i] == monitorOption) {
+        if (args[i] == TagMonitor::kMonitorOption) {
             if (i + 1 < n) {
                 String8 monitorTags = String8(args[i + 1]);
                 if (monitorTags == "off") {
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 7faa6e5..13b83ba 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -96,7 +96,7 @@
     metadata_vendor_id_t getVendorTagId() const override { return mVendorTagId; }
 
     // Transitions to idle state on success.
-    status_t initialize(sp<CameraProviderManager> manager) override;
+    status_t initialize(sp<CameraProviderManager> manager, const String8& monitorTags) override;
     status_t disconnect() override;
     status_t dump(int fd, const Vector<String16> &args) override;
     const CameraMetadata& info() const override;
diff --git a/services/camera/libcameraservice/utils/TagMonitor.cpp b/services/camera/libcameraservice/utils/TagMonitor.cpp
index dec97d7..c0a353f 100644
--- a/services/camera/libcameraservice/utils/TagMonitor.cpp
+++ b/services/camera/libcameraservice/utils/TagMonitor.cpp
@@ -33,6 +33,8 @@
         mVendorTagId(CAMERA_METADATA_INVALID_VENDOR_ID)
 {}
 
+const String16 TagMonitor::kMonitorOption = String16("-m");
+
 const char* TagMonitor::k3aTags =
         "android.control.aeMode, android.control.afMode, android.control.awbMode,"
         "android.control.aeState, android.control.afState, android.control.awbState,"
diff --git a/services/camera/libcameraservice/utils/TagMonitor.h b/services/camera/libcameraservice/utils/TagMonitor.h
index 7155314..2dece62 100644
--- a/services/camera/libcameraservice/utils/TagMonitor.h
+++ b/services/camera/libcameraservice/utils/TagMonitor.h
@@ -38,6 +38,10 @@
  * buffer log that can be dumped at will. */
 class TagMonitor {
   public:
+
+    // Monitor argument
+    static const String16 kMonitorOption;
+
     enum eventSource {
         REQUEST,
         RESULT