Merge "Prevent Hearing Aid fromn spamming logs" into pi-dev
diff --git a/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h
new file mode 120000
index 0000000..e2dd082
--- /dev/null
+++ b/include/media/ExtractorUtils.h
@@ -0,0 +1 @@
+../../media/libmediaextractor/include/media/ExtractorUtils.h
\ No newline at end of file
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index 3f832bc..fc60fd4 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -22,6 +22,7 @@
 #include "MatroskaExtractor.h"
 
 #include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
 #include <media/MediaTrack.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AUtils.h>
@@ -1108,7 +1109,7 @@
     meta.setData(kKeyFlacMetadata, 0, codecPrivate, codecPrivateSize);
 
     int32_t maxInputSize = 64 << 10;
-    sp<FLACDecoder> flacDecoder = FLACDecoder::Create();
+    FLACDecoder *flacDecoder = FLACDecoder::Create();
     if (flacDecoder != NULL
             && flacDecoder->parseMetadata((const uint8_t*)codecPrivate, codecPrivateSize) == OK) {
         FLAC__StreamMetadata_StreamInfo streamInfo = flacDecoder->getStreamInfo();
@@ -1120,6 +1121,7 @@
                     && streamInfo.channels != 0
                     && ((streamInfo.bits_per_sample + 7) / 8) >
                         INT32_MAX / streamInfo.max_blocksize / streamInfo.channels) {
+                delete flacDecoder;
                 return ERROR_MALFORMED;
             }
             maxInputSize = ((streamInfo.bits_per_sample + 7) / 8)
@@ -1128,6 +1130,7 @@
     }
     meta.setInt32(kKeyMaxInputSize, maxInputSize);
 
+    delete flacDecoder;
     return OK;
 }
 
@@ -1143,13 +1146,13 @@
     }
 
     const mkvparser::Block::Frame &frame = block->GetFrame(0);
-    sp<ABuffer> abuf = new ABuffer(frame.len);
-    long n = frame.Read(mReader, abuf->data());
+    auto tmpData = heapbuffer<unsigned char>(frame.len);
+    long n = frame.Read(mReader, tmpData.get());
     if (n != 0) {
         return ERROR_MALFORMED;
     }
 
-    if (!MakeAVCCodecSpecificData(trackInfo->mMeta, abuf)) {
+    if (!MakeAVCCodecSpecificData(trackInfo->mMeta, tmpData.get(), frame.len)) {
         return ERROR_MALFORMED;
     }
 
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index 4d49013..b2fe69c 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -22,6 +22,7 @@
 
 #include <cutils/properties.h>
 #include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
 #include <media/MediaTrack.h>
 #include <media/VorbisComment.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -990,21 +991,11 @@
     return OK;
 }
 
-struct TmpData {
-    uint8_t *data;
-    TmpData(size_t size) {
-        data = (uint8_t*) malloc(size);
-    }
-    ~TmpData() {
-        free(data);
-    }
-};
-
 status_t MyOpusExtractor::verifyOpusComments(MediaBufferBase *buffer) {
     // add artificial framing bit so we can reuse _vorbis_unpack_comment
     int32_t commentSize = buffer->range_length() + 1;
-    TmpData commentDataHolder(commentSize);
-    uint8_t *commentData = commentDataHolder.data;
+    auto tmp = heapbuffer<uint8_t>(commentSize);
+    uint8_t *commentData = tmp.get();
     if (commentData == nullptr) {
         return ERROR_MALFORMED;
     }
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
index 9eed96d..61d7d27 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
@@ -64,27 +64,54 @@
  * The read and write must be symmetric.
  */
 status_t AudioEndpointParcelable::writeToParcel(Parcel* parcel) const {
-    parcel->writeInt32(mNumSharedMemories);
+    status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+    if (status != NO_ERROR) goto error;
+
+    status = parcel->writeInt32(mNumSharedMemories);
+    if (status != NO_ERROR) goto error;
+
     for (int i = 0; i < mNumSharedMemories; i++) {
-        mSharedMemories[i].writeToParcel(parcel);
+        status = mSharedMemories[i].writeToParcel(parcel);
+        if (status != NO_ERROR) goto error;
     }
-    mUpMessageQueueParcelable.writeToParcel(parcel);
-    mDownMessageQueueParcelable.writeToParcel(parcel);
-    mUpDataQueueParcelable.writeToParcel(parcel);
-    mDownDataQueueParcelable.writeToParcel(parcel);
-    return NO_ERROR; // TODO check for errors above
+    status = mUpMessageQueueParcelable.writeToParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mDownMessageQueueParcelable.writeToParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mUpDataQueueParcelable.writeToParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mDownDataQueueParcelable.writeToParcel(parcel);
+    if (status != NO_ERROR) goto error;
+
+    return NO_ERROR;
+
+error:
+    ALOGE("%s returning %d", __func__, status);
+    return status;
 }
 
 status_t AudioEndpointParcelable::readFromParcel(const Parcel* parcel) {
-    parcel->readInt32(&mNumSharedMemories);
+    status_t status = parcel->readInt32(&mNumSharedMemories);
+    if (status != NO_ERROR) goto error;
+
     for (int i = 0; i < mNumSharedMemories; i++) {
         mSharedMemories[i].readFromParcel(parcel);
+        if (status != NO_ERROR) goto error;
     }
-    mUpMessageQueueParcelable.readFromParcel(parcel);
-    mDownMessageQueueParcelable.readFromParcel(parcel);
-    mUpDataQueueParcelable.readFromParcel(parcel);
-    mDownDataQueueParcelable.readFromParcel(parcel);
-    return NO_ERROR; // TODO check for errors above
+    status = mUpMessageQueueParcelable.readFromParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mDownMessageQueueParcelable.readFromParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mUpDataQueueParcelable.readFromParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mDownDataQueueParcelable.readFromParcel(parcel);
+    if (status != NO_ERROR) goto error;
+
+    return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
+    ALOGE("%s returning %d", __func__, status);
+    return status;
 }
 
 aaudio_result_t AudioEndpointParcelable::resolve(EndpointDescriptor *descriptor) {
@@ -109,35 +136,11 @@
     return AAudioConvert_androidToAAudioResult(err);
 }
 
-aaudio_result_t AudioEndpointParcelable::validate() {
-    aaudio_result_t result;
+aaudio_result_t AudioEndpointParcelable::validate() const {
     if (mNumSharedMemories < 0 || mNumSharedMemories >= MAX_SHARED_MEMORIES) {
         ALOGE("invalid mNumSharedMemories = %d", mNumSharedMemories);
         return AAUDIO_ERROR_INTERNAL;
     }
-    for (int i = 0; i < mNumSharedMemories; i++) {
-        result = mSharedMemories[i].validate();
-        if (result != AAUDIO_OK) {
-            ALOGE("invalid mSharedMemories[%d] = %d", i, result);
-            return result;
-        }
-    }
-    if ((result = mUpMessageQueueParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mUpMessageQueueParcelable = %d", result);
-        return result;
-    }
-    if ((result = mDownMessageQueueParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mDownMessageQueueParcelable = %d", result);
-        return result;
-    }
-    if ((result = mUpDataQueueParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mUpDataQueueParcelable = %d", result);
-        return result;
-    }
-    if ((result = mDownDataQueueParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mDownDataQueueParcelable = %d", result);
-        return result;
-    }
     return AAUDIO_OK;
 }
 
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.h b/media/libaaudio/src/binding/AudioEndpointParcelable.h
index aa8573f..e4f8b9e 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.h
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.h
@@ -56,8 +56,6 @@
 
     aaudio_result_t resolve(EndpointDescriptor *descriptor);
 
-    aaudio_result_t validate();
-
     aaudio_result_t close();
 
     void dump();
@@ -70,6 +68,8 @@
     RingBufferParcelable    mDownDataQueueParcelable;    // eg. playback
 
 private:
+    aaudio_result_t         validate() const;
+
     int32_t                 mNumSharedMemories = 0;
     SharedMemoryParcelable  mSharedMemories[MAX_SHARED_MEMORIES];
 };
diff --git a/media/libaaudio/src/binding/IAAudioService.cpp b/media/libaaudio/src/binding/IAAudioService.cpp
index b3c4934..620edc7 100644
--- a/media/libaaudio/src/binding/IAAudioService.cpp
+++ b/media/libaaudio/src/binding/IAAudioService.cpp
@@ -121,17 +121,11 @@
             ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION passed result %d", result);
             return result;
         }
-        err = parcelable.readFromParcel(&reply);;
+        err = parcelable.readFromParcel(&reply);
         if (err != NO_ERROR) {
             ALOGE("BpAAudioService::client transact(GET_STREAM_DESCRIPTION) read endpoint %d", err);
             return AAudioConvert_androidToAAudioResult(err);
         }
-        //parcelable.dump();
-        result = parcelable.validate();
-        if (result != AAUDIO_OK) {
-            ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION validation fails %d", result);
-            return result;
-        }
         return result;
     }
 
@@ -250,6 +244,7 @@
     pid_t tid;
     int64_t nanoseconds;
     aaudio_result_t result;
+    status_t status = NO_ERROR;
     ALOGV("BnAAudioService::onTransact(%i) %i", code, flags);
 
     switch(code) {
@@ -294,21 +289,20 @@
 
         case GET_STREAM_DESCRIPTION: {
             CHECK_INTERFACE(IAAudioService, data, reply);
-            data.readInt32(&streamHandle);
+            status = data.readInt32(&streamHandle);
+            if (status != NO_ERROR) {
+                return status;
+            }
             aaudio::AudioEndpointParcelable parcelable;
             result = getStreamDescription(streamHandle, parcelable);
             if (result != AAUDIO_OK) {
                 return AAudioConvert_aaudioToAndroidStatus(result);
             }
-            result = parcelable.validate();
-            if (result != AAUDIO_OK) {
-                ALOGE("BnAAudioService::onTransact getStreamDescription() returns %d", result);
-                parcelable.dump();
-                return AAudioConvert_aaudioToAndroidStatus(result);
+            status = reply->writeInt32(result);
+            if (status != NO_ERROR) {
+                return status;
             }
-            reply->writeInt32(result);
-            parcelable.writeToParcel(reply);
-            return NO_ERROR;
+            return parcelable.writeToParcel(reply);
         } break;
 
         case START_STREAM: {
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.cpp b/media/libaaudio/src/binding/RingBufferParcelable.cpp
index 2babbff..4996b3f 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.cpp
+++ b/media/libaaudio/src/binding/RingBufferParcelable.cpp
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include <binder/Parcelable.h>
+#include <utility/AAudioUtilities.h>
 
 #include "binding/AAudioServiceDefinitions.h"
 #include "binding/SharedRegionParcelable.h"
@@ -79,7 +80,10 @@
  * The read and write must be symmetric.
  */
 status_t RingBufferParcelable::writeToParcel(Parcel* parcel) const {
-    status_t status = parcel->writeInt32(mCapacityInFrames);
+    status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+    if (status != NO_ERROR) goto error;
+
+    status = parcel->writeInt32(mCapacityInFrames);
     if (status != NO_ERROR) goto error;
     if (mCapacityInFrames > 0) {
         status = parcel->writeInt32(mBytesPerFrame);
@@ -97,7 +101,7 @@
     }
     return NO_ERROR;
 error:
-    ALOGE("writeToParcel() error = %d", status);
+    ALOGE("%s returning %d", __func__, status);
     return status;
 }
 
@@ -118,9 +122,9 @@
         status = mDataParcelable.readFromParcel(parcel);
         if (status != NO_ERROR) goto error;
     }
-    return NO_ERROR;
+    return AAudioConvert_aaudioToAndroidStatus(validate());
 error:
-    ALOGE("readFromParcel() error = %d", status);
+    ALOGE("%s returning %d", __func__, status);
     return status;
 }
 
@@ -151,8 +155,7 @@
     return AAUDIO_OK;
 }
 
-aaudio_result_t RingBufferParcelable::validate() {
-    aaudio_result_t result;
+aaudio_result_t RingBufferParcelable::validate() const {
     if (mCapacityInFrames < 0 || mCapacityInFrames >= 32 * 1024) {
         ALOGE("invalid mCapacityInFrames = %d", mCapacityInFrames);
         return AAUDIO_ERROR_INTERNAL;
@@ -165,18 +168,6 @@
         ALOGE("invalid mFramesPerBurst = %d", mFramesPerBurst);
         return AAUDIO_ERROR_INTERNAL;
     }
-    if ((result = mReadCounterParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mReadCounterParcelable = %d", result);
-        return result;
-    }
-    if ((result = mWriteCounterParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mWriteCounterParcelable = %d", result);
-        return result;
-    }
-    if ((result = mDataParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mDataParcelable = %d", result);
-        return result;
-    }
     return AAUDIO_OK;
 }
 
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.h b/media/libaaudio/src/binding/RingBufferParcelable.h
index bd562f2..1dbcf07 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.h
+++ b/media/libaaudio/src/binding/RingBufferParcelable.h
@@ -66,11 +66,12 @@
 
     aaudio_result_t resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor);
 
-    aaudio_result_t validate();
-
     void dump();
 
 private:
+
+    aaudio_result_t validate() const;
+
     SharedRegionParcelable  mReadCounterParcelable;
     SharedRegionParcelable  mWriteCounterParcelable;
     SharedRegionParcelable  mDataParcelable;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
index 4e3e5d1..0b0cf77 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
@@ -48,7 +48,10 @@
 }
 
 status_t SharedMemoryParcelable::writeToParcel(Parcel* parcel) const {
-    status_t status = parcel->writeInt32(mSizeInBytes);
+    status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+    if (status != NO_ERROR) return status;
+
+    status = parcel->writeInt32(mSizeInBytes);
     if (status != NO_ERROR) return status;
     if (mSizeInBytes > 0) {
         ALOGV("writeToParcel() mFd = %d, this = %p\n", mFd.get(), this);
@@ -61,21 +64,27 @@
 
 status_t SharedMemoryParcelable::readFromParcel(const Parcel* parcel) {
     status_t status = parcel->readInt32(&mSizeInBytes);
-    if (status != NO_ERROR) {
-        return status;
-    }
+    if (status != NO_ERROR) goto error;
+
     if (mSizeInBytes > 0) {
         // The Parcel owns the file descriptor and will close it later.
         unique_fd mmapFd;
         status = parcel->readUniqueFileDescriptor(&mmapFd);
         if (status != NO_ERROR) {
             ALOGE("readFromParcel() readUniqueFileDescriptor() failed : %d", status);
-        } else {
-            // Resolve the memory now while we still have the FD from the Parcel.
-            // Closing the FD will not affect the shared memory once mmap() has been called.
-            status = AAudioConvert_androidToAAudioResult(resolveSharedMemory(mmapFd));
+            goto error;
         }
+
+        // Resolve the memory now while we still have the FD from the Parcel.
+        // Closing the FD will not affect the shared memory once mmap() has been called.
+        aaudio_result_t result = resolveSharedMemory(mmapFd);
+        status = AAudioConvert_aaudioToAndroidStatus(result);
+        if (status != NO_ERROR) goto error;
     }
+
+    return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
     return status;
 }
 
@@ -136,7 +145,7 @@
     return mSizeInBytes;
 }
 
-aaudio_result_t SharedMemoryParcelable::validate() {
+aaudio_result_t SharedMemoryParcelable::validate() const {
     if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
         ALOGE("invalid mSizeInBytes = %d", mSizeInBytes);
         return AAUDIO_ERROR_OUT_OF_RANGE;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.h b/media/libaaudio/src/binding/SharedMemoryParcelable.h
index 2a634e0..82c2240 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.h
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.h
@@ -61,8 +61,6 @@
 
     int32_t getSizeInBytes();
 
-    aaudio_result_t validate();
-
     void dump();
 
 protected:
@@ -74,6 +72,11 @@
     android::base::unique_fd   mFd;
     int32_t     mSizeInBytes = 0;
     uint8_t    *mResolvedAddress = MMAP_UNRESOLVED_ADDRESS;
+
+private:
+
+    aaudio_result_t validate() const;
+
 };
 
 } /* namespace aaudio */
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.cpp b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
index 7aa80bf..c776116 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
@@ -24,6 +24,7 @@
 #include <binder/Parcelable.h>
 
 #include <aaudio/AAudio.h>
+#include <utility/AAudioUtilities.h>
 
 #include "binding/SharedMemoryParcelable.h"
 #include "binding/SharedRegionParcelable.h"
@@ -47,21 +48,38 @@
 }
 
 status_t SharedRegionParcelable::writeToParcel(Parcel* parcel) const {
-    parcel->writeInt32(mSizeInBytes);
+    status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+    if (status != NO_ERROR) goto error;
+
+    status = parcel->writeInt32(mSizeInBytes);
+    if (status != NO_ERROR) goto error;
     if (mSizeInBytes > 0) {
-        parcel->writeInt32(mSharedMemoryIndex);
-        parcel->writeInt32(mOffsetInBytes);
+        status = parcel->writeInt32(mSharedMemoryIndex);
+        if (status != NO_ERROR) goto error;
+        status = parcel->writeInt32(mOffsetInBytes);
+        if (status != NO_ERROR) goto error;
     }
-    return NO_ERROR; // TODO check for errors above
+    return NO_ERROR;
+
+error:
+    ALOGE("%s returning %d", __func__, status);
+    return status;
 }
 
 status_t SharedRegionParcelable::readFromParcel(const Parcel* parcel) {
-    parcel->readInt32(&mSizeInBytes);
+    status_t status = parcel->readInt32(&mSizeInBytes);
+    if (status != NO_ERROR) goto error;
     if (mSizeInBytes > 0) {
-        parcel->readInt32(&mSharedMemoryIndex);
-        parcel->readInt32(&mOffsetInBytes);
+        status = parcel->readInt32(&mSharedMemoryIndex);
+        if (status != NO_ERROR) goto error;
+        status = parcel->readInt32(&mOffsetInBytes);
+        if (status != NO_ERROR) goto error;
     }
-    return NO_ERROR; // TODO check for errors above
+    return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
+    ALOGE("%s returning %d", __func__, status);
+    return status;
 }
 
 aaudio_result_t SharedRegionParcelable::resolve(SharedMemoryParcelable *memoryParcels,
@@ -78,7 +96,7 @@
     return memoryParcel->resolve(mOffsetInBytes, mSizeInBytes, regionAddressPtr);
 }
 
-aaudio_result_t SharedRegionParcelable::validate() {
+aaudio_result_t SharedRegionParcelable::validate() const {
     if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
         ALOGE("invalid mSizeInBytes = %d", mSizeInBytes);
         return AAUDIO_ERROR_OUT_OF_RANGE;
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.h b/media/libaaudio/src/binding/SharedRegionParcelable.h
index f6babfd..0cd8c04 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.h
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.h
@@ -47,14 +47,15 @@
 
     bool isFileDescriptorSafe(SharedMemoryParcelable *memoryParcels);
 
-    aaudio_result_t validate();
-
     void dump();
 
 protected:
     int32_t mSharedMemoryIndex = -1;
     int32_t mOffsetInBytes     = 0;
     int32_t mSizeInBytes       = 0;
+
+private:
+    aaudio_result_t validate() const;
 };
 
 } /* namespace aaudio */
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
index c43509c..7ff1ec7d 100644
--- a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
@@ -33,7 +33,8 @@
 }
 
 status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
-    if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
+    if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 &&
+        strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) {
         return mHidlFactory->openDevice(name, device);
     }
     return mLocalFactory->openDevice(name, device);
diff --git a/media/libmedia/NdkWrapper.cpp b/media/libmedia/NdkWrapper.cpp
index 5418af9..2cdb44e 100644
--- a/media/libmedia/NdkWrapper.cpp
+++ b/media/libmedia/NdkWrapper.cpp
@@ -1068,6 +1068,14 @@
     return OK;
 }
 
+status_t AMediaExtractorWrapper::disconnect() {
+    if (mAMediaExtractor != NULL) {
+        media_status_t err = AMediaExtractor_disconnect(mAMediaExtractor);
+        return translateErrorCode(err);
+    }
+    return DEAD_OBJECT;
+}
+
 AMediaExtractor *AMediaExtractorWrapper::getAMediaExtractor() const {
     return mAMediaExtractor;
 }
diff --git a/media/libmedia/include/media/NdkWrapper.h b/media/libmedia/include/media/NdkWrapper.h
index 191665a..c97d171 100644
--- a/media/libmedia/include/media/NdkWrapper.h
+++ b/media/libmedia/include/media/NdkWrapper.h
@@ -287,6 +287,8 @@
 
     status_t release();
 
+    status_t disconnect();
+
     status_t setDataSource(int fd, off64_t offset, off64_t length);
 
     status_t setDataSource(const char *location);
diff --git a/media/libmediaextractor/include/media/ExtractorUtils.h b/media/libmediaextractor/include/media/ExtractorUtils.h
new file mode 100644
index 0000000..22f9349
--- /dev/null
+++ b/media/libmediaextractor/include/media/ExtractorUtils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EXTRACTOR_UTILS_H_
+
+#define EXTRACTOR_UTILS_H_
+
+#include <memory>
+
+namespace android {
+
+template <class T>
+std::unique_ptr<T[]> heapbuffer(size_t size) {
+    return std::unique_ptr<T[]>(new (std::nothrow) T[size]);
+}
+
+}  // namespace android
+
+#endif  // UTILS_H_
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index b296622..6fd5677 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -37,7 +37,6 @@
 
 #include <media/stagefright/BufferProducerWrapper.h>
 #include <media/stagefright/MediaCodec.h>
-#include <media/stagefright/MediaCodecList.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/OMXClient.h>
 #include <media/stagefright/PersistentSurface.h>
@@ -6346,37 +6345,19 @@
 
     sp<AMessage> notify = new AMessage(kWhatOMXDied, mCodec);
 
-    Vector<AString> matchingCodecs;
-    Vector<AString> owners;
-
-    AString componentName;
-    CHECK(msg->findString("componentName", &componentName));
-
-    sp<IMediaCodecList> list = MediaCodecList::getInstance();
-    if (list == nullptr) {
-        ALOGE("Unable to obtain MediaCodecList while "
-                "attempting to create codec \"%s\"",
-                componentName.c_str());
-        mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
-        return false;
-    }
-    ssize_t index = list->findCodecByName(componentName.c_str());
-    if (index < 0) {
-        ALOGE("Unable to find codec \"%s\"",
-                componentName.c_str());
-        mCodec->signalError(OMX_ErrorInvalidComponent, NAME_NOT_FOUND);
-        return false;
-    }
-    sp<MediaCodecInfo> info = list->getCodecInfo(index);
+    sp<RefBase> obj;
+    CHECK(msg->findObject("codecInfo", &obj));
+    sp<MediaCodecInfo> info = (MediaCodecInfo *)obj.get();
     if (info == nullptr) {
-        ALOGE("Unexpected error (index out-of-bound) while "
-                "retrieving information for codec \"%s\"",
-                componentName.c_str());
+        ALOGE("Unexpected nullptr for codec information");
         mCodec->signalError(OMX_ErrorUndefined, UNKNOWN_ERROR);
         return false;
     }
     AString owner = (info->getOwnerName() == nullptr) ? "default" : info->getOwnerName();
 
+    AString componentName;
+    CHECK(msg->findString("componentName", &componentName));
+
     sp<CodecObserver> observer = new CodecObserver;
     sp<IOMX> omx;
     sp<IOMXNode> omxNode;
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 13d80f5..71bff84 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -49,6 +49,100 @@
 }
 
 cc_library_shared {
+    name: "libstagefright_codecbase",
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "CodecBase.cpp",
+        "FrameRenderTracker.cpp",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+    shared_libs: [
+        "libgui",
+        "liblog",
+        "libmedia",
+        "libstagefright_foundation",
+        "libui",
+        "libutils",
+        "android.hardware.cas.native@1.0",
+    ],
+
+    sanitize: {
+        cfi: true,
+        misc_undefined: [
+            "unsigned-integer-overflow",
+            "signed-integer-overflow",
+        ],
+        diag: {
+            cfi: true,
+        },
+    },
+}
+
+cc_library_shared {
+    name: "libstagefright_ccodec",
+
+    local_include_dirs: ["include"],
+
+    srcs: [
+        "C2OMXNode.cpp",
+        "CCodec.cpp",
+        "CCodecBufferChannel.cpp",
+        "Codec2Buffer.cpp",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+    header_libs: [
+        "libstagefright_codec2_internal",
+    ],
+
+    shared_libs: [
+        "libbinder",
+        "libcutils",
+        "libgui",
+        "libhidlallocatorutils",
+        "libhidlbase",
+        "liblog",
+        "libmedia",
+        "libmedia_omx",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_codecbase",
+        "libstagefright_foundation",
+        "libstagefright_omx_utils",
+        "libui",
+        "libutils",
+        "libv4l2_c2componentstore",
+        "android.hardware.cas.native@1.0",
+
+        // TODO: do not link directly with impl
+        "libstagefright_bufferqueue_helper",
+        "android.hardware.media.c2@1.0-service-impl",
+    ],
+
+    sanitize: {
+        cfi: true,
+        misc_undefined: [
+            "unsigned-integer-overflow",
+            "signed-integer-overflow",
+        ],
+        diag: {
+            cfi: true,
+        },
+    },
+}
+
+cc_library_shared {
     name: "libstagefright",
 
     srcs: [
@@ -60,11 +154,7 @@
         "AudioPresentationInfo.cpp",
         "AudioSource.cpp",
         "BufferImpl.cpp",
-        "C2OMXNode.cpp",
-        "CCodec.cpp",
-        "CCodecBufferChannel.cpp",
         "Codec2InfoBuilder.cpp",
-        "CodecBase.cpp",
         "CallbackDataSource.cpp",
         "CallbackMediaSource.cpp",
         "CameraSource.cpp",
@@ -74,7 +164,6 @@
         "DataURISource.cpp",
         "FileSource.cpp",
         "FrameDecoder.cpp",
-        "FrameRenderTracker.cpp",
         "HTTPBase.cpp",
         "HevcUtils.cpp",
         "InterfaceUtils.cpp",
@@ -107,10 +196,6 @@
         "VideoFrameScheduler.cpp",
     ],
 
-    header_libs: [
-        "libstagefright_codec2_internal",
-    ],
-
     shared_libs: [
         "libaudioutils",
         "libbinder",
@@ -131,8 +216,10 @@
         "libui",
         "libutils",
         "libmedia_helper",
+        "libstagefright_ccodec",
         "libstagefright_codec2",
         "libstagefright_codec2_vndk",
+        "libstagefright_codecbase",
         "libstagefright_foundation",
         "libstagefright_omx",
         "libstagefright_omx_utils",
@@ -149,10 +236,6 @@
         "android.hardware.media.omx@1.0",
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.mapper@2.0",
-
-        // TODO: do not link directly with impl
-        "android.hardware.media.c2@1.0-service-impl",
-        "libstagefright_bufferqueue_helper",
     ],
 
     static_libs: [
diff --git a/media/libstagefright/BufferImpl.cpp b/media/libstagefright/BufferImpl.cpp
index 7b3fa02..b760273 100644
--- a/media/libstagefright/BufferImpl.cpp
+++ b/media/libstagefright/BufferImpl.cpp
@@ -24,7 +24,6 @@
 #include <media/ICrypto.h>
 #include <utils/NativeHandle.h>
 
-#include "include/Codec2Buffer.h"
 #include "include/SecureBuffer.h"
 #include "include/SharedMemoryBuffer.h"
 
@@ -64,592 +63,4 @@
     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) {
-        // Nothing to copy, so we can copy by doing nothing.
-        return true;
-    }
-    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 || 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(
-        const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
-    C2WriteView writeView(block->map().get());
-    if (writeView.error() != C2_OK) {
-        return nullptr;
-    }
-    return new LinearBlockBuffer(format, std::move(writeView), block);
-}
-
-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)
-    : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
-      mWriteView(writeView),
-      mBlock(block) {
-}
-
-// ConstLinearBlockBuffer
-
-// static
-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), buffer);
-}
-
-ConstLinearBlockBuffer::ConstLinearBlockBuffer(
-        const sp<AMessage> &format,
-        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),
-      mBufferRef(buffer) {
-}
-
-std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
-    return std::move(mBufferRef);
-}
-
-// GraphicView2MediaImageConverter
-
-namespace {
-
-class GraphicView2MediaImageConverter {
-public:
-    explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
-        : mInitCheck(NO_INIT),
-          mView(view),
-          mWidth(view.width()),
-          mHeight(view.height()),
-          mAllocatedDepth(0),
-          mBackBufferSize(0),
-          mMediaImage(new ABuffer(sizeof(MediaImage2))) {
-        if (view.error() != C2_OK) {
-            ALOGD("Converter: view.error() = %d", view.error());
-            mInitCheck = BAD_VALUE;
-            return;
-        }
-        MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
-        const C2PlanarLayout &layout = view.layout();
-        if (layout.numPlanes == 0) {
-            ALOGD("Converter: 0 planes");
-            mInitCheck = BAD_VALUE;
-            return;
-        }
-        mAllocatedDepth = layout.planes[0].allocatedDepth;
-        uint32_t bitDepth = layout.planes[0].bitDepth;
-
-        switch (layout.type) {
-            case C2PlanarLayout::TYPE_YUV:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
-            case C2PlanarLayout::TYPE_YUVA:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
-            case C2PlanarLayout::TYPE_RGB:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
-            case C2PlanarLayout::TYPE_RGBA:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
-            default:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
-        }
-        mediaImage->mNumPlanes = layout.numPlanes;
-        mediaImage->mWidth = mWidth;
-        mediaImage->mHeight = mHeight;
-        mediaImage->mBitDepth = bitDepth;
-        mediaImage->mBitDepthAllocated = mAllocatedDepth;
-
-        uint32_t bufferSize = 0;
-        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            if (plane.rightShift != 0) {
-                ALOGV("rightShift value of %u unsupported", plane.rightShift);
-                mInitCheck = BAD_VALUE;
-                return;
-            }
-            if (plane.endianness != C2PlaneInfo::NATIVE) {
-                ALOGV("endianness value of %u unsupported", plane.endianness);
-                mInitCheck = BAD_VALUE;
-                return;
-            }
-            if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
-                ALOGV("different allocatedDepth/bitDepth per plane unsupported");
-                mInitCheck = BAD_VALUE;
-                return;
-            }
-            bufferSize += mWidth * mHeight
-                    / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
-        }
-
-        mBackBufferSize = bufferSize;
-        mInitCheck = OK;
-    }
-
-    status_t initCheck() const { return mInitCheck; }
-
-    uint32_t backBufferSize() const { return mBackBufferSize; }
-
-    /**
-     * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
-     * is not copied over in this function --- the caller should use
-     * CopyGraphicView2MediaImage() function to do that explicitly.
-     *
-     * \param   view[in]          source C2GraphicView object.
-     * \param   alloc[in]         allocator function for ABuffer.
-     * \param   mediaImage[out]   destination MediaImage2 object.
-     * \param   buffer[out]       new buffer object.
-     * \param   wrapped[out]      whether we wrapped around existing map or
-     *                            allocated a new buffer
-     *
-     * \return  true              if conversion succeeds,
-     *          false             otherwise; all output params should be ignored.
-     */
-    sp<ABuffer> wrap() {
-        MediaImage2 *mediaImage = getMediaImage();
-        const C2PlanarLayout &layout = mView.layout();
-        if (layout.numPlanes == 1) {
-            const C2PlaneInfo &plane = layout.planes[0];
-            ssize_t offset = plane.minOffset(mWidth, mHeight);
-            mediaImage->mPlane[0].mOffset = -offset;
-            mediaImage->mPlane[0].mColInc = plane.colInc;
-            mediaImage->mPlane[0].mRowInc = plane.rowInc;
-            mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
-            mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
-            return new ABuffer(
-                    const_cast<uint8_t *>(mView.data()[0] + offset),
-                    plane.maxOffset(mWidth, mHeight) - offset + 1);
-        }
-        const uint8_t *minPtr = mView.data()[0];
-        const uint8_t *maxPtr = mView.data()[0];
-        int32_t planeSize = 0;
-        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            ssize_t minOffset = plane.minOffset(mWidth, mHeight);
-            ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
-            if (minPtr > mView.data()[i] + minOffset) {
-                minPtr = mView.data()[i] + minOffset;
-            }
-            if (maxPtr < mView.data()[i] + maxOffset) {
-                maxPtr = mView.data()[i] + maxOffset;
-            }
-            planeSize += std::abs(plane.rowInc) * mHeight
-                    / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
-        }
-
-        if ((maxPtr - minPtr + 1) <= planeSize) {
-            // FIXME: this is risky as reading/writing data out of bound results in
-            //        an undefined behavior.
-            for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-                const C2PlaneInfo &plane = layout.planes[i];
-                mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
-                mediaImage->mPlane[i].mColInc = plane.colInc;
-                mediaImage->mPlane[i].mRowInc = plane.rowInc;
-                mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
-                mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
-            }
-            return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
-        }
-
-        return nullptr;
-    }
-
-    bool setBackBuffer(const sp<ABuffer> &backBuffer) {
-        if (backBuffer->capacity() < mBackBufferSize) {
-            return false;
-        }
-        backBuffer->setRange(0, mBackBufferSize);
-
-        const C2PlanarLayout &layout = mView.layout();
-        MediaImage2 *mediaImage = getMediaImage();
-        uint32_t offset = 0;
-        // TODO: keep interleaved planes together
-        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            mediaImage->mPlane[i].mOffset = offset;
-            mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
-            mediaImage->mPlane[i].mRowInc =
-                mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
-            mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
-            mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
-            offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
-        }
-        mBackBuffer = backBuffer;
-        return true;
-    }
-
-    /**
-     * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
-     * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
-     *
-     * \param   view[in]          source C2GraphicView object.
-     * \param   mediaImage[in]    destination MediaImage2 object.
-     * \param   buffer[out]       new buffer object.
-     */
-    void copy() {
-        // TODO: more efficient copying --- e.g. one row at a time, copying
-        //       interleaved planes together, etc.
-        const C2PlanarLayout &layout = mView.layout();
-        MediaImage2 *mediaImage = getMediaImage();
-        uint8_t *dst = mBackBuffer->base();
-        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            const uint8_t *src = mView.data()[i];
-            int32_t planeW = mWidth / plane.colSampling;
-            int32_t planeH = mHeight / plane.rowSampling;
-            for (int32_t row = 0; row < planeH; ++row) {
-                for(int32_t col = 0; col < planeW; ++col) {
-                    memcpy(dst, src, mAllocatedDepth / 8);
-                    dst += mediaImage->mPlane[i].mColInc;
-                    src += plane.colInc;
-                }
-                dst -= mediaImage->mPlane[i].mColInc * planeW;
-                dst += mediaImage->mPlane[i].mRowInc;
-                src -= plane.colInc * planeW;
-                src += plane.rowInc;
-            }
-        }
-    }
-
-    const sp<ABuffer> &imageData() const { return mMediaImage; }
-
-private:
-    status_t mInitCheck;
-
-    const C2GraphicView mView;
-    uint32_t mWidth;
-    uint32_t mHeight;
-    uint32_t mAllocatedDepth;
-    uint32_t mBackBufferSize;
-    sp<ABuffer> mMediaImage;
-    std::function<sp<ABuffer>(size_t)> mAlloc;
-
-    sp<ABuffer> mBackBuffer;
-
-    MediaImage2 *getMediaImage() {
-        return (MediaImage2 *)mMediaImage->base();
-    }
-};
-
-}  // namespace
-
-// GraphicBlockBuffer
-
-// static
-sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
-        const sp<AMessage> &format,
-        const std::shared_ptr<C2GraphicBlock> &block,
-        std::function<sp<ABuffer>(size_t)> alloc) {
-    C2GraphicView view(block->map().get());
-    if (view.error() != C2_OK) {
-        ALOGD("C2GraphicBlock::map failed: %d", view.error());
-        return nullptr;
-    }
-    GraphicView2MediaImageConverter converter(view);
-    if (converter.initCheck() != OK) {
-        ALOGD("Converter init failed: %d", converter.initCheck());
-        return nullptr;
-    }
-    bool wrapped = true;
-    sp<ABuffer> buffer = converter.wrap();
-    if (buffer == nullptr) {
-        buffer = alloc(converter.backBufferSize());
-        if (!converter.setBackBuffer(buffer)) {
-            ALOGD("Converter failed to set back buffer");
-            return nullptr;
-        }
-        wrapped = false;
-    }
-    return new GraphicBlockBuffer(
-            format,
-            buffer,
-            std::move(view),
-            block,
-            converter.imageData(),
-            wrapped);
-}
-
-GraphicBlockBuffer::GraphicBlockBuffer(
-        const sp<AMessage> &format,
-        const sp<ABuffer> &buffer,
-        C2GraphicView &&view,
-        const std::shared_ptr<C2GraphicBlock> &block,
-        const sp<ABuffer> &imageData,
-        bool wrapped)
-    : Codec2Buffer(format, buffer),
-      mView(view),
-      mBlock(block),
-      mImageData(imageData),
-      mWrapped(wrapped) {
-    meta()->setBuffer("image-data", imageData);
-}
-
-std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
-    uint32_t width = mView.width();
-    uint32_t height = mView.height();
-    if (!mWrapped) {
-        MediaImage2 *mediaImage = imageData();
-        const C2PlanarLayout &layout = mView.layout();
-        for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            int32_t planeW = width / plane.colSampling;
-            int32_t planeH = height / plane.rowSampling;
-            const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
-            uint8_t *dst = mView.data()[i];
-            for (int32_t row = 0; row < planeH; ++row) {
-                for (int32_t col = 0; col < planeW; ++col) {
-                    memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
-                    src += mediaImage->mPlane[i].mColInc;
-                    dst += plane.colInc;
-                }
-                src -= mediaImage->mPlane[i].mColInc * planeW;
-                dst -= plane.colInc * planeW;
-                src += mediaImage->mPlane[i].mRowInc;
-                dst += plane.rowInc;
-            }
-        }
-    }
-    return C2Buffer::CreateGraphicBuffer(
-            mBlock->share(C2Rect(width, height), C2Fence()));
-}
-
-// ConstGraphicBlockBuffer
-
-// static
-sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
-        const sp<AMessage> &format,
-        const std::shared_ptr<C2Buffer> &buffer,
-        std::function<sp<ABuffer>(size_t)> alloc) {
-    if (!buffer
-            || buffer->data().type() != C2BufferData::GRAPHIC
-            || buffer->data().graphicBlocks().size() != 1u) {
-        ALOGD("C2Buffer precond fail");
-        return nullptr;
-    }
-    std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
-            buffer->data().graphicBlocks()[0].map().get()));
-    std::unique_ptr<const C2GraphicView> holder;
-
-    GraphicView2MediaImageConverter converter(*view);
-    if (converter.initCheck() != OK) {
-        ALOGD("Converter init failed: %d", converter.initCheck());
-        return nullptr;
-    }
-    bool wrapped = true;
-    sp<ABuffer> aBuffer = converter.wrap();
-    if (aBuffer == nullptr) {
-        aBuffer = alloc(converter.backBufferSize());
-        if (!converter.setBackBuffer(aBuffer)) {
-            ALOGD("Converter failed to set back buffer");
-            return nullptr;
-        }
-        wrapped = false;
-        converter.copy();
-        // We don't need the view.
-        holder = std::move(view);
-    }
-    return new ConstGraphicBlockBuffer(
-            format,
-            aBuffer,
-            std::move(view),
-            buffer,
-            converter.imageData(),
-            wrapped);
-}
-
-// static
-sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
-        const sp<AMessage> &format,
-        std::function<sp<ABuffer>(size_t)> alloc) {
-    int32_t width, height;
-    if (!format->findInt32("width", &width)
-            || !format->findInt32("height", &height)) {
-        ALOGD("format had no width / height");
-        return nullptr;
-    }
-    sp<ABuffer> aBuffer(alloc(width * height * 4));
-    return new ConstGraphicBlockBuffer(
-            format,
-            aBuffer,
-            nullptr,
-            nullptr,
-            nullptr,
-            false);
-}
-
-ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
-        const sp<AMessage> &format,
-        const sp<ABuffer> &aBuffer,
-        std::unique_ptr<const C2GraphicView> &&view,
-        const std::shared_ptr<C2Buffer> &buffer,
-        const sp<ABuffer> &imageData,
-        bool wrapped)
-    : Codec2Buffer(format, aBuffer),
-      mView(std::move(view)),
-      mBufferRef(buffer),
-      mWrapped(wrapped) {
-    if (imageData != nullptr) {
-        meta()->setBuffer("image-data", imageData);
-    }
-}
-
-std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
-    mView.reset();
-    return std::move(mBufferRef);
-}
-
-bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
-    if (mWrapped || mBufferRef) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
-                mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
-        return false;
-    }
-    if (!buffer) {
-        // Nothing to copy, so we can copy by doing nothing.
-        return true;
-    }
-    if (buffer->data().type() != C2BufferData::GRAPHIC) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
-        return false;
-    }
-    if (buffer->data().graphicBlocks().size() == 0) {
-        return true;
-    } else if (buffer->data().graphicBlocks().size() != 1u) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
-        return false;
-    }
-    GraphicView2MediaImageConverter converter(
-            buffer->data().graphicBlocks()[0].map().get());
-    if (converter.initCheck() != OK) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
-        return false;
-    }
-    if (converter.backBufferSize() > capacity()) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
-                converter.backBufferSize(), capacity());
-        return false;
-    }
-    return true;
-}
-
-bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
-    if (!buffer || buffer->data().graphicBlocks().size() == 0) {
-        setRange(0, 0);
-        return true;
-    }
-    GraphicView2MediaImageConverter converter(
-            buffer->data().graphicBlocks()[0].map().get());
-    if (converter.initCheck() != OK) {
-        ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
-        return false;
-    }
-    sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
-    if (!converter.setBackBuffer(aBuffer)) {
-        ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
-        return false;
-    }
-    converter.copy();
-    meta()->setBuffer("image-data", converter.imageData());
-    mBufferRef = buffer;
-    return true;
-}
-
 }  // namespace android
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index 0bdd808..0a20d34 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -259,20 +259,26 @@
         return;
     }
 
-    AString componentName;
-    if (!msg->findString("componentName", &componentName)) {
-        // TODO: find componentName appropriate with the media type
-    }
+    sp<RefBase> codecInfo;
+    CHECK(msg->findObject("codecInfo", &codecInfo));
+    // For Codec 2.0 components, componentName == codecInfo->getCodecName().
 
     sp<AMessage> allocMsg(new AMessage(kWhatAllocate, this));
-    allocMsg->setString("componentName", componentName);
+    allocMsg->setObject("codecInfo", codecInfo);
     allocMsg->post();
 }
 
-void CCodec::allocate(const AString &componentName) {
-    ALOGV("allocate(%s)", componentName.c_str());
+void CCodec::allocate(const sp<MediaCodecInfo> &codecInfo) {
+    if (codecInfo == nullptr) {
+        mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+        return;
+    }
+    ALOGV("allocate(%s)", codecInfo->getCodecName());
     mListener.reset(new CCodecListener(this));
 
+    AString componentName = codecInfo->getCodecName();
+    // TODO: use codecInfo->getOwnerName() for connecting to remote process.
+
     std::shared_ptr<C2Component> comp;
     c2_status_t err = GetCodec2PlatformComponentStore()->createComponent(
             componentName.c_str(), &comp);
@@ -812,9 +818,9 @@
         case kWhatAllocate: {
             // C2ComponentStore::createComponent() should return within 100ms.
             setDeadline(now + 150ms, "allocate");
-            AString componentName;
-            CHECK(msg->findString("componentName", &componentName));
-            allocate(componentName);
+            sp<RefBase> obj;
+            CHECK(msg->findObject("codecInfo", &obj));
+            allocate((MediaCodecInfo *)obj.get());
             break;
         }
         case kWhatConfigure: {
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index 65d637b..cbe4f16 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -49,6 +49,8 @@
 using namespace hardware::cas::V1_0;
 using namespace hardware::cas::native::V1_0;
 
+using CasStatus = hardware::cas::V1_0::Status;
+
 /**
  * Base class for representation of buffers at one port.
  */
@@ -181,6 +183,7 @@
 // TODO: get this info from component
 const static size_t kMinBufferArraySize = 16;
 const static size_t kLinearBufferSize = 524288;
+const static size_t kMaxGraphicBufferRefCount = 4;
 
 /**
  * Simple local buffer pool backed by std::vector.
@@ -291,21 +294,6 @@
     DISALLOW_EVIL_CONSTRUCTORS(LocalBufferPool);
 };
 
-sp<LinearBlockBuffer> AllocateLinearBuffer(
-        const std::shared_ptr<C2BlockPool> &pool,
-        const sp<AMessage> &format,
-        size_t size,
-        const C2MemoryUsage &usage) {
-    std::shared_ptr<C2LinearBlock> block;
-
-    c2_status_t err = pool->fetchLinearBlock(size, usage, &block);
-    if (err != C2_OK) {
-        return nullptr;
-    }
-
-    return LinearBlockBuffer::Allocate(format, block);
-}
-
 sp<GraphicBlockBuffer> AllocateGraphicBuffer(
         const std::shared_ptr<C2BlockPool> &pool,
         const sp<AMessage> &format,
@@ -572,9 +560,7 @@
     bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
         // 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);
+        sp<Codec2Buffer> newBuffer = alloc(kLinearBufferSize);
         if (newBuffer == nullptr) {
             return false;
         }
@@ -598,17 +584,88 @@
         array->initialize(
                 mImpl,
                 kMinBufferArraySize,
-                [pool = mPool, format = mFormat] () -> sp<Codec2Buffer> {
-                    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-                    return AllocateLinearBuffer(pool, format, kLinearBufferSize, usage);
-                });
+                [this] () -> sp<Codec2Buffer> { return alloc(kLinearBufferSize); });
         return std::move(array);
     }
 
+    virtual sp<Codec2Buffer> alloc(size_t size) const {
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        std::shared_ptr<C2LinearBlock> block;
+
+        c2_status_t err = mPool->fetchLinearBlock(size, usage, &block);
+        if (err != C2_OK) {
+            return nullptr;
+        }
+
+        return LinearBlockBuffer::Allocate(mFormat, block);
+    }
+
 private:
     FlexBuffersImpl mImpl;
 };
 
+class EncryptedLinearInputBuffers : public LinearInputBuffers {
+public:
+    EncryptedLinearInputBuffers(
+            bool secure,
+            const sp<MemoryDealer> &dealer,
+            const sp<ICrypto> &crypto,
+            int32_t heapSeqNum)
+        : mUsage({0, 0}),
+          mDealer(dealer),
+          mCrypto(crypto),
+          mHeapSeqNum(heapSeqNum) {
+        if (secure) {
+            mUsage = { C2MemoryUsage::READ_PROTECTED, 0 };
+        } else {
+            mUsage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        }
+        for (size_t i = 0; i < kMinBufferArraySize; ++i) {
+            sp<IMemory> memory = mDealer->allocate(kLinearBufferSize);
+            if (memory == nullptr) {
+                ALOGD("Failed to allocate memory from dealer: only %zu slots allocated", i);
+                break;
+            }
+            mMemoryVector.push_back({std::weak_ptr<C2LinearBlock>(), memory});
+        }
+    }
+
+    ~EncryptedLinearInputBuffers() override {
+    }
+
+    sp<Codec2Buffer> alloc(size_t size) const override {
+        sp<IMemory> memory;
+        for (const Entry &entry : mMemoryVector) {
+            if (entry.block.expired()) {
+                memory = entry.memory;
+                break;
+            }
+        }
+        if (memory == nullptr) {
+            return nullptr;
+        }
+
+        std::shared_ptr<C2LinearBlock> block;
+        c2_status_t err = mPool->fetchLinearBlock(size, mUsage, &block);
+        if (err != C2_OK) {
+            return nullptr;
+        }
+
+        return new EncryptedLinearBlockBuffer(mFormat, block, memory, mHeapSeqNum);
+    }
+
+private:
+    C2MemoryUsage mUsage;
+    sp<MemoryDealer> mDealer;
+    sp<ICrypto> mCrypto;
+    int32_t mHeapSeqNum;
+    struct Entry {
+        std::weak_ptr<C2LinearBlock> block;
+        sp<IMemory> memory;
+    };
+    std::vector<Entry> mMemoryVector;
+};
+
 class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
 public:
     GraphicInputBuffers() : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {}
@@ -956,7 +1013,8 @@
 
 CCodecBufferChannel::CCodecBufferChannel(
         const std::function<void(status_t, enum ActionCode)> &onError)
-    : mOnError(onError),
+    : mHeapSeqNum(-1),
+      mOnError(onError),
       mFrameIndex(0u),
       mFirstValidFrameIndex(0u) {
 }
@@ -978,13 +1036,7 @@
     return OK;
 }
 
-status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
-    QueueGuard guard(mSync);
-    if (!guard.isRunning()) {
-        ALOGW("No more buffers should be queued at current state.");
-        return -ENOSYS;
-    }
-
+status_t CCodecBufferChannel::queueInputBufferInternal(const sp<MediaCodecBuffer> &buffer) {
     int64_t timeUs;
     CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
 
@@ -1005,7 +1057,11 @@
     work->input.buffers.clear();
     {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
-        work->input.buffers.push_back((*buffers)->releaseBuffer(buffer));
+        std::shared_ptr<C2Buffer> c2buffer = (*buffers)->releaseBuffer(buffer);
+        if (!c2buffer) {
+            return -ENOENT;
+        }
+        work->input.buffers.push_back(c2buffer);
     }
     // TODO: fill info's
 
@@ -1017,22 +1073,103 @@
     return mComponent->queue_nb(&items);
 }
 
+status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
+    QueueGuard guard(mSync);
+    if (!guard.isRunning()) {
+        ALOGW("No more buffers should be queued at current state.");
+        return -ENOSYS;
+    }
+    return queueInputBufferInternal(buffer);
+}
+
 status_t CCodecBufferChannel::queueSecureInputBuffer(
         const sp<MediaCodecBuffer> &buffer, bool secure, const uint8_t *key,
         const uint8_t *iv, CryptoPlugin::Mode mode, CryptoPlugin::Pattern pattern,
         const CryptoPlugin::SubSample *subSamples, size_t numSubSamples,
         AString *errorDetailMsg) {
-    // TODO
-    (void) buffer;
-    (void) secure;
-    (void) key;
-    (void) iv;
-    (void) mode;
-    (void) pattern;
-    (void) subSamples;
-    (void) numSubSamples;
-    (void) errorDetailMsg;
-    return -ENOSYS;
+    QueueGuard guard(mSync);
+    if (!guard.isRunning()) {
+        ALOGW("No more buffers should be queued at current state.");
+        return -ENOSYS;
+    }
+
+    if (!hasCryptoOrDescrambler()) {
+        return -ENOSYS;
+    }
+    sp<EncryptedLinearBlockBuffer> encryptedBuffer((EncryptedLinearBlockBuffer *)buffer.get());
+
+    ssize_t result = -1;
+    if (mCrypto != nullptr) {
+        ICrypto::DestinationBuffer destination;
+        if (secure) {
+            destination.mType = ICrypto::kDestinationTypeNativeHandle;
+            destination.mHandle = encryptedBuffer->handle();
+        } else {
+            destination.mType = ICrypto::kDestinationTypeSharedMemory;
+            destination.mSharedMemory = mDecryptDestination;
+        }
+        ICrypto::SourceBuffer source;
+        encryptedBuffer->fillSourceBuffer(&source);
+        result = mCrypto->decrypt(
+                key, iv, mode, pattern, source, buffer->offset(),
+                subSamples, numSubSamples, destination, errorDetailMsg);
+        if (result < 0) {
+            return result;
+        }
+        if (destination.mType == ICrypto::kDestinationTypeSharedMemory) {
+            encryptedBuffer->copyDecryptedContent(mDecryptDestination, result);
+        }
+    } else {
+        // Here we cast CryptoPlugin::SubSample to hardware::cas::native::V1_0::SubSample
+        // directly, the structure definitions should match as checked in DescramblerImpl.cpp.
+        hidl_vec<SubSample> hidlSubSamples;
+        hidlSubSamples.setToExternal((SubSample *)subSamples, numSubSamples, false /*own*/);
+
+        hardware::cas::native::V1_0::SharedBuffer srcBuffer;
+        encryptedBuffer->fillSourceBuffer(&srcBuffer);
+
+        DestinationBuffer dstBuffer;
+        if (secure) {
+            dstBuffer.type = BufferType::NATIVE_HANDLE;
+            dstBuffer.secureMemory = hidl_handle(encryptedBuffer->handle());
+        } else {
+            dstBuffer.type = BufferType::SHARED_MEMORY;
+            dstBuffer.nonsecureMemory = srcBuffer;
+        }
+
+        CasStatus status = CasStatus::OK;
+        hidl_string detailedError;
+
+        auto returnVoid = mDescrambler->descramble(
+                key != NULL ? (ScramblingControl)key[0] : ScramblingControl::UNSCRAMBLED,
+                hidlSubSamples,
+                srcBuffer,
+                0,
+                dstBuffer,
+                0,
+                [&status, &result, &detailedError] (
+                        CasStatus _status, uint32_t _bytesWritten,
+                        const hidl_string& _detailedError) {
+                    status = _status;
+                    result = (ssize_t)_bytesWritten;
+                    detailedError = _detailedError;
+                });
+
+        if (!returnVoid.isOk() || status != CasStatus::OK || result < 0) {
+            ALOGE("descramble failed, trans=%s, status=%d, result=%zd",
+                    returnVoid.description().c_str(), status, result);
+            return UNKNOWN_ERROR;
+        }
+
+        ALOGV("descramble succeeded, %zd bytes", result);
+
+        if (dstBuffer.type == BufferType::SHARED_MEMORY) {
+            encryptedBuffer->copyDecryptedContentFromMemory(result);
+        }
+    }
+
+    buffer->setRange(0, result);
+    return queueInputBufferInternal(buffer);
 }
 
 void CCodecBufferChannel::feedInputBufferIfAvailable() {
@@ -1061,8 +1198,8 @@
         c2Buffer = (*buffers)->releaseBuffer(buffer);
     }
 
-    Mutexed<sp<Surface>>::Locked surface(mSurface);
-    if (*surface == nullptr) {
+    Mutexed<OutputSurface>::Locked output(mOutputSurface);
+    if (output->surface == nullptr) {
         ALOGE("no surface");
         return OK;
     }
@@ -1079,7 +1216,7 @@
             GraphicBuffer::CLONE_HANDLE,
             blocks.front().width(),
             blocks.front().height(),
-            HAL_PIXEL_FORMAT_YV12,
+            HAL_PIXEL_FORMAT_YCbCr_420_888,
             // TODO
             1,
             (uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1087,7 +1224,7 @@
             blocks.front().width()));
     native_handle_delete(grallocHandle);
 
-    status_t result = (*surface)->attachBuffer(graphicBuffer.get());
+    status_t result = output->surface->attachBuffer(graphicBuffer.get());
     if (result != OK) {
         ALOGE("attachBuffer failed: %d", result);
         return result;
@@ -1095,23 +1232,32 @@
 
     // TODO: read and set crop
 
-    result = native_window_set_buffers_timestamp((*surface).get(), timestampNs);
+    result = native_window_set_buffers_timestamp(output->surface.get(), timestampNs);
     ALOGW_IF(result != OK, "failed to set buffer timestamp: %d", result);
 
     // TODO: fix after C2Fence implementation
 #if 0
     const C2Fence &fence = blocks.front().fence();
-    result = ((ANativeWindow *)(*surface).get())->queueBuffer(
-            (*surface).get(), graphicBuffer.get(), fence.valid() ? fence.fd() : -1);
+    result = ((ANativeWindow *)output->surface.get())->queueBuffer(
+            output->surface.get(), graphicBuffer.get(), fence.valid() ? fence.fd() : -1);
 #else
-    result = ((ANativeWindow *)(*surface).get())->queueBuffer(
-            (*surface).get(), graphicBuffer.get(), -1);
+    result = ((ANativeWindow *)output->surface.get())->queueBuffer(
+            output->surface.get(), graphicBuffer.get(), -1);
 #endif
     if (result != OK) {
         ALOGE("queueBuffer failed: %d", result);
         return result;
     }
 
+    // XXX: Hack to keep C2Buffers unreleased until the consumer is done
+    //      reading the content. Eventually IGBP-based C2BlockPool should handle
+    //      the lifecycle.
+    output->bufferRefs.push_back(c2Buffer);
+    if (output->bufferRefs.size() > output->maxBufferCount + 1) {
+        output->bufferRefs.pop_front();
+        ALOGV("%zu buffer refs remaining", output->bufferRefs.size());
+    }
+
     return OK;
 }
 
@@ -1166,6 +1312,7 @@
     if (err != C2_OK) {
         return UNKNOWN_ERROR;
     }
+    bool secure = mComponent->intf()->getName().find(".secure") != std::string::npos;
 
     if (inputFormat != nullptr) {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
@@ -1178,7 +1325,24 @@
                 buffers->reset(new GraphicInputBuffers);
             }
         } else {
-            buffers->reset(new LinearInputBuffers);
+            if (hasCryptoOrDescrambler()) {
+                if (mDealer == nullptr) {
+                    mDealer = new MemoryDealer(
+                            align(kLinearBufferSize, MemoryDealer::getAllocationAlignment())
+                                * (kMinBufferArraySize + 1),
+                            "EncryptedLinearInputBuffers");
+                    mDecryptDestination = mDealer->allocate(kLinearBufferSize);
+                }
+                if (mCrypto != nullptr && mHeapSeqNum < 0) {
+                    mHeapSeqNum = mCrypto->setHeap(mDealer->getMemoryHeap());
+                } else {
+                    mHeapSeqNum = -1;
+                }
+                buffers->reset(new EncryptedLinearInputBuffers(
+                        secure, mDealer, mCrypto, mHeapSeqNum));
+            } else {
+                buffers->reset(new LinearInputBuffers);
+            }
         }
         (*buffers)->setFormat(inputFormat);
 
@@ -1200,8 +1364,8 @@
     if (outputFormat != nullptr) {
         bool hasOutputSurface = false;
         {
-            Mutexed<sp<Surface>>::Locked surface(mSurface);
-            hasOutputSurface = (*surface != nullptr);
+            Mutexed<OutputSurface>::Locked output(mOutputSurface);
+            hasOutputSurface = (output->surface != nullptr);
         }
 
         Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
@@ -1381,7 +1545,7 @@
         newSurface->setScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
     }
 
-    Mutexed<sp<Surface>>::Locked surface(mSurface);
+    Mutexed<OutputSurface>::Locked output(mOutputSurface);
 //    if (newSurface == nullptr) {
 //        if (*surface != nullptr) {
 //            ALOGW("cannot unset a surface");
@@ -1395,7 +1559,11 @@
 //        return INVALID_OPERATION;
 //    }
 
-    *surface = newSurface;
+    output->surface = newSurface;
+    output->bufferRefs.clear();
+    // XXX: hack
+    output->maxBufferCount = kMaxGraphicBufferRefCount;
+
     return OK;
 }
 
diff --git a/media/libstagefright/Codec2Buffer.cpp b/media/libstagefright/Codec2Buffer.cpp
new file mode 100644
index 0000000..d2ef229
--- /dev/null
+++ b/media/libstagefright/Codec2Buffer.cpp
@@ -0,0 +1,672 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Codec2Buffer"
+#include <utils/Log.h>
+
+#include <hidlmemory/FrameworkUtils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "include/Codec2Buffer.h"
+
+namespace android {
+
+// Codec2Buffer
+
+bool Codec2Buffer::canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const {
+    if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
+        return false;
+    }
+    if (!buffer) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    }
+    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 || 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(
+        const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
+    C2WriteView writeView(block->map().get());
+    if (writeView.error() != C2_OK) {
+        return nullptr;
+    }
+    return new LinearBlockBuffer(format, std::move(writeView), block);
+}
+
+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)
+    : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
+      mWriteView(writeView),
+      mBlock(block) {
+}
+
+// ConstLinearBlockBuffer
+
+// static
+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), buffer);
+}
+
+ConstLinearBlockBuffer::ConstLinearBlockBuffer(
+        const sp<AMessage> &format,
+        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),
+      mBufferRef(buffer) {
+}
+
+std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
+    return std::move(mBufferRef);
+}
+
+// GraphicView2MediaImageConverter
+
+namespace {
+
+class GraphicView2MediaImageConverter {
+public:
+    explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
+        : mInitCheck(NO_INIT),
+          mView(view),
+          mWidth(view.width()),
+          mHeight(view.height()),
+          mAllocatedDepth(0),
+          mBackBufferSize(0),
+          mMediaImage(new ABuffer(sizeof(MediaImage2))) {
+        if (view.error() != C2_OK) {
+            ALOGD("Converter: view.error() = %d", view.error());
+            mInitCheck = BAD_VALUE;
+            return;
+        }
+        MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
+        const C2PlanarLayout &layout = view.layout();
+        if (layout.numPlanes == 0) {
+            ALOGD("Converter: 0 planes");
+            mInitCheck = BAD_VALUE;
+            return;
+        }
+        mAllocatedDepth = layout.planes[0].allocatedDepth;
+        uint32_t bitDepth = layout.planes[0].bitDepth;
+
+        switch (layout.type) {
+            case C2PlanarLayout::TYPE_YUV:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
+            case C2PlanarLayout::TYPE_YUVA:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
+            case C2PlanarLayout::TYPE_RGB:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
+            case C2PlanarLayout::TYPE_RGBA:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
+            default:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
+        }
+        mediaImage->mNumPlanes = layout.numPlanes;
+        mediaImage->mWidth = mWidth;
+        mediaImage->mHeight = mHeight;
+        mediaImage->mBitDepth = bitDepth;
+        mediaImage->mBitDepthAllocated = mAllocatedDepth;
+
+        uint32_t bufferSize = 0;
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            if (plane.rightShift != 0) {
+                ALOGV("rightShift value of %u unsupported", plane.rightShift);
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            if (plane.endianness != C2PlaneInfo::NATIVE) {
+                ALOGV("endianness value of %u unsupported", plane.endianness);
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
+                ALOGV("different allocatedDepth/bitDepth per plane unsupported");
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            bufferSize += mWidth * mHeight
+                    / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
+        }
+
+        mBackBufferSize = bufferSize;
+        mInitCheck = OK;
+    }
+
+    status_t initCheck() const { return mInitCheck; }
+
+    uint32_t backBufferSize() const { return mBackBufferSize; }
+
+    /**
+     * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
+     * is not copied over in this function --- the caller should use
+     * CopyGraphicView2MediaImage() function to do that explicitly.
+     *
+     * \param   view[in]          source C2GraphicView object.
+     * \param   alloc[in]         allocator function for ABuffer.
+     * \param   mediaImage[out]   destination MediaImage2 object.
+     * \param   buffer[out]       new buffer object.
+     * \param   wrapped[out]      whether we wrapped around existing map or
+     *                            allocated a new buffer
+     *
+     * \return  true              if conversion succeeds,
+     *          false             otherwise; all output params should be ignored.
+     */
+    sp<ABuffer> wrap() {
+        MediaImage2 *mediaImage = getMediaImage();
+        const C2PlanarLayout &layout = mView.layout();
+        if (layout.numPlanes == 1) {
+            const C2PlaneInfo &plane = layout.planes[0];
+            ssize_t offset = plane.minOffset(mWidth, mHeight);
+            mediaImage->mPlane[0].mOffset = -offset;
+            mediaImage->mPlane[0].mColInc = plane.colInc;
+            mediaImage->mPlane[0].mRowInc = plane.rowInc;
+            mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
+            mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
+            return new ABuffer(
+                    const_cast<uint8_t *>(mView.data()[0] + offset),
+                    plane.maxOffset(mWidth, mHeight) - offset + 1);
+        }
+        const uint8_t *minPtr = mView.data()[0];
+        const uint8_t *maxPtr = mView.data()[0];
+        int32_t planeSize = 0;
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            ssize_t minOffset = plane.minOffset(mWidth, mHeight);
+            ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
+            if (minPtr > mView.data()[i] + minOffset) {
+                minPtr = mView.data()[i] + minOffset;
+            }
+            if (maxPtr < mView.data()[i] + maxOffset) {
+                maxPtr = mView.data()[i] + maxOffset;
+            }
+            planeSize += std::abs(plane.rowInc) * mHeight
+                    / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
+        }
+
+        if ((maxPtr - minPtr + 1) <= planeSize) {
+            // FIXME: this is risky as reading/writing data out of bound results in
+            //        an undefined behavior.
+            for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+                const C2PlaneInfo &plane = layout.planes[i];
+                mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
+                mediaImage->mPlane[i].mColInc = plane.colInc;
+                mediaImage->mPlane[i].mRowInc = plane.rowInc;
+                mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+                mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+            }
+            return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
+        }
+
+        return nullptr;
+    }
+
+    bool setBackBuffer(const sp<ABuffer> &backBuffer) {
+        if (backBuffer->capacity() < mBackBufferSize) {
+            return false;
+        }
+        backBuffer->setRange(0, mBackBufferSize);
+
+        const C2PlanarLayout &layout = mView.layout();
+        MediaImage2 *mediaImage = getMediaImage();
+        uint32_t offset = 0;
+        // TODO: keep interleaved planes together
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            mediaImage->mPlane[i].mOffset = offset;
+            mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
+            mediaImage->mPlane[i].mRowInc =
+                mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
+            mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+            mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+            offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
+        }
+        mBackBuffer = backBuffer;
+        return true;
+    }
+
+    /**
+     * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
+     * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
+     *
+     * \param   view[in]          source C2GraphicView object.
+     * \param   mediaImage[in]    destination MediaImage2 object.
+     * \param   buffer[out]       new buffer object.
+     */
+    void copy() {
+        // TODO: more efficient copying --- e.g. one row at a time, copying
+        //       interleaved planes together, etc.
+        const C2PlanarLayout &layout = mView.layout();
+        MediaImage2 *mediaImage = getMediaImage();
+        uint8_t *dst = mBackBuffer->base();
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            const uint8_t *src = mView.data()[i];
+            int32_t planeW = mWidth / plane.colSampling;
+            int32_t planeH = mHeight / plane.rowSampling;
+            for (int32_t row = 0; row < planeH; ++row) {
+                for(int32_t col = 0; col < planeW; ++col) {
+                    memcpy(dst, src, mAllocatedDepth / 8);
+                    dst += mediaImage->mPlane[i].mColInc;
+                    src += plane.colInc;
+                }
+                dst -= mediaImage->mPlane[i].mColInc * planeW;
+                dst += mediaImage->mPlane[i].mRowInc;
+                src -= plane.colInc * planeW;
+                src += plane.rowInc;
+            }
+        }
+    }
+
+    const sp<ABuffer> &imageData() const { return mMediaImage; }
+
+private:
+    status_t mInitCheck;
+
+    const C2GraphicView mView;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mAllocatedDepth;
+    uint32_t mBackBufferSize;
+    sp<ABuffer> mMediaImage;
+    std::function<sp<ABuffer>(size_t)> mAlloc;
+
+    sp<ABuffer> mBackBuffer;
+
+    MediaImage2 *getMediaImage() {
+        return (MediaImage2 *)mMediaImage->base();
+    }
+};
+
+}  // namespace
+
+// GraphicBlockBuffer
+
+// static
+sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2GraphicBlock> &block,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    C2GraphicView view(block->map().get());
+    if (view.error() != C2_OK) {
+        ALOGD("C2GraphicBlock::map failed: %d", view.error());
+        return nullptr;
+    }
+    GraphicView2MediaImageConverter converter(view);
+    if (converter.initCheck() != OK) {
+        ALOGD("Converter init failed: %d", converter.initCheck());
+        return nullptr;
+    }
+    bool wrapped = true;
+    sp<ABuffer> buffer = converter.wrap();
+    if (buffer == nullptr) {
+        buffer = alloc(converter.backBufferSize());
+        if (!converter.setBackBuffer(buffer)) {
+            ALOGD("Converter failed to set back buffer");
+            return nullptr;
+        }
+        wrapped = false;
+    }
+    return new GraphicBlockBuffer(
+            format,
+            buffer,
+            std::move(view),
+            block,
+            converter.imageData(),
+            wrapped);
+}
+
+GraphicBlockBuffer::GraphicBlockBuffer(
+        const sp<AMessage> &format,
+        const sp<ABuffer> &buffer,
+        C2GraphicView &&view,
+        const std::shared_ptr<C2GraphicBlock> &block,
+        const sp<ABuffer> &imageData,
+        bool wrapped)
+    : Codec2Buffer(format, buffer),
+      mView(view),
+      mBlock(block),
+      mImageData(imageData),
+      mWrapped(wrapped) {
+    meta()->setBuffer("image-data", imageData);
+}
+
+std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
+    uint32_t width = mView.width();
+    uint32_t height = mView.height();
+    if (!mWrapped) {
+        MediaImage2 *mediaImage = imageData();
+        const C2PlanarLayout &layout = mView.layout();
+        for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            int32_t planeW = width / plane.colSampling;
+            int32_t planeH = height / plane.rowSampling;
+            const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
+            uint8_t *dst = mView.data()[i];
+            for (int32_t row = 0; row < planeH; ++row) {
+                for (int32_t col = 0; col < planeW; ++col) {
+                    memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
+                    src += mediaImage->mPlane[i].mColInc;
+                    dst += plane.colInc;
+                }
+                src -= mediaImage->mPlane[i].mColInc * planeW;
+                dst -= plane.colInc * planeW;
+                src += mediaImage->mPlane[i].mRowInc;
+                dst += plane.rowInc;
+            }
+        }
+    }
+    return C2Buffer::CreateGraphicBuffer(
+            mBlock->share(C2Rect(width, height), C2Fence()));
+}
+
+// ConstGraphicBlockBuffer
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2Buffer> &buffer,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    if (!buffer
+            || buffer->data().type() != C2BufferData::GRAPHIC
+            || buffer->data().graphicBlocks().size() != 1u) {
+        ALOGD("C2Buffer precond fail");
+        return nullptr;
+    }
+    std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
+            buffer->data().graphicBlocks()[0].map().get()));
+    std::unique_ptr<const C2GraphicView> holder;
+
+    GraphicView2MediaImageConverter converter(*view);
+    if (converter.initCheck() != OK) {
+        ALOGD("Converter init failed: %d", converter.initCheck());
+        return nullptr;
+    }
+    bool wrapped = true;
+    sp<ABuffer> aBuffer = converter.wrap();
+    if (aBuffer == nullptr) {
+        aBuffer = alloc(converter.backBufferSize());
+        if (!converter.setBackBuffer(aBuffer)) {
+            ALOGD("Converter failed to set back buffer");
+            return nullptr;
+        }
+        wrapped = false;
+        converter.copy();
+        // We don't need the view.
+        holder = std::move(view);
+    }
+    return new ConstGraphicBlockBuffer(
+            format,
+            aBuffer,
+            std::move(view),
+            buffer,
+            converter.imageData(),
+            wrapped);
+}
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
+        const sp<AMessage> &format,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    int32_t width, height;
+    if (!format->findInt32("width", &width)
+            || !format->findInt32("height", &height)) {
+        ALOGD("format had no width / height");
+        return nullptr;
+    }
+    sp<ABuffer> aBuffer(alloc(width * height * 4));
+    return new ConstGraphicBlockBuffer(
+            format,
+            aBuffer,
+            nullptr,
+            nullptr,
+            nullptr,
+            false);
+}
+
+ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
+        const sp<AMessage> &format,
+        const sp<ABuffer> &aBuffer,
+        std::unique_ptr<const C2GraphicView> &&view,
+        const std::shared_ptr<C2Buffer> &buffer,
+        const sp<ABuffer> &imageData,
+        bool wrapped)
+    : Codec2Buffer(format, aBuffer),
+      mView(std::move(view)),
+      mBufferRef(buffer),
+      mWrapped(wrapped) {
+    if (imageData != nullptr) {
+        meta()->setBuffer("image-data", imageData);
+    }
+}
+
+std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
+    mView.reset();
+    return std::move(mBufferRef);
+}
+
+bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+    if (mWrapped || mBufferRef) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
+                mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
+        return false;
+    }
+    if (!buffer) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    }
+    if (buffer->data().type() != C2BufferData::GRAPHIC) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
+        return false;
+    }
+    if (buffer->data().graphicBlocks().size() == 0) {
+        return true;
+    } else if (buffer->data().graphicBlocks().size() != 1u) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
+        return false;
+    }
+    GraphicView2MediaImageConverter converter(
+            buffer->data().graphicBlocks()[0].map().get());
+    if (converter.initCheck() != OK) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
+        return false;
+    }
+    if (converter.backBufferSize() > capacity()) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
+                converter.backBufferSize(), capacity());
+        return false;
+    }
+    return true;
+}
+
+bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    if (!buffer || buffer->data().graphicBlocks().size() == 0) {
+        setRange(0, 0);
+        return true;
+    }
+    GraphicView2MediaImageConverter converter(
+            buffer->data().graphicBlocks()[0].map().get());
+    if (converter.initCheck() != OK) {
+        ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
+        return false;
+    }
+    sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
+    if (!converter.setBackBuffer(aBuffer)) {
+        ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
+        return false;
+    }
+    converter.copy();
+    meta()->setBuffer("image-data", converter.imageData());
+    mBufferRef = buffer;
+    return true;
+}
+
+// EncryptedLinearBlockBuffer
+
+EncryptedLinearBlockBuffer::EncryptedLinearBlockBuffer(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2LinearBlock> &block,
+        const sp<IMemory> &memory,
+        int32_t heapSeqNum)
+    : Codec2Buffer(format, new ABuffer(memory->pointer(), memory->size())),
+      mBlock(block),
+      mMemory(memory),
+      mHeapSeqNum(heapSeqNum) {
+}
+
+std::shared_ptr<C2Buffer> EncryptedLinearBlockBuffer::asC2Buffer() {
+    return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
+}
+
+void EncryptedLinearBlockBuffer::fillSourceBuffer(
+        ICrypto::SourceBuffer *source) {
+    source->mSharedMemory = mMemory;
+    source->mHeapSeqNum = mHeapSeqNum;
+}
+
+void EncryptedLinearBlockBuffer::fillSourceBuffer(
+        hardware::cas::native::V1_0::SharedBuffer *source) {
+    ssize_t offset;
+    size_t size;
+
+    mHidlMemory = hardware::fromHeap(mMemory->getMemory(&offset, &size));
+    source->heapBase = *mHidlMemory;
+    source->offset = offset;
+    source->size = size;
+}
+
+bool EncryptedLinearBlockBuffer::copyDecryptedContent(
+        const sp<IMemory> &decrypted, size_t length) {
+    C2WriteView view = mBlock->map().get();
+    if (view.error() != C2_OK) {
+        return false;
+    }
+    if (view.size() < length) {
+        return false;
+    }
+    memcpy(view.data(), decrypted->pointer(), length);
+    return true;
+}
+
+bool EncryptedLinearBlockBuffer::copyDecryptedContentFromMemory(size_t length) {
+    return copyDecryptedContent(mMemory, length);
+}
+
+native_handle_t *EncryptedLinearBlockBuffer::handle() const {
+    return const_cast<native_handle_t *>(mBlock->handle());
+}
+
+}  // namespace android
diff --git a/media/libstagefright/Codec2InfoBuilder.cpp b/media/libstagefright/Codec2InfoBuilder.cpp
index 7ce2ff1..78c4e38 100644
--- a/media/libstagefright/Codec2InfoBuilder.cpp
+++ b/media/libstagefright/Codec2InfoBuilder.cpp
@@ -31,6 +31,63 @@
 
 using ConstTraitsPtr = std::shared_ptr<const C2Component::Traits>;
 
+struct ProfileLevel {
+    uint32_t profile;
+    uint32_t level;
+};
+static const ProfileLevel kAvcProfileLevels[] = {
+    { 0x01, 0x0001 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1  },
+    { 0x01, 0x0002 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1b },
+    { 0x01, 0x0004 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel11 },
+    { 0x01, 0x0008 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel12 },
+    { 0x01, 0x0010 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel13 },
+    { 0x01, 0x0020 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel2  },
+    { 0x01, 0x0040 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel21 },
+    { 0x01, 0x0080 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel22 },
+    { 0x01, 0x0100 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel3  },
+    { 0x01, 0x0200 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel31 },
+    { 0x01, 0x0400 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel32 },
+    { 0x01, 0x0800 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel4  },
+    { 0x01, 0x1000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel41 },
+    { 0x01, 0x2000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel42 },
+    { 0x01, 0x4000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel5  },
+    { 0x01, 0x8000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel51 },
+
+    { 0x02, 0x0001 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel1  },
+    { 0x02, 0x0002 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel1b },
+    { 0x02, 0x0004 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel11 },
+    { 0x02, 0x0008 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel12 },
+    { 0x02, 0x0010 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel13 },
+    { 0x02, 0x0020 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel2  },
+    { 0x02, 0x0040 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel21 },
+    { 0x02, 0x0080 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel22 },
+    { 0x02, 0x0100 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel3  },
+    { 0x02, 0x0200 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel31 },
+    { 0x02, 0x0400 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel32 },
+    { 0x02, 0x0800 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel4  },
+    { 0x02, 0x1000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel41 },
+    { 0x02, 0x2000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel42 },
+    { 0x02, 0x4000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel5  },
+    { 0x02, 0x8000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel51 },
+
+    { 0x04, 0x0001 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel1  },
+    { 0x04, 0x0002 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel1b },
+    { 0x04, 0x0004 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel11 },
+    { 0x04, 0x0008 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel12 },
+    { 0x04, 0x0010 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel13 },
+    { 0x04, 0x0020 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel2  },
+    { 0x04, 0x0040 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel21 },
+    { 0x04, 0x0080 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel22 },
+    { 0x04, 0x0100 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel3  },
+    { 0x04, 0x0200 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel31 },
+    { 0x04, 0x0400 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel32 },
+    { 0x04, 0x0800 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel4  },
+    { 0x04, 0x1000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel41 },
+    { 0x04, 0x2000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel42 },
+    { 0x04, 0x4000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel5  },
+    { 0x04, 0x8000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel51 },
+};
+
 status_t Codec2InfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) {
     // Obtain C2ComponentStore
     std::shared_ptr<C2ComponentStore> store = GetCodec2PlatformComponentStore();
@@ -86,6 +143,12 @@
                     caps->addDetail(key.c_str(), value.c_str());
                 }
             }
+            // TODO: get this from intf(), and apply to other codecs as well.
+            if (mediaType.find("video/avc") != std::string::npos && !encoder) {
+                for (const auto& pl : kAvcProfileLevels) {
+                    caps->addProfileLevel(pl.profile, pl.level);
+                }
+            }
             // TODO: get this from intf().
             if (mediaType.find("video") != std::string::npos && !encoder) {
                 caps->addColorFormat(0x7F420888);  // COLOR_FormatYUV420Flexible
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index cfbbcb2..768df26 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -1376,15 +1376,12 @@
 }
 
 void MPEG4Writer::addMultipleLengthPrefixedSamples_l(MediaBuffer *buffer) {
-    const size_t kExtensionNALSearchRange = 64; // bytes to look for non-VCL NALUs
-
     const uint8_t *dataStart = (const uint8_t *)buffer->data() + buffer->range_offset();
     const uint8_t *currentNalStart = dataStart;
     const uint8_t *nextNalStart;
     const uint8_t *data = dataStart;
     size_t nextNalSize;
-    size_t searchSize = buffer->range_length() > kExtensionNALSearchRange ?
-                   kExtensionNALSearchRange : buffer->range_length();
+    size_t searchSize = buffer->range_length();
 
     while (getNextNALUnit(&data, &searchSize, &nextNalStart,
             &nextNalSize, true) == OK) {
@@ -1962,6 +1959,17 @@
         return;
     }
 
+    // Rotation angle in HEIF is CCW, framework angle is CW.
+    int32_t heifRotation = 0;
+    switch(mRotation) {
+        case 90: heifRotation = 3; break;
+        case 180: heifRotation = 2; break;
+        case 270: heifRotation = 1; break;
+        default: break; // don't set if invalid
+    }
+
+    bool hasGrid = (mNumTiles > 1);
+
     if (mProperties.empty()) {
         mProperties.push_back(mOwner->addProperty_l({
             .type = FOURCC('h', 'v', 'c', 'C'),
@@ -1970,22 +1978,29 @@
 
         mProperties.push_back(mOwner->addProperty_l({
             .type = FOURCC('i', 's', 'p', 'e'),
-            .width = (mNumTiles > 1) ? mGridWidth : mWidth,
-            .height = (mNumTiles > 1) ? mGridHeight : mHeight,
+            .width = hasGrid ? mGridWidth : mWidth,
+            .height = hasGrid ? mGridHeight : mHeight,
         }));
+
+        if (!hasGrid && heifRotation > 0) {
+            mProperties.push_back(mOwner->addProperty_l({
+                .type = FOURCC('i', 'r', 'o', 't'),
+                .rotation = heifRotation,
+            }));
+        }
     }
 
     uint16_t itemId = mOwner->addItem_l({
         .itemType = "hvc1",
-        .isPrimary = (mNumTiles > 1) ? false : (mIsPrimary != 0),
-        .isHidden = (mNumTiles > 1),
+        .isPrimary = hasGrid ? false : (mIsPrimary != 0),
+        .isHidden = hasGrid,
         .offset = (uint32_t)offset,
         .size = (uint32_t)size,
         .properties = mProperties,
     });
 
     mTileIndex++;
-    if (mNumTiles > 1) {
+    if (hasGrid) {
         mDimgRefs.push_back(itemId);
 
         if (mTileIndex == mNumTiles) {
@@ -1995,6 +2010,12 @@
                 .width = mWidth,
                 .height = mHeight,
             }));
+            if (heifRotation > 0) {
+                mProperties.push_back(mOwner->addProperty_l({
+                    .type = FOURCC('i', 'r', 'o', 't'),
+                    .rotation = heifRotation,
+                }));
+            }
             mOwner->addItem_l({
                 .itemType = "grid",
                 .isPrimary = (mIsPrimary != 0),
@@ -2305,7 +2326,8 @@
     mStartTimeRealUs = startTimeUs;
 
     int32_t rotationDegrees;
-    if (mIsVideo && params && params->findInt32(kKeyRotation, &rotationDegrees)) {
+    if ((mIsVideo || mIsHeic) && params &&
+            params->findInt32(kKeyRotation, &rotationDegrees)) {
         mRotation = rotationDegrees;
     }
 
@@ -3430,16 +3452,36 @@
 
 int32_t MPEG4Writer::Track::getMetaSizeIncrease() const {
     CHECK(mIsHeic);
-    return    20                           // 1. 'ispe' property
-            + (8 + mCodecSpecificDataSize) // 2. 'hvcC' property
-            + (20                          // 3. extra 'ispe'
-            + (8 + 2 + 2 + mNumTiles * 2)  // 4. 'dimg' ref
-            + 12)                          // 5. ImageGrid in 'idat' (worst case)
-            * (mNumTiles > 1)              // -  (3~5: applicable only if grid)
-            + (16                          // 6. increase to 'iloc'
-            + 21                           // 7. increase to 'iinf'
-            + (3 + 2 * 2))                 // 8. increase to 'ipma' (worst case)
-            * (mNumTiles + 1);             // -  (6~8: are per-item)
+
+    int32_t grid = (mNumTiles > 1);
+
+    // Note that the rotation angle is in the file meta, and we don't have
+    // it until start, so here the calculation has to assume rotation.
+
+    // increase to ipco
+    int32_t increase = 20 * (grid + 1)              // 'ispe' property
+                     + (8 + mCodecSpecificDataSize) // 'hvcC' property
+                     + 9;                           // 'irot' property (worst case)
+
+    // increase to iref and idat
+    if (grid) {
+        increase += (8 + 2 + 2 + mNumTiles * 2)  // 'dimg' in iref
+                  + 12;                          // ImageGrid in 'idat' (worst case)
+    }
+
+    // increase to iloc, iinf and ipma
+    increase += (16             // increase to 'iloc'
+              + 21              // increase to 'iinf'
+              + (3 + 2 * 2))    // increase to 'ipma' (worst case, 2 props x 2 bytes)
+              * (mNumTiles + grid);
+
+    // adjust to ipma:
+    // if rotation is present and only one tile, it could ref 3 properties
+    if (!grid) {
+        increase += 2;
+    }
+
+    return increase;
 }
 
 status_t MPEG4Writer::Track::checkCodecSpecificData() const {
@@ -4267,24 +4309,38 @@
         numProperties = 32767;
     }
     for (size_t propIndex = 0; propIndex < numProperties; propIndex++) {
-        if (mProperties[propIndex].type == FOURCC('h', 'v', 'c', 'C')) {
-            beginBox("hvcC");
-            sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
-            // Patch avcc's lengthSize field to match the number
-            // of bytes we use to indicate the size of a nal unit.
-            uint8_t *ptr = (uint8_t *)hvcc->data();
-            ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
-            write(hvcc->data(), hvcc->size());
-            endBox();
-        } else if (mProperties[propIndex].type == FOURCC('i', 's', 'p', 'e')) {
-            beginBox("ispe");
-            writeInt32(0); // Version = 0, Flags = 0
-            writeInt32(mProperties[propIndex].width);
-            writeInt32(mProperties[propIndex].height);
-            endBox();
-        } else {
-            ALOGW("Skipping unrecognized property: type 0x%08x",
-                    mProperties[propIndex].type);
+        switch (mProperties[propIndex].type) {
+            case FOURCC('h', 'v', 'c', 'C'):
+            {
+                beginBox("hvcC");
+                sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
+                // Patch avcc's lengthSize field to match the number
+                // of bytes we use to indicate the size of a nal unit.
+                uint8_t *ptr = (uint8_t *)hvcc->data();
+                ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
+                write(hvcc->data(), hvcc->size());
+                endBox();
+                break;
+            }
+            case FOURCC('i', 's', 'p', 'e'):
+            {
+                beginBox("ispe");
+                writeInt32(0); // Version = 0, Flags = 0
+                writeInt32(mProperties[propIndex].width);
+                writeInt32(mProperties[propIndex].height);
+                endBox();
+                break;
+            }
+            case FOURCC('i', 'r', 'o', 't'):
+            {
+                beginBox("irot");
+                writeInt8(mProperties[propIndex].rotation);
+                endBox();
+                break;
+            }
+            default:
+                ALOGW("Skipping unrecognized property: type 0x%08x",
+                        mProperties[propIndex].type);
         }
     }
     endBox();
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 7d5c63a..c66a18f 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -603,6 +603,8 @@
         return NAME_NOT_FOUND;
     }
 
+    mCodecInfo.clear();
+
     bool secureCodec = false;
     AString tmp = name;
     if (tmp.endsWith(".secure")) {
@@ -614,17 +616,24 @@
         mCodec = NULL;  // remove the codec.
         return NO_INIT; // if called from Java should raise IOException
     }
-    ssize_t codecIdx = mcl->findCodecByName(tmp.c_str());
-    if (codecIdx >= 0) {
-        const sp<MediaCodecInfo> info = mcl->getCodecInfo(codecIdx);
+    for (const AString &codecName : { name, tmp }) {
+        ssize_t codecIdx = mcl->findCodecByName(codecName.c_str());
+        if (codecIdx < 0) {
+            continue;
+        }
+        mCodecInfo = mcl->getCodecInfo(codecIdx);
         Vector<AString> mimes;
-        info->getSupportedMimes(&mimes);
+        mCodecInfo->getSupportedMimes(&mimes);
         for (size_t i = 0; i < mimes.size(); i++) {
             if (mimes[i].startsWith("video/")) {
                 mIsVideo = true;
                 break;
             }
         }
+        break;
+    }
+    if (mCodecInfo == nullptr) {
+        return NAME_NOT_FOUND;
     }
 
     if (mIsVideo) {
@@ -651,6 +660,9 @@
                     new BufferCallback(new AMessage(kWhatCodecNotify, this))));
 
     sp<AMessage> msg = new AMessage(kWhatInit, this);
+    msg->setObject("codecInfo", mCodecInfo);
+    // name may be different from mCodecInfo->getCodecName() if we stripped
+    // ".secure"
     msg->setString("name", name);
 
     if (mAnalyticsItem != NULL) {
@@ -1972,11 +1984,14 @@
             mReplyID = replyID;
             setState(INITIALIZING);
 
+            sp<RefBase> codecInfo;
+            CHECK(msg->findObject("codecInfo", &codecInfo));
             AString name;
             CHECK(msg->findString("name", &name));
 
             sp<AMessage> format = new AMessage;
-            format->setString("componentName", name.c_str());
+            format->setObject("codecInfo", codecInfo);
+            format->setString("componentName", name);
 
             mCodec->initiateAllocateComponent(format);
             break;
diff --git a/media/libstagefright/MetaDataUtils.cpp b/media/libstagefright/MetaDataUtils.cpp
index af8f539..04f6ade 100644
--- a/media/libstagefright/MetaDataUtils.cpp
+++ b/media/libstagefright/MetaDataUtils.cpp
@@ -24,11 +24,12 @@
 
 namespace android {
 
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit) {
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size) {
     int32_t width;
     int32_t height;
     int32_t sarWidth;
     int32_t sarHeight;
+    sp<ABuffer> accessUnit = new ABuffer((void*)data,  size);
     sp<ABuffer> csd = MakeAVCCodecSpecificData(accessUnit, &width, &height, &sarWidth, &sarHeight);
     if (csd == nullptr) {
         return false;
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index f6fc813..8c0cd3e 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -205,6 +205,15 @@
     return OK;
 }
 
+void NuMediaExtractor::disconnect() {
+    if (mDataSource != NULL) {
+        // disconnect data source
+        if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
+            static_cast<NuCachedSource2 *>(mDataSource.get())->disconnect();
+        }
+    }
+}
+
 status_t NuMediaExtractor::updateDurationAndBitrate() {
     if (mImpl->countTracks() > kMaxTrackCount) {
         return ERROR_UNSUPPORTED;
diff --git a/media/libstagefright/bqhelper/GraphicBufferSource.cpp b/media/libstagefright/bqhelper/GraphicBufferSource.cpp
index 87d2555..68ae8ec 100644
--- a/media/libstagefright/bqhelper/GraphicBufferSource.cpp
+++ b/media/libstagefright/bqhelper/GraphicBufferSource.cpp
@@ -421,26 +421,31 @@
 }
 
 Status GraphicBufferSource::release(){
-    Mutex::Autolock autoLock(mMutex);
-    if (mLooper != NULL) {
-        mLooper->unregisterHandler(mReflector->id());
-        mReflector.clear();
+    sp<ALooper> looper;
+    {
+        Mutex::Autolock autoLock(mMutex);
+        looper = mLooper;
+        if (mLooper != NULL) {
+            mLooper->unregisterHandler(mReflector->id());
+            mReflector.clear();
 
-        mLooper->stop();
-        mLooper.clear();
+            mLooper.clear();
+        }
+
+        ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
+                mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
+                mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
+
+        // Codec is no longer executing.  Releasing all buffers to bq.
+        mFreeCodecBuffers.clear();
+        mSubmittedCodecBuffers.clear();
+        mLatestBuffer.mBuffer.reset();
+        mComponent.clear();
+        mExecuting = false;
     }
-
-    ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
-            mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
-            mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
-
-    // Codec is no longer executing.  Releasing all buffers to bq.
-    mFreeCodecBuffers.clear();
-    mSubmittedCodecBuffers.clear();
-    mLatestBuffer.mBuffer.reset();
-    mComponent.clear();
-    mExecuting = false;
-
+    if (looper != NULL) {
+        looper->stop();
+    }
     return Status::ok();
 }
 
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
index 0f1fecc..87138af 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
@@ -48,6 +48,7 @@
 }
 
 C2SoftFlacDecoder::~C2SoftFlacDecoder() {
+    delete mFLACDecoder;
 }
 
 c2_status_t C2SoftFlacDecoder::onInit() {
@@ -77,6 +78,9 @@
 }
 
 status_t C2SoftFlacDecoder::initDecoder() {
+    if (mFLACDecoder) {
+        delete mFLACDecoder;
+    }
     mFLACDecoder = FLACDecoder::Create();
     if (!mFLACDecoder) {
         ALOGE("initDecoder: failed to create FLACDecoder");
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
index a5c01a9..43d913b 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
@@ -46,7 +46,7 @@
         kMaxBlockSize   = 4096
     };
 
-    sp<FLACDecoder> mFLACDecoder;
+    FLACDecoder *mFLACDecoder;
     FLAC__StreamMetadata_StreamInfo mStreamInfo;
     bool mSignalledError;
     bool mSignalledOutputEos;
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
index 4ab1ab2..d0b72b7 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
@@ -57,6 +57,7 @@
 
 SoftFlacDecoder::~SoftFlacDecoder() {
     ALOGV("dtor:");
+    delete mFLACDecoder;
 }
 
 void SoftFlacDecoder::initPorts() {
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
index 4a21c34..0f17ed8 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
@@ -50,7 +50,7 @@
         kNumOutputBuffers  = 4,
     };
 
-    sp<FLACDecoder> mFLACDecoder;
+    FLACDecoder *mFLACDecoder;
     FLAC__StreamMetadata_StreamInfo mStreamInfo;
     bool mHasStreamInfo;
     size_t mInputBufferCount;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.cpp b/media/libstagefright/flac/dec/FLACDecoder.cpp
index 8c7137c..e0e9211 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.cpp
+++ b/media/libstagefright/flac/dec/FLACDecoder.cpp
@@ -220,9 +220,10 @@
 }
 
 // static
-sp<FLACDecoder> FLACDecoder::Create() {
-    sp<FLACDecoder> decoder = new FLACDecoder();
-    if (decoder->init() != OK) {
+FLACDecoder *FLACDecoder::Create() {
+    FLACDecoder *decoder = new (std::nothrow) FLACDecoder();
+    if (decoder == NULL || decoder->init() != OK) {
+        delete decoder;
         return NULL;
     }
     return decoder;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.h b/media/libstagefright/flac/dec/FLACDecoder.h
index 36282a8..1a33cae 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.h
+++ b/media/libstagefright/flac/dec/FLACDecoder.h
@@ -26,14 +26,14 @@
 namespace android {
 
 // packet based FLAC decoder, wrapps libFLAC stream decoder.
-class FLACDecoder : public RefBase {
+class FLACDecoder {
 
 public:
     enum {
         kMaxChannels = 8,
     };
 
-    static sp<FLACDecoder> Create();
+    static FLACDecoder *Create();
 
     FLAC__StreamMetadata_StreamInfo getStreamInfo() const {
         return mStreamInfo;
@@ -43,10 +43,10 @@
     status_t decodeOneFrame(const uint8_t *inBuffer, size_t inBufferLen,
             short *outBuffer, size_t *outBufferLen);
     void flush();
+    virtual ~FLACDecoder();
 
 protected:
     FLACDecoder();
-    virtual ~FLACDecoder() override;
 
 private:
     // stream properties
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index 51eee10..c8618db 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -162,6 +162,7 @@
     };
 
     void feedInputBufferIfAvailable();
+    status_t queueInputBufferInternal(const sp<MediaCodecBuffer> &buffer);
 
     QueueSync mSync;
     sp<MemoryDealer> mDealer;
@@ -180,7 +181,13 @@
     std::atomic_uint64_t mFirstValidFrameIndex;
 
     sp<MemoryDealer> makeMemoryDealer(size_t heapSize);
-    Mutexed<sp<Surface>> mSurface;
+
+    struct OutputSurface {
+        sp<Surface> surface;
+        std::list<std::shared_ptr<C2Buffer>> bufferRefs;
+        size_t maxBufferCount;
+    };
+    Mutexed<OutputSurface> mOutputSurface;
 
     std::shared_ptr<InputSurfaceWrapper> mInputSurface;
 
diff --git a/media/libstagefright/include/Codec2Buffer.h b/media/libstagefright/include/Codec2Buffer.h
index eeb889d..b2e3b1b 100644
--- a/media/libstagefright/include/Codec2Buffer.h
+++ b/media/libstagefright/include/Codec2Buffer.h
@@ -20,8 +20,11 @@
 
 #include <C2Buffer.h>
 
+#include <android/hardware/cas/native/1.0/types.h>
+#include <binder/IMemory.h>
 #include <media/hardware/VideoAPI.h>
 #include <media/MediaCodecBuffer.h>
+#include <media/ICrypto.h>
 
 namespace android {
 
@@ -271,6 +274,79 @@
     const bool mWrapped;
 };
 
+/**
+ * MediaCodecBuffer implementation wraps around C2LinearBlock for component
+ * and IMemory for client. Underlying C2LinearBlock won't be mapped for secure
+ * usecases..
+ */
+class EncryptedLinearBlockBuffer : public Codec2Buffer {
+public:
+    /**
+     * Construct a new EncryptedLinearBufferBlock wrapping around C2LinearBlock
+     * object and writable IMemory region.
+     *
+     * \param   format      mandatory buffer format for MediaCodecBuffer
+     * \param   block       C2LinearBlock object to wrap around.
+     * \param   memory      IMemory object to store encrypted content.
+     * \param   heapSeqNum  Heap sequence number from ICrypto; -1 if N/A
+     */
+    EncryptedLinearBlockBuffer(
+            const sp<AMessage> &format,
+            const std::shared_ptr<C2LinearBlock> &block,
+            const sp<IMemory> &memory,
+            int32_t heapSeqNum = -1);
+    EncryptedLinearBlockBuffer() = delete;
+
+    virtual ~EncryptedLinearBlockBuffer() = default;
+
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+
+    /**
+     * Fill the source buffer structure with appropriate value based on
+     * internal IMemory object.
+     *
+     * \param source  source buffer structure to fill.
+     */
+    void fillSourceBuffer(ICrypto::SourceBuffer *source);
+    void fillSourceBuffer(
+            hardware::cas::native::V1_0::SharedBuffer *source);
+
+    /**
+     * Copy the content of |decrypted| into C2LinearBlock inside. This shall
+     * only be called in non-secure usecases.
+     *
+     * \param   decrypted   decrypted content to copy from.
+     * \param   length      length of the content
+     * \return  true        if successful
+     *          false       otherwise.
+     */
+    bool copyDecryptedContent(const sp<IMemory> &decrypted, size_t length);
+
+    /**
+     * Copy the content of internal IMemory object into C2LinearBlock inside.
+     * This shall only be called in non-secure usecases.
+     *
+     * \param   length      length of the content
+     * \return  true        if successful
+     *          false       otherwise.
+     */
+    bool copyDecryptedContentFromMemory(size_t length);
+
+    /**
+     * Return native handle of secure buffer understood by ICrypto.
+     *
+     * \return secure buffer handle
+     */
+    native_handle_t *handle() const;
+
+private:
+
+    std::shared_ptr<C2LinearBlock> mBlock;
+    sp<IMemory> mMemory;
+    sp<hardware::HidlMemory> mHidlMemory;
+    int32_t mHeapSeqNum;
+};
+
 }  // namespace android
 
 #endif  // CODEC2_BUFFER_H_
diff --git a/media/libstagefright/include/media/stagefright/CCodec.h b/media/libstagefright/include/media/stagefright/CCodec.h
index 078b03e..86fbd3a 100644
--- a/media/libstagefright/include/media/stagefright/CCodec.h
+++ b/media/libstagefright/include/media/stagefright/CCodec.h
@@ -36,6 +36,7 @@
 
 class CCodecBufferChannel;
 class InputSurfaceWrapper;
+struct MediaCodecInfo;
 
 class CCodec : public CodecBase {
 public:
@@ -74,7 +75,7 @@
     void initiateStop();
     void initiateRelease(bool sendCallback = true);
 
-    void allocate(const AString &componentName);
+    void allocate(const sp<MediaCodecInfo> &codecInfo);
     void configure(const sp<AMessage> &msg);
     void start();
     void stop();
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 2a062cc..7b41362 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -195,6 +195,7 @@
         uint32_t type;
         int32_t width;
         int32_t height;
+        int32_t rotation;
         sp<ABuffer> hvcc;
     } ItemProperty;
 
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index e7faea5..5a7d26a 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -308,6 +308,7 @@
     sp<ALooper> mCodecLooper;
     sp<CodecBase> mCodec;
     AString mComponentName;
+    sp<MediaCodecInfo> mCodecInfo;
     sp<AReplyToken> mReplyID;
     uint32_t mFlags;
     status_t mStickyError;
diff --git a/media/libstagefright/include/media/stagefright/MetaDataUtils.h b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
index 3af2218..d5a8080 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataUtils.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
@@ -23,7 +23,7 @@
 namespace android {
 
 struct ABuffer;
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit);
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size);
 bool MakeAACCodecSpecificData(MetaDataBase &meta, unsigned profile, unsigned sampling_freq_index,
         unsigned channel_configuration);
 
diff --git a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
index 5e5ef6e..675c932 100644
--- a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
@@ -64,6 +64,8 @@
 
     status_t setMediaCas(const HInterfaceToken &casToken);
 
+    void disconnect();
+
     size_t countTracks() const;
     status_t getTrackFormat(size_t index, sp<AMessage> *format, uint32_t flags = 0) const;
 
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 090d4e1..0fa9fcb 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -634,7 +634,7 @@
 
         if (mFormat == NULL) {
             mFormat = new MetaData;
-            if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+            if (!MakeAVCCodecSpecificData(*mFormat, accessUnit->data(), accessUnit->size())) {
                 mFormat.clear();
             }
         }
@@ -1010,7 +1010,7 @@
         }
         if (mFormat == NULL) {
             mFormat = new MetaData;
-            if (!MakeAVCCodecSpecificData(*mFormat, mBuffer)) {
+            if (!MakeAVCCodecSpecificData(*mFormat, mBuffer->data(), mBuffer->size())) {
                 ALOGW("Creating dummy AVC format for scrambled content");
                 mFormat = new MetaData;
                 mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
@@ -1172,7 +1172,9 @@
 
             if (mFormat == NULL) {
                 mFormat = new MetaData;
-                if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+                if (!MakeAVCCodecSpecificData(*mFormat,
+                        accessUnit->data(),
+                        accessUnit->size())) {
                     mFormat.clear();
                 }
             }
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index ac837a3..b5e60a4 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -475,5 +475,11 @@
     return AMEDIA_OK;
 }
 
+EXPORT
+media_status_t AMediaExtractor_disconnect(AMediaExtractor * ex) {
+    ex->mImpl->disconnect();
+    return AMEDIA_OK;
+}
+
 } // extern "C"
 
diff --git a/media/ndk/include/media/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index f7b9cfd..1d295e4 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -216,6 +216,12 @@
 
 #endif /* __ANDROID_API__ >= 28 */
 
+#if __ANDROID_API__ >= 29
+
+media_status_t AMediaExtractor_disconnect(AMediaExtractor *ex);
+
+#endif /* __ANDROID_API__ >= 29 */
+
 #endif /* __ANDROID_API__ >= 21 */
 
 __END_DECLS
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 19e4b24..b0d8e7d 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -19,7 +19,7 @@
 ifneq ($(TARGET_BUILD_PDK),true)
 # Build MediaComponents only if this is not a PDK build.  MediaComponents won't
 # build in PDK builds because frameworks/base/core/java is not available but
-# IMediaSession2.aidl and IMediaSession2Callback.aidl are using classes from
+# IMediaSession2.aidl and IMediaController2.aidl are using classes from
 # frameworks/base/core/java.
 
 include $(CLEAR_VARS)
diff --git a/packages/MediaComponents/res/drawable/custom_progress.xml b/packages/MediaComponents/res/drawable/custom_progress.xml
new file mode 100644
index 0000000..9731a6e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle" >
+            <solid android:color="#26000000" />
+        </shape>
+    </item>
+    <item android:id="@android:id/secondaryProgress">
+        <clip>
+            <shape android:shape="rectangle" >
+                <solid android:color="#5Cffffff" />
+            </shape>
+        </clip>
+    </item>
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape android:shape="rectangle" >
+                <solid android:color="#ffffff" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/custom_progress_thumb.xml b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
new file mode 100644
index 0000000..2e247f2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval" >
+    <solid android:color="#ffffff" />
+    <size
+        android:height="12dp"
+        android:width="12dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_high_quality.xml b/packages/MediaComponents/res/drawable/ic_high_quality.xml
index e27d3e2..f76e22f 100644
--- a/packages/MediaComponents/res/drawable/ic_high_quality.xml
+++ b/packages/MediaComponents/res/drawable/ic_high_quality.xml
@@ -1,9 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
+    android:width="34dp"
+    android:height="34dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M0 0h24v24H0z" />
     <path
         android:fillColor="#FFFFFF"
-        android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
+        android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 11H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 0.55-0.45 1-1 1h-0.75v1.5h-1.5V15H14c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v4zm-3.5-0.5h2v-3h-2v3z" />
 </vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
index 389396b..a56d5d9 100644
--- a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
+++ b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="40dp"
+    android:height="40dp"
     android:viewportWidth="24"
     android:viewportHeight="24">
 
diff --git a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
similarity index 75%
rename from packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
rename to packages/MediaComponents/res/drawable/ic_subtitle_off.xml
index a79cd11..c0a727a 100644
--- a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
@@ -9,8 +9,7 @@
         android:pathData="M0,0h24v24H0V0z" />
     <path
         android:fillColor="#FFFFFF"
-        android:pathData="M19,5.5c0.27,0,0.5,0.23,0.5,0.5v12c0,0.27-0.23,0.5-0.5,0.5H5c-0.28,0-0.5-0.22-0.5-0.5V6c0-0.28,0.22-0.5,0.5-0.5H19
-M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
+        android:pathData="M19.5,5.5v13h-15v-13H19.5z M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
     <path
         android:fillColor="#FFFFFF"
         android:pathData="M11,11H9.5v-0.5h-2v3h2V13H11v1c0,0.55-0.45,1-1,1H7c-0.55,0-1-0.45-1-1v-4c0-0.55,0.45-1,1-1h3c0.55,0,1,0.45,1,1V11z" />
diff --git a/packages/MediaComponents/res/drawable/ic_subtitle_on.xml b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
new file mode 100644
index 0000000..7c91c06
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M0 0h24v24H0z" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 7H9.5v-0.5h-2v3h2V13H11v1c0 0.55-0.45 1-1 1H7c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1zm7 0h-1.5v-0.5h-2v3h2V13H18v1c0 0.55-0.45 1-1 1h-3c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1z" />
+</vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index 8488c84..eab2309 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -129,10 +129,10 @@
     <SeekBar
         android:id="@+id/mediacontroller_progress"
         android:layout_width="match_parent"
-        android:layout_height="32dp"
-        android:padding="0dp"
-        android:progressTint="#FFFFFFFF"
-        android:thumbTint="#FFFFFFFF"/>
+        android:layout_height="12dp"
+        android:maxHeight="2dp"
+        android:minHeight="2dp"
+        android:padding="0dp"/>
 
     <RelativeLayout
         android:layout_width="match_parent"
@@ -199,6 +199,7 @@
             <ImageButton
                 android:id="@+id/subtitle"
                 android:scaleType="fitCenter"
+                android:visibility="gone"
                 style="@style/BottomBarButton.CC" />
             <ImageButton
                 android:id="@+id/fullscreen"
@@ -224,9 +225,6 @@
                 android:layout_height="wrap_content" />
 
             <ImageButton
-                android:id="@+id/aspect_ratio"
-                style="@style/BottomBarButton.AspectRatio" />
-            <ImageButton
                 android:id="@+id/video_quality"
                 style="@style/BottomBarButton.VideoQuality" />
             <ImageButton
diff --git a/packages/MediaComponents/res/layout/settings_list_item.xml b/packages/MediaComponents/res/layout/settings_list_item.xml
index e7522b7..81b3275 100644
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/settings_list_item.xml
@@ -28,14 +28,6 @@
         android:orientation="horizontal">
 
         <ImageView
-            android:id="@+id/check"
-            android:layout_width="@dimen/MediaControlView2_settings_icon_size"
-            android:layout_height="@dimen/MediaControlView2_settings_icon_size"
-            android:gravity="center"
-            android:paddingLeft="2dp"
-            android:src="@drawable/ic_check"/>
-
-        <ImageView
             android:id="@+id/icon"
             android:layout_width="@dimen/MediaControlView2_settings_icon_size"
             android:layout_height="@dimen/MediaControlView2_settings_icon_size"
diff --git a/packages/MediaComponents/res/layout/sub_settings_list_item.xml b/packages/MediaComponents/res/layout/sub_settings_list_item.xml
new file mode 100644
index 0000000..9de7f2b
--- /dev/null
+++ b/packages/MediaComponents/res/layout/sub_settings_list_item.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/MediaControlView2_settings_width"
+    android:layout_height="@dimen/MediaControlView2_settings_height"
+    android:orientation="horizontal"
+    android:background="@color/black_transparent_70">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/MediaControlView2_settings_height"
+        android:paddingRight="2dp"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/check"
+            android:layout_width="@dimen/MediaControlView2_settings_icon_size"
+            android:layout_height="@dimen/MediaControlView2_settings_icon_size"
+            android:gravity="center"
+            android:paddingLeft="2dp"
+            android:src="@drawable/ic_check"/>
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/MediaControlView2_settings_height"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/MediaControlView2_text_width"
+            android:paddingLeft="2dp"
+            android:textColor="@color/white"
+            android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/MediaComponents/res/values/arrays.xml b/packages/MediaComponents/res/values/arrays.xml
new file mode 100644
index 0000000..1187320
--- /dev/null
+++ b/packages/MediaComponents/res/values/arrays.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <integer-array name="speed_multiplied_by_100">
+        <item>25</item>
+        <item>50</item>
+        <item>75</item>
+        <item>100</item>
+        <item>125</item>
+        <item>150</item>
+        <item>200</item>
+    </integer-array>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index 305cef4..c80aaf3 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -111,15 +111,18 @@
     <string name="MediaControlView2_audio_track_none_text">None</string>
     <string name="MediaControlView2_video_quality_text">Video quality</string>
     <string name="MediaControlView2_video_quality_auto_text">Auto</string>
-    <string name="MediaControlView2_playback_speed_text">Playback speed</string>
-    <string name="MediaControlView2_playback_speed_0_25x_text">0.25x</string>
-    <string name="MediaControlView2_playback_speed_0_5x_text">0.5x</string>
-    <string name="MediaControlView2_playback_speed_0_75x_text">0.75x</string>
-    <string name="MediaControlView2_playback_speed_1x_text">Normal</string>
-    <string name="MediaControlView2_playback_speed_1_25x_text">1.25x</string>
-    <string name="MediaControlView2_playback_speed_1_5x_text">1.5x</string>
-    <string name="MediaControlView2_playback_speed_2x_text">2x</string>
     <string name="MediaControlView2_help_text">Help &amp; feedback</string>
+    <string name="MediaControlView2_playback_speed_text">Playback speed</string>
+    <string-array name="MediaControlView2_playback_speeds">
+        <item>0.25x</item>
+        <item>0.5x</item>
+        <item>0.75x</item>
+        <item>Normal</item>
+        <item>1.25x</item>
+        <item>1.5x</item>
+        <item>2x</item>
+    </string-array>
+
     <!-- Text for displaying subtitle track number. -->
     <string name="MediaControlView2_subtitle_track_number_text">
         Track <xliff:g id="track_number" example="1">%1$s</xliff:g>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index 23c7bc9..e6ed039 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -4,6 +4,7 @@
         <item name="android:background">@null</item>
         <item name="android:layout_width">70dp</item>
         <item name="android:layout_height">40dp</item>
+        <item name="android:visibility">gone</item>
     </style>
 
     <style name="TransportControlsButton.Previous">
@@ -55,7 +56,7 @@
     </style>
 
     <style name="BottomBarButton.CC">
-        <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
+        <item name="android:src">@drawable/ic_subtitle_off</item>
     </style>
 
     <style name="BottomBarButton.FullScreen">
@@ -74,12 +75,8 @@
         <item name="android:src">@drawable/ic_settings</item>
     </style>
 
-    <style name="BottomBarButton.AspectRatio">
-        <item name="android:src">@drawable/ic_aspect_ratio</item>
-    </style>
-
     <style name="BottomBarButton.Mute">
-        <item name="android:src">@drawable/ic_mute</item>
+        <item name="android:src">@drawable/ic_unmute</item>
     </style>
 
     <style name="BottomBarButton.VideoQuality">
diff --git a/packages/MediaComponents/runcts.sh b/packages/MediaComponents/runcts.sh
index 0cf0e44..61b1a1e 100644
--- a/packages/MediaComponents/runcts.sh
+++ b/packages/MediaComponents/runcts.sh
@@ -23,6 +23,7 @@
   echo '     -h|--help: This help'
   echo '     --skip: Skip build and flash. Just rerun-tests'
   echo '     --min: Only rebuild tests and updatable library.'
+  echo '     --test: Only rebuild tests'
   echo '     -s [device_id]: Specify a device name to run test against.'
   echo '                     You can define ${ADBHOST} instead.'
   echo '     -r [count]: Repeat tests for given count. It will stop when fails.'
@@ -58,6 +59,7 @@
   while true; do
     local OPTION_SKIP="false"
     local OPTION_MIN="false"
+    local OPTION_TEST="false"
     local OPTION_REPEAT_COUNT="1"
     local OPTION_IGNORE="false"
     local OPTION_TEST_TARGET="${DEFAULT_TEST_TARGET}"
@@ -74,6 +76,9 @@
         --min)
           OPTION_MIN="true"
           ;;
+        --test)
+          OPTION_TEST="true"
+          ;;
         -s)
           shift
           adbhost_local=${1}
@@ -133,36 +138,43 @@
       fi
 
       # Build test apk and required apk.
-      local build_targets="${BUILD_TARGETS[@]}"
-      if [[ "${OPTION_MIN}" != "true" ]]; then
-        build_targets="${build_targets} droid"
+      local build_targets
+      if [[ "${OPTION_TEST}" == "true" ]]; then
+        build_targets="${INSTALL_TARGETS[@]}"
+      elif [[ "${OPTION_MIN}" == "true" ]]; then
+        build_targets="${BUILD_TARGETS[@]}"
+      else
+        build_targets="${BUILD_TARGETS[@]} droid"
       fi
       m ${build_targets} -j || break
 
-      local device_build_type="$(${adb} shell getprop ro.build.type)"
-      if [[ "${device_build_type}" == "user" ]]; then
-        # User build. Cannot adb sync
-        ${adb} reboot bootloader
-        fastboot flashall
-      else
-        ${adb} root
-        local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
-        if [[ "${device_verity_mode}" != "disabled" ]]; then
-          ${adb} disable-verity
-          ${adb} reboot
-          ${adb} wait-for-device || break
+      if [[ "${OPTION_TEST}" != "true" ]]; then
+        # Flash only when needed
+        local device_build_type="$(${adb} shell getprop ro.build.type)"
+        if [[ "${device_build_type}" == "user" ]]; then
+          # User build. Cannot adb sync
+          ${adb} reboot bootloader
+          fastboot flashall
+        else
           ${adb} root
+          local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
+          if [[ "${device_verity_mode}" != "disabled" ]]; then
+            ${adb} disable-verity
+            ${adb} reboot
+            ${adb} wait-for-device || break
+            ${adb} root
+          fi
+          ${adb} remount
+          ${adb} shell stop
+          ${adb} shell setprop log.tag.MediaSessionService DEBUG
+          ${adb} sync
+          ${adb} shell start
         fi
-        ${adb} remount
-        ${adb} shell stop
-        ${adb} shell setprop log.tag.MediaSessionService DEBUG
-        ${adb} sync
-        ${adb} shell start
+        ${adb} wait-for-device || break
+        # Ensure package manager is loaded.
+        # TODO(jaewan): Find better way to wait
+        sleep 15
       fi
-      ${adb} wait-for-device || break
-      # Ensure package manager is loaded.
-      # TODO(jaewan): Find better way to wait
-      sleep 15
 
       # Install apks
       local install_failed="false"
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
similarity index 71%
rename from packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
rename to packages/MediaComponents/src/com/android/media/IMediaController2.aidl
index 3d812f8..d6c8e21 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
@@ -23,21 +23,27 @@
 import com.android.media.IMediaSession2;
 
 /**
- * Interface from MediaSession2 to MediaSession2Record.
+ * Interface from MediaSession2 to MediaController2.
  * <p>
  * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
  * and holds calls from session to make session owner(s) frozen.
  */
-oneway interface IMediaSession2Callback {
-    void onPlaybackStateChanged(in Bundle state);
-    void onPlaylistChanged(in List<Bundle> playlist);
+// TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+//               Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
+oneway interface IMediaController2 {
+    void onPlayerStateChanged(int state);
+    void onPositionChanged(long eventTimeMs, long positionMs);
+    void onPlaybackSpeedChanged(float speed);
+    void onBufferedPositionChanged(long bufferedPositionMs);
+    void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
+    void onPlaylistMetadataChanged(in Bundle metadata);
     void onPlaylistParamsChanged(in Bundle params);
     void onPlaybackInfoChanged(in Bundle playbackInfo);
 
-    // 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,
-            in PendingIntent sessionActivity);
+    void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
+            int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+            long bufferedPositionMs, in Bundle playbackInfo, in Bundle params,
+            in List<Bundle> playlist, in PendingIntent sessionActivity);
     void onDisconnected();
 
     void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index ef7120d..a241abc 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -20,55 +20,61 @@
 import android.os.ResultReceiver;
 import android.net.Uri;
 
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
 
 /**
- * Interface to MediaSession2.
+ * Interface from MediaController2 to MediaSession2.
  * <p>
  * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
  * and holds calls from session to make session owner(s) frozen.
  */
+ // TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+ //               Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
 oneway interface IMediaSession2 {
     // TODO(jaewan): add onCommand() to send private command
-    // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
-    //               Add id for individual calls to address this.
 
-    // TODO(jaewan): We may consider to add another binder just for the connection
+    // TODO(jaewan): (Post P) We may consider to add another binder just for the connection
     //               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(IMediaSession2Callback caller, String callingPackage);
-    void release(IMediaSession2Callback caller);
+    void connect(IMediaController2 caller, String callingPackage);
+    void release(IMediaController2 caller);
 
-    void setVolumeTo(IMediaSession2Callback caller, int value, int flags);
-    void adjustVolume(IMediaSession2Callback caller, int direction, int flags);
+    void setVolumeTo(IMediaController2 caller, int value, int flags);
+    void adjustVolume(IMediaController2 caller, int direction, int flags);
 
     //////////////////////////////////////////////////////////////////////////////////////////////
     // send command
     //////////////////////////////////////////////////////////////////////////////////////////////
-    void sendTransportControlCommand(IMediaSession2Callback caller,
+    void sendTransportControlCommand(IMediaController2 caller,
             int commandCode, in Bundle args);
-    void sendCustomCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args,
+    void sendCustomCommand(IMediaController2 caller, in Bundle command, in Bundle args,
             in ResultReceiver receiver);
 
-    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);
+    void prepareFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+    void prepareFromSearch(IMediaController2 caller, String query, in Bundle extras);
+    void prepareFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+    void playFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+    void playFromSearch(IMediaController2 caller, String query, in Bundle extras);
+    void playFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+    void setRating(IMediaController2 caller, String mediaId, in Bundle rating);
+
+    void setPlaylist(IMediaController2 caller, in List<Bundle> playlist, in Bundle metadata);
+    void updatePlaylistMetadata(IMediaController2 caller, in Bundle metadata);
+    void addPlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
+    void removePlaylistItem(IMediaController2 caller, in Bundle mediaItem);
+    void replacePlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
 
     //////////////////////////////////////////////////////////////////////////////////////////////
     // library service specific
     //////////////////////////////////////////////////////////////////////////////////////////////
-    void getLibraryRoot(IMediaSession2Callback caller, in Bundle rootHints);
-    void getItem(IMediaSession2Callback caller, String mediaId);
-    void getChildren(IMediaSession2Callback caller, String parentId, int page, int pageSize,
+    void getLibraryRoot(IMediaController2 caller, in Bundle rootHints);
+    void getItem(IMediaController2 caller, String mediaId);
+    void getChildren(IMediaController2 caller, String parentId, int page, int pageSize,
             in Bundle extras);
-    void search(IMediaSession2Callback caller, String query, in Bundle extras);
-    void getSearchResult(IMediaSession2Callback caller, String query, int page, int pageSize,
+    void search(IMediaController2 caller, String query, in Bundle extras);
+    void getSearchResult(IMediaController2 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);
+    void subscribe(IMediaController2 caller, String parentId, in Bundle extras);
+    void unsubscribe(IMediaController2 caller, String parentId);
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 96fdda0..b6d3e55 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -16,7 +16,18 @@
 
 package com.android.media;
 
-import static android.media.MediaSession2.*;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_URI;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_URI;
 
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -28,13 +39,13 @@
 import android.media.MediaController2.ControllerCallback;
 import android.media.MediaController2.PlaybackInfo;
 import android.media.MediaItem2;
+import android.media.MediaMetadata2;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.PlaylistParams;
 import android.media.MediaSessionService2;
-import android.media.PlaybackState2;
 import android.media.Rating2;
 import android.media.SessionToken2;
 import android.media.update.MediaController2Provider;
@@ -61,7 +72,7 @@
     private final Context mContext;
     private final Object mLock = new Object();
 
-    private final MediaSession2CallbackStub mSessionCallbackStub;
+    private final MediaController2Stub mControllerStub;
     private final SessionToken2 mToken;
     private final ControllerCallback mCallback;
     private final Executor mCallbackExecutor;
@@ -72,12 +83,22 @@
     @GuardedBy("mLock")
     private boolean mIsReleased;
     @GuardedBy("mLock")
-    private PlaybackState2 mPlaybackState;
-    @GuardedBy("mLock")
     private List<MediaItem2> mPlaylist;
     @GuardedBy("mLock")
+    private MediaMetadata2 mPlaylistMetadata;
+    @GuardedBy("mLock")
     private PlaylistParams mPlaylistParams;
     @GuardedBy("mLock")
+    private int mPlayerState;
+    @GuardedBy("mLock")
+    private long mPositionEventTimeMs;
+    @GuardedBy("mLock")
+    private long mPositionMs;
+    @GuardedBy("mLock")
+    private float mPlaybackSpeed;
+    @GuardedBy("mLock")
+    private long mBufferedPositionMs;
+    @GuardedBy("mLock")
     private PlaybackInfo mPlaybackInfo;
     @GuardedBy("mLock")
     private PendingIntent mSessionActivity;
@@ -110,7 +131,7 @@
             throw new IllegalArgumentException("executor shouldn't be null");
         }
         mContext = context;
-        mSessionCallbackStub = new MediaSession2CallbackStub(this);
+        mControllerStub = new MediaController2Stub(this);
         mToken = token;
         mCallback = callback;
         mCallbackExecutor = executor;
@@ -191,7 +212,7 @@
 
     private void connectToSession(IMediaSession2 sessionBinder) {
         try {
-            sessionBinder.connect(mSessionCallbackStub, mContext.getPackageName());
+            sessionBinder.connect(mControllerStub, mContext.getPackageName());
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to call connection request. Framework will retry"
                     + " automatically");
@@ -216,12 +237,12 @@
             }
             binder = mSessionBinder;
             mSessionBinder = null;
-            mSessionCallbackStub.destroy();
+            mControllerStub.destroy();
         }
         if (binder != null) {
             try {
                 binder.asBinder().unlinkToDeath(mDeathRecipient, 0);
-                binder.release(mSessionCallbackStub);
+                binder.release(mControllerStub);
             } catch (RemoteException e) {
                 // No-op.
             }
@@ -235,8 +256,8 @@
         return mSessionBinder;
     }
 
-    MediaSession2CallbackStub getControllerStub() {
-        return mSessionCallbackStub;
+    MediaController2Stub getControllerStub() {
+        return mControllerStub;
     }
 
     Executor getCallbackExecutor() {
@@ -331,7 +352,7 @@
         final IMediaSession2 binder = mSessionBinder;
         if (binder != null) {
             try {
-                binder.sendTransportControlCommand(mSessionCallbackStub, commandCode, args);
+                binder.sendTransportControlCommand(mControllerStub, commandCode, args);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -354,7 +375,7 @@
         final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
         if (binder != null) {
             try {
-                binder.setVolumeTo(mSessionCallbackStub, value, flags);
+                binder.setVolumeTo(mControllerStub, value, flags);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -369,7 +390,7 @@
         final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
         if (binder != null) {
             try {
-                binder.adjustVolume(mSessionCallbackStub, direction, flags);
+                binder.adjustVolume(mControllerStub, direction, flags);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -386,7 +407,7 @@
         }
         if (binder != null) {
             try {
-                binder.prepareFromUri(mSessionCallbackStub, uri, extras);
+                binder.prepareFromUri(mControllerStub, uri, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -403,7 +424,7 @@
         }
         if (binder != null) {
             try {
-                binder.prepareFromSearch(mSessionCallbackStub, query, extras);
+                binder.prepareFromSearch(mControllerStub, query, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -420,7 +441,7 @@
         }
         if (binder != null) {
             try {
-                binder.prepareFromMediaId(mSessionCallbackStub, mediaId, extras);
+                binder.prepareFromMediaId(mControllerStub, mediaId, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -437,7 +458,7 @@
         }
         if (binder != null) {
             try {
-                binder.playFromUri(mSessionCallbackStub, uri, extras);
+                binder.playFromUri(mControllerStub, uri, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -454,7 +475,7 @@
         }
         if (binder != null) {
             try {
-                binder.playFromSearch(mSessionCallbackStub, query, extras);
+                binder.playFromSearch(mControllerStub, query, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -471,7 +492,7 @@
         }
         if (binder != null) {
             try {
-                binder.playFromMediaId(mSessionCallbackStub, mediaId, extras);
+                binder.playFromMediaId(mControllerStub, mediaId, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -492,7 +513,7 @@
         final IMediaSession2 binder = mSessionBinder;
         if (binder != null) {
             try {
-                binder.setRating(mSessionCallbackStub, mediaId, rating.toBundle());
+                binder.setRating(mControllerStub, mediaId, rating.toBundle());
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -509,7 +530,7 @@
         final IMediaSession2 binder = getSessionBinderIfAble(command);
         if (binder != null) {
             try {
-                binder.sendCustomCommand(mSessionCallbackStub, command.toBundle(), args, cb);
+                binder.sendCustomCommand(mControllerStub, command.toBundle(), args, cb);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -526,6 +547,51 @@
     }
 
     @Override
+    public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+        if (list == null) {
+            throw new IllegalArgumentException("list shouldn't be null");
+        }
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_LIST);
+        if (binder != null) {
+            List<Bundle> bundleList = new ArrayList<>();
+            for (int i = 0; i < list.size(); i++) {
+                bundleList.add(list.get(i).toBundle());
+            }
+            Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+            try {
+                binder.setPlaylist(mControllerStub, bundleList, metadataBundle);
+            } catch (RemoteException e) {
+                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 MediaMetadata2 getPlaylistMetadata_impl() {
+        synchronized (mLock) {
+            return mPlaylistMetadata;
+        }
+    }
+
+    @Override
+    public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+        final IMediaSession2 binder = getSessionBinderIfAble(
+                COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+        if (binder != null) {
+            Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+            try {
+                binder.updatePlaylistMetadata(mControllerStub, metadataBundle);
+            } catch (RemoteException e) {
+                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 prepare_impl() {
         sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
     }
@@ -566,40 +632,60 @@
     }
 
     @Override
-    public PlaybackState2 getPlaybackState_impl() {
-        synchronized (mLock) {
-            return mPlaybackState;
-        }
-    }
-
-    @Override
     public void addPlaylistItem_impl(int index, MediaItem2 item) {
-        // TODO(jaewan): Implement (b/73149584)
         if (index < 0) {
             throw new IllegalArgumentException("index shouldn't be negative");
         }
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_ADD_ITEM);
+        if (binder != null) {
+            try {
+                binder.addPlaylistItem(mControllerStub, index, item.toBundle());
+            } catch (RemoteException e) {
+                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 removePlaylistItem_impl(MediaItem2 item) {
-        // TODO(jaewan): Implement (b/73149584)
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+        if (binder != null) {
+            try {
+                binder.removePlaylistItem(mControllerStub, item.toBundle());
+            } catch (RemoteException e) {
+                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 replacePlaylistItem_impl(int index, MediaItem2 item) {
-        // TODO: Implement this (b/73149407)
         if (index < 0) {
             throw new IllegalArgumentException("index shouldn't be negative");
         }
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+        if (binder != null) {
+            try {
+                binder.replacePlaylistItem(mControllerStub, index, item.toBundle());
+            } catch (RemoteException e) {
+                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
@@ -616,38 +702,47 @@
         }
     }
 
+    // TODO(jaewan): Remove (b/74116823)
     @Override
     public void setPlaylistParams_impl(PlaylistParams params) {
         if (params == null) {
             throw new IllegalArgumentException("params shouldn't be null");
         }
+        /*
         Bundle args = new Bundle();
         args.putBundle(MediaSession2Stub.ARGUMENT_KEY_PLAYLIST_PARAMS, params.toBundle());
         sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS, args);
+        */
     }
 
     @Override
     public int getPlayerState_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        synchronized (mLock) {
+            return mPlayerState;
+        }
     }
 
     @Override
     public long getPosition_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        synchronized (mLock) {
+            long timeDiff = System.currentTimeMillis() - mPositionEventTimeMs;
+            long expectedPosition = mPositionMs + (long) (mPlaybackSpeed * timeDiff);
+            return Math.max(0, expectedPosition);
+        }
     }
 
     @Override
     public float getPlaybackSpeed_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        synchronized (mLock) {
+            return mPlaybackSpeed;
+        }
     }
 
     @Override
     public long getBufferedPosition_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        synchronized (mLock) {
+            return mBufferedPositionMs;
+        }
     }
 
     @Override
@@ -656,15 +751,52 @@
         return null;
     }
 
-    void pushPlaybackStateChanges(final PlaybackState2 state) {
+    void pushPlayerStateChanges(final int state) {
         synchronized (mLock) {
-            mPlaybackState = state;
+            mPlayerState = state;
         }
         mCallbackExecutor.execute(() -> {
             if (!mInstance.isConnected()) {
                 return;
             }
-            mCallback.onPlaybackStateChanged(mInstance, state);
+            mCallback.onPlayerStateChanged(mInstance, state);
+        });
+    }
+
+    void pushPositionChanges(final long eventTimeMs, final long positionMs) {
+        synchronized (mLock) {
+            mPositionEventTimeMs = eventTimeMs;
+            mPositionMs = positionMs;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            mCallback.onPositionChanged(mInstance, eventTimeMs, positionMs);
+        });
+    }
+
+    void pushPlaybackSpeedChanges(final float speed) {
+        synchronized (mLock) {
+            mPlaybackSpeed = speed;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            mCallback.onPlaybackSpeedChanged(mInstance, speed);
+        });
+    }
+
+    void pushBufferedPositionChanges(final long bufferedPositionMs) {
+        synchronized (mLock) {
+            mBufferedPositionMs = bufferedPositionMs;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            mCallback.onBufferedPositionChanged(mInstance, bufferedPositionMs);
         });
     }
 
@@ -692,21 +824,42 @@
         });
     }
 
-    void pushPlaylistChanges(final List<MediaItem2> playlist) {
+    void pushPlaylistChanges(final List<MediaItem2> playlist, final MediaMetadata2 metadata) {
         synchronized (mLock) {
             mPlaylist = playlist;
-            mCallbackExecutor.execute(() -> {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                mCallback.onPlaylistChanged(mInstance, playlist);
-            });
+            mPlaylistMetadata = metadata;
         }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            // TODO(jaewan): Fix public API not to take playlistAgent.
+            mCallback.onPlaylistChanged(mInstance, null, playlist, metadata);
+        });
+    }
+
+    public void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
+        synchronized (mLock) {
+            mPlaylistMetadata = metadata;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            // TODO(jaewan): Fix public API not to take playlistAgent.
+            mCallback.onPlaylistMetadataChanged(mInstance, null, metadata);
+        });
     }
 
     // Should be used without a lock to prevent potential deadlock.
     void onConnectedNotLocked(IMediaSession2 sessionBinder,
-            final CommandGroup allowedCommands, final PlaybackState2 state, final PlaybackInfo info,
+            final CommandGroup allowedCommands,
+            final int playerState,
+            final long positionEventTimeMs,
+            final long positionMs,
+            final float playbackSpeed,
+            final long bufferedPositionMs,
+            final PlaybackInfo info,
             final PlaylistParams params, final List<MediaItem2> playlist,
             final PendingIntent sessionActivity) {
         if (DEBUG) {
@@ -731,7 +884,11 @@
                     return;
                 }
                 mAllowedCommands = allowedCommands;
-                mPlaybackState = state;
+                mPlayerState = playerState;
+                mPositionEventTimeMs = positionEventTimeMs;
+                mPositionMs = positionMs;
+                mPlaybackSpeed = playbackSpeed;
+                mBufferedPositionMs = bufferedPositionMs;
                 mPlaybackInfo = info;
                 mPlaylistParams = params;
                 mPlaylist = playlist;
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
similarity index 80%
rename from packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
rename to packages/MediaComponents/src/com/android/media/MediaController2Stub.java
index 451368f..99bdfce 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
@@ -20,11 +20,11 @@
 import android.content.Context;
 import android.media.MediaController2;
 import android.media.MediaItem2;
+import android.media.MediaMetadata2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.text.TextUtils;
@@ -37,13 +37,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
-    private static final String TAG = "MS2CallbackStub";
+public class MediaController2Stub extends IMediaController2.Stub {
+    private static final String TAG = "MediaController2Stub";
     private static final boolean DEBUG = true; // TODO(jaewan): Change
 
     private final WeakReference<MediaController2Impl> mController;
 
-    MediaSession2CallbackStub(MediaController2Impl controller) {
+    MediaController2Stub(MediaController2Impl controller) {
         mController = new WeakReference<>(controller);
     }
 
@@ -68,7 +68,7 @@
     }
 
     @Override
-    public void onPlaybackStateChanged(Bundle state) throws RuntimeException {
+    public void onPlayerStateChanged(int state) {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -76,12 +76,59 @@
             Log.w(TAG, "Don't fail silently here. Highly likely a bug");
             return;
         }
-        controller.pushPlaybackStateChanges(
-                PlaybackState2.fromBundle(controller.getContext(), state));
+        controller.pushPlayerStateChanges(state);
     }
 
     @Override
-    public void onPlaylistChanged(List<Bundle> playlistBundle) throws RuntimeException {
+    public void onPositionChanged(long eventTimeMs, long positionMs) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (eventTimeMs < 0) {
+            Log.w(TAG, "onPositionChanged(): Ignoring negative eventTimeMs");
+            return;
+        }
+        if (positionMs < 0) {
+            Log.w(TAG, "onPositionChanged(): Ignoring negative positionMs");
+            return;
+        }
+        controller.pushPositionChanges(eventTimeMs, positionMs);
+    }
+
+    @Override
+    public void onPlaybackSpeedChanged(float speed) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        controller.pushPlaybackSpeedChanges(speed);
+    }
+
+    @Override
+    public void onBufferedPositionChanged(long bufferedPositionMs) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (bufferedPositionMs < 0) {
+            Log.w(TAG, "onBufferedPositionChanged(): Ignoring negative bufferedPositionMs");
+            return;
+        }
+        controller.pushBufferedPositionChanges(bufferedPositionMs);
+    }
+
+    @Override
+    public void onPlaylistChanged(List<Bundle> playlistBundle, Bundle metadataBundle) {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -90,7 +137,7 @@
             return;
         }
         if (playlistBundle == null) {
-            Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist");
+            Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist from " + controller);
             return;
         }
         List<MediaItem2> playlist = new ArrayList<>();
@@ -102,7 +149,23 @@
                 playlist.add(item);
             }
         }
-        controller.pushPlaylistChanges(playlist);
+        MediaMetadata2 metadata =
+                MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+        controller.pushPlaylistChanges(playlist, metadata);
+    }
+
+    @Override
+    public void onPlaylistMetadataChanged(Bundle metadataBundle) throws RuntimeException {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        MediaMetadata2 metadata =
+                MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+        controller.pushPlaylistMetadataChanges(metadata);
     }
 
     @Override
@@ -146,8 +209,9 @@
 
     @Override
     public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
-            Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
-            itemBundleList, PendingIntent sessionActivity) {
+            int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+            long bufferedPositionMs, Bundle playbackInfo, Bundle playlistParams,
+            List<Bundle> itemBundleList, PendingIntent sessionActivity) {
         final MediaController2Impl controller = mController.get();
         if (controller == null) {
             if (DEBUG) {
@@ -168,7 +232,7 @@
         }
         controller.onConnectedNotLocked(sessionBinder,
                 CommandGroup.fromBundle(context, commandGroup),
-                PlaybackState2.fromBundle(context, playbackState),
+                playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
                 PlaybackInfoImpl.fromBundle(context, playbackInfo),
                 PlaylistParams.fromBundle(context, playlistParams),
                 itemList, sessionActivity);
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index 039ff8f..c95b43f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -31,21 +31,31 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 
+import java.util.UUID;
+
 public class MediaItem2Impl implements MediaItem2Provider {
     private static final String KEY_ID = "android.media.mediaitem2.id";
     private static final String KEY_FLAGS = "android.media.mediaitem2.flags";
     private static final String KEY_METADATA = "android.media.mediaitem2.metadata";
+    private static final String KEY_UUID = "android.media.mediaitem2.uuid";
 
     private final Context mContext;
     private final MediaItem2 mInstance;
     private final String mId;
     private final int mFlags;
+    private final UUID mUUID;
     private MediaMetadata2 mMetadata;
     private DataSourceDesc mDataSourceDesc;
 
     // From the public API
     public MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
             @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags) {
+        this(context, mediaId, dsd, metadata, flags, null);
+    }
+
+    private MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
+            @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags,
+            @Nullable UUID uuid) {
         if (mediaId == null) {
             throw new IllegalArgumentException("mediaId shouldn't be null");
         }
@@ -58,24 +68,18 @@
         mDataSourceDesc = dsd;
         mMetadata = metadata;
         mFlags = flags;
+        mUUID = (uuid == null) ? UUID.randomUUID() : uuid;
 
         mInstance = new MediaItem2(this);
     }
 
-    // Create anonymized version
-    public MediaItem2Impl(Context context, String mediaId, MediaMetadata2 metadata,
-            @Flags int flags) {
-        if (mediaId == null) {
-            throw new IllegalArgumentException("mediaId shouldn't be null");
+    @Override
+    public boolean equals_impl(Object obj) {
+        if (!(obj instanceof MediaItem2)) {
+            return false;
         }
-        if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
-            throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
-        }
-        mContext = context;
-        mId = mediaId;
-        mMetadata = metadata;
-        mFlags = flags;
-        mInstance = new MediaItem2(this);
+        MediaItem2 other = (MediaItem2) obj;
+        return mUUID.equals(((MediaItem2Impl) other.getProvider()).mUUID);
     }
 
     /**
@@ -90,10 +94,37 @@
         if (mMetadata != null) {
             bundle.putBundle(KEY_METADATA, mMetadata.toBundle());
         }
+        bundle.putString(KEY_UUID, mUUID.toString());
         return bundle;
     }
 
-    public static MediaItem2 fromBundle(Context context, Bundle bundle) {
+    /**
+     * Create a MediaItem2 from the {@link Bundle}.
+     *
+     * @param context A context.
+     * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+     * @return The newly created MediaItem2
+     */
+    public static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
+        if (bundle == null) {
+            return null;
+        }
+        final String uuidString = bundle.getString(KEY_UUID);
+        return fromBundle(context, bundle, UUID.fromString(uuidString));
+    }
+
+    /**
+     * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID}.
+     * If {@link UUID}
+     * can be null for creating new.
+     *
+     * @param context A context.
+     * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+     * @param uuid A {@link UUID} to override. Can be {@link null} for override.
+     * @return The newly created MediaItem2
+     */
+    static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle,
+            @Nullable UUID uuid) {
         if (bundle == null) {
             return null;
         }
@@ -102,7 +133,7 @@
         final MediaMetadata2 metadata = metadataBundle != null
                 ? MediaMetadata2.fromBundle(context, metadataBundle) : null;
         final int flags = bundle.getInt(KEY_FLAGS);
-        return new MediaItem2Impl(context, id, metadata, flags).getInstance();
+        return new MediaItem2Impl(context, id, null, metadata, flags, uuid).getInstance();
     }
 
     private MediaItem2 getInstance() {
diff --git a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
index 333455d..286f5ee 100644
--- a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
@@ -228,7 +228,7 @@
     }
 
     public static MediaMetadata2 fromBundle(Context context, Bundle bundle) {
-        return new MediaMetadata2Impl(context, bundle).getInstance();
+        return (bundle == null) ? null : new MediaMetadata2Impl(context, bundle).getInstance();
     }
 
     public static final class BuilderImpl implements MediaMetadata2Provider.BuilderProvider {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 5c18515..b8c185a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -52,7 +52,6 @@
 import android.media.MediaSession2.PlaylistParams.ShuffleMode;
 import android.media.MediaSession2.SessionCallback;
 import android.media.MediaSessionService2;
-import android.media.PlaybackState2;
 import android.media.SessionToken2;
 import android.media.VolumeProvider2;
 import android.media.session.MediaSessionManager;
@@ -68,7 +67,9 @@
 import android.util.Log;
 
 import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -248,11 +249,11 @@
                 oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
             }
         }
-        // TODO(jaewan): Notify controllers about the change in the media player base (b/74370608)
-        //               Note that notification will be done indirectly by telling player state,
-        //               position, buffered position, etc.
-        mSessionStub.notifyPlaybackInfoChanged(info);
-        notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
+
+        if (oldPlayer != null) {
+            mSessionStub.notifyPlaybackInfoChanged(info);
+            notifyPlayerUpdatedNotLocked(oldPlayer);
+        }
     }
 
     private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -496,22 +497,27 @@
     }
 
     @Override
-    public void setPlaylist_impl(List<MediaItem2> playlist) {
-        if (playlist == null) {
-            throw new IllegalArgumentException("playlist shouldn't be null");
+    public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+        if (list == null) {
+            throw new IllegalArgumentException("list shouldn't be null");
         }
         ensureCallingThread();
-        // TODO: Uncomment or remove
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-            // TODO implement and use SessionPlaylistAgent
-            //player.setPlaylist(playlist);
-            mSessionStub.notifyPlaylistChanged(playlist);
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.setPlaylist(list, metadata);
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
+    }
+
+    @Override
+    public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.updatePlaylistMetadata(metadata);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
@@ -522,7 +528,12 @@
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
-        // TODO(jaewan): Implement
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.addPlaylistItem(index, item);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
@@ -530,15 +541,12 @@
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
-        // TODO(jaewan): Implement
-    }
-
-    @Override
-    public void editPlaylistItem_impl(MediaItem2 item) {
-        if (item == null) {
-            throw new IllegalArgumentException("item shouldn't be null");
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.removePlaylistItem(item);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        // TODO(jaewan): Implement
     }
 
     @Override
@@ -549,24 +557,33 @@
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
-        // TODO(jaewan): Implement
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.replacePlaylistItem(index, item);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public List<MediaItem2> getPlaylist_impl() {
-        // TODO: Uncomment or remove
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-            // TODO(jaewan): Is it safe to be called on any thread?
-            //               Otherwise MediaSession2 should cache parameter of setPlaylist.
-            // TODO implement
-            //return player.getPlaylist();
-            return null;
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            return agent.getPlaylist();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
+        return null;
+    }
+
+    @Override
+    public MediaMetadata2 getPlaylistMetadata_impl() {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            return agent.getPlaylistMetadata();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
         return null;
     }
 
@@ -660,12 +677,6 @@
             return;
         }
         mCallbacks.put(callback, executor);
-        // TODO: Uncomment or remove
-        /*
-        // TODO(jaewan): Double check if we need this.
-        final PlaybackState2 state = getInstance().getPlaybackState();
-        executor.execute(() -> callback.onPlaybackStateChanged(state));
-        */
     }
 
     @Override
@@ -678,25 +689,6 @@
     }
 
     @Override
-    public PlaybackState2 getPlaybackState_impl() {
-        ensureCallingThread();
-        // TODO: Uncomment or remove
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-           // TODO(jaewan): Is it safe to be called on any thread?
-            //               Otherwise MediaSession2 should cache the result from listener.
-            // TODO implement
-            //return player.getPlaybackState();
-            return null;
-        } else if (DEBUG) {
-            Log.d(TAG, "API calls after the close()", new IllegalStateException());
-        }
-        */
-        return null;
-    }
-
-    @Override
     public void notifyError_impl(int errorCode, Bundle extras) {
         // TODO(jaewan): Implement
     }
@@ -725,9 +717,31 @@
         }*/
     }
 
-    private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
+    private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+            List<MediaItem2> list, MediaMetadata2 metadata) {
+        if (playlistAgent != mPlaylistAgent) {
+            // Ignore calls from the old agent. Ignore.
+            return;
+        }
+        mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
+        mSessionStub.notifyPlaylistChangedNotLocked(list, metadata);
+    }
+
+    private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+            MediaMetadata2 metadata) {
+        if (playlistAgent != mPlaylistAgent) {
+            // Ignore calls from the old agent. Ignore.
+            return;
+        }
+        mCallback.onPlaylistMetadataChanged(mInstance, playlistAgent, metadata);
+        mSessionStub.notifyPlaylistMetadataChangedNotLocked(metadata);
+    }
+
+    private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) {
         ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
+        MediaPlayerBase player;
         synchronized (mLock) {
+            player = mPlayer;
             callbacks.putAll(mCallbacks);
         }
         // Notify to callbacks added directly to this session
@@ -738,7 +752,26 @@
             //executor.execute(() -> callback.onPlaybackStateChanged(state));
         }
         // Notify to controllers as well.
-        mSessionStub.notifyPlaybackStateChangedNotLocked(state);
+        final int state = player.getPlayerState();
+        if (state != oldPlayer.getPlayerState()) {
+            mSessionStub.notifyPlayerStateChangedNotLocked(state);
+        }
+
+        final long currentTimeMs = System.currentTimeMillis();
+        final long position = player.getCurrentPosition();
+        if (position != oldPlayer.getCurrentPosition()) {
+            mSessionStub.notifyPositionChangedNotLocked(currentTimeMs, position);
+        }
+
+        final float speed = player.getPlaybackSpeed();
+        if (speed != oldPlayer.getPlaybackSpeed()) {
+            mSessionStub.notifyPlaybackSpeedChangedNotLocked(speed);
+        }
+
+        final long bufferedPosition = player.getBufferedPosition();
+        if (bufferedPosition != oldPlayer.getBufferedPosition()) {
+            mSessionStub.notifyBufferedPositionChangedNotLocked(bufferedPosition);
+        }
     }
 
     private void notifyErrorNotLocked(String mediaId, int what, int extra) {
@@ -768,6 +801,10 @@
         return mPlayer;
     }
 
+    MediaPlaylistAgent getPlaylistAgent() {
+        return mPlaylistAgent;
+    }
+
     Executor getCallbackExecutor() {
         return mCallbackExecutor;
     }
@@ -803,26 +840,64 @@
 
         @Override
         public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
-            super.onCurrentDataSourceChanged(mpb, dsd);
-            // TODO(jaewan): Handle this b/74370608
+            MediaSession2Impl session = getSession();
+            if (session == null) {
+                return;
+            }
+            session.getCallbackExecutor().execute(() -> {
+                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+                session.getCallback().onCurrentMediaItemChanged(
+                        session.getInstance(), mpb, null /* MediaItem */);
+            });
         }
 
         @Override
         public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
-            super.onMediaPrepared(mpb, dsd);
-            // TODO(jaewan): Handle this b/74370608
+            MediaSession2Impl session = getSession();
+            if (session == null) {
+                return;
+            }
+            session.getCallbackExecutor().execute(() -> {
+                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+                session.getCallback().onMediaPrepared(
+                        session.getInstance(), mpb, null /* MediaItem */);
+            });
         }
 
         @Override
         public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
-            super.onPlayerStateChanged(mpb, state);
-            // TODO(jaewan): Handle this b/74370608
+            MediaSession2Impl session = getSession();
+            if (session == null) {
+                return;
+            }
+            session.getCallbackExecutor().execute(() -> {
+                session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state);
+                session.getSessionStub().notifyPlayerStateChangedNotLocked(state);
+            });
         }
 
         @Override
         public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
-            super.onBufferingStateChanged(mpb, dsd, state);
-            // TODO(jaewan): Handle this b/74370608
+            MediaSession2Impl session = getSession();
+            if (session == null) {
+                return;
+            }
+            session.getCallbackExecutor().execute(() -> {
+                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+                session.getCallback().onBufferingStateChanged(
+                        session.getInstance(), mpb, null /* MediaItem */, state);
+            });
+        }
+
+        private MediaSession2Impl getSession() {
+            final MediaSession2Impl session = mSession.get();
+            if (session == null && DEBUG) {
+                Log.d(TAG, "Session is closed", new IllegalStateException());
+            }
+            return session;
         }
     }
 
@@ -836,15 +911,21 @@
         @Override
         public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
                 MediaMetadata2 metadata) {
-            super.onPlaylistChanged(playlistAgent, list, metadata);
-            // TODO(jaewan): Handle this (b/74326040)
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata);
         }
 
         @Override
         public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
                 MediaMetadata2 metadata) {
-            super.onPlaylistMetadataChanged(playlistAgent, metadata);
-            // TODO(jaewan): Handle this (b/74174649)
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata);
         }
 
         @Override
@@ -891,14 +972,17 @@
             mExtras = extras;
         }
 
+        @Override
         public int getCommandCode_impl() {
             return mCommandCode;
         }
 
+        @Override
         public @Nullable String getCustomCommand_impl() {
             return mCustomCommand;
         }
 
+        @Override
         public @Nullable Bundle getExtras_impl() {
             return mExtras;
         }
@@ -906,6 +990,7 @@
         /**
          * @return a new Bundle instance from the Command
          */
+        @Override
         public Bundle toBundle_impl() {
             Bundle bundle = new Bundle();
             bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
@@ -959,6 +1044,16 @@
     public static class CommandGroupImpl implements CommandGroupProvider {
         private static final String KEY_COMMANDS =
                 "android.media.mediasession2.commandgroup.commands";
+
+        // Prefix for all command codes
+        private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
+
+        // Prefix for command codes that will be sent directly to the MediaPlayerBase
+        private static final String PREFIX_COMMAND_CODE_PLAYBACK = "COMMAND_CODE_PLAYBACK_";
+
+        // Prefix for command codes that will be sent directly to the MediaPlaylistAgent
+        private static final String PREFIX_COMMAND_CODE_PLAYLIST = "COMMAND_CODE_PLAYLIST_";
+
         private List<Command> mCommands = new ArrayList<>();
         private final Context mContext;
         private final CommandGroup mInstance;
@@ -971,6 +1066,11 @@
             }
         }
 
+        public CommandGroupImpl(Context context) {
+            mContext = context;
+            mInstance = new CommandGroup(this);
+        }
+
         @Override
         public void addCommand_impl(Command command) {
             if (command == null) {
@@ -981,8 +1081,30 @@
 
         @Override
         public void addAllPredefinedCommands_impl() {
-            for (int i = 1; i <= MediaSession2.COMMAND_CODE_MAX; i++) {
-                mCommands.add(new Command(mContext, i));
+            addCommandsWithPrefix(PREFIX_COMMAND_CODE);
+        }
+
+        public void addAllPlaybackCommands() {
+            addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYBACK);
+        }
+
+        public void addAllPlaylistCommands() {
+            addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYLIST);
+        }
+
+        private void addCommandsWithPrefix(String prefix) {
+            // TODO(jaewan): (Can be post-P): Don't use reflection for this purpose.
+            final Field[] fields = MediaSession2.class.getFields();
+            if (fields != null) {
+                for (int i = 0; i < fields.length; i++) {
+                    if (fields[i].getName().startsWith(prefix)) {
+                        try {
+                            mCommands.add(new Command(mContext, fields[i].getInt(null)));
+                        } catch (IllegalAccessException e) {
+                            Log.w(TAG, "Unexpected " + fields[i] + " in MediaSession2");
+                        }
+                    }
+                }
             }
         }
 
@@ -1017,7 +1139,11 @@
 
         @Override
         public List<Command> getCommands_impl() {
-            return mCommands;
+            return getCommands();
+        }
+
+        public List<Command> getCommands() {
+            return Collections.unmodifiableList(mCommands);
         }
 
         /**
@@ -1068,10 +1194,10 @@
         private final int mUid;
         private final String mPackageName;
         private final boolean mIsTrusted;
-        private final IMediaSession2Callback mControllerBinder;
+        private final IMediaController2 mControllerBinder;
 
         public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
-                int pid, String packageName, IMediaSession2Callback callback) {
+                int pid, String packageName, IMediaController2 callback) {
             if (TextUtils.isEmpty(packageName)) {
                 throw new IllegalArgumentException("packageName shouldn't be empty");
             }
@@ -1147,7 +1273,7 @@
             return mControllerBinder.asBinder();
         }
 
-        public IMediaSession2Callback getControllerBinder() {
+        public IMediaController2 getControllerBinder() {
             return mControllerBinder;
         }
 
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index a9c5224..8d9cf64 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -21,13 +21,14 @@
 import android.media.MediaController2;
 import android.media.MediaItem2;
 import android.media.MediaLibraryService2.LibraryRoot;
+import android.media.MediaMetadata2;
+import android.media.MediaPlayerBase;
 import android.media.MediaSession2;
 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.PlaybackState2;
 import android.media.Rating2;
 import android.media.VolumeProvider2;
 import android.net.Uri;
@@ -37,12 +38,15 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.media.MediaLibraryService2Impl.MediaLibrarySessionImpl;
 import com.android.media.MediaSession2Impl.CommandButtonImpl;
+import com.android.media.MediaSession2Impl.CommandGroupImpl;
 import com.android.media.MediaSession2Impl.ControllerInfoImpl;
 
 import java.lang.ref.WeakReference;
@@ -61,6 +65,8 @@
     private static final String TAG = "MediaSession2Stub";
     private static final boolean DEBUG = true; // TODO(jaewan): Rename.
 
+    private static final SparseArray<Command> sCommandsForOnCommandRequest = new SparseArray<>();
+
     private final Object mLock = new Object();
     private final WeakReference<MediaSession2Impl> mSession;
 
@@ -75,6 +81,19 @@
 
     public MediaSession2Stub(MediaSession2Impl session) {
         mSession = new WeakReference<>(session);
+
+        synchronized (sCommandsForOnCommandRequest) {
+            if (sCommandsForOnCommandRequest.size() == 0) {
+                CommandGroupImpl group = new CommandGroupImpl(session.getContext());
+                group.addAllPlaybackCommands();
+                group.addAllPlaylistCommands();
+                List<Command> commands = group.getCommands();
+                for (int i = 0; i < commands.size(); i++) {
+                    Command command = commands.get(i);
+                    sCommandsForOnCommandRequest.append(command.getCommandCode(), command);
+                }
+            }
+        }
     }
 
     public void destroyNotLocked() {
@@ -85,7 +104,7 @@
             mControllers.clear();
         }
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback controllerBinder =
+            IMediaController2 controllerBinder =
                     ((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
             try {
                 // Should be used without a lock hold to prevent potential deadlock.
@@ -113,7 +132,7 @@
     }
 
     // Get controller if the command from caller to session is able to be handled.
-    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller) {
+    private ControllerInfo getControllerIfAble(IMediaController2 caller) {
         synchronized (mLock) {
             final ControllerInfo controllerInfo = mControllers.get(caller.asBinder());
             if (controllerInfo == null && DEBUG) {
@@ -124,7 +143,7 @@
     }
 
     // Get controller if the command from caller to session is able to be handled.
-    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, int commandCode) {
+    private ControllerInfo getControllerIfAble(IMediaController2 caller, int commandCode) {
         synchronized (mLock) {
             final ControllerInfo controllerInfo = getControllerIfAble(caller);
             if (controllerInfo == null) {
@@ -147,7 +166,7 @@
     }
 
     // Get controller if the command from caller to session is able to be handled.
-    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, Command command) {
+    private ControllerInfo getControllerIfAble(IMediaController2 caller, Command command) {
         synchronized (mLock) {
             final ControllerInfo controllerInfo = getControllerIfAble(caller);
             if (controllerInfo == null) {
@@ -170,7 +189,7 @@
     }
 
     // Return binder if the session is able to send a command to the controller.
-    private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller) {
+    private IMediaController2 getControllerBinderIfAble(ControllerInfo controller) {
         if (getSession() == null) {
             // getSession() already logged if session is closed.
             return null;
@@ -190,7 +209,7 @@
     }
 
     // Return binder if the session is able to send a command to the controller.
-    private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller,
+    private IMediaController2 getControllerBinderIfAble(ControllerInfo controller,
             int commandCode) {
         synchronized (mLock) {
             CommandGroup allowedCommands = mAllowedCommandGroupMap.get(controller);
@@ -208,11 +227,55 @@
         }
     }
 
+    private void onCommand(@NonNull IMediaController2 caller, int commandCode,
+            @NonNull SessionRunnable runnable) {
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(caller, commandCode);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, commandCode) == null) {
+                return;
+            }
+            Command command = sCommandsForOnCommandRequest.get(commandCode);
+            if (command != null) {
+                boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                        controller, command);
+                if (!accepted) {
+                    // Don't run rejected command.
+                    if (DEBUG) {
+                        Log.d(TAG, "Command (code=" + commandCode + ") from "
+                                + controller + " was rejected by " + session);
+                    }
+                    return;
+                }
+            }
+            runnable.run(session, controller);
+        });
+    }
+
+    private void onBrowserCommand(@NonNull IMediaController2 caller,
+            @NonNull LibrarySessionRunnable runnable) {
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller =
+                getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+                return;
+            }
+            runnable.run(session, controller);
+        });
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////////////
     // AIDL methods for session overrides
     //////////////////////////////////////////////////////////////////////////////////////////////
     @Override
-    public void connect(final IMediaSession2Callback caller, final String callingPackage)
+    public void connect(final IMediaController2 caller, final String callingPackage)
             throws RuntimeException {
         final MediaSession2Impl session = getSession();
         if (session == null) {
@@ -256,11 +319,13 @@
                 // It's needed because we cannot call synchronous calls between session/controller.
                 // Note: We're doing this after the onConnectionChanged(), but there's no guarantee
                 //       that events here are notified after the onConnected() because
-                //       IMediaSession2Callback is oneway (i.e. async call) and CallbackStub will
+                //       IMediaController2 is oneway (i.e. async call) and Stub will
                 //       use thread poll for incoming calls.
-                // TODO(jaewan): Should we protect getting playback state?
-                final PlaybackState2 state = session.getInstance().getPlaybackState();
-                final Bundle playbackStateBundle = (state != null) ? state.toBundle() : null;
+                final int playerState = session.getInstance().getPlayerState();
+                final long positionEventTimeMs = System.currentTimeMillis();
+                final long positionMs = session.getInstance().getCurrentPosition();
+                final float playbackSpeed = session.getInstance().getPlaybackSpeed();
+                final long bufferedPositionMs = session.getInstance().getBufferedPosition();
                 final Bundle playbackInfoBundle = ((MediaController2Impl.PlaybackInfoImpl)
                         session.getPlaybackInfo().getProvider()).toBundle();
                 final PlaylistParams params = session.getInstance().getPlaylistParams();
@@ -292,9 +357,10 @@
                     return;
                 }
                 try {
-                    caller.onConnected(MediaSession2Stub.this,
-                            allowedCommands.toBundle(), playbackStateBundle, playbackInfoBundle,
-                            paramsBundle, playlistBundle, sessionActivity);
+                    caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
+                            playerState, positionEventTimeMs, positionMs, playbackSpeed,
+                            bufferedPositionMs, playbackInfoBundle, paramsBundle, playlistBundle,
+                            sessionActivity);
                 } catch (RemoteException e) {
                     // Controller may be died prematurely.
                     // TODO(jaewan): Handle here.
@@ -317,43 +383,28 @@
     }
 
     @Override
-    public void release(final IMediaSession2Callback caller) throws RemoteException {
+    public void release(final IMediaController2 caller) throws RemoteException {
+        ControllerInfo controller;
         synchronized (mLock) {
-            ControllerInfo controllerInfo = mControllers.remove(caller.asBinder());
+            controller = mControllers.remove(caller.asBinder());
             if (DEBUG) {
-                Log.d(TAG, "releasing " + controllerInfo);
+                Log.d(TAG, "releasing " + controller);
             }
-            mSubscriptions.remove(controllerInfo);
+            mSubscriptions.remove(controller);
         }
-    }
-
-    @Override
-    public void setVolumeTo(final IMediaSession2Callback caller, final int value, final int flags)
-            throws RuntimeException {
         final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
         if (session == null || controller == null) {
             return;
         }
         session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
-                return;
-            }
-            // TODO(jaewan): Sanity check.
-            Command command = new Command(
-                    session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
-            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
-                    controller, command);
-            if (!accepted) {
-                // Don't run rejected command.
-                if (DEBUG) {
-                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
-                            + controller + " was rejected by " + session);
-                }
-                return;
-            }
+            session.getCallback().onDisconnected(session.getInstance(), controller);
+        });
+    }
 
+    @Override
+    public void setVolumeTo(final IMediaController2 caller, final int value, final int flags)
+            throws RuntimeException {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME, (session, controller) -> {
             VolumeProvider2 volumeProvider = session.getVolumeProvider();
             if (volumeProvider == null) {
                 // TODO(jaewan): Set local stream volume
@@ -364,32 +415,9 @@
     }
 
     @Override
-    public void adjustVolume(IMediaSession2Callback caller, int direction, int flags)
+    public void adjustVolume(IMediaController2 caller, int direction, int flags)
             throws RuntimeException {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
-                return;
-            }
-            // TODO(jaewan): Sanity check.
-            Command command = new Command(
-                    session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
-            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
-                    controller, command);
-            if (!accepted) {
-                // Don't run rejected command.
-                if (DEBUG) {
-                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
-                            + controller + " was rejected by " + session);
-                }
-                return;
-            }
-
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME, (session, controller) -> {
             VolumeProvider2 volumeProvider = session.getVolumeProvider();
             if (volumeProvider == null) {
                 // TODO(jaewan): Adjust local stream volume
@@ -400,30 +428,9 @@
     }
 
     @Override
-    public void sendTransportControlCommand(IMediaSession2Callback caller,
+    public void sendTransportControlCommand(IMediaController2 caller,
             int commandCode, Bundle args) throws RuntimeException {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(caller, commandCode);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, commandCode) == null) {
-                return;
-            }
-            // TODO(jaewan): Sanity check.
-            Command command = new Command(session.getContext(), commandCode);
-            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
-                    controller, command);
-            if (!accepted) {
-                // Don't run rejected command.
-                if (DEBUG) {
-                    Log.d(TAG, "Command " + commandCode + " from "
-                            + controller + " was rejected by " + session);
-                }
-                return;
-            }
-
+        onCommand(caller, commandCode, (session, controller) -> {
             switch (commandCode) {
                 case MediaSession2.COMMAND_CODE_PLAYBACK_PLAY:
                     session.getInstance().play();
@@ -474,7 +481,7 @@
     }
 
     @Override
-    public void sendCustomCommand(final IMediaSession2Callback caller, final Bundle commandBundle,
+    public void sendCustomCommand(final IMediaController2 caller, final Bundle commandBundle,
             final Bundle args, final ResultReceiver receiver) {
         final MediaSession2Impl session = getSession();
         if (session == null) {
@@ -500,44 +507,23 @@
     }
 
     @Override
-    public void prepareFromUri(final IMediaSession2Callback caller, final Uri uri,
+    public void prepareFromUri(final IMediaController2 caller, final Uri uri,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (uri == null) {
-            Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI, (session, controller) -> {
+            if (uri == null) {
+                Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
                 return;
             }
-            session.getCallback().onPrepareFromUri(session.getInstance(),
-                    controller, uri, extras);
+            session.getCallback().onPrepareFromUri(session.getInstance(), controller, uri, extras);
         });
     }
 
     @Override
-    public void prepareFromSearch(final IMediaSession2Callback caller, final String query,
+    public void prepareFromSearch(final IMediaController2 caller, final String query,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH, (session, controller) -> {
+            if (TextUtils.isEmpty(query)) {
+                Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
                 return;
             }
             session.getCallback().onPrepareFromSearch(session.getInstance(),
@@ -546,21 +532,12 @@
     }
 
     @Override
-    public void prepareFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+    public void prepareFromMediaId(final IMediaController2 caller, final String mediaId,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (mediaId == null) {
-            Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID,
+                (session, controller) -> {
+            if (mediaId == null) {
+                Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
                 return;
             }
             session.getCallback().onPrepareFromMediaId(session.getInstance(),
@@ -569,21 +546,11 @@
     }
 
     @Override
-    public void playFromUri(final IMediaSession2Callback caller, final Uri uri,
+    public void playFromUri(final IMediaController2 caller, final Uri uri,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (uri == null) {
-            Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI, (session, controller) -> {
+            if (uri == null) {
+                Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
                 return;
             }
             session.getCallback().onPlayFromUri(session.getInstance(), controller, uri, extras);
@@ -591,21 +558,11 @@
     }
 
     @Override
-    public void playFromSearch(final IMediaSession2Callback caller, final String query,
+    public void playFromSearch(final IMediaController2 caller, final String query,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH, (session, controller) -> {
+            if (TextUtils.isEmpty(query)) {
+                Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
                 return;
             }
             session.getCallback().onPlayFromSearch(session.getInstance(),
@@ -614,20 +571,11 @@
     }
 
     @Override
-    public void playFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+    public void playFromMediaId(final IMediaController2 caller, final String mediaId,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (mediaId == null) {
-            Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (session == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID, (session, controller) -> {
+            if (mediaId == null) {
+                Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
                 return;
             }
             session.getCallback().onPlayFromMediaId(session.getInstance(),
@@ -636,47 +584,101 @@
     }
 
     @Override
-    public void setRating(final IMediaSession2Callback caller, final String mediaId,
+    public void setRating(final IMediaController2 caller, final String mediaId,
             final Bundle ratingBundle) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getControllerIfAble(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+        // TODO(jaewan): Define COMMAND_CODE_SET_RATING
+        onCommand(caller, MediaSession2.COMMAND_CODE_SET_RATING, (session, controller) -> {
+            if (mediaId == null) {
+                Log.w(TAG, "setRating(): Ignoring null mediaId from " + controller);
+                return;
             }
-            return;
-        }
-        Rating2 rating = Rating2Impl.fromBundle(sessionImpl.getContext(), ratingBundle);
-        if (rating == null) {
-            Log.w(TAG, "setRating(): Ignore null rating");
-            return;
-        }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+            if (ratingBundle == null) {
+                Log.w(TAG, "setRating(): Ignoring null ratingBundle from " + controller);
+                return;
+            }
+            Rating2 rating = Rating2Impl.fromBundle(session.getContext(), ratingBundle);
+            if (rating == null) {
+                if (ratingBundle == null) {
+                    Log.w(TAG, "setRating(): Ignoring null rating from " + controller);
+                    return;
+                }
                 return;
             }
             session.getCallback().onSetRating(session.getInstance(), controller, mediaId, rating);
         });
     }
 
+    @Override
+    public void setPlaylist(final IMediaController2 caller, final List<Bundle> playlist,
+            final Bundle metadata) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST, (session, controller) -> {
+            if (playlist == null) {
+                Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
+                return;
+            }
+            List<MediaItem2> list = new ArrayList<>();
+            for (int i = 0; i < playlist.size(); i++) {
+                // Recreates UUID in the playlist
+                MediaItem2 item = MediaItem2Impl.fromBundle(
+                        session.getContext(), playlist.get(i), null);
+                if (item != null) {
+                    list.add(item);
+                }
+            }
+            session.getInstance().setPlaylist(list,
+                    MediaMetadata2.fromBundle(session.getContext(), metadata));
+        });
+    }
+
+    @Override
+    public void updatePlaylistMetadata(final IMediaController2 caller, final Bundle metadata) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA,
+                (session, controller) -> {
+            session.getInstance().updatePlaylistMetadata(
+                    MediaMetadata2.fromBundle(session.getContext(), metadata));
+        });
+    }
+
+    @Override
+    public void addPlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM, (session, controller) -> {
+            // Resets the UUID from the incoming media id, so controller may reuse a media item
+            // multiple times for addPlaylistItem.
+            session.getInstance().addPlaylistItem(index,
+                    MediaItem2Impl.fromBundle(session.getContext(), mediaItem, null));
+        });
+    }
+
+    @Override
+    public void removePlaylistItem(IMediaController2 caller, Bundle mediaItem) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM,
+                (session, controller) -> {
+            MediaItem2 item = MediaItem2.fromBundle(session.getContext(), mediaItem);
+            List<MediaItem2> list = session.getInstance().getPlaylist();
+            // Trick to use the same reference for calls from the controller.
+            session.getInstance().removePlaylistItem(list.get(list.indexOf(item)));
+        });
+    }
+
+    @Override
+    public void replacePlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM,
+                (session, controller) -> {
+                    // Resets the UUID from the incoming media id, so controller may reuse a media
+                    // item multiple times for replacePlaylistItem.
+                    session.getInstance().replacePlaylistItem(index,
+                            MediaItem2.fromBundle(session.getContext(), mediaItem));
+                });
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////////////
     // AIDL methods for LibrarySession overrides
     //////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
-    public void getLibraryRoot(final IMediaSession2Callback caller, final Bundle rootHints)
+    public void getLibraryRoot(final IMediaController2 caller, final Bundle rootHints)
             throws RuntimeException {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
-                return;
-            }
+        onBrowserCommand(caller, (session, controller) -> {
             LibraryRoot root = session.getCallback().onGetLibraryRoot(session.getInstance(),
                     controller, rootHints);
             try {
@@ -691,22 +693,13 @@
     }
 
     @Override
-    public void getItem(final IMediaSession2Callback caller, final String mediaId)
+    public void getItem(final IMediaController2 caller, final String mediaId)
             throws RuntimeException {
-        if (mediaId == null) {
-            if (DEBUG) {
-                Log.d(TAG, "mediaId shouldn't be null");
-            }
-            return;
-        }
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (mediaId == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "mediaId shouldn't be null");
+                }
                 return;
             }
             MediaItem2 result = session.getCallback().onGetItem(session.getInstance(),
@@ -721,28 +714,19 @@
     }
 
     @Override
-    public void getChildren(final IMediaSession2Callback caller, final String parentId,
+    public void getChildren(final IMediaController2 caller, final String parentId,
             final int page, final int pageSize, final Bundle extras) throws RuntimeException {
-        if (parentId == null) {
-            if (DEBUG) {
-                Log.d(TAG, "parentId shouldn't be null");
+        onBrowserCommand(caller, (session, controller) -> {
+            if (parentId == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "parentId shouldn't be null");
+                }
+                return;
             }
-            return;
-        }
-        if (page < 1 || pageSize < 1) {
-            if (DEBUG) {
-                Log.d(TAG, "Neither page nor pageSize should be less than 1");
-            }
-            return;
-        }
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+            if (page < 1 || pageSize < 1) {
+                if (DEBUG) {
+                    Log.d(TAG, "Neither page nor pageSize should be less than 1");
+                }
                 return;
             }
             List<MediaItem2> result = session.getCallback().onGetChildren(session.getInstance(),
@@ -769,19 +753,10 @@
     }
 
     @Override
-    public void search(IMediaSession2Callback caller, String query, Bundle extras) {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "search(): Ignoring empty query from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+    public void search(IMediaController2 caller, String query, Bundle extras) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (TextUtils.isEmpty(query)) {
+                Log.w(TAG, "search(): Ignoring empty query from " + controller);
                 return;
             }
             session.getCallback().onSearch(session.getInstance(), controller, query, extras);
@@ -789,25 +764,16 @@
     }
 
     @Override
-    public void getSearchResult(final IMediaSession2Callback caller, final String query,
+    public void getSearchResult(final IMediaController2 caller, final String query,
             final int page, final int pageSize, final Bundle extras) {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
-            return;
-        }
-        if (page < 1 || pageSize < 1) {
-            Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
-                    + " page=" + page + " pageSize=" + pageSize + " from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (TextUtils.isEmpty(query)) {
+                Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
+                return;
+            }
+            if (page < 1 || pageSize < 1) {
+                Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
+                        + " page=" + page + " pageSize=" + pageSize + " from " + controller);
                 return;
             }
             List<MediaItem2> result = session.getCallback().onGetSearchResult(session.getInstance(),
@@ -824,7 +790,6 @@
                     bundleList.add(item == null ? null : item.toBundle());
                 }
             }
-
             try {
                 caller.onGetSearchResultDone(query, page, pageSize, bundleList, extras);
             } catch (RemoteException e) {
@@ -835,20 +800,11 @@
     }
 
     @Override
-    public void subscribe(final IMediaSession2Callback caller, final String parentId,
+    public void subscribe(final IMediaController2 caller, final String parentId,
             final Bundle option) {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (parentId == null) {
-            Log.w(TAG, "subscribe(): Ignoring null parentId from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (parentId == null) {
+                Log.w(TAG, "subscribe(): Ignoring null parentId from " + controller);
                 return;
             }
             session.getCallback().onSubscribe(session.getInstance(),
@@ -865,19 +821,10 @@
     }
 
     @Override
-    public void unsubscribe(final IMediaSession2Callback caller, final String parentId) {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (parentId == null) {
-            Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+    public void unsubscribe(final IMediaController2 caller, final String parentId) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (parentId == null) {
+                Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
                 return;
             }
             session.getCallback().onUnsubscribe(session.getInstance(), controller, parentId);
@@ -903,16 +850,63 @@
     }
 
     // Should be used without a lock to prevent potential deadlock.
-    public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
+    public void notifyPlayerStateChangedNotLocked(int state) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
             if (controllerBinder == null) {
                 return;
             }
             try {
-                final Bundle bundle = state != null ? state.toBundle() : null;
-                controllerBinder.onPlaybackStateChanged(bundle);
+                controllerBinder.onPlayerStateChanged(state);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Controller is gone", e);
+                // TODO(jaewan): What to do when the controller is gone?
+            }
+        }
+    }
+
+    public void notifyPositionChangedNotLocked(long eventTimeMs, long positionMs) {
+        final List<ControllerInfo> list = getControllers();
+        for (int i = 0; i < list.size(); i++) {
+            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+            if (controllerBinder == null) {
+                return;
+            }
+            try {
+                controllerBinder.onPositionChanged(eventTimeMs, positionMs);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Controller is gone", e);
+                // TODO(jaewan): What to do when the controller is gone?
+            }
+        }
+    }
+
+    public void notifyPlaybackSpeedChangedNotLocked(float speed) {
+        final List<ControllerInfo> list = getControllers();
+        for (int i = 0; i < list.size(); i++) {
+            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+            if (controllerBinder == null) {
+                return;
+            }
+            try {
+                controllerBinder.onPlaybackSpeedChanged(speed);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Controller is gone", e);
+                // TODO(jaewan): What to do when the controller is gone?
+            }
+        }
+    }
+
+    public void notifyBufferedPositionChangedNotLocked(long bufferedPositionMs) {
+        final List<ControllerInfo> list = getControllers();
+        for (int i = 0; i < list.size(); i++) {
+            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+            if (controllerBinder == null) {
+                return;
+            }
+            try {
+                controllerBinder.onBufferedPositionChanged(bufferedPositionMs);
             } catch (RemoteException e) {
                 Log.w(TAG, "Controller is gone", e);
                 // TODO(jaewan): What to do when the controller is gone?
@@ -921,7 +915,7 @@
     }
 
     public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
         if (controllerBinder == null) {
             return;
         }
@@ -940,23 +934,57 @@
         }
     }
 
-    public void notifyPlaylistChanged(List<MediaItem2> playlist) {
-        final List<Bundle> bundleList = new ArrayList<>();
-        for (int i = 0; i < playlist.size(); i++) {
-            if (playlist.get(i) != null) {
-                Bundle bundle = playlist.get(i).toBundle();
-                if (bundle != null) {
-                    bundleList.add(bundle);
+    public void notifyPlaylistChangedNotLocked(List<MediaItem2> playlist, MediaMetadata2 metadata) {
+        final List<Bundle> bundleList;
+        if (playlist != null) {
+            bundleList = new ArrayList<>();
+            for (int i = 0; i < playlist.size(); i++) {
+                if (playlist.get(i) != null) {
+                    Bundle bundle = playlist.get(i).toBundle();
+                    if (bundle != null) {
+                        bundleList.add(bundle);
+                    }
                 }
             }
+        } else {
+            bundleList = null;
         }
+        final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(
+            final IMediaController2 controllerBinder = getControllerBinderIfAble(
                     list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST);
             if (controllerBinder != null) {
                 try {
-                    controllerBinder.onPlaylistChanged(bundleList);
+                    controllerBinder.onPlaylistChanged(bundleList, metadataBundle);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Controller is gone", e);
+                    // TODO(jaewan): What to do when the controller is gone?
+                }
+            } else {
+                final IMediaController2 binder = getControllerBinderIfAble(
+                        list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
+                if (binder != null) {
+                    try {
+                        binder.onPlaylistMetadataChanged(metadataBundle);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Controller is gone", e);
+                        // TODO(jaewan): What to do when the controller is gone?
+                    }
+                }
+            }
+        }
+    }
+
+    public void notifyPlaylistMetadataChangedNotLocked(MediaMetadata2 metadata) {
+        final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+        final List<ControllerInfo> list = getControllers();
+        for (int i = 0; i < list.size(); i++) {
+            final IMediaController2 controllerBinder = getControllerBinderIfAble(
+                    list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
+            if (controllerBinder != null) {
+                try {
+                    controllerBinder.onPlaylistMetadataChanged(metadataBundle);
                 } catch (RemoteException e) {
                     Log.w(TAG, "Controller is gone", e);
                     // TODO(jaewan): What to do when the controller is gone?
@@ -968,7 +996,7 @@
     public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
             if (controllerBinder == null) {
                 return;
             }
@@ -984,7 +1012,7 @@
     public void notifyPlaybackInfoChanged(MediaController2.PlaybackInfo playbackInfo) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
             if (controllerBinder == null) {
                 return;
             }
@@ -1002,7 +1030,7 @@
         synchronized (mLock) {
             mAllowedCommandGroupMap.put(controller, commands);
         }
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
         if (controllerBinder == null) {
             return;
         }
@@ -1038,7 +1066,7 @@
 
     private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
             ResultReceiver receiver) {
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
         if (controllerBinder == null) {
             return;
         }
@@ -1057,7 +1085,7 @@
 
     public void notifySearchResultChanged(ControllerInfo controller, String query, int itemCount,
             Bundle extras) {
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
         if (controllerBinder == null) {
             return;
         }
@@ -1091,7 +1119,7 @@
                 return;
             }
         }
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
         if (controller == null) {
             return;
         }
@@ -1101,4 +1129,18 @@
             // TODO(jaewan): Handle controller removed?
         }
     }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // Misc
+    //////////////////////////////////////////////////////////////////////////////////////////////
+
+    @FunctionalInterface
+    private interface SessionRunnable {
+        void run(final MediaSession2Impl session, final ControllerInfo controller);
+    }
+
+    @FunctionalInterface
+    private interface LibrarySessionRunnable {
+        void run(final MediaLibrarySessionImpl session, final ControllerInfo controller);
+    }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 6bd2b2a..a0123b5 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -27,10 +27,8 @@
 import android.media.MediaSession2;
 import android.media.MediaSessionService2;
 import android.media.MediaSessionService2.MediaNotification;
-import android.media.PlaybackState2;
 import android.media.SessionToken2;
 import android.media.SessionToken2.TokenType;
-import android.media.session.PlaybackState;
 import android.media.update.MediaSessionService2Provider;
 import android.os.IBinder;
 import android.support.annotation.GuardedBy;
@@ -109,13 +107,13 @@
         return null;
     }
 
-    private void updateNotification(PlaybackState2 state) {
+    private void updateNotification(int playerState) {
         MediaNotification mediaNotification = mInstance.onUpdateNotification();
         if (mediaNotification == null) {
             return;
         }
-        switch((int) state.getState()) {
-            case PlaybackState.STATE_PLAYING:
+        switch(playerState) {
+            case MediaPlayerBase.PLAYER_STATE_PLAYING:
                 if (!mIsRunningForeground) {
                     mIsRunningForeground = true;
                     mInstance.startForegroundService(mStartSelfIntent);
@@ -124,7 +122,8 @@
                     return;
                 }
                 break;
-            case PlaybackState.STATE_STOPPED:
+            case MediaPlayerBase.PLAYER_STATE_IDLE:
+            case MediaPlayerBase.PLAYER_STATE_ERROR:
                 if (mIsRunningForeground) {
                     mIsRunningForeground = false;
                     mInstance.stopForeground(true);
@@ -142,15 +141,6 @@
             // TODO: Implement this
             return;
         }
-        // TODO: Uncomment or remove
-        //public void onPlaybackStateChanged(PlaybackState2 state) {
-        //    if (state == null) {
-        //        Log.w(TAG, "Ignoring null playback state");
-        //        return;
-        //    }
-        //    MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
-        //    updateNotification(impl.getInstance().getPlaybackState());
-        //}
     }
 
     public static class MediaNotificationImpl implements MediaNotificationProvider {
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java b/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
deleted file mode 100644
index ee8d6d7..0000000
--- a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
+++ /dev/null
@@ -1,132 +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.content.Context;
-import android.media.PlaybackState2;
-import android.media.update.PlaybackState2Provider;
-import android.os.Bundle;
-
-public final class PlaybackState2Impl implements PlaybackState2Provider {
-    /**
-     * Keys used for converting a PlaybackState2 to a bundle object and vice versa.
-     */
-    private static final String KEY_STATE = "android.media.playbackstate2.state";
-    private static final String KEY_POSITION = "android.media.playbackstate2.position";
-    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_UPDATE_TIME = "android.media.playbackstate2.update_time";
-    private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id";
-
-    private final Context mContext;
-    private final PlaybackState2 mInstance;
-    private final int mState;
-    private final long mPosition;
-    private final long mUpdateTime;
-    private final float mSpeed;
-    private final long mBufferedPosition;
-    private final long mActiveItemId;
-
-    public PlaybackState2Impl(Context context, PlaybackState2 instance, int state, long position,
-            long updateTime, float speed, long bufferedPosition, long activeItemId) {
-        mContext = context;
-        mInstance = instance;
-        mState = state;
-        mPosition = position;
-        mSpeed = speed;
-        mUpdateTime = updateTime;
-        mBufferedPosition = bufferedPosition;
-        mActiveItemId = activeItemId;
-    }
-
-    @Override
-    public String toString_impl() {
-        StringBuilder bob = new StringBuilder("PlaybackState {");
-        bob.append("state=").append(mState);
-        bob.append(", position=").append(mPosition);
-        bob.append(", buffered position=").append(mBufferedPosition);
-        bob.append(", speed=").append(mSpeed);
-        bob.append(", updated=").append(mUpdateTime);
-        bob.append(", active item id=").append(mActiveItemId);
-        bob.append("}");
-        return bob.toString();
-    }
-
-    @Override
-    public int getState_impl() {
-        return mState;
-    }
-
-    @Override
-    public long getPosition_impl() {
-        return mPosition;
-    }
-
-    @Override
-    public long getBufferedPosition_impl() {
-        return mBufferedPosition;
-    }
-
-    @Override
-    public float getPlaybackSpeed_impl() {
-        return mSpeed;
-    }
-
-    @Override
-    public long getLastPositionUpdateTime_impl() {
-        return mUpdateTime;
-    }
-
-    @Override
-    public long getCurrentPlaylistItemIndex_impl() {
-        return mActiveItemId;
-    }
-
-    @Override
-    public Bundle toBundle_impl() {
-        Bundle bundle = new Bundle();
-        bundle.putInt(KEY_STATE, mState);
-        bundle.putLong(KEY_POSITION, mPosition);
-        bundle.putLong(KEY_UPDATE_TIME, mUpdateTime);
-        bundle.putFloat(KEY_SPEED, mSpeed);
-        bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition);
-        bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId);
-        return bundle;
-    }
-
-    public static PlaybackState2 fromBundle(Context context, Bundle bundle) {
-        if (bundle == null) {
-            return null;
-        }
-        if (!bundle.containsKey(KEY_STATE)
-                || !bundle.containsKey(KEY_POSITION)
-                || !bundle.containsKey(KEY_UPDATE_TIME)
-                || !bundle.containsKey(KEY_SPEED)
-                || !bundle.containsKey(KEY_BUFFERED_POSITION)
-                || !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));
-    }
-}
\ 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 07b8788..7f225de 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
-import android.media.DataSourceDesc;
 import android.media.MediaBrowser2;
 import android.media.MediaBrowser2.BrowserCallback;
 import android.media.MediaController2;
@@ -31,7 +30,6 @@
 import android.media.MediaLibraryService2.MediaLibrarySession;
 import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
 import android.media.MediaPlaylistAgent;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
@@ -41,7 +39,6 @@
 import android.media.MediaSession2.SessionCallback;
 import android.media.MediaSessionService2;
 import android.media.MediaSessionService2.MediaNotification;
-import android.media.PlaybackState2;
 import android.media.Rating2;
 import android.media.SessionToken2;
 import android.media.VolumeProvider2;
@@ -58,7 +55,6 @@
 import android.media.update.MediaSession2Provider.PlaylistParamsProvider;
 import android.media.update.MediaSessionService2Provider;
 import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
-import android.media.update.PlaybackState2Provider;
 import android.media.update.SessionToken2Provider;
 import android.media.update.StaticProvider;
 import android.media.update.VideoView2Provider;
@@ -71,7 +67,7 @@
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
 
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
 import com.android.media.MediaBrowser2Impl;
 import com.android.media.MediaController2Impl;
 import com.android.media.MediaItem2Impl;
@@ -82,7 +78,6 @@
 import com.android.media.MediaSession2Impl;
 import com.android.media.MediaSession2Impl.PlaylistParamsImpl;
 import com.android.media.MediaSessionService2Impl;
-import com.android.media.PlaybackState2Impl;
 import com.android.media.Rating2Impl;
 import com.android.media.SessionToken2Impl;
 import com.android.media.VolumeProvider2Impl;
@@ -142,7 +137,7 @@
             Context context, ControllerInfo instance, int uid, int pid, String packageName,
             IInterface callback) {
         return new MediaSession2Impl.ControllerInfoImpl(context,
-                instance, uid, pid, packageName, (IMediaSession2Callback) callback);
+                instance, uid, pid, packageName, (IMediaController2) callback);
     }
 
     @Override
@@ -293,19 +288,6 @@
     }
 
     @Override
-    public PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance,
-            int state, long position, long updateTime, float speed, long bufferedPosition,
-            long activeItemId) {
-        return new PlaybackState2Impl(context, instance, state, position, updateTime, speed,
-                bufferedPosition, activeItemId);
-    }
-
-    @Override
-    public PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle) {
-        return PlaybackState2Impl.fromBundle(context, bundle);
-    }
-
-    @Override
     public MediaPlaylistAgentProvider createMediaPlaylistAgent(Context context,
             MediaPlaylistAgent instance) {
         return new MediaPlaylistAgentImpl(context, instance);
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index 69febc2..16707c6 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -52,6 +52,7 @@
 import com.android.support.mediarouter.media.MediaRouter;
 import com.android.support.mediarouter.media.MediaRouteSelector;
 
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Formatter;
 import java.util.List;
@@ -69,19 +70,37 @@
     static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount";
     static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount";
     static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount";
+    static final String KEY_PLAYBACK_SPEED = "PlaybackSpeed";
+    static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex";
+    static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex";
     static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus";
 
     // TODO: Remove this once integrating with MediaSession2 & MediaMetadata2
     static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement";
     static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus";
 
-    // String for receiving command to show subtitle from MediaSession.
+    // String for sending command to show subtitle to MediaSession.
     static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
-    // String for receiving command to hide subtitle from MediaSession.
+    // String for sending command to hide subtitle to MediaSession.
     static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
     // TODO: remove once the implementation is revised
     public static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+    // String for sending command to select audio track to MediaSession.
+    static final String COMMAND_SELECT_AUDIO_TRACK = "SelectTrack";
+    // String for sending command to set playback speed to MediaSession.
+    static final String COMMAND_SET_PLAYBACK_SPEED = "SetPlaybackSpeed";
+    // String for sending command to mute audio to MediaSession.
+    static final String COMMAND_MUTE= "Mute";
+    // String for sending command to unmute audio to MediaSession.
+    static final String COMMAND_UNMUTE = "Unmute";
 
+    private static final int SETTINGS_MODE_AUDIO_TRACK = 0;
+    private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1;
+    private static final int SETTINGS_MODE_HELP = 2;
+    private static final int SETTINGS_MODE_SUBTITLE_TRACK = 3;
+    private static final int SETTINGS_MODE_VIDEO_QUALITY = 4;
+    private static final int SETTINGS_MODE_MAIN = 5;
+    private static final int PLAYBACK_SPEED_1x_INDEX = 3;
     private static final int MAX_PROGRESS = 1000;
     private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
     private static final int REWIND_TIME_MS = 10000;
@@ -100,6 +119,7 @@
     private TextView mTitleView;
     private TextView mAdSkipView, mAdRemainingView;
     private View mAdExternalLink;
+    private View mTitleBar;
     private View mRoot;
     private int mDuration;
     private int mPrevState;
@@ -107,6 +127,13 @@
     private int mVideoTrackCount;
     private int mAudioTrackCount;
     private int mSubtitleTrackCount;
+    private int mSettingsMode;
+    private int mSelectedSubtitleTrackIndex;
+    private int mSelectedAudioTrackIndex;
+    private int mSelectedVideoQualityIndex;
+    private int mSelectedSpeedIndex;
+    private int mSettingsItemHeight;
+    private int mSettingsWindowMargin;
     private long mPlaybackActions;
     private boolean mDragging;
     private boolean mIsFullScreen;
@@ -115,11 +142,13 @@
     private boolean mSubtitleIsEnabled;
     private boolean mSeekAvailable;
     private boolean mIsAdvertisement;
+    private boolean mIsMute;
     private ImageButton mPlayPauseButton;
     private ImageButton mFfwdButton;
     private ImageButton mRewButton;
     private ImageButton mNextButton;
     private ImageButton mPrevButton;
+    private ImageButton mBackButton;
 
     private ViewGroup mBasicControls;
     private ImageButton mSubtitleButton;
@@ -130,12 +159,13 @@
     private ViewGroup mCustomButtons;
     private ImageButton mOverflowButtonLeft;
     private ImageButton mMuteButton;
-    private ImageButton mAspectRationButton;
+    private ImageButton mVideoQualityButton;
     private ImageButton mSettingsButton;
 
     private ListView mSettingsListView;
     private PopupWindow mSettingsWindow;
     private SettingsAdapter mSettingsAdapter;
+    private SubSettingsAdapter mSubSettingsAdapter;
 
     private List<String> mSettingsMainTextsList;
     private List<String> mSettingsSubTextsList;
@@ -143,7 +173,8 @@
     private List<String> mSubtitleDescriptionsList;
     private List<String> mAudioTrackList;
     private List<String> mVideoQualityList;
-    private List<String> mPlaybackSpeedTextIdsList;
+    private List<String> mPlaybackSpeedTextList;
+    private List<Float> mPlaybackSpeedList;
 
     private CharSequence mPlayDescription;
     private CharSequence mPauseDescription;
@@ -166,7 +197,6 @@
         mResources = ApiHelper.getLibResources();
         // Inflate MediaControlView2 from XML
         mRoot = makeControllerView();
-        mRoot.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
         mInstance.addView(mRoot);
     }
 
@@ -217,15 +247,11 @@
                 }
                 break;
             case MediaControlView2.BUTTON_NEXT:
-                // TODO: this button is not visible unless its listener is manually set. Should this
-                // function still be provided?
                 if (mNextButton != null) {
                     mNextButton.setVisibility(visibility);
                 }
                 break;
             case MediaControlView2.BUTTON_PREV:
-                // TODO: this button is not visible unless its listener is manually set. Should this
-                // function still be provided?
                 if (mPrevButton != null) {
                     mPrevButton.setVisibility(visibility);
                 }
@@ -250,11 +276,6 @@
                     mMuteButton.setVisibility(visibility);
                 }
                 break;
-            case MediaControlView2.BUTTON_ASPECT_RATIO:
-                if (mAspectRationButton != null) {
-                    mAspectRationButton.setVisibility(visibility);
-                }
-                break;
             case MediaControlView2.BUTTON_SETTINGS:
                 if (mSettingsButton != null) {
                     mSettingsButton.setVisibility(visibility);
@@ -363,7 +384,8 @@
         }
         mPlaybackState = mController.getPlaybackState();
         if (mPlaybackState != null) {
-            return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+            long bufferedPos = mPlaybackState.getBufferedPosition();
+            return (bufferedPos == -1) ? -1 : (int) (bufferedPos * 100 / mDuration);
         }
         return 0;
     }
@@ -415,20 +437,14 @@
         if (mPlayPauseButton != null) {
             mPlayPauseButton.requestFocus();
             mPlayPauseButton.setOnClickListener(mPlayPauseListener);
-            mPlayPauseButton.setColorFilter(R.color.gray);
-            mPlayPauseButton.setEnabled(false);
         }
         mFfwdButton = v.findViewById(R.id.ffwd);
         if (mFfwdButton != null) {
             mFfwdButton.setOnClickListener(mFfwdListener);
-            mFfwdButton.setColorFilter(R.color.gray);
-            mFfwdButton.setEnabled(false);
         }
         mRewButton = v.findViewById(R.id.rew);
         if (mRewButton != null) {
             mRewButton.setOnClickListener(mRewListener);
-            mRewButton.setColorFilter(R.color.gray);
-            mRewButton.setEnabled(false);
         }
         mNextButton = v.findViewById(R.id.next);
         if (mNextButton != null) {
@@ -438,13 +454,15 @@
         if (mPrevButton != null) {
             mPrevButton.setOnClickListener(mPrevListener);
         }
+        mBackButton = v.findViewById(R.id.back);
+        if (mBackButton != null) {
+            mBackButton.setOnClickListener(mBackListener);
+        }
 
         mBasicControls = v.findViewById(R.id.basic_controls);
         mSubtitleButton = v.findViewById(R.id.subtitle);
         if (mSubtitleButton != null) {
             mSubtitleButton.setOnClickListener(mSubtitleListener);
-            mSubtitleButton.setColorFilter(R.color.gray);
-            mSubtitleButton.setEnabled(false);
         }
         mFullScreenButton = v.findViewById(R.id.fullscreen);
         if (mFullScreenButton != null) {
@@ -464,21 +482,33 @@
             mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
         }
         mMuteButton = v.findViewById(R.id.mute);
-        mAspectRationButton = v.findViewById(R.id.aspect_ratio);
+        if (mMuteButton != null) {
+            mMuteButton.setOnClickListener(mMuteButtonListener);
+        }
         mSettingsButton = v.findViewById(R.id.settings);
         if (mSettingsButton != null) {
             mSettingsButton.setOnClickListener(mSettingsButtonListener);
         }
+        mVideoQualityButton = v.findViewById(R.id.video_quality);
+        if (mVideoQualityButton != null) {
+            mVideoQualityButton.setOnClickListener(mVideoQualityListener);
+        }
 
         mProgress = v.findViewById(R.id.mediacontroller_progress);
         if (mProgress != null) {
             if (mProgress instanceof SeekBar) {
                 SeekBar seeker = (SeekBar) mProgress;
                 seeker.setOnSeekBarChangeListener(mSeekListener);
+                seeker.setProgressDrawable(mResources.getDrawable(R.drawable.custom_progress));
+                seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
             }
             mProgress.setMax(MAX_PROGRESS);
         }
 
+        mTitleBar = v.findViewById(R.id.title_bar);
+        if (mTitleBar != null) {
+            mTitleBar.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
+        }
         mTitleView = v.findViewById(R.id.title_text);
 
         mEndTime = v.findViewById(R.id.time);
@@ -494,10 +524,16 @@
         mSettingsListView = (ListView) ApiHelper.inflateLibLayout(mInstance.getContext(),
                 R.layout.settings_list);
         mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList,
-                mSettingsIconIdsList, false);
+                mSettingsIconIdsList);
+        mSubSettingsAdapter = new SubSettingsAdapter(null, 0);
         mSettingsListView.setAdapter(mSettingsAdapter);
         mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
+
+        mSettingsItemHeight = mResources.getDimensionPixelSize(
+                R.dimen.MediaControlView2_settings_height);
+        mSettingsWindowMargin = (-1) * mResources.getDimensionPixelSize(
+                R.dimen.MediaControlView2_settings_offset);
         int width = mResources.getDimensionPixelSize(R.dimen.MediaControlView2_settings_width);
         mSettingsWindow = new PopupWindow(mSettingsListView, width,
                 ViewGroup.LayoutParams.WRAP_CONTENT, true);
@@ -574,7 +610,13 @@
         }
         if (mProgress != null && currentPosition != mDuration) {
             mProgress.setProgress(positionOnProgressBar);
-            mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+            // If the media is a local file, there is no need to set a buffer, so set secondary
+            // progress to maximum.
+            if (getBufferPercentage() < 0) {
+                mProgress.setSecondaryProgress(MAX_PROGRESS);
+            } else {
+                mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+            }
         }
 
         if (mEndTime != null) {
@@ -744,20 +786,30 @@
         }
     };
 
+    private final View.OnClickListener mBackListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            // TODO: implement
+        }
+    };
+
     private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            if (!mSubtitleIsEnabled) {
-                mSubtitleButton.setImageDrawable(
-                        mResources.getDrawable(R.drawable.ic_media_subtitle_enabled, null));
-                mController.sendCommand(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, null);
-                mSubtitleIsEnabled = true;
-            } else {
-                mSubtitleButton.setImageDrawable(
-                        mResources.getDrawable(R.drawable.ic_media_subtitle_disabled, null));
-                mController.sendCommand(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
-                mSubtitleIsEnabled = false;
-            }
+            mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK;
+            mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList);
+            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
+            displaySettingsWindow(mSubSettingsAdapter);
+        }
+    };
+
+    private final View.OnClickListener mVideoQualityListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mSettingsMode = SETTINGS_MODE_VIDEO_QUALITY;
+            mSubSettingsAdapter.setTexts(mVideoQualityList);
+            mSubSettingsAdapter.setCheckPosition(mSelectedVideoQualityIndex);
+            displaySettingsWindow(mSubSettingsAdapter);
         }
     };
 
@@ -797,20 +849,29 @@
         }
     };
 
+    private final View.OnClickListener mMuteButtonListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (!mIsMute) {
+                mMuteButton.setImageDrawable(
+                        mResources.getDrawable(R.drawable.ic_mute, null));
+                mIsMute = true;
+                mController.sendCommand(COMMAND_MUTE, null, null);
+            } else {
+                mMuteButton.setImageDrawable(
+                        mResources.getDrawable(R.drawable.ic_unmute, null));
+                mIsMute = false;
+                mController.sendCommand(COMMAND_UNMUTE, null, null);
+            }
+        }
+    };
+
     private final View.OnClickListener mSettingsButtonListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList,
-                    mSettingsSubTextsList, mSettingsIconIdsList, false);
-            mSettingsListView.setAdapter(mSettingsAdapter);
-            int itemHeight = mResources.getDimensionPixelSize(
-                    R.dimen.MediaControlView2_settings_height);
-            int totalHeight = mSettingsAdapter.getCount() * itemHeight;
-            int margin = (-1) * mResources.getDimensionPixelSize(
-                    R.dimen.MediaControlView2_settings_offset);
-            mSettingsWindow.dismiss();
-            mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
-                    Gravity.BOTTOM | Gravity.RIGHT);
+            mSettingsMode = SETTINGS_MODE_MAIN;
+            mSettingsAdapter.setSubTexts(mSettingsSubTextsList);
+            displaySettingsWindow(mSettingsAdapter);
         }
     };
 
@@ -818,56 +879,78 @@
             = new AdapterView.OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            switch (position) {
-                // change to identifiers
-                case 0:
-                    // TODO: add additional subtitle track details
-                    mSubtitleDescriptionsList = new ArrayList<String>();
-                    mSubtitleDescriptionsList.add(mResources.getString(
-                            R.string.MediaControlView2_subtitle_off_text));
-                    for (int i = 0; i < mSubtitleTrackCount; i++) {
-                        String track = mResources.getString(
-                                R.string.MediaControlView2_subtitle_track_number_text, i + 1);
-                        mSubtitleDescriptionsList.add(track);
+            switch (mSettingsMode) {
+                case SETTINGS_MODE_MAIN:
+                    if (position == SETTINGS_MODE_AUDIO_TRACK) {
+                        mSubSettingsAdapter.setTexts(mAudioTrackList);
+                        mSubSettingsAdapter.setCheckPosition(mSelectedAudioTrackIndex);
+                        mSettingsMode = SETTINGS_MODE_AUDIO_TRACK;
+                    } else if (position == SETTINGS_MODE_PLAYBACK_SPEED) {
+                        mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
+                        mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
+                        mSettingsMode = SETTINGS_MODE_PLAYBACK_SPEED;
+                    } else if (position == SETTINGS_MODE_HELP) {
+                        // TODO: implement this.
+                        mSettingsWindow.dismiss();
+                        return;
                     }
-                    mSettingsAdapter = new SettingsAdapter(mSubtitleDescriptionsList, null,
-                            null, true);
+                    displaySettingsWindow(mSubSettingsAdapter);
                     break;
-                case 1:
-                    // TODO: add additional audio track details
-                    mAudioTrackList = new ArrayList<String>();
-                    mAudioTrackList.add(mResources.getString(
-                            R.string.MediaControlView2_audio_track_none_text));
-                    for (int i = 0; i < mAudioTrackCount; i++) {
-                        String track = mResources.getString(
-                                R.string.MediaControlView2_audio_track_number_text, i + 1);
-                        mAudioTrackList.add(track);
+                case SETTINGS_MODE_AUDIO_TRACK:
+                    if (position != mSelectedAudioTrackIndex) {
+                        mSelectedAudioTrackIndex = position;
+                        if (mAudioTrackCount > 0) {
+                            Bundle extra = new Bundle();
+                            extra.putInt(KEY_SELECTED_AUDIO_INDEX, position);
+                            mController.sendCommand(COMMAND_SELECT_AUDIO_TRACK, extra, null);
+                        }
+                        mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+                                mSubSettingsAdapter.getMainText(position));
                     }
-                    mSettingsAdapter = new SettingsAdapter(mAudioTrackList, null,
-                            null, true);
+                    mSettingsWindow.dismiss();
                     break;
-                case 2:
-                    // TODO: add support for multiple quality video tracks
-                    mSettingsAdapter = new SettingsAdapter(mVideoQualityList, null,
-                            null, true);
+                case SETTINGS_MODE_PLAYBACK_SPEED:
+                    if (position != mSelectedSpeedIndex) {
+                        mSelectedSpeedIndex = position;
+                        Bundle extra = new Bundle();
+                        extra.putFloat(KEY_PLAYBACK_SPEED, mPlaybackSpeedList.get(position));
+                        mController.sendCommand(COMMAND_SET_PLAYBACK_SPEED, extra, null);
+                        mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED,
+                                mSubSettingsAdapter.getMainText(position));
+                    }
+                    mSettingsWindow.dismiss();
                     break;
-                case 3:
-                    // TODO: implement code to reflect change in speed.
-                    mSettingsAdapter = new SettingsAdapter(mPlaybackSpeedTextIdsList, null,
-                            null, true);
+                case SETTINGS_MODE_HELP:
+                    // TODO: implement this.
                     break;
-                default:
-                    return;
+                case SETTINGS_MODE_SUBTITLE_TRACK:
+                    if (position != mSelectedSubtitleTrackIndex) {
+                        mSelectedSubtitleTrackIndex = position;
+                        if (position > 0) {
+                            Bundle extra = new Bundle();
+                            extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, position - 1);
+                            mController.sendCommand(
+                                    MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, extra, null);
+                            mSubtitleButton.setImageDrawable(
+                                    mResources.getDrawable(R.drawable.ic_subtitle_on, null));
+                            mSubtitleIsEnabled = true;
+                        } else {
+                            mController.sendCommand(
+                                    MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
+                            mSubtitleButton.setImageDrawable(
+                                    mResources.getDrawable(R.drawable.ic_subtitle_off, null));
+
+                            mSubtitleIsEnabled = false;
+                        }
+                    }
+                    mSettingsWindow.dismiss();
+                    break;
+                case SETTINGS_MODE_VIDEO_QUALITY:
+                    // TODO: add support for video quality
+                    mSelectedVideoQualityIndex = position;
+                    mSettingsWindow.dismiss();
+                    break;
             }
-            mSettingsListView.setAdapter(mSettingsAdapter);
-            int itemHeight = mResources.getDimensionPixelSize(
-                    R.dimen.MediaControlView2_settings_height);
-            int totalHeight = mSettingsAdapter.getCount() * itemHeight;
-            int margin = (-1) * mResources.getDimensionPixelSize(
-                    R.dimen.MediaControlView2_settings_offset);
-            mSettingsWindow.dismiss();
-            mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
-                    Gravity.BOTTOM | Gravity.RIGHT);
         }
     };
 
@@ -953,80 +1036,56 @@
     }
 
     private void initializeSettingsLists() {
-        if (mSettingsMainTextsList == null) {
-            mSettingsMainTextsList = new ArrayList<String>();
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_subtitle_text));
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_audio_track_text));
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_video_quality_text));
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_text));
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_help_text));
-        }
+        mSettingsMainTextsList = new ArrayList<String>();
+        mSettingsMainTextsList.add(
+                mResources.getString(R.string.MediaControlView2_audio_track_text));
+        mSettingsMainTextsList.add(
+                mResources.getString(R.string.MediaControlView2_playback_speed_text));
+        mSettingsMainTextsList.add(
+                mResources.getString(R.string.MediaControlView2_help_text));
 
-        // TODO: Update the following code to be dynamic.
-        if (mSettingsSubTextsList == null) {
-            mSettingsSubTextsList = new ArrayList<String>();
-            mSettingsSubTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_subtitle_off_text));
-            mSettingsSubTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_audio_track_none_text));
-            mSettingsSubTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
-            mSettingsSubTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
-            mSettingsSubTextsList.add(RESOURCE_EMPTY);
-        }
+        mSettingsSubTextsList = new ArrayList<String>();
+        mSettingsSubTextsList.add(
+                mResources.getString(R.string.MediaControlView2_audio_track_none_text));
+        mSettingsSubTextsList.add(
+                mResources.getStringArray(
+                        R.array.MediaControlView2_playback_speeds)[PLAYBACK_SPEED_1x_INDEX]);
+        mSettingsSubTextsList.add(RESOURCE_EMPTY);
 
-        if (mSettingsIconIdsList == null) {
-            mSettingsIconIdsList = new ArrayList<Integer>();
-            mSettingsIconIdsList.add(R.drawable.ic_closed_caption_off);
-            mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
-            mSettingsIconIdsList.add(R.drawable.ic_high_quality);
-            mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
-            mSettingsIconIdsList.add(R.drawable.ic_help);
-        }
+        mSettingsIconIdsList = new ArrayList<Integer>();
+        mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
+        mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
+        mSettingsIconIdsList.add(R.drawable.ic_help);
 
-        if (mSubtitleDescriptionsList == null) {
-            mSubtitleDescriptionsList = new ArrayList<String>();
-            mSubtitleDescriptionsList.add(
-                    mResources.getString(R.string.MediaControlView2_subtitle_off_text));
-        }
+        mAudioTrackList = new ArrayList<String>();
+        mAudioTrackList.add(
+                mResources.getString(R.string.MediaControlView2_audio_track_none_text));
 
-        if (mAudioTrackList == null) {
-            mAudioTrackList = new ArrayList<String>();
-            mAudioTrackList.add(
-                    mResources.getString(R.string.MediaControlView2_audio_track_none_text));
-        }
+        mVideoQualityList = new ArrayList<String>();
+        mVideoQualityList.add(
+                mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
 
-        if (mVideoQualityList == null) {
-            mVideoQualityList = new ArrayList<String>();
-            mVideoQualityList.add(
-                    mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
-        }
+        mPlaybackSpeedTextList = new ArrayList<String>(Arrays.asList(
+                mResources.getStringArray(R.array.MediaControlView2_playback_speeds)));
+        // Select the "1x" speed as the default value.
+        mSelectedSpeedIndex = PLAYBACK_SPEED_1x_INDEX;
 
-        if (mPlaybackSpeedTextIdsList == null) {
-            mPlaybackSpeedTextIdsList = new ArrayList<String>();
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_0_25x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_0_5x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_0_75x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_1_25x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_1_5x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_2x_text));
+        mPlaybackSpeedList = new ArrayList<Float>();
+        int[] speeds = mResources.getIntArray(R.array.speed_multiplied_by_100);
+        for (int i = 0; i < speeds.length; i++) {
+            float speed = (float) speeds[i] / 100.0f;
+            mPlaybackSpeedList.add(speed);
         }
     }
 
+    private void displaySettingsWindow(BaseAdapter adapter) {
+        mSettingsListView.setAdapter(adapter);
+        int totalHeight = adapter.getCount() * mSettingsItemHeight;
+        mSettingsWindow.dismiss();
+        mSettingsWindow.showAsDropDown(mInstance, mSettingsWindowMargin,
+                mSettingsWindowMargin - totalHeight, Gravity.BOTTOM | Gravity.RIGHT);
+    }
+
     private class MediaControllerCallback extends MediaController.Callback {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -1065,16 +1124,13 @@
             if (mPlaybackActions != mPlaybackState.getActions()) {
                 long newActions = mPlaybackState.getActions();
                 if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
-                    mPlayPauseButton.clearColorFilter();
-                    mPlayPauseButton.setEnabled(true);
+                    mPlayPauseButton.setVisibility(View.VISIBLE);
                 }
                 if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
-                    mRewButton.clearColorFilter();
-                    mRewButton.setEnabled(true);
+                    mRewButton.setVisibility(View.VISIBLE);
                 }
                 if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
-                    mFfwdButton.clearColorFilter();
-                    mFfwdButton.setEnabled(true);
+                    mFfwdButton.setVisibility(View.VISIBLE);
                 }
                 if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
                     mSeekAvailable = true;
@@ -1122,16 +1178,42 @@
             switch (event) {
                 case EVENT_UPDATE_TRACK_STATUS:
                     mVideoTrackCount = extras.getInt(KEY_VIDEO_TRACK_COUNT);
+                    // If there is one or more audio tracks, and this information has not been
+                    // reflected into the Settings window yet, automatically check the first track.
+                    // Otherwise, the Audio Track selection will be defaulted to "None".
                     mAudioTrackCount = extras.getInt(KEY_AUDIO_TRACK_COUNT);
-                    int newSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
-                    if (newSubtitleTrackCount > 0) {
-                        mSubtitleButton.clearColorFilter();
-                        mSubtitleButton.setEnabled(true);
+                    mAudioTrackList = new ArrayList<String>();
+                    if (mAudioTrackCount > 0) {
+                        // TODO: add more text about track info.
+                        for (int i = 0; i < mAudioTrackCount; i++) {
+                            String track = mResources.getString(
+                                    R.string.MediaControlView2_audio_track_number_text, i + 1);
+                            mAudioTrackList.add(track);
+                        }
+                        // Change sub text inside the Settings window.
+                        mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+                                mAudioTrackList.get(0));
                     } else {
-                        mSubtitleButton.setColorFilter(R.color.gray);
+                        mAudioTrackList.add(mResources.getString(
+                                R.string.MediaControlView2_audio_track_none_text));
+                    }
+
+                    mSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
+                    mSubtitleDescriptionsList = new ArrayList<String>();
+                    if (mSubtitleTrackCount > 0) {
+                        mSubtitleButton.setVisibility(View.VISIBLE);
+                        mSubtitleButton.setEnabled(true);
+                        mSubtitleDescriptionsList.add(mResources.getString(
+                                R.string.MediaControlView2_subtitle_off_text));
+                        for (int i = 0; i < mSubtitleTrackCount; i++) {
+                            String track = mResources.getString(
+                                    R.string.MediaControlView2_subtitle_track_number_text, i + 1);
+                            mSubtitleDescriptionsList.add(track);
+                        }
+                    } else {
+                        mSubtitleButton.setVisibility(View.GONE);
                         mSubtitleButton.setEnabled(false);
                     }
-                    mSubtitleTrackCount = newSubtitleTrackCount;
                     break;
                 case EVENT_UPDATE_MEDIA_TYPE_STATUS:
                     boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
@@ -1145,17 +1227,29 @@
     }
 
     private class SettingsAdapter extends BaseAdapter {
-        List<Integer> mIconIds;
-        List<String> mMainTexts;
-        List<String> mSubTexts;
-        boolean mIsCheckable;
+        private List<Integer> mIconIds;
+        private List<String> mMainTexts;
+        private List<String> mSubTexts;
 
         public SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts,
-                @Nullable List<Integer> iconIds, boolean isCheckable) {
+                @Nullable List<Integer> iconIds) {
             mMainTexts = mainTexts;
             mSubTexts = subTexts;
             mIconIds = iconIds;
-            mIsCheckable = isCheckable;
+        }
+
+        public void updateSubTexts(List<String> subTexts) {
+            mSubTexts = subTexts;
+            notifyDataSetChanged();
+        }
+
+        public String getMainText(int position) {
+            if (mMainTexts != null) {
+                if (position < mMainTexts.size()) {
+                    return mMainTexts.get(position);
+                }
+            }
+            return RESOURCE_EMPTY;
         }
 
         @Override
@@ -1184,7 +1278,6 @@
             TextView mainTextView = (TextView) row.findViewById(R.id.main_text);
             TextView subTextView = (TextView) row.findViewById(R.id.sub_text);
             ImageView iconView = (ImageView) row.findViewById(R.id.icon);
-            ImageView checkView = (ImageView) row.findViewById(R.id.check);
 
             // Set main text
             mainTextView.setText(mMainTexts.get(position));
@@ -1206,13 +1299,72 @@
                 // Otherwise, set main icon.
                 iconView.setImageDrawable(mResources.getDrawable(mIconIds.get(position), null));
             }
+            return row;
+        }
 
-            // Set check icon
-            // TODO: make the following code dynamic
-            if (!mIsCheckable) {
-                checkView.setVisibility(View.GONE);
+        public void setSubTexts(List<String> subTexts) {
+            mSubTexts = subTexts;
+        }
+    }
+
+    // TODO: extend this class from SettingsAdapter
+    private class SubSettingsAdapter extends BaseAdapter {
+        private List<String> mTexts;
+        private int mCheckPosition;
+
+        public SubSettingsAdapter(List<String> texts, int checkPosition) {
+            mTexts = texts;
+            mCheckPosition = checkPosition;
+        }
+
+        public String getMainText(int position) {
+            if (mTexts != null) {
+                if (position < mTexts.size()) {
+                    return mTexts.get(position);
+                }
+            }
+            return RESOURCE_EMPTY;
+        }
+
+        @Override
+        public int getCount() {
+            return (mTexts == null) ? 0 : mTexts.size();
+        }
+
+        @Override
+        public long getItemId(int position) {
+            // Auto-generated method stub--does not have any purpose here
+            // TODO: implement this.
+            return 0;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            // Auto-generated method stub--does not have any purpose here
+            // TODO: implement this.
+            return null;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup container) {
+            View row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+                    R.layout.sub_settings_list_item);
+            TextView textView = (TextView) row.findViewById(R.id.text);
+            ImageView checkView = (ImageView) row.findViewById(R.id.check);
+
+            textView.setText(mTexts.get(position));
+            if (position != mCheckPosition) {
+                checkView.setVisibility(View.INVISIBLE);
             }
             return row;
         }
+
+        public void setTexts(List<String> texts) {
+            mTexts = texts;
+        }
+
+        public void setCheckPosition(int checkPosition) {
+            mCheckPosition = checkPosition;
+        }
     }
 }
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 0bb659d..264f632 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -86,6 +86,7 @@
     private static final int STATE_PLAYBACK_COMPLETED = 5;
 
     private static final int INVALID_TRACK_INDEX = -1;
+    private static final float INVALID_SPEED = 0f;
 
     private AccessibilityManager mAccessibilityManager;
     private AudioManager mAudioManager;
@@ -137,6 +138,9 @@
     // TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams().
     // Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
     private float mFallbackSpeed;  // keep the original speed before 'pause' is called.
+    private float mVolumeLevelFloat;
+    private int mVolumeLevel;
+
     private long mShowControllerIntervalMs;
 
     private MediaRouter mMediaRouter;
@@ -651,7 +655,7 @@
             };
             mMediaPlayer.setMediaPlayer2EventCallback(executor, mMediaPlayer2Callback);
 
-            mCurrentBufferPercentage = 0;
+            mCurrentBufferPercentage = -1;
             mMediaPlayer.setDataSource(dsd);
             mMediaPlayer.setAudioAttributes(mAudioAttributes);
             // we don't set the target state here either, but preserve the
@@ -753,8 +757,12 @@
             && mCurrentState != STATE_PREPARING) {
             // TODO: this should be replaced with MediaPlayer2.getBufferedPosition() once it is
             // implemented.
-            mStateBuilder.setBufferedPosition(
-                    (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+            if (mCurrentBufferPercentage == -1) {
+                mStateBuilder.setBufferedPosition(-1);
+            } else {
+                mStateBuilder.setBufferedPosition(
+                        (long) (mCurrentBufferPercentage / 100.0 * mMediaPlayer.getDuration()));
+            }
         }
 
         // Set PlaybackState for MediaSession
@@ -854,8 +862,6 @@
         }
         if (select) {
             if (mSubtitleTrackIndices.size() > 0) {
-                // TODO: make this selection dynamic
-                mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0);
                 mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
                 mSubtitleView.setVisibility(View.VISIBLE);
             }
@@ -881,9 +887,17 @@
                 mAudioTrackIndices.add(i);
             } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
                     || trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
-                  mSubtitleTrackIndices.add(i);
+                mSubtitleTrackIndices.add(i);
             }
         }
+        // Select first tracks as default
+        if (mVideoTrackIndices.size() > 0) {
+            mSelectedVideoTrackIndex = 0;
+        }
+        if (mAudioTrackIndices.size() > 0) {
+            mSelectedAudioTrackIndex = 0;
+        }
+
         Bundle data = new Bundle();
         data.putInt(MediaControlView2Impl.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
         data.putInt(MediaControlView2Impl.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
@@ -1048,10 +1062,19 @@
             } else {
                 switch (command) {
                     case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
-                        mInstance.setSubtitleEnabled(true);
+                        int subtitleIndex = args.getInt(
+                                MediaControlView2Impl.KEY_SELECTED_SUBTITLE_INDEX,
+                                INVALID_TRACK_INDEX);
+                        if (subtitleIndex != INVALID_TRACK_INDEX) {
+                            int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex);
+                            if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
+                                mSelectedSubtitleTrackIndex = subtitleTrackIndex;
+                                selectOrDeselectSubtitle(true);
+                            }
+                        }
                         break;
                     case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
-                        mInstance.setSubtitleEnabled(false);
+                        selectOrDeselectSubtitle(false);
                         break;
                     case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
                         if (mFullScreenRequestListener != null) {
@@ -1060,6 +1083,32 @@
                                     args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
                         }
                         break;
+                    case MediaControlView2Impl.COMMAND_SELECT_AUDIO_TRACK:
+                        int audioIndex = args.getInt(MediaControlView2Impl.KEY_SELECTED_AUDIO_INDEX,
+                                INVALID_TRACK_INDEX);
+                        if (audioIndex != INVALID_TRACK_INDEX) {
+                            int audioTrackIndex = mAudioTrackIndices.get(audioIndex);
+                            if (audioTrackIndex != mSelectedAudioTrackIndex) {
+                                mSelectedAudioTrackIndex = audioTrackIndex;
+                                mMediaPlayer.selectTrack(mSelectedAudioTrackIndex);
+                            }
+                        }
+                        break;
+                    case MediaControlView2Impl.COMMAND_SET_PLAYBACK_SPEED:
+                        float speed = args.getFloat(
+                                MediaControlView2Impl.KEY_PLAYBACK_SPEED, INVALID_SPEED);
+                        if (speed != INVALID_SPEED && speed != mSpeed) {
+                            mInstance.setSpeed(speed);
+                            mSpeed = speed;
+                        }
+                        break;
+                    case MediaControlView2Impl.COMMAND_MUTE:
+                        mVolumeLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+                        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
+                        break;
+                    case MediaControlView2Impl.COMMAND_UNMUTE:
+                        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeLevel, 0);
+                        break;
                 }
             }
             showController();
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 229e08e..8033382 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -3051,6 +3051,7 @@
 
         // check recording permission for visualizer
         if ((memcmp(&desc.type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0) &&
+            // TODO: Do we need to start/stop op - i.e. is there recording being performed?
             !recordingAllowed(opPackageName, pid, IPCThreadState::self()->getCallingUid())) {
             lStatus = PERMISSION_DENIED;
             goto Exit;
diff --git a/services/audioflinger/ServiceUtilities.cpp b/services/audioflinger/ServiceUtilities.cpp
index f08698e..aa267ea 100644
--- a/services/audioflinger/ServiceUtilities.cpp
+++ b/services/audioflinger/ServiceUtilities.cpp
@@ -30,6 +30,8 @@
 
 namespace android {
 
+static const String16 sAndroidPermissionRecordAudio("android.permission.RECORD_AUDIO");
+
 // Not valid until initialized by AudioFlinger constructor.  It would have to be
 // re-initialized if the process containing AudioFlinger service forks (which it doesn't).
 // This is often used to validate binder interface calls within audioserver
@@ -48,26 +50,11 @@
     }
 }
 
-bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
-    // we're always OK.
-    if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
-
-    static const String16 sRecordAudio("android.permission.RECORD_AUDIO");
-
-    // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
-    // may open a record track on behalf of a client.  Note that pid may be a tid.
-    // IMPORTANT: Don't use PermissionCache - a runtime permission and may change.
-    const bool ok = checkPermission(sRecordAudio, pid, uid);
-    if (!ok) {
-        ALOGE("Request requires android.permission.RECORD_AUDIO");
-        return false;
+static String16 resolveCallingPackage(PermissionController& permissionController,
+        const String16& opPackageName, uid_t uid) {
+    if (opPackageName.size() > 0) {
+        return opPackageName;
     }
-
-    // To permit command-line native tests
-    if (uid == AID_ROOT) return true;
-
-    String16 checkedOpPackageName = opPackageName;
-
     // In some cases the calling code has no access to the package it runs under.
     // For example, code using the wilhelm framework's OpenSL-ES APIs. In this
     // case we will get the packages for the calling UID and pick the first one
@@ -75,40 +62,89 @@
     // as for legacy apps we will toggle the app op for all packages in the UID.
     // The caveat is that the operation may be attributed to the wrong package and
     // stats based on app ops may be slightly off.
-    if (checkedOpPackageName.size() <= 0) {
-        sp<IServiceManager> sm = defaultServiceManager();
-        sp<IBinder> binder = sm->getService(String16("permission"));
-        if (binder == 0) {
-            ALOGE("Cannot get permission service");
-            return false;
-        }
+    Vector<String16> packages;
+    permissionController.getPackagesForUid(uid, packages);
+    if (packages.isEmpty()) {
+        ALOGE("No packages for uid %d", uid);
+        return opPackageName; // empty string
+    }
+    return packages[0];
+}
 
-        sp<IPermissionController> permCtrl = interface_cast<IPermissionController>(binder);
-        Vector<String16> packages;
+static inline bool isAudioServerOrRoot(uid_t uid) {
+    // AID_ROOT is OK for command-line tests.  Native unforked audioserver always OK.
+    return uid == AID_ROOT || uid == AID_AUDIOSERVER ;
+}
 
-        permCtrl->getPackagesForUid(uid, packages);
+static bool checkRecordingInternal(const String16& opPackageName, pid_t pid,
+        uid_t uid, bool start) {
+    // Okay to not track in app ops as audio server is us and if
+    // device is rooted security model is considered compromised.
+    if (isAudioServerOrRoot(uid)) return true;
 
-        if (packages.isEmpty()) {
-            ALOGE("No packages for calling UID");
-            return false;
-        }
-        checkedOpPackageName = packages[0];
+    // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
+    // may open a record track on behalf of a client.  Note that pid may be a tid.
+    // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
+    PermissionController permissionController;
+    const bool ok = permissionController.checkPermission(sAndroidPermissionRecordAudio, pid, uid);
+    if (!ok) {
+        ALOGE("Request requires %s", String8(sAndroidPermissionRecordAudio).c_str());
+        return false;
+    }
+
+    String16 resolvedOpPackageName = resolveCallingPackage(
+            permissionController, opPackageName, uid);
+    if (resolvedOpPackageName.size() == 0) {
+        return false;
     }
 
     AppOpsManager appOps;
-    if (appOps.noteOp(AppOpsManager::OP_RECORD_AUDIO, uid, checkedOpPackageName)
-            != AppOpsManager::MODE_ALLOWED) {
-        ALOGE("Request denied by app op OP_RECORD_AUDIO");
-        return false;
+    const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+    if (start) {
+        if (appOps.startOpNoThrow(op, uid, resolvedOpPackageName, /*startIfModeDefault*/ false)
+                != AppOpsManager::MODE_ALLOWED) {
+            ALOGE("Request denied by app op: %d", op);
+            return false;
+        }
+    } else {
+        if (appOps.noteOp(op, uid, resolvedOpPackageName) != AppOpsManager::MODE_ALLOWED) {
+            ALOGE("Request denied by app op: %d", op);
+            return false;
+        }
     }
 
     return true;
 }
 
+bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
+    return checkRecordingInternal(opPackageName, pid, uid, /*start*/ false);
+}
+
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid) {
+     return checkRecordingInternal(opPackageName, pid, uid, /*start*/ true);
+}
+
+void finishRecording(const String16& opPackageName, uid_t uid) {
+    // Okay to not track in app ops as audio server is us and if
+    // device is rooted security model is considered compromised.
+    if (isAudioServerOrRoot(uid)) return;
+
+    PermissionController permissionController;
+    String16 resolvedOpPackageName = resolveCallingPackage(
+            permissionController, opPackageName, uid);
+    if (resolvedOpPackageName.size() == 0) {
+        return;
+    }
+
+    AppOpsManager appOps;
+    const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+    appOps.finishOp(op, uid, resolvedOpPackageName);
+}
+
 bool captureAudioOutputAllowed(pid_t pid, uid_t uid) {
     if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
     static const String16 sCaptureAudioOutput("android.permission.CAPTURE_AUDIO_OUTPUT");
-    bool ok = checkPermission(sCaptureAudioOutput, pid, uid);
+    bool ok = PermissionCache::checkPermission(sCaptureAudioOutput, pid, uid);
     if (!ok) ALOGE("Request requires android.permission.CAPTURE_AUDIO_OUTPUT");
     return ok;
 }
@@ -155,7 +191,7 @@
 
 bool modifyPhoneStateAllowed(pid_t pid, uid_t uid) {
     static const String16 sModifyPhoneState("android.permission.MODIFY_PHONE_STATE");
-    bool ok = checkPermission(sModifyPhoneState, pid, uid);
+    bool ok = PermissionCache::checkPermission(sModifyPhoneState, pid, uid);
     if (!ok) ALOGE("Request requires android.permission.MODIFY_PHONE_STATE");
     return ok;
 }
diff --git a/services/audioflinger/ServiceUtilities.h b/services/audioflinger/ServiceUtilities.h
index 83533dd..f45ada1 100644
--- a/services/audioflinger/ServiceUtilities.h
+++ b/services/audioflinger/ServiceUtilities.h
@@ -16,11 +16,15 @@
 
 #include <unistd.h>
 
+#include <binder/PermissionController.h>
+
 namespace android {
 
 extern pid_t getpid_cached;
 bool isTrustedCallingUid(uid_t uid);
 bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid);
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid);
+void finishRecording(const String16& opPackageName, uid_t uid);
 bool captureAudioOutputAllowed(pid_t pid, uid_t uid);
 bool captureHotwordAllowed(pid_t pid, uid_t uid);
 bool settingsAllowed();
diff --git a/services/audiopolicy/common/include/Volume.h b/services/audiopolicy/common/include/Volume.h
index 1239fe0..4862684 100644
--- a/services/audiopolicy/common/include/Volume.h
+++ b/services/audiopolicy/common/include/Volume.h
@@ -125,6 +125,7 @@
         case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP:
         case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
         case AUDIO_DEVICE_OUT_USB_HEADSET:
+        case AUDIO_DEVICE_OUT_HEARING_AID:
             return DEVICE_CATEGORY_HEADSET;
         case AUDIO_DEVICE_OUT_LINE:
         case AUDIO_DEVICE_OUT_AUX_DIGITAL:
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 78a184b..5e5d38b 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -203,6 +203,16 @@
      */
     audio_io_handle_t getA2dpOutput() const;
 
+    /**
+     * returns true if primary HAL supports A2DP Offload
+     */
+    bool isA2dpOffloadedOnPrimary() const;
+
+    /**
+     * returns true if A2DP is supported (either via hardware offload or software encoding)
+     */
+    bool isA2dpSupported() const;
+
     sp<SwAudioOutputDescriptor> getOutputFromId(audio_port_handle_t id) const;
 
     sp<SwAudioOutputDescriptor> getPrimaryOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index caaa0f7..294a2a6 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -645,6 +645,29 @@
     return 0;
 }
 
+bool SwAudioOutputCollection::isA2dpOffloadedOnPrimary() const
+{
+    sp<SwAudioOutputDescriptor> primaryOutput = getPrimaryOutput();
+
+    if ((primaryOutput != NULL) && (primaryOutput->mProfile != NULL)
+        && (primaryOutput->mProfile->mModule != NULL)) {
+        sp<HwModule> primaryHwModule = primaryOutput->mProfile->mModule;
+        Vector <sp<IOProfile>> primaryHwModuleOutputProfiles =
+                                   primaryHwModule->getOutputProfiles();
+        for (size_t i = 0; i < primaryHwModuleOutputProfiles.size(); i++) {
+            if (primaryHwModuleOutputProfiles[i]->supportDevice(AUDIO_DEVICE_OUT_ALL_A2DP)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+bool SwAudioOutputCollection::isA2dpSupported() const
+{
+    return (isA2dpOffloadedOnPrimary() || (getA2dpOutput() != 0));
+}
+
 sp<SwAudioOutputDescriptor> SwAudioOutputCollection::getPrimaryOutput() const
 {
     for (size_t i = 0; i < size(); i++) {
diff --git a/services/audiopolicy/config/audio_policy_configuration.xml b/services/audiopolicy/config/audio_policy_configuration.xml
index 73efe8e..a75f1cb 100644
--- a/services/audiopolicy/config/audio_policy_configuration.xml
+++ b/services/audiopolicy/config/audio_policy_configuration.xml
@@ -182,6 +182,9 @@
         <!-- Remote Submix Audio HAL -->
         <xi:include href="r_submix_audio_policy_configuration.xml"/>
 
+        <!-- Hearing aid Audio HAL -->
+        <xi:include href="hearing_aid_audio_policy_configuration.xml"/>
+
     </modules>
     <!-- End of Modules section -->
 
diff --git a/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
new file mode 100644
index 0000000..3c48e88
--- /dev/null
+++ b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Hearing aid Audio HAL Audio Policy Configuration file -->
+<module name="hearing_aid" halVersion="2.0">
+    <mixPorts>
+        <mixPort name="hearing aid output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
+            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                     samplingRates="24000,16000"
+                     channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+        </mixPort>
+    </mixPorts>
+    <devicePorts>
+        <devicePort tagName="BT Hearing Aid Out" type="AUDIO_DEVICE_OUT_HEARING_AID" role="sink"/>
+    </devicePorts>
+    <routes>
+        <route type="mix" sink="BT Hearing Aid Out" sources="hearing aid output"/>
+    </routes>
+</module>
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 5ec0475..977a396 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -306,8 +306,14 @@
             sp<AudioOutputDescriptor> primaryOutput = outputs.getPrimaryOutput();
             audio_devices_t availPrimaryInputDevices =
                  availableInputDevices.getDevicesFromHwModule(primaryOutput->getModuleHandle());
+
+            // TODO: getPrimaryOutput return only devices from first module in
+            // audio_policy_configuration.xml, hearing aid is not there, but it's
+            // a primary device
+            // FIXME: this is not the right way of solving this problem
             audio_devices_t availPrimaryOutputDevices =
-                    primaryOutput->supportedDevices() & availableOutputDevices.types();
+                (primaryOutput->supportedDevices() | AUDIO_DEVICE_OUT_HEARING_AID) &
+                availableOutputDevices.types();
 
             if (((availableInputDevices.types() &
                     AUDIO_DEVICE_IN_TELEPHONY_RX & ~AUDIO_DEVICE_BIT_IN) == 0) ||
@@ -332,10 +338,12 @@
             // FALL THROUGH
 
         default:    // FORCE_NONE
+            device = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+            if (device) break;
             // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
             if (!isInCall() &&
                     (mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
-                    (outputs.getA2dpOutput() != 0)) {
+                     outputs.isA2dpSupported()) {
                 device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
                 if (device) break;
                 device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
@@ -369,7 +377,7 @@
             // A2DP speaker when forcing to speaker output
             if (!isInCall() &&
                     (mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
-                    (outputs.getA2dpOutput() != 0)) {
+                     outputs.isA2dpSupported()) {
                 device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
                 if (device) break;
             }
@@ -481,9 +489,12 @@
                     outputDeviceTypesToIgnore);
             break;
         }
+        if (device2 == AUDIO_DEVICE_NONE) {
+            device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+        }
         if ((device2 == AUDIO_DEVICE_NONE) &&
                 (mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
-                (outputs.getA2dpOutput() != 0)) {
+                 outputs.isA2dpSupported()) {
             device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
             if (device2 == AUDIO_DEVICE_NONE) {
                 device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 42b199a..74ca902 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -26,6 +26,7 @@
 
 #define AUDIO_POLICY_XML_CONFIG_FILE_PATH_MAX_LENGTH 128
 #define AUDIO_POLICY_XML_CONFIG_FILE_NAME "audio_policy_configuration.xml"
+#define AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME "audio_policy_a2dp_offload_configuration.xml"
 
 #include <inttypes.h>
 #include <math.h>
@@ -3517,11 +3518,14 @@
 
     for (int i = 0; i < kConfigLocationListSize; i++) {
         PolicySerializer serializer;
+        bool use_a2dp_offload_config =
+                 property_get_bool("persist.bluetooth.a2dp_offload.enable", false);
         snprintf(audioPolicyXmlConfigFile,
                  sizeof(audioPolicyXmlConfigFile),
                  "%s/%s",
                  kConfigLocationList[i],
-                 AUDIO_POLICY_XML_CONFIG_FILE_NAME);
+                 use_a2dp_offload_config ? AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME :
+                     AUDIO_POLICY_XML_CONFIG_FILE_NAME);
         ret = serializer.deserialize(audioPolicyXmlConfigFile, config);
         if (ret == NO_ERROR) {
             break;
@@ -4381,7 +4385,7 @@
 void AudioPolicyManager::checkA2dpSuspend()
 {
     audio_io_handle_t a2dpOutput = mOutputs.getA2dpOutput();
-    if (a2dpOutput == 0) {
+    if (a2dpOutput == 0 || mOutputs.isA2dpOffloadedOnPrimary()) {
         mA2dpSuspended = false;
         return;
     }
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 306de3f..8f0c846 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -412,7 +412,7 @@
     }
 
     // check calling permissions
-    if (!recordingAllowed(client->opPackageName, client->pid, client->uid)) {
+    if (!startRecording(client->opPackageName, client->pid, client->uid)) {
         ALOGE("%s permission denied: recording not allowed for uid %d pid %d",
                 __func__, client->uid, client->pid);
         return PERMISSION_DENIED;
@@ -439,6 +439,8 @@
         if (concurrency & AudioPolicyInterface::API_INPUT_CONCURRENCY_CAPTURE) {
             //TODO: check concurrent capture permission
         }
+    } else {
+        finishRecording(client->opPackageName, client->uid);
     }
 
     return status;
@@ -457,6 +459,9 @@
     }
     sp<AudioRecordClient> client = mAudioRecordClients.valueAt(index);
 
+    // finish the recording app op
+    finishRecording(client->opPackageName, client->uid);
+
     return mAudioPolicyManager->stopInput(client->input, client->session);
 }
 
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index 9ab2d88..c49de8e 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1002,10 +1002,6 @@
         case HAL_PIXEL_FORMAT_BLOB:
         case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
         case HAL_PIXEL_FORMAT_YCbCr_420_888:
-        case HAL_PIXEL_FORMAT_YCbCr_422_888:
-        case HAL_PIXEL_FORMAT_YCbCr_444_888:
-        case HAL_PIXEL_FORMAT_FLEX_RGB_888:
-        case HAL_PIXEL_FORMAT_FLEX_RGBA_8888:
         case HAL_PIXEL_FORMAT_YCbCr_422_SP:
         case HAL_PIXEL_FORMAT_YCrCb_420_SP:
         case HAL_PIXEL_FORMAT_YCbCr_422_I:
diff --git a/services/mediacodec/seccomp_policy/mediacodec-x86.policy b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
index 1553ec7..bbbe552 100644
--- a/services/mediacodec/seccomp_policy/mediacodec-x86.policy
+++ b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
@@ -16,12 +16,14 @@
 mprotect: 1
 prctl: 1
 openat: 1
+open: 1
 getuid32: 1
 writev: 1
 ioctl: 1
 close: 1
 mmap2: 1
 fstat64: 1
+stat64: 1
 madvise: 1
 fstatat64: 1
 futex: 1