Merge "NdkMediaExtractor: add disconnect API" 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/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp
index cef5f4a..c082fc1 100644
--- a/media/extractors/mp4/ItemTable.cpp
+++ b/media/extractors/mp4/ItemTable.cpp
@@ -982,26 +982,24 @@
 
 bool InfeBox::parseNullTerminatedString(
         off64_t *offset, size_t *size, String8 *out) {
-    char tmp[256];
-    size_t len = 0;
+    char tmp;
+    Vector<char> buf;
+    buf.setCapacity(256);
     off64_t newOffset = *offset;
     off64_t stopOffset = *offset + *size;
     while (newOffset < stopOffset) {
-        if (!source()->readAt(newOffset++, &tmp[len], 1)) {
+        if (!source()->readAt(newOffset++, &tmp, 1)) {
             return false;
         }
-        if (tmp[len] == 0) {
-            out->append(tmp, len);
+        buf.push_back(tmp);
+        if (tmp == 0) {
+            out->setTo(buf.array());
 
             *offset = newOffset;
             *size = stopOffset - newOffset;
 
             return true;
         }
-        if (++len >= sizeof(tmp)) {
-            out->append(tmp, len);
-            len = 0;
-        }
     }
     return false;
 }
@@ -1060,9 +1058,12 @@
                 return ERROR_MALFORMED;
             }
 
-            String8 content_encoding;
-            if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
-                return ERROR_MALFORMED;
+            // content_encoding is optional; can be omitted if would be empty
+            if (size > 0) {
+                String8 content_encoding;
+                if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
+                    return ERROR_MALFORMED;
+                }
             }
         } else if (item_type == FOURCC('u', 'r', 'i', ' ')) {
             String8 item_uri_type;
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/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/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index 2dfbdca..63130c4 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -140,6 +140,11 @@
     // have been caught above.
     CHECK(offset >= mCachedOffset);
 
+    off64_t resultOffset;
+    if (__builtin_add_overflow(offset, size, &resultOffset)) {
+        return ERROR_IO;
+    }
+
     if (size == 0) {
         return 0;
     }
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/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 9ab9aae..30c0b1c 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -89,6 +89,13 @@
 static const char *kRecorderVideoTimescale = "android.media.mediarecorder.video-timescale";
 static const char *kRecorderWidth = "android.media.mediarecorder.width";
 
+// new fields, not yet frozen in the public Java API definitions
+static const char *kRecorderAudioMime = "android.media.mediarecorder.audio.mime";
+static const char *kRecorderVideoMime = "android.media.mediarecorder.video.mime";
+static const char *kRecorderDurationMs = "android.media.mediarecorder.durationMs";
+static const char *kRecorderPaused = "android.media.mediarecorder.pausedMs";
+static const char *kRecorderNumPauses = "android.media.mediarecorder.NPauses";
+
 
 // To collect the encoder usage for the battery app
 static void addBatteryData(uint32_t params) {
@@ -126,21 +133,18 @@
     }
 
     // log the current record, provided it has some information worth recording
-    if (mAnalyticsDirty && mAnalyticsItem != NULL) {
-        updateMetrics();
-        if (mAnalyticsItem->count() > 0) {
-            mAnalyticsItem->selfrecord();
-        }
-        delete mAnalyticsItem;
-        mAnalyticsItem = NULL;
-    }
+    // NB: this also reclaims & clears mAnalyticsItem.
+    flushAndResetMetrics(false);
 }
 
 void StagefrightRecorder::updateMetrics() {
     ALOGV("updateMetrics");
 
-    // we'll populate the values from the raw fields.
-    // (NOT going to populate as we go through the various set* ops)
+    // we run as part of the media player service; what we really want to
+    // know is the app which requested the recording.
+    mAnalyticsItem->setUid(mClientUid);
+
+    // populate the values from the raw fields.
 
     // TBD mOutputFormat  = OUTPUT_FORMAT_THREE_GPP;
     // TBD mAudioEncoder  = AUDIO_ENCODER_AMR_NB;
@@ -168,7 +172,6 @@
     // TBD mTrackEveryTimeDurationUs = 0;
     mAnalyticsItem->setInt32(kRecorderCaptureFpsEnable, mCaptureFpsEnable);
     mAnalyticsItem->setDouble(kRecorderCaptureFps, mCaptureFps);
-    // TBD mCaptureFps = -1.0;
     // TBD mCameraSourceTimeLapse = NULL;
     // TBD mMetaDataStoredInVideoBuffers = kMetadataBufferTypeInvalid;
     // TBD mEncoderProfiles = MediaProfiles::getInstance();
@@ -177,14 +180,17 @@
     // PII mLongitudex10000 = -3600000;
     // TBD mTotalBitRate = 0;
 
-    // TBD: some duration information (capture, paused)
-    //
-
+    // duration information (recorded, paused, # of pauses)
+    mAnalyticsItem->setInt64(kRecorderDurationMs, (mDurationRecordedUs+500)/1000 );
+    if (mNPauses != 0) {
+        mAnalyticsItem->setInt64(kRecorderPaused, (mDurationPausedUs+500)/1000 );
+        mAnalyticsItem->setInt32(kRecorderNumPauses, mNPauses);
+    }
 }
 
-void StagefrightRecorder::resetMetrics() {
-    ALOGV("resetMetrics");
-    // flush anything we have, restart the record
+void StagefrightRecorder::flushAndResetMetrics(bool reinitialize) {
+    ALOGV("flushAndResetMetrics");
+    // flush anything we have, maybe setup a new record
     if (mAnalyticsDirty && mAnalyticsItem != NULL) {
         updateMetrics();
         if (mAnalyticsItem->count() > 0) {
@@ -193,8 +199,10 @@
         delete mAnalyticsItem;
         mAnalyticsItem = NULL;
     }
-    mAnalyticsItem = new MediaAnalyticsItem(kKeyRecorder);
     mAnalyticsDirty = false;
+    if (reinitialize) {
+        mAnalyticsItem = new MediaAnalyticsItem(kKeyRecorder);
+    }
 }
 
 status_t StagefrightRecorder::init() {
@@ -1030,6 +1038,8 @@
         mAnalyticsDirty = true;
         mStarted = true;
 
+        mStartedRecordingUs = systemTime() / 1000;
+
         uint32_t params = IMediaPlayerService::kBatteryDataCodecStarted;
         if (mAudioSource != AUDIO_SOURCE_CNT) {
             params |= IMediaPlayerService::kBatteryDataTrackAudio;
@@ -1109,6 +1119,14 @@
             return NULL;
     }
 
+    // log audio mime type for media metrics
+    if (mAnalyticsItem != NULL) {
+        AString audiomime;
+        if (format->findString("mime", &audiomime)) {
+            mAnalyticsItem->setCString(kRecorderAudioMime, audiomime.c_str());
+        }
+    }
+
     int32_t maxInputSize;
     CHECK(audioSource->getFormat()->findInt32(
                 kKeyMaxInputSize, &maxInputSize));
@@ -1655,6 +1673,14 @@
             break;
     }
 
+    // log video mime type for media metrics
+    if (mAnalyticsItem != NULL) {
+        AString videomime;
+        if (format->findString("mime", &videomime)) {
+            mAnalyticsItem->setCString(kRecorderVideoMime, videomime.c_str());
+        }
+    }
+
     if (cameraSource != NULL) {
         sp<MetaData> meta = cameraSource->getFormat();
 
@@ -1917,6 +1943,13 @@
     sp<MetaData> meta = new MetaData;
     meta->setInt64(kKeyTime, mPauseStartTimeUs);
 
+    if (mStartedRecordingUs != 0) {
+        // should always be true
+        int64_t recordingUs = mPauseStartTimeUs - mStartedRecordingUs;
+        mDurationRecordedUs += recordingUs;
+        mStartedRecordingUs = 0;
+    }
+
     if (mAudioEncoderSource != NULL) {
         mAudioEncoderSource->pause();
     }
@@ -1975,6 +2008,16 @@
         source->setInputBufferTimeOffset((int64_t)timeOffset);
         source->start(meta.get());
     }
+
+
+    // sum info on pause duration
+    // (ignore the 30msec of overlap adjustment factored into mTotalPausedDurationUs)
+    int64_t pausedUs = resumeStartTimeUs - mPauseStartTimeUs;
+    mDurationPausedUs += pausedUs;
+    mNPauses++;
+    // and a timestamp marking that we're back to recording....
+    mStartedRecordingUs = resumeStartTimeUs;
+
     mPauseStartTimeUs = 0;
 
     return OK;
@@ -2003,10 +2046,28 @@
         mWriter.clear();
     }
 
-    resetMetrics();
+    // account for the last 'segment' -- whether paused or recording
+    if (mPauseStartTimeUs != 0) {
+        // we were paused
+        int64_t additive = stopTimeUs - mPauseStartTimeUs;
+        mDurationPausedUs += additive;
+        mNPauses++;
+    } else if (mStartedRecordingUs != 0) {
+        // we were recording
+        int64_t additive = stopTimeUs - mStartedRecordingUs;
+        mDurationRecordedUs += additive;
+    } else {
+        ALOGW("stop while neither recording nor paused");
+    }
 
+    flushAndResetMetrics(true);
+
+    mDurationRecordedUs = 0;
+    mDurationPausedUs = 0;
+    mNPauses = 0;
     mTotalPausedDurationUs = 0;
     mPauseStartTimeUs = 0;
+    mStartedRecordingUs = 0;
 
     mGraphicBufferProducer.clear();
     mPersistentSurface.clear();
@@ -2085,6 +2146,12 @@
     mLongitudex10000 = -3600000;
     mTotalBitRate = 0;
 
+    // tracking how long we recorded.
+    mDurationRecordedUs = 0;
+    mStartedRecordingUs = 0;
+    mDurationPausedUs = 0;
+    mNPauses = 0;
+
     mOutputFd = -1;
 
     return OK;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 18c9256..faa2e59 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -95,7 +95,7 @@
 
     MediaAnalyticsItem *mAnalyticsItem;
     bool mAnalyticsDirty;
-    void resetMetrics();
+    void flushAndResetMetrics(bool reinitialize);
     void updateMetrics();
 
     audio_source_t mAudioSource;
@@ -127,6 +127,11 @@
     int32_t mStartTimeOffsetMs;
     int32_t mTotalBitRate;
 
+    int64_t mDurationRecordedUs;
+    int64_t mStartedRecordingUs;
+    int64_t mDurationPausedUs;
+    int32_t mNPauses;
+
     bool mCaptureFpsEnable;
     double mCaptureFps;
     int64_t mTimeBetweenCaptureUs;
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/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/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/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/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/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/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..8c6cfda 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
@@ -628,26 +714,32 @@
 
     @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 +748,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 +821,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 +881,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..6a53b05 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;
@@ -248,11 +247,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 +495,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 +526,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 +539,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 +555,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 +675,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 +687,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 +715,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 +750,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 +799,10 @@
         return mPlayer;
     }
 
+    MediaPlaylistAgent getPlaylistAgent() {
+        return mPlaylistAgent;
+    }
+
     Executor getCallbackExecutor() {
         return mCallbackExecutor;
     }
@@ -803,26 +838,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 +909,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
@@ -1068,10 +1147,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 +1226,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..379e49a 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;
@@ -85,7 +86,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 +114,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 +125,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 +148,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 +171,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 +191,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);
@@ -212,7 +213,7 @@
     // 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 +257,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 +295,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,18 +321,26 @@
     }
 
     @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);
         }
+        final MediaSession2Impl session = getSession();
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            session.getCallback().onDisconnected(session.getInstance(), controller);
+        });
     }
 
     @Override
-    public void setVolumeTo(final IMediaSession2Callback caller, final int value, final int flags)
+    public void setVolumeTo(final IMediaController2 caller, final int value, final int flags)
             throws RuntimeException {
         final MediaSession2Impl session = getSession();
         final ControllerInfo controller = getControllerIfAble(
@@ -364,7 +376,7 @@
     }
 
     @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(
@@ -400,7 +412,7 @@
     }
 
     @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);
@@ -474,7 +486,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,7 +512,7 @@
     }
 
     @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(
@@ -523,7 +535,7 @@
     }
 
     @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(
@@ -546,7 +558,7 @@
     }
 
     @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(
@@ -569,7 +581,7 @@
     }
 
     @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(
@@ -591,7 +603,7 @@
     }
 
     @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(
@@ -614,7 +626,7 @@
     }
 
     @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(
@@ -636,36 +648,206 @@
     }
 
     @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 MediaSession2Impl session = getSession();
         final ControllerInfo controller = getControllerIfAble(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+        if (session == null || controller == null) {
+            return;
+        }
+        if (mediaId == null) {
+            Log.w(TAG, "setRating(): Ignoring null mediaId from " + controller);
+            return;
+        }
+        if (ratingBundle == null) {
+            Log.w(TAG, "setRating(): Ignoring null ratingBundle from " + controller);
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller) == null) {
+                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) {
+            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) {
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST);
+        if (session == null || controller == null) {
+            return;
+        }
+        if (playlist == null) {
+            Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST) == null) {
+                return;
+            }
+            Command command = new Command(session.getContext(),
+                    MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
+            if (!accepted) {
+                // Don't run rejected command.
+                if (DEBUG) {
+                    Log.d(TAG, "setPlaylist() from " + controller + " was rejected");
+                }
+                return;
+            }
+            List<MediaItem2> list = new ArrayList<>();
+            for (int i = 0; i < playlist.size(); i++) {
+                MediaItem2 item = MediaItem2.fromBundle(session.getContext(), playlist.get(i));
+                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) {
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA) == null) {
+                return;
+            }
+            Command command = new Command(session.getContext(),
+                    MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
+            if (!accepted) {
+                // Don't run rejected command.
+                if (DEBUG) {
+                    Log.d(TAG, "setPlaylist() from " + controller + " was rejected");
+                }
+                return;
+            }
+            session.getInstance().updatePlaylistMetadata(
+                    MediaMetadata2.fromBundle(session.getContext(), metadata));
+        });
+    }
+
+    @Override
+    public void addPlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM) == null) {
+                return;
+            }
+            Command command = new Command(session.getContext(),
+                    MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
+            if (!accepted) {
+                // Don't run rejected command.
+                if (DEBUG) {
+                    Log.d(TAG, "addPlaylistItem() from " + controller + " was rejected");
+                }
+                return;
+            }
+            // 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) {
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM) == null) {
+                return;
+            }
+            Command command = new Command(session.getContext(),
+                    MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
+            if (!accepted) {
+                // Don't run rejected command.
+                if (DEBUG) {
+                    Log.d(TAG, "removePlaylistItem() from " + controller + " was rejected");
+                }
+                return;
+            }
+            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) {
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM) == null) {
+                return;
+            }
+            Command command = new Command(session.getContext(),
+                    MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
+            if (!accepted) {
+                // Don't run rejected command.
+                if (DEBUG) {
+                    Log.d(TAG, "replacePlaylistItem() from " + controller + " was rejected");
+                }
+                return;
+            }
+            // 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(
@@ -691,7 +873,7 @@
     }
 
     @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) {
@@ -721,7 +903,7 @@
     }
 
     @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) {
@@ -769,7 +951,7 @@
     }
 
     @Override
-    public void search(IMediaSession2Callback caller, String query, Bundle extras) {
+    public void search(IMediaController2 caller, String query, Bundle extras) {
         final MediaLibrarySessionImpl session = getLibrarySession();
         final ControllerInfo controller = getControllerIfAble(
                 caller, MediaSession2.COMMAND_CODE_BROWSER);
@@ -789,7 +971,7 @@
     }
 
     @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(
@@ -835,7 +1017,7 @@
     }
 
     @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(
@@ -865,7 +1047,7 @@
     }
 
     @Override
-    public void unsubscribe(final IMediaSession2Callback caller, final String parentId) {
+    public void unsubscribe(final IMediaController2 caller, final String parentId) {
         final MediaLibrarySessionImpl session = getLibrarySession();
         final ControllerInfo controller = getControllerIfAble(
                 caller, MediaSession2.COMMAND_CODE_BROWSER);
@@ -903,16 +1085,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 +1150,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 +1169,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 +1231,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 +1247,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 +1265,7 @@
         synchronized (mLock) {
             mAllowedCommandGroupMap.put(controller, commands);
         }
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
         if (controllerBinder == null) {
             return;
         }
@@ -1038,7 +1301,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 +1320,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 +1354,7 @@
                 return;
             }
         }
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
         if (controller == null) {
             return;
         }
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/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index 3578bba..65faac9 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -779,7 +779,35 @@
     int lastJpegStreamId = mJpegProcessor->getStreamId();
     // If jpeg stream will slow down preview, make sure we remove it before starting preview
     if (params.slowJpegMode) {
-        mJpegProcessor->deleteStream();
+        // Pause preview if we are streaming
+        int32_t activeRequestId = mStreamingProcessor->getActiveRequestId();
+        if (activeRequestId != 0) {
+            res = mStreamingProcessor->togglePauseStream(/*pause*/true);
+            if (res != OK) {
+                ALOGE("%s: Camera %d: Can't pause streaming: %s (%d)",
+                        __FUNCTION__, mCameraId, strerror(-res), res);
+            }
+            res = mDevice->waitUntilDrained();
+            if (res != OK) {
+                ALOGE("%s: Camera %d: Waiting to stop streaming failed: %s (%d)",
+                        __FUNCTION__, mCameraId, strerror(-res), res);
+            }
+        }
+
+        res = mJpegProcessor->deleteStream();
+
+        if (res != OK) {
+            ALOGE("%s: Camera %d: delete Jpeg stream failed: %s (%d)",
+                    __FUNCTION__, mCameraId,  strerror(-res), res);
+        }
+
+        if (activeRequestId != 0) {
+            res = mStreamingProcessor->togglePauseStream(/*pause*/false);
+            if (res != OK) {
+                ALOGE("%s: Camera %d: Can't unpause streaming: %s (%d)",
+                        __FUNCTION__, mCameraId, strerror(-res), res);
+            }
+        }
     } else {
         res = updateProcessorStream(mJpegProcessor, params);
         if (res != OK) {
diff --git a/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp b/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
index cc4249f..b7020fe 100755
--- a/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
+++ b/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
@@ -199,7 +199,11 @@
             return INVALID_OPERATION;
         }
 
-        device->deleteStream(mCaptureStreamId);
+        status_t res = device->deleteStream(mCaptureStreamId);
+        if (res != OK) {
+            ALOGE("%s: delete stream %d failed!", __FUNCTION__, mCaptureStreamId);
+            return res;
+        }
 
         mCaptureHeap.clear();
         mCaptureWindow.clear();
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/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 42bcb2d..67b5e06 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -1550,7 +1550,7 @@
     // CameraDevice semantics require device to already be idle before
     // deleteStream is called, unlike for createStream.
     if (mStatus == STATUS_ACTIVE) {
-        ALOGV("%s: Camera %s: Device not idle", __FUNCTION__, mId.string());
+        ALOGW("%s: Camera %s: Device not idle", __FUNCTION__, mId.string());
         return -EBUSY;
     }
 
diff --git a/services/codec2/Android.bp b/services/codec2/Android.bp
new file mode 100644
index 0000000..4cfca1d
--- /dev/null
+++ b/services/codec2/Android.bp
@@ -0,0 +1,101 @@
+cc_binary {
+    name: "vendor.google.media.c2@1.0-service",
+    defaults: ["hidl_defaults"],
+    soc_specific: true,
+    relative_install_path: "hw",
+    srcs: [
+        "vendor.cpp",
+    ],
+
+    init_rc: ["vendor.google.media.c2@1.0-service.rc"],
+
+    shared_libs: [
+        "vendor.google.media.c2@1.0",
+        "libavservices_minijail_vendor",
+        "libhidlbase",
+        "libhidltransport",
+        "libhwbinder",
+        "liblog",
+        "libstagefright_codec2_hidl@1.0",
+        "libstagefright_codec2_vndk",
+        "libutils",
+    ],
+
+    arch: {
+        arm: {
+            required: ["codec2.vendor.base.policy"],
+        },
+        x86: {
+            required: ["codec2.vendor.base.policy"],
+        },
+    },
+
+    compile_multilib: "32",
+}
+
+cc_binary {
+    name: "vendor.google.media.c2@1.0-service-system",
+    defaults: ["hidl_defaults"],
+    relative_install_path: "hw",
+    srcs: [
+        "system.cpp",
+    ],
+
+    init_rc: ["vendor.google.media.c2@1.0-service-system.rc"],
+
+    shared_libs: [
+        "vendor.google.media.c2@1.0",
+        "libavservices_minijail",
+        "libcutils",
+        "libhidlbase",
+        "libhidltransport",
+        "libhwbinder",
+        "liblog",
+        "libstagefright_codec2_hidl@1.0",
+        "libstagefright_codec2_vndk",
+        "libutils",
+        "libv4l2_c2componentstore",
+    ],
+
+    arch: {
+        arm: {
+            required: ["codec2.system.base.policy"],
+        },
+        x86: {
+            required: ["codec2.system.base.policy"],
+        },
+    },
+
+    required: [
+        "libstagefright_soft_c2avcdec",
+        "libstagefright_soft_c2avcenc",
+        "libstagefright_soft_c2aacdec",
+        "libstagefright_soft_c2aacenc",
+        "libstagefright_soft_c2amrnbdec",
+       	"libstagefright_soft_c2amrnbenc",
+       	"libstagefright_soft_c2amrwbdec",
+       	"libstagefright_soft_c2amrwbenc",
+       	"libstagefright_soft_c2hevcdec",
+       	"libstagefright_soft_c2g711alawdec",
+       	"libstagefright_soft_c2g711mlawdec",
+       	"libstagefright_soft_c2mpeg2dec",
+       	"libstagefright_soft_c2h263dec",
+       	"libstagefright_soft_c2h263enc",
+       	"libstagefright_soft_c2mpeg4dec",
+       	"libstagefright_soft_c2mpeg4enc",
+       	"libstagefright_soft_c2mp3dec",
+       	"libstagefright_soft_c2vorbisdec",
+       	"libstagefright_soft_c2opusdec",
+       	"libstagefright_soft_c2vp8dec",
+       	"libstagefright_soft_c2vp9dec",
+       	"libstagefright_soft_c2vp8enc",
+       	"libstagefright_soft_c2vp9enc",
+       	"libstagefright_soft_c2rawdec",
+       	"libstagefright_soft_c2flacdec",
+       	"libstagefright_soft_c2flacenc",
+       	"libstagefright_soft_c2gsmdec",
+    ],
+
+    compile_multilib: "32",
+}
+
diff --git a/services/codec2/Android.mk b/services/codec2/Android.mk
new file mode 100644
index 0000000..fa49875
--- /dev/null
+++ b/services/codec2/Android.mk
@@ -0,0 +1,43 @@
+LOCAL_PATH := $(call my-dir)
+
+# vendor service seccomp policy
+ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), x86 x86_64 arm arm64))
+include $(CLEAR_VARS)
+LOCAL_MODULE := codec2.vendor.base.policy
+LOCAL_VENDOR_MODULE := true
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc/seccomp_policy
+LOCAL_REQUIRED_MODULES := crash_dump.policy
+ifdef TARGET_2ND_ARCH
+    ifneq ($(TARGET_TRANSLATE_2ND_ARCH),true)
+        LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_2ND_ARCH).policy
+    else
+        LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_ARCH).policy
+    endif
+else
+    LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_ARCH).policy
+endif
+include $(BUILD_PREBUILT)
+endif
+
+# system service seccomp policy
+ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), x86 x86_64 arm arm64))
+include $(CLEAR_VARS)
+LOCAL_MODULE := codec2.system.base.policy
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT)/etc/seccomp_policy
+LOCAL_REQUIRED_MODULES := crash_dump.policy
+ifdef TARGET_2ND_ARCH
+    ifneq ($(TARGET_TRANSLATE_2ND_ARCH),true)
+        LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_2ND_ARCH).policy
+    else
+        LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_ARCH).policy
+    endif
+else
+    LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_ARCH).policy
+endif
+include $(BUILD_PREBUILT)
+endif
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
+
diff --git a/services/codec2/seccomp_policy/codec2.system.base-arm.policy b/services/codec2/seccomp_policy/codec2.system.base-arm.policy
new file mode 100644
index 0000000..d5871d1
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.system.base-arm.policy
@@ -0,0 +1,73 @@
+# 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.
+
+# Organized by frequency of systemcall - in descending order for
+# best performance.
+futex: 1
+ioctl: 1
+write: 1
+prctl: 1
+clock_gettime: 1
+getpriority: 1
+read: 1
+close: 1
+writev: 1
+dup: 1
+ppoll: 1
+mmap2: 1
+getrandom: 1
+
+# mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail
+# parser support for '<' is in this needs to be modified to also prevent
+# |old_address| and |new_address| from touching the exception vector page, which
+# on ARM is statically loaded at 0xffff 0000. See
+# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0211h/Babfeega.html
+# for more details.
+mremap: arg3 == 3
+munmap: 1
+mprotect: 1
+madvise: 1
+openat: 1
+sigaltstack: 1
+clone: 1
+setpriority: 1
+getuid32: 1
+fstat64: 1
+fstatfs64: 1
+pread64: 1
+faccessat: 1
+readlinkat: 1
+exit: 1
+rt_sigprocmask: 1
+set_tid_address: 1
+restart_syscall: 1
+exit_group: 1
+rt_sigreturn: 1
+pipe2: 1
+gettimeofday: 1
+sched_yield: 1
+nanosleep: 1
+lseek: 1
+_llseek: 1
+sched_get_priority_max: 1
+sched_get_priority_min: 1
+statfs64: 1
+sched_setscheduler: 1
+fstatat64: 1
+ugetrlimit: 1
+getdents64: 1
+getrandom: 1
+
+@include /system/etc/seccomp_policy/crash_dump.arm.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.system.base-x86.policy b/services/codec2/seccomp_policy/codec2.system.base-x86.policy
new file mode 100644
index 0000000..20c7625
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.system.base-x86.policy
@@ -0,0 +1,57 @@
+# 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.
+
+read: 1
+mprotect: 1
+prctl: 1
+openat: 1
+getuid32: 1
+writev: 1
+ioctl: 1
+close: 1
+mmap2: 1
+fstat64: 1
+madvise: 1
+fstatat64: 1
+futex: 1
+munmap: 1
+faccessat: 1
+_llseek: 1
+lseek: 1
+clone: 1
+sigaltstack: 1
+setpriority: 1
+restart_syscall: 1
+exit: 1
+exit_group: 1
+rt_sigreturn: 1
+ugetrlimit: 1
+readlinkat: 1
+_llseek: 1
+fstatfs64: 1
+pread64: 1
+mremap: 1
+dup: 1
+set_tid_address: 1
+write: 1
+nanosleep: 1
+
+# Required by AddressSanitizer
+gettid: 1
+sched_yield: 1
+getpid: 1
+gettid: 1
+
+@include /system/etc/seccomp_policy/crash_dump.x86.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy b/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy
new file mode 100644
index 0000000..d5871d1
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy
@@ -0,0 +1,73 @@
+# 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.
+
+# Organized by frequency of systemcall - in descending order for
+# best performance.
+futex: 1
+ioctl: 1
+write: 1
+prctl: 1
+clock_gettime: 1
+getpriority: 1
+read: 1
+close: 1
+writev: 1
+dup: 1
+ppoll: 1
+mmap2: 1
+getrandom: 1
+
+# mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail
+# parser support for '<' is in this needs to be modified to also prevent
+# |old_address| and |new_address| from touching the exception vector page, which
+# on ARM is statically loaded at 0xffff 0000. See
+# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0211h/Babfeega.html
+# for more details.
+mremap: arg3 == 3
+munmap: 1
+mprotect: 1
+madvise: 1
+openat: 1
+sigaltstack: 1
+clone: 1
+setpriority: 1
+getuid32: 1
+fstat64: 1
+fstatfs64: 1
+pread64: 1
+faccessat: 1
+readlinkat: 1
+exit: 1
+rt_sigprocmask: 1
+set_tid_address: 1
+restart_syscall: 1
+exit_group: 1
+rt_sigreturn: 1
+pipe2: 1
+gettimeofday: 1
+sched_yield: 1
+nanosleep: 1
+lseek: 1
+_llseek: 1
+sched_get_priority_max: 1
+sched_get_priority_min: 1
+statfs64: 1
+sched_setscheduler: 1
+fstatat64: 1
+ugetrlimit: 1
+getdents64: 1
+getrandom: 1
+
+@include /system/etc/seccomp_policy/crash_dump.arm.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy b/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy
new file mode 100644
index 0000000..20c7625
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy
@@ -0,0 +1,57 @@
+# 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.
+
+read: 1
+mprotect: 1
+prctl: 1
+openat: 1
+getuid32: 1
+writev: 1
+ioctl: 1
+close: 1
+mmap2: 1
+fstat64: 1
+madvise: 1
+fstatat64: 1
+futex: 1
+munmap: 1
+faccessat: 1
+_llseek: 1
+lseek: 1
+clone: 1
+sigaltstack: 1
+setpriority: 1
+restart_syscall: 1
+exit: 1
+exit_group: 1
+rt_sigreturn: 1
+ugetrlimit: 1
+readlinkat: 1
+_llseek: 1
+fstatfs64: 1
+pread64: 1
+mremap: 1
+dup: 1
+set_tid_address: 1
+write: 1
+nanosleep: 1
+
+# Required by AddressSanitizer
+gettid: 1
+sched_yield: 1
+getpid: 1
+gettid: 1
+
+@include /system/etc/seccomp_policy/crash_dump.x86.policy
+
diff --git a/services/codec2/system.cpp b/services/codec2/system.cpp
new file mode 100644
index 0000000..d6ec644
--- /dev/null
+++ b/services/codec2/system.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "vendor.google.media.c2@1.0-service"
+
+#include <C2PlatformSupport.h>
+#include <C2V4l2Support.h>
+#include <cutils/properties.h>
+
+#include <codec2/hidl/1.0/ComponentStore.h>
+#include <hidl/HidlTransportSupport.h>
+#include <minijail.h>
+
+// TODO: Remove this once "setenv()" call is removed.
+#include <stdlib.h>
+
+// This is created by module "codec2.system.base.policy". This can be modified.
+static constexpr char kBaseSeccompPolicyPath[] =
+        "/system/etc/seccomp_policy/codec2.system.base.policy";
+
+// Additional device-specific seccomp permissions can be added in this file.
+static constexpr char kExtSeccompPolicyPath[] =
+        "/system/etc/seccomp_policy/codec2.system.ext.policy";
+
+int main(int /* argc */, char** /* argv */) {
+    ALOGD("vendor.google.media.c2@1.0-service-system starting...");
+
+    // TODO: Remove this when all the build settings and sepolicies are in place.
+    setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+
+    signal(SIGPIPE, SIG_IGN);
+    android::SetUpMinijail(kBaseSeccompPolicyPath, kExtSeccompPolicyPath);
+
+    // Extra threads may be needed to handle a stacked IPC sequence that
+    // contains alternating binder and hwbinder calls. (See b/35283480.)
+    android::hardware::configureRpcThreadpool(8, true /* callerWillJoin */);
+
+    // Create IComponentStore service.
+    {
+        using namespace ::vendor::google::media::c2::V1_0;
+        android::sp<IComponentStore> store =
+                new implementation::ComponentStore(
+                android::GetCodec2PlatformComponentStore());
+        if (store == nullptr) {
+            ALOGE("Cannot create Codec2's IComponentStore system service.");
+        } else {
+            if (store->registerAsService("system") != android::OK) {
+                ALOGE("Cannot register Codec2's "
+                        "IComponentStore system service.");
+            } else {
+                ALOGI("Codec2's IComponentStore system service created.");
+            }
+        }
+
+        // To enable the v4l2 service, set this sysprop and add "v4l2" instance
+        // to the system manifest file.
+        if (property_get_bool("debug.stagefright.ccodec_v4l2", false)) {
+            store = new implementation::ComponentStore(
+                    android::GetCodec2VDAComponentStore());
+            if (store == nullptr) {
+                ALOGE("Cannot create Codec2's IComponentStore V4L2 service.");
+            } else {
+                if (store->registerAsService("v4l2") != android::OK) {
+                    ALOGE("Cannot register Codec2's "
+                            "IComponentStore V4L2 service.");
+                } else {
+                    ALOGI("Codec2's IComponentStore V4L2 service created.");
+                }
+            }
+        }
+    }
+
+    android::hardware::joinRpcThreadpool();
+    return 0;
+}
+
diff --git a/services/codec2/vendor.cpp b/services/codec2/vendor.cpp
new file mode 100644
index 0000000..60b51e2
--- /dev/null
+++ b/services/codec2/vendor.cpp
@@ -0,0 +1,135 @@
+/*
+ * 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 "vendor.google.media.c2@1.0-service"
+
+#include <codec2/hidl/1.0/ComponentStore.h>
+#include <hidl/HidlTransportSupport.h>
+#include <minijail.h>
+
+#include <C2Component.h>
+
+// TODO: Remove this once "setenv()" call is removed.
+#include <stdlib.h>
+
+// This is created by module "codec2.vendor.base.policy". This can be modified.
+static constexpr char kBaseSeccompPolicyPath[] =
+        "/vendor/etc/seccomp_policy/codec2.vendor.base.policy";
+
+// Additional device-specific seccomp permissions can be added in this file.
+static constexpr char kExtSeccompPolicyPath[] =
+        "/vendor/etc/seccomp_policy/codec2.vendor.ext.policy";
+
+// TODO: Replace with a valid C2ComponentStore implementation.
+class DummyC2Store : public C2ComponentStore {
+public:
+    DummyC2Store() = default;
+
+    virtual ~DummyC2Store() override = default;
+
+    virtual C2String getName() const override {
+        return "default";
+    }
+
+    virtual c2_status_t createComponent(
+            C2String /*name*/,
+            std::shared_ptr<C2Component>* const /*component*/) override {
+        return C2_NOT_FOUND;
+    }
+
+    virtual c2_status_t createInterface(
+            C2String /* name */,
+            std::shared_ptr<C2ComponentInterface>* const /* interface */) override {
+        return C2_NOT_FOUND;
+    }
+
+    virtual std::vector<std::shared_ptr<const C2Component::Traits>>
+            listComponents() override {
+        return {};
+    }
+
+    virtual c2_status_t copyBuffer(
+            std::shared_ptr<C2GraphicBuffer> /* src */,
+            std::shared_ptr<C2GraphicBuffer> /* dst */) override {
+        return C2_OMITTED;
+    }
+
+    virtual c2_status_t query_sm(
+        const std::vector<C2Param*>& /* stackParams */,
+        const std::vector<C2Param::Index>& /* heapParamIndices */,
+        std::vector<std::unique_ptr<C2Param>>* const /* heapParams */) const override {
+        return C2_OMITTED;
+    }
+
+    virtual c2_status_t config_sm(
+            const std::vector<C2Param*>& /* params */,
+            std::vector<std::unique_ptr<C2SettingResult>>* const /* failures */) override {
+        return C2_OMITTED;
+    }
+
+    virtual std::shared_ptr<C2ParamReflector> getParamReflector() const override {
+        return nullptr;
+    }
+
+    virtual c2_status_t querySupportedParams_nb(
+            std::vector<std::shared_ptr<C2ParamDescriptor>>* const /* params */) const override {
+        return C2_OMITTED;
+    }
+
+    virtual c2_status_t querySupportedValues_sm(
+            std::vector<C2FieldSupportedValuesQuery>& /* fields */) const override {
+        return C2_OMITTED;
+    }
+};
+
+int main(int /* argc */, char** /* argv */) {
+    ALOGD("vendor.google.media.c2@1.0-service starting...");
+
+    // TODO: Remove this when all the build settings and sepolicies are in place.
+    setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+
+    signal(SIGPIPE, SIG_IGN);
+    android::SetUpMinijail(kBaseSeccompPolicyPath, kExtSeccompPolicyPath);
+
+    // Extra threads may be needed to handle a stacked IPC sequence that
+    // contains alternating binder and hwbinder calls. (See b/35283480.)
+    android::hardware::configureRpcThreadpool(8, true /* callerWillJoin */);
+
+    // Create IComponentStore service.
+    {
+        using namespace ::vendor::google::media::c2::V1_0;
+        android::sp<IComponentStore> store =
+                new implementation::ComponentStore(
+                // TODO: Replace this with a valid C2ComponentStore
+                // implementation.
+                std::make_shared<DummyC2Store>());
+        if (store == nullptr) {
+            ALOGE("Cannot create Codec2's IComponentStore service.");
+        } else {
+            if (store->registerAsService("default") != android::OK) {
+                ALOGE("Cannot register Codec2's "
+                        "IComponentStore service.");
+            } else {
+                ALOGI("Codec2's IComponentStore service created.");
+            }
+        }
+    }
+
+    android::hardware::joinRpcThreadpool();
+    return 0;
+}
+
diff --git a/services/codec2/vendor.google.media.c2@1.0-service-system.rc b/services/codec2/vendor.google.media.c2@1.0-service-system.rc
new file mode 100644
index 0000000..0577a1d
--- /dev/null
+++ b/services/codec2/vendor.google.media.c2@1.0-service-system.rc
@@ -0,0 +1,7 @@
+service vendor-google-media-c2-system-hal-1-0 /system/bin/hw/vendor.google.media.c2@1.0-service-system
+    class hal
+    user media
+    group mediadrm drmrpc
+    ioprio rt 4
+    writepid /dev/cpuset/foreground/tasks
+
diff --git a/services/codec2/vendor.google.media.c2@1.0-service.rc b/services/codec2/vendor.google.media.c2@1.0-service.rc
new file mode 100644
index 0000000..3e7e0a6
--- /dev/null
+++ b/services/codec2/vendor.google.media.c2@1.0-service.rc
@@ -0,0 +1,7 @@
+service vendor-google-media-c2-hal-1-0 /vendor/bin/hw/vendor.google.media.c2@1.0-service
+    class hal
+    user media
+    group mediadrm drmrpc
+    ioprio rt 4
+    writepid /dev/cpuset/foreground/tasks
+