Merge "Open some more metrics from app-space"
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp
index c4b197c..e51ec4d 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -38,7 +38,6 @@
 #include <media/stagefright/MediaErrors.h>
 
 using drm::V1_0::KeyedVector;
-using drm::V1_0::KeyRequestType;
 using drm::V1_0::KeyStatusType;
 using drm::V1_0::KeyType;
 using drm::V1_0::KeyValue;
@@ -228,6 +227,7 @@
 DrmHal::DrmHal()
    : mDrmSessionClient(new DrmSessionClient(this)),
      mFactories(makeDrmFactories()),
+     mOpenSessionCounter("/drm/mediadrm/open_session", "status"),
      mInitCheck((mFactories.size() == 0) ? ERROR_UNSUPPORTED : NO_INIT) {
 }
 
@@ -518,6 +518,8 @@
                 mDrmSessionClient, sessionId);
         mOpenSessions.push(sessionId);
     }
+
+    mOpenSessionCounter.Increment(err);
     return err;
 }
 
@@ -567,23 +569,63 @@
 
     status_t err = UNKNOWN_ERROR;
 
+    if (mPluginV1_1 != NULL) {
+        Return<void> hResult =
+            mPluginV1_1->getKeyRequest_1_1(
+                toHidlVec(sessionId), toHidlVec(initData),
+                toHidlString(mimeType), hKeyType, hOptionalParameters,
+                [&](Status status, const hidl_vec<uint8_t>& hRequest,
+                    drm::V1_1::KeyRequestType hKeyRequestType,
+                    const hidl_string& hDefaultUrl) {
+
+            if (status == Status::OK) {
+                request = toVector(hRequest);
+                defaultUrl = toString8(hDefaultUrl);
+
+                switch (hKeyRequestType) {
+                    case drm::V1_1::KeyRequestType::INITIAL:
+                        *keyRequestType = DrmPlugin::kKeyRequestType_Initial;
+                        break;
+                    case drm::V1_1::KeyRequestType::RENEWAL:
+                        *keyRequestType = DrmPlugin::kKeyRequestType_Renewal;
+                        break;
+                    case drm::V1_1::KeyRequestType::RELEASE:
+                        *keyRequestType = DrmPlugin::kKeyRequestType_Release;
+                        break;
+                    case drm::V1_1::KeyRequestType::NONE:
+                        *keyRequestType = DrmPlugin::kKeyRequestType_None;
+                        break;
+                    case drm::V1_1::KeyRequestType::UPDATE:
+                        *keyRequestType = DrmPlugin::kKeyRequestType_Update;
+                        break;
+                    default:
+                        *keyRequestType = DrmPlugin::kKeyRequestType_Unknown;
+                        break;
+                }
+                err = toStatusT(status);
+            }
+        });
+        return hResult.isOk() ? err : DEAD_OBJECT;
+    }
+
     Return<void> hResult = mPlugin->getKeyRequest(toHidlVec(sessionId),
             toHidlVec(initData), toHidlString(mimeType), hKeyType, hOptionalParameters,
             [&](Status status, const hidl_vec<uint8_t>& hRequest,
-                    KeyRequestType hKeyRequestType, const hidl_string& hDefaultUrl) {
+                    drm::V1_0::KeyRequestType hKeyRequestType,
+                    const hidl_string& hDefaultUrl) {
 
                 if (status == Status::OK) {
                     request = toVector(hRequest);
                     defaultUrl = toString8(hDefaultUrl);
 
                     switch (hKeyRequestType) {
-                    case KeyRequestType::INITIAL:
+                    case drm::V1_0::KeyRequestType::INITIAL:
                         *keyRequestType = DrmPlugin::kKeyRequestType_Initial;
                         break;
-                    case KeyRequestType::RENEWAL:
+                    case drm::V1_0::KeyRequestType::RENEWAL:
                         *keyRequestType = DrmPlugin::kKeyRequestType_Renewal;
                         break;
-                    case KeyRequestType::RELEASE:
+                    case drm::V1_0::KeyRequestType::RELEASE:
                         *keyRequestType = DrmPlugin::kKeyRequestType_Release;
                         break;
                     default:
@@ -946,8 +988,25 @@
 }
 
 status_t DrmHal::getMetrics(MediaAnalyticsItem* metrics) {
-    // TODO: Replace this with real metrics.
-    metrics->setCString("/drm/mediadrm/dummymetric", "dummy");
+    // TODO: Move mOpenSessionCounter and suffixes to a separate class
+    // that manages the collection of metrics and exporting them.
+    std::string success_count_name =
+        mOpenSessionCounter.metric_name() + "/ok/count";
+    std::string error_count_name =
+        mOpenSessionCounter.metric_name() + "/error/count";
+    mOpenSessionCounter.ExportValues(
+        [&] (status_t status, int64_t value) {
+            if (status == OK) {
+                metrics->setInt64(success_count_name.c_str(), value);
+            } else {
+                int64_t total_errors(0);
+                metrics->getInt64(error_count_name.c_str(), &total_errors);
+                metrics->setInt64(error_count_name.c_str(),
+                                  total_errors + value);
+                // TODO: Add support for exporting the list of error values.
+                // This probably needs to be added to MediaAnalyticsItem.
+            }
+        });
     return OK;
 }
 
diff --git a/drm/libmediadrm/tests/Android.bp b/drm/libmediadrm/tests/Android.bp
new file mode 100644
index 0000000..674d3eb
--- /dev/null
+++ b/drm/libmediadrm/tests/Android.bp
@@ -0,0 +1,12 @@
+// Build definitions for unit tests.
+
+cc_test {
+    name: "CounterMetric_test",
+    srcs: ["CounterMetric_test.cpp"],
+    shared_libs: ["libmediadrm"],
+    include_dirs: ["frameworks/av/include/media"],
+    cflags: [
+      "-Werror",
+      "-Wall",
+    ],
+}
diff --git a/drm/libmediadrm/tests/CounterMetric_test.cpp b/drm/libmediadrm/tests/CounterMetric_test.cpp
new file mode 100644
index 0000000..6bca0da
--- /dev/null
+++ b/drm/libmediadrm/tests/CounterMetric_test.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "CounterMetric.h"
+
+namespace android {
+
+/**
+ * Unit tests for the CounterMetric class.
+ */
+class CounterMetricTest : public ::testing::Test {
+};
+
+TEST_F(CounterMetricTest, IntDataTypeEmpty) {
+  CounterMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+  std::map<int, int64_t> values;
+
+  metric.ExportValues(
+      [&] (int attribute_value, int64_t value) {
+          values[attribute_value] = value;
+      });
+
+  EXPECT_TRUE(values.empty());
+}
+
+TEST_F(CounterMetricTest, IntDataType) {
+  CounterMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+  std::map<int, int64_t> values;
+
+  metric.Increment(7);
+  metric.Increment(8);
+  metric.Increment(8);
+
+  metric.ExportValues(
+      [&] (int attribute_value, int64_t value) {
+          values[attribute_value] = value;
+      });
+
+  ASSERT_EQ(2u, values.size());
+  EXPECT_EQ(1, values[7]);
+  EXPECT_EQ(2, values[8]);
+}
+
+TEST_F(CounterMetricTest, StringDataType) {
+  CounterMetric<std::string> metric("MyMetricName", "MetricAttributeName");
+
+  std::map<std::string, int64_t> values;
+
+  metric.Increment("a");
+  metric.Increment("b");
+  metric.Increment("b");
+
+  metric.ExportValues(
+      [&] (std::string attribute_value, int64_t value) {
+          values[attribute_value] = value;
+      });
+
+  ASSERT_EQ(2u, values.size());
+  EXPECT_EQ(1, values["a"]);
+  EXPECT_EQ(2, values["b"]);
+}
+
+}  // namespace android
diff --git a/include/media/CounterMetric.h b/include/media/CounterMetric.h
new file mode 120000
index 0000000..baba043
--- /dev/null
+++ b/include/media/CounterMetric.h
@@ -0,0 +1 @@
+../../media/libmedia/include/media/CounterMetric.h
\ No newline at end of file
diff --git a/media/libaaudio/src/binding/AAudioServiceMessage.h b/media/libaaudio/src/binding/AAudioServiceMessage.h
index 9779f24..3981454 100644
--- a/media/libaaudio/src/binding/AAudioServiceMessage.h
+++ b/media/libaaudio/src/binding/AAudioServiceMessage.h
@@ -36,7 +36,6 @@
     AAUDIO_SERVICE_EVENT_PAUSED,
     AAUDIO_SERVICE_EVENT_STOPPED,
     AAUDIO_SERVICE_EVENT_FLUSHED,
-    AAUDIO_SERVICE_EVENT_CLOSED,
     AAUDIO_SERVICE_EVENT_DISCONNECTED,
     AAUDIO_SERVICE_EVENT_VOLUME,
     AAUDIO_SERVICE_EVENT_XRUN
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 4980e97..b611160 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -340,8 +340,13 @@
     }
 }
 
-aaudio_result_t AudioStreamInternal::requestStopInternal()
+aaudio_result_t AudioStreamInternal::requestStop()
 {
+    aaudio_result_t result = stopCallback();
+    if (result != AAUDIO_OK) {
+        return result;
+    }
+
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         ALOGE("requestStopInternal() mServiceStreamHandle invalid = 0x%08X",
               mServiceStreamHandle);
@@ -355,16 +360,6 @@
     return mServiceInterface.stopStream(mServiceStreamHandle);
 }
 
-aaudio_result_t AudioStreamInternal::requestStop()
-{
-    aaudio_result_t result = stopCallback();
-    if (result != AAUDIO_OK) {
-        return result;
-    }
-    result = requestStopInternal();
-    return result;
-}
-
 aaudio_result_t AudioStreamInternal::registerThread() {
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         ALOGE("registerThread() mServiceStreamHandle invalid");
@@ -483,10 +478,6 @@
                 onFlushFromServer();
             }
             break;
-        case AAUDIO_SERVICE_EVENT_CLOSED:
-            ALOGD("%s - got AAUDIO_SERVICE_EVENT_CLOSED", __func__);
-            setState(AAUDIO_STREAM_STATE_CLOSED);
-            break;
         case AAUDIO_SERVICE_EVENT_DISCONNECTED:
             // Prevent hardware from looping on old data and making buzzing sounds.
             if (getDirection() == AAUDIO_DIRECTION_OUTPUT) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 117756d..0f54f8c 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -121,8 +121,6 @@
 
     aaudio_result_t processCommands();
 
-    aaudio_result_t requestStopInternal();
-
     aaudio_result_t stopCallback();
 
     virtual void advanceClientToMatchServerPosition() = 0;
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index 5de6a11..5660c1b 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -38,9 +38,12 @@
 
 AudioStreamInternalPlay::~AudioStreamInternalPlay() {}
 
-
-aaudio_result_t AudioStreamInternalPlay::requestPauseInternal()
+aaudio_result_t AudioStreamInternalPlay::requestPause()
 {
+    aaudio_result_t result = stopCallback();
+    if (result != AAUDIO_OK) {
+        return result;
+    }
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         ALOGE("AudioStreamInternal::requestPauseInternal() mServiceStreamHandle invalid = 0x%08X",
               mServiceStreamHandle);
@@ -53,16 +56,6 @@
     return mServiceInterface.pauseStream(mServiceStreamHandle);
 }
 
-aaudio_result_t AudioStreamInternalPlay::requestPause()
-{
-    aaudio_result_t result = stopCallback();
-    if (result != AAUDIO_OK) {
-        return result;
-    }
-    result = requestPauseInternal();
-    return result;
-}
-
 aaudio_result_t AudioStreamInternalPlay::requestFlush() {
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         ALOGE("AudioStreamInternal::requestFlush() mServiceStreamHandle invalid = 0x%08X",
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h
index d5c1b1e..04e4a62 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.h
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -37,6 +37,16 @@
 
     aaudio_result_t requestFlush() override;
 
+    bool isFlushSupported() const override {
+        // Only implement FLUSH for OUTPUT streams.
+        return true;
+    }
+
+    bool isPauseSupported() const override {
+        // Only implement PAUSE for OUTPUT streams.
+        return true;
+    }
+
     aaudio_result_t write(const void *buffer,
                           int32_t numFrames,
                           int64_t timeoutNanoseconds) override;
@@ -52,8 +62,6 @@
 
 protected:
 
-    aaudio_result_t requestPauseInternal();
-
     void advanceClientToMatchServerPosition() override;
 
     void onFlushFromServer() override;
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.cpp b/media/libaaudio/src/core/AAudioStreamParameters.cpp
index 9645ea8..d56701b 100644
--- a/media/libaaudio/src/core/AAudioStreamParameters.cpp
+++ b/media/libaaudio/src/core/AAudioStreamParameters.cpp
@@ -17,7 +17,7 @@
 
 #define LOG_TAG "AAudioStreamParameters"
 #include <utils/Log.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
 
 #include "AAudioStreamParameters.h"
 
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 9a2405a..c4465fd 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -130,29 +130,106 @@
 }
 
 aaudio_result_t AudioStream::safePause() {
+    if (!isPauseSupported()) {
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+
     std::lock_guard<std::mutex> lock(mStreamLock);
     if (collidesWithCallback()) {
         ALOGE("%s cannot be called from a callback!", __func__);
         return AAUDIO_ERROR_INVALID_STATE;
     }
+
+    switch (getState()) {
+        // Proceed with pausing.
+        case AAUDIO_STREAM_STATE_STARTING:
+        case AAUDIO_STREAM_STATE_STARTED:
+        case AAUDIO_STREAM_STATE_DISCONNECTED:
+            break;
+
+            // Transition from one inactive state to another.
+        case AAUDIO_STREAM_STATE_OPEN:
+        case AAUDIO_STREAM_STATE_STOPPED:
+        case AAUDIO_STREAM_STATE_FLUSHED:
+            setState(AAUDIO_STREAM_STATE_PAUSED);
+            return AAUDIO_OK;
+
+            // Redundant?
+        case AAUDIO_STREAM_STATE_PAUSING:
+        case AAUDIO_STREAM_STATE_PAUSED:
+            return AAUDIO_OK;
+
+            // Don't interfere with transitional states or when closed.
+        case AAUDIO_STREAM_STATE_STOPPING:
+        case AAUDIO_STREAM_STATE_FLUSHING:
+        case AAUDIO_STREAM_STATE_CLOSING:
+        case AAUDIO_STREAM_STATE_CLOSED:
+        default:
+            ALOGW("safePause() stream not running, state = %s",
+                  AAudio_convertStreamStateToText(getState()));
+            return AAUDIO_ERROR_INVALID_STATE;
+    }
+
     return requestPause();
 }
 
 aaudio_result_t AudioStream::safeFlush() {
+    if (!isFlushSupported()) {
+        ALOGE("flush not supported for this stream");
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+
     std::lock_guard<std::mutex> lock(mStreamLock);
     if (collidesWithCallback()) {
-        ALOGE("%s cannot be called from a callback!", __func__);
+        ALOGE("stream cannot be flushed from a callback!");
         return AAUDIO_ERROR_INVALID_STATE;
     }
+
+    aaudio_result_t result = AAudio_isFlushAllowed(getState());
+    if (result != AAUDIO_OK) {
+        return result;
+    }
+
     return requestFlush();
 }
 
 aaudio_result_t AudioStream::safeStop() {
     std::lock_guard<std::mutex> lock(mStreamLock);
     if (collidesWithCallback()) {
-        ALOGE("%s cannot be called from a callback!", __func__);
+        ALOGE("stream cannot be stopped from a callback!");
         return AAUDIO_ERROR_INVALID_STATE;
     }
+
+    switch (getState()) {
+        // Proceed with stopping.
+        case AAUDIO_STREAM_STATE_STARTING:
+        case AAUDIO_STREAM_STATE_STARTED:
+        case AAUDIO_STREAM_STATE_DISCONNECTED:
+            break;
+
+        // Transition from one inactive state to another.
+        case AAUDIO_STREAM_STATE_OPEN:
+        case AAUDIO_STREAM_STATE_PAUSED:
+        case AAUDIO_STREAM_STATE_FLUSHED:
+            setState(AAUDIO_STREAM_STATE_STOPPED);
+            return AAUDIO_OK;
+
+        // Redundant?
+        case AAUDIO_STREAM_STATE_STOPPING:
+        case AAUDIO_STREAM_STATE_STOPPED:
+            return AAUDIO_OK;
+
+        // Don't interfere with transitional states or when closed.
+        case AAUDIO_STREAM_STATE_PAUSING:
+        case AAUDIO_STREAM_STATE_FLUSHING:
+        case AAUDIO_STREAM_STATE_CLOSING:
+        case AAUDIO_STREAM_STATE_CLOSED:
+        default:
+            ALOGW("requestStop() stream not running, state = %s",
+                  AAudio_convertStreamStateToText(getState()));
+            return AAUDIO_ERROR_INVALID_STATE;
+    }
+
     return requestStop();
 }
 
@@ -238,6 +315,7 @@
     if (err != 0) {
         return AAudioConvert_androidToAAudioResult(-errno);
     } else {
+        // TODO Use AAudioThread or maybe AndroidThread
         // Name the thread with an increasing index, "AAudio_#", for debugging.
         static std::atomic<uint32_t> nextThreadIndex{1};
         char name[16]; // max length for a pthread_name
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index c59ee6c..c0db0f9 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -73,6 +73,24 @@
      */
     virtual aaudio_result_t requestStart() = 0;
 
+    /**
+     * Check the state to see if Pause if currently legal.
+     *
+     * @param result pointer to return code
+     * @return true if OK to continue, if false then return result
+     */
+    bool checkPauseStateTransition(aaudio_result_t *result);
+
+    virtual bool isFlushSupported() const {
+        // Only implement FLUSH for OUTPUT streams.
+        return false;
+    }
+
+    virtual bool isPauseSupported() const {
+        // Only implement PAUSE for OUTPUT streams.
+        return false;
+    }
+
     virtual aaudio_result_t requestPause()
     {
         // Only implement this for OUTPUT streams.
@@ -341,11 +359,13 @@
         return mPlayerBase->getResult();
     }
 
+    // Pass pause request through PlayerBase for tracking.
     aaudio_result_t systemPause() {
         mPlayerBase->pause();
         return mPlayerBase->getResult();
     }
 
+    // Pass stop request through PlayerBase for tracking.
     aaudio_result_t systemStop() {
         mPlayerBase->stop();
         return mPlayerBase->getResult();
@@ -452,7 +472,14 @@
     }
 
     void setState(aaudio_stream_state_t state) {
-        mState = state;
+        if (mState == AAUDIO_STREAM_STATE_CLOSED) {
+            ; // CLOSED is a final state
+        } else if (mState == AAUDIO_STREAM_STATE_DISCONNECTED
+                && state != AAUDIO_STREAM_STATE_CLOSED) {
+            ; // Once DISCONNECTED, we can only move to CLOSED state.
+        } else {
+            mState = state;
+        }
     }
 
     void setDeviceId(int32_t deviceId) {
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 4d1b187..ee069ee 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -287,13 +287,8 @@
     if (mAudioTrack.get() == nullptr) {
         ALOGE("requestPause() no AudioTrack");
         return AAUDIO_ERROR_INVALID_STATE;
-    } else if (getState() != AAUDIO_STREAM_STATE_STARTING
-            && getState() != AAUDIO_STREAM_STATE_STARTED) {
-            // TODO What about DISCONNECTED?
-        ALOGE("requestPause(), called when state is %s",
-              AAudio_convertStreamStateToText(getState()));
-        return AAUDIO_ERROR_INVALID_STATE;
     }
+
     setState(AAUDIO_STREAM_STATE_PAUSING);
     mAudioTrack->pause();
     mCallbackEnabled.store(false);
@@ -308,10 +303,8 @@
     if (mAudioTrack.get() == nullptr) {
         ALOGE("requestFlush() no AudioTrack");
         return AAUDIO_ERROR_INVALID_STATE;
-    } else if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
-        ALOGE("requestFlush() not paused");
-        return AAUDIO_ERROR_INVALID_STATE;
     }
+
     setState(AAUDIO_STREAM_STATE_FLUSHING);
     incrementFramesRead(getFramesWritten() - getFramesRead());
     mAudioTrack->flush();
@@ -325,6 +318,7 @@
         ALOGE("requestStop() no AudioTrack");
         return AAUDIO_ERROR_INVALID_STATE;
     }
+
     setState(AAUDIO_STREAM_STATE_STOPPING);
     incrementFramesRead(getFramesWritten() - getFramesRead()); // TODO review
     mTimestampPosition.set(getFramesWritten());
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.h b/media/libaaudio/src/legacy/AudioStreamTrack.h
index a871db4..68608de 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.h
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.h
@@ -48,6 +48,16 @@
     aaudio_result_t requestFlush() override;
     aaudio_result_t requestStop() override;
 
+    bool isFlushSupported() const override {
+        // Only implement FLUSH for OUTPUT streams.
+        return true;
+    }
+
+    bool isPauseSupported() const override {
+        // Only implement PAUSE for OUTPUT streams.
+        return true;
+    }
+
     aaudio_result_t getTimestamp(clockid_t clockId,
                                        int64_t *framePosition,
                                        int64_t *timeNanoseconds) override;
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 2a34016..adc4904 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -441,3 +441,31 @@
     }
     return prop;
 }
+
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state) {
+    aaudio_result_t result = AAUDIO_OK;
+    switch (state) {
+// Proceed with flushing.
+        case AAUDIO_STREAM_STATE_OPEN:
+        case AAUDIO_STREAM_STATE_PAUSED:
+        case AAUDIO_STREAM_STATE_STOPPED:
+        case AAUDIO_STREAM_STATE_FLUSHED:
+            break;
+
+// Transition from one inactive state to another.
+        case AAUDIO_STREAM_STATE_STARTING:
+        case AAUDIO_STREAM_STATE_STARTED:
+        case AAUDIO_STREAM_STATE_STOPPING:
+        case AAUDIO_STREAM_STATE_PAUSING:
+        case AAUDIO_STREAM_STATE_FLUSHING:
+        case AAUDIO_STREAM_STATE_CLOSING:
+        case AAUDIO_STREAM_STATE_CLOSED:
+        case AAUDIO_STREAM_STATE_DISCONNECTED:
+        default:
+            ALOGE("can only flush stream when PAUSED, OPEN or STOPPED, state = %s",
+                  AAudio_convertStreamStateToText(state));
+            result =  AAUDIO_ERROR_INVALID_STATE;
+            break;
+    }
+    return result;
+}
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index 0c59f6d..3673c34 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -23,7 +23,7 @@
 #include <sys/types.h>
 
 #include <utils/Errors.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
 
 #include "aaudio/AAudio.h"
 
@@ -268,6 +268,14 @@
  */
 int32_t AAudioProperty_getHardwareBurstMinMicros();
 
+
+/**
+ * Is flush allowed for the given state?
+ * @param state
+ * @return AAUDIO_OK if allowed or an error
+ */
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state);
+
 /**
  * Try a function f until it returns true.
  *
diff --git a/media/libaaudio/tests/test_various.cpp b/media/libaaudio/tests/test_various.cpp
index dc19985..4b065c9 100644
--- a/media/libaaudio/tests/test_various.cpp
+++ b/media/libaaudio/tests/test_various.cpp
@@ -26,7 +26,6 @@
 #include <gtest/gtest.h>
 #include <unistd.h>
 
-
 // Callback function that does nothing.
 aaudio_data_callback_result_t NoopDataCallbackProc(
         AAudioStream *stream,
@@ -45,73 +44,376 @@
 
 constexpr int64_t NANOS_PER_MILLISECOND = 1000 * 1000;
 
-//int foo() { // To fix Android Studio formatting when editing.
-TEST(test_various, aaudio_stop_when_open) {
+enum FunctionToCall {
+    CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH
+};
+
+void checkStateTransition(aaudio_performance_mode_t perfMode,
+                          aaudio_stream_state_t originalState,
+                          FunctionToCall functionToCall,
+                          aaudio_result_t expectedResult,
+                          aaudio_stream_state_t expectedState) {
     AAudioStreamBuilder *aaudioBuilder = nullptr;
     AAudioStream *aaudioStream = nullptr;
 
-// Use an AAudioStreamBuilder to contain requested parameters.
+    // Use an AAudioStreamBuilder to contain requested parameters.
     ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
 
-// Request stream properties.
+    // Request stream properties.
     AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
-    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
 
-// Create an AAudioStream using the Builder.
-    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
 
-
+    // Verify Open State
     aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
     EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
                                                          AAUDIO_STREAM_STATE_UNKNOWN, &state,
                                                          1000 * NANOS_PER_MILLISECOND));
     EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
 
-    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+    // Put stream into desired state.
+    aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_UNINITIALIZED;
+    if (originalState != AAUDIO_STREAM_STATE_OPEN) {
 
-    state = AAUDIO_STREAM_STATE_UNKNOWN;
+        ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
+
+        if (originalState != AAUDIO_STREAM_STATE_STARTING) {
+
+            ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+                                                                 AAUDIO_STREAM_STATE_STARTING,
+                                                                 &state,
+                                                                 1000 * NANOS_PER_MILLISECOND));
+            ASSERT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
+
+            if (originalState == AAUDIO_STREAM_STATE_STOPPING) {
+                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+            } else if (originalState == AAUDIO_STREAM_STATE_STOPPED) {
+                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+                inputState = AAUDIO_STREAM_STATE_STOPPING;
+            } else if (originalState == AAUDIO_STREAM_STATE_PAUSING) {
+                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+            } else if (originalState == AAUDIO_STREAM_STATE_PAUSED) {
+                ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+                inputState = AAUDIO_STREAM_STATE_PAUSING;
+            }
+        }
+    }
+
+    // Wait until past transitional state.
+    if (inputState != AAUDIO_STREAM_STATE_UNINITIALIZED) {
+        ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+                                                             inputState,
+                                                             &state,
+                                                             1000 * NANOS_PER_MILLISECOND));
+        ASSERT_EQ(originalState, state);
+    }
+
+    aaudio_stream_state_t transitionalState = originalState;
+    switch(functionToCall) {
+        case FunctionToCall::CALL_START:
+            EXPECT_EQ(expectedResult, AAudioStream_requestStart(aaudioStream));
+            transitionalState = AAUDIO_STREAM_STATE_STARTING;
+            break;
+        case FunctionToCall::CALL_STOP:
+            EXPECT_EQ(expectedResult, AAudioStream_requestStop(aaudioStream));
+            transitionalState = AAUDIO_STREAM_STATE_STOPPING;
+            break;
+        case FunctionToCall::CALL_PAUSE:
+            EXPECT_EQ(expectedResult, AAudioStream_requestPause(aaudioStream));
+            transitionalState = AAUDIO_STREAM_STATE_PAUSING;
+            break;
+        case FunctionToCall::CALL_FLUSH:
+            EXPECT_EQ(expectedResult, AAudioStream_requestFlush(aaudioStream));
+            transitionalState = AAUDIO_STREAM_STATE_FLUSHING;
+            break;
+    }
+
     EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
-                                                         AAUDIO_STREAM_STATE_UNKNOWN, &state, 0));
-    EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
-
-    AAudioStream_close(aaudioStream);
-    AAudioStreamBuilder_delete(aaudioBuilder);
-}
-
-//int boo() { // To fix Android Studio formatting when editing.
-TEST(test_various, aaudio_flush_when_started) {
-    AAudioStreamBuilder *aaudioBuilder = nullptr;
-    AAudioStream *aaudioStream = nullptr;
-
-// Use an AAudioStreamBuilder to contain requested parameters.
-    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
-
-// Request stream properties.
-    AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
-    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
-
-// Create an AAudioStream using the Builder.
-    EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
-    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
-
-    aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
-    EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
-                                                         AAUDIO_STREAM_STATE_STARTING, &state,
+                                                         transitionalState,
+                                                         &state,
                                                          1000 * NANOS_PER_MILLISECOND));
-    EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
-
-    EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, AAudioStream_requestFlush(aaudioStream));
-
-    state = AAUDIO_STREAM_STATE_UNKNOWN;
-    EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
-                                                         AAUDIO_STREAM_STATE_UNKNOWN, &state, 0));
-    EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
+    // We should not change state when a function fails.
+    if (expectedResult != AAUDIO_OK) {
+        ASSERT_EQ(originalState, expectedState);
+    }
+    EXPECT_EQ(expectedState, state);
+    if (state != expectedState) {
+        printf("ERROR - expected %s, actual = %s\n",
+                AAudio_convertStreamStateToText(expectedState),
+                AAudio_convertStreamStateToText(state));
+        fflush(stdout);
+    }
 
     AAudioStream_close(aaudioStream);
     AAudioStreamBuilder_delete(aaudioBuilder);
 }
 
-//int main() { // To fix Android Studio formatting when editing.
+// TODO Use parameterized tests instead of these individual specific tests.
+
+// OPEN =================================================================
+TEST(test_various, aaudio_state_lowlat_open_start) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_OPEN,
+            FunctionToCall::CALL_START,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_open_start) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_OPEN,
+            FunctionToCall::CALL_START,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_stop) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_OPEN,
+            FunctionToCall::CALL_STOP,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_open_stop) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_OPEN,
+            FunctionToCall::CALL_STOP,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_pause) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_OPEN,
+            FunctionToCall::CALL_PAUSE,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_open_pause) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_OPEN,
+            FunctionToCall::CALL_PAUSE,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_flush) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_OPEN,
+            FunctionToCall::CALL_FLUSH,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_open_flush) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_OPEN,
+            FunctionToCall::CALL_FLUSH,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+
+// STARTED =================================================================
+TEST(test_various, aaudio_state_lowlat_started_start) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_STARTED,
+            FunctionToCall::CALL_START,
+            AAUDIO_ERROR_INVALID_STATE,
+            AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_started_start) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_STARTED,
+            FunctionToCall::CALL_START,
+            AAUDIO_ERROR_INVALID_STATE,
+            AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_stop) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_STARTED,
+            FunctionToCall::CALL_STOP,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_started_stop) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_STARTED,
+            FunctionToCall::CALL_STOP,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_pause) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_STARTED,
+            FunctionToCall::CALL_PAUSE,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_started_pause) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_STARTED,
+            FunctionToCall::CALL_PAUSE,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_flush) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_STARTED,
+            FunctionToCall::CALL_FLUSH,
+            AAUDIO_ERROR_INVALID_STATE,
+            AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_started_flush) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_STARTED,
+            FunctionToCall::CALL_FLUSH,
+            AAUDIO_ERROR_INVALID_STATE,
+            AAUDIO_STREAM_STATE_STARTED);
+}
+
+// STOPPED =================================================================
+TEST(test_various, aaudio_state_lowlat_stopped_start) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_STOPPED,
+            FunctionToCall::CALL_START,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_start) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_STOPPED,
+            FunctionToCall::CALL_START,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_stop) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_STOPPED,
+            FunctionToCall::CALL_STOP,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_stop) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_STOPPED,
+            FunctionToCall::CALL_STOP,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_pause) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_STOPPED,
+            FunctionToCall::CALL_PAUSE,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_pause) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_STOPPED,
+            FunctionToCall::CALL_PAUSE,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_flush) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+            AAUDIO_STREAM_STATE_STOPPED,
+            FunctionToCall::CALL_FLUSH,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_flush) {
+    checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+            AAUDIO_STREAM_STATE_STOPPED,
+            FunctionToCall::CALL_FLUSH,
+            AAUDIO_OK,
+            AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+// PAUSED =================================================================
+TEST(test_various, aaudio_state_lowlat_paused_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+        AAUDIO_STREAM_STATE_PAUSED,
+        FunctionToCall::CALL_START,
+        AAUDIO_OK,
+        AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_paused_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+        AAUDIO_STREAM_STATE_PAUSED,
+        FunctionToCall::CALL_START,
+        AAUDIO_OK,
+        AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+        AAUDIO_STREAM_STATE_PAUSED,
+        FunctionToCall::CALL_STOP,
+        AAUDIO_OK,
+        AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_paused_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+        AAUDIO_STREAM_STATE_PAUSED,
+        FunctionToCall::CALL_STOP,
+        AAUDIO_OK,
+        AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_pause) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+        AAUDIO_STREAM_STATE_PAUSED,
+        FunctionToCall::CALL_PAUSE,
+        AAUDIO_OK,
+        AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_paused_pause) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+        AAUDIO_STREAM_STATE_PAUSED,
+        FunctionToCall::CALL_PAUSE,
+        AAUDIO_OK,
+        AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_flush) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+        AAUDIO_STREAM_STATE_PAUSED,
+        FunctionToCall::CALL_FLUSH,
+        AAUDIO_OK,
+        AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_paused_flush) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+        AAUDIO_STREAM_STATE_PAUSED,
+        FunctionToCall::CALL_FLUSH,
+        AAUDIO_OK,
+        AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+// ==========================================================================
 TEST(test_various, aaudio_set_buffer_size) {
 
     int32_t bufferCapacity;
@@ -174,7 +476,6 @@
     AAudioStreamBuilder_delete(aaudioBuilder);
 }
 
-
 // ************************************************************
 // Test to make sure that AAUDIO_CALLBACK_RESULT_STOP works.
 
diff --git a/media/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index 7969ed0..3bb09d2 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -1059,11 +1059,11 @@
     return af->getPrimaryOutputFrameCount();
 }
 
-status_t AudioSystem::setLowRamDevice(bool isLowRamDevice)
+status_t AudioSystem::setLowRamDevice(bool isLowRamDevice, int64_t totalMemory)
 {
     const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
     if (af == 0) return PERMISSION_DENIED;
-    return af->setLowRamDevice(isLowRamDevice);
+    return af->setLowRamDevice(isLowRamDevice, totalMemory);
 }
 
 void AudioSystem::clearAudioConfigCache()
diff --git a/media/libaudioclient/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp
index 6507a5c..ae9c96f 100644
--- a/media/libaudioclient/IAudioFlinger.cpp
+++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -707,14 +707,18 @@
         return reply.readInt64();
     }
 
-    virtual status_t setLowRamDevice(bool isLowRamDevice)
+    virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) override
     {
         Parcel data, reply;
-        data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
-        data.writeInt32((int) isLowRamDevice);
-        remote()->transact(SET_LOW_RAM_DEVICE, data, &reply);
-        return reply.readInt32();
+
+        static_assert(NO_ERROR == 0, "NO_ERROR must be 0");
+        return data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor())
+                ?: data.writeInt32((int) isLowRamDevice)
+                ?: data.writeInt64(totalMemory)
+                ?: remote()->transact(SET_LOW_RAM_DEVICE, data, &reply)
+                ?: reply.readInt32();
     }
+
     virtual status_t listAudioPorts(unsigned int *num_ports,
                                     struct audio_port *ports)
     {
@@ -1281,8 +1285,13 @@
         } break;
         case SET_LOW_RAM_DEVICE: {
             CHECK_INTERFACE(IAudioFlinger, data, reply);
-            bool isLowRamDevice = data.readInt32() != 0;
-            reply->writeInt32(setLowRamDevice(isLowRamDevice));
+            int32_t isLowRamDevice;
+            int64_t totalMemory;
+            const status_t status =
+                    data.readInt32(&isLowRamDevice) ?:
+                    data.readInt64(&totalMemory) ?:
+                    setLowRamDevice(isLowRamDevice != 0, totalMemory);
+            (void)reply->writeInt32(status);
             return NO_ERROR;
         } break;
         case LIST_AUDIO_PORTS: {
diff --git a/media/libaudioclient/include/media/AudioSystem.h b/media/libaudioclient/include/media/AudioSystem.h
index e452837..f7c3d03 100644
--- a/media/libaudioclient/include/media/AudioSystem.h
+++ b/media/libaudioclient/include/media/AudioSystem.h
@@ -285,7 +285,7 @@
     static uint32_t getPrimaryOutputSamplingRate();
     static size_t getPrimaryOutputFrameCount();
 
-    static status_t setLowRamDevice(bool isLowRamDevice);
+    static status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory);
 
     // Check if hw offload is possible for given format, stream type, sample rate,
     // bit rate, duration, video and streaming or offload property is enabled
diff --git a/media/libaudioclient/include/media/IAudioFlinger.h b/media/libaudioclient/include/media/IAudioFlinger.h
index 472a3da..e8d405b 100644
--- a/media/libaudioclient/include/media/IAudioFlinger.h
+++ b/media/libaudioclient/include/media/IAudioFlinger.h
@@ -454,8 +454,9 @@
 
     // Intended for AudioService to inform AudioFlinger of device's low RAM attribute,
     // and should be called at most once.  For a definition of what "low RAM" means, see
-    // android.app.ActivityManager.isLowRamDevice().
-    virtual status_t setLowRamDevice(bool isLowRamDevice) = 0;
+    // android.app.ActivityManager.isLowRamDevice().  The totalMemory parameter
+    // is obtained from android.app.ActivityManager.MemoryInfo.totalMem.
+    virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) = 0;
 
     /* List available audio ports and their attributes */
     virtual status_t listAudioPorts(unsigned int *num_ports,
diff --git a/media/libmedia/MediaCodecBuffer.cpp b/media/libmedia/MediaCodecBuffer.cpp
index 59d6164..68ae3ea 100644
--- a/media/libmedia/MediaCodecBuffer.cpp
+++ b/media/libmedia/MediaCodecBuffer.cpp
@@ -21,15 +21,13 @@
 #include <media/MediaCodecBuffer.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/foundation/MediaBufferBase.h>
 
 namespace android {
 
 MediaCodecBuffer::MediaCodecBuffer(const sp<AMessage> &format, const sp<ABuffer> &buffer)
     : mMeta(new AMessage),
       mFormat(format),
-      mBuffer(buffer),
-      mMediaBufferBase(nullptr) {
+      mBuffer(buffer) {
 }
 
 // ABuffer-like interface
@@ -58,20 +56,6 @@
     return OK;
 }
 
-MediaBufferBase *MediaCodecBuffer::getMediaBufferBase() {
-    if (mMediaBufferBase != NULL) {
-        mMediaBufferBase->add_ref();
-    }
-    return mMediaBufferBase;
-}
-
-void MediaCodecBuffer::setMediaBufferBase(MediaBufferBase *mediaBuffer) {
-    if (mMediaBufferBase != NULL) {
-        mMediaBufferBase->release();
-    }
-    mMediaBufferBase = mediaBuffer;
-}
-
 sp<AMessage> MediaCodecBuffer::meta() {
     return mMeta;
 }
diff --git a/media/libmedia/include/media/CounterMetric.h b/media/libmedia/include/media/CounterMetric.h
new file mode 100644
index 0000000..f39ca7c
--- /dev/null
+++ b/media/libmedia/include/media/CounterMetric.h
@@ -0,0 +1,90 @@
+/*
+ * 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 ANDROID_COUNTER_METRIC_H_
+#define ANDROID_COUNTER_METRIC_H_
+
+#include <media/MediaAnalyticsItem.h>
+#include <utils/Log.h>
+
+namespace android {
+
+
+// The CounterMetric class is used to hold counts of operations or events.
+// A CounterMetric can break down counts by a dimension specified by the
+// application. E.g. an application may want to track counts broken out by
+// error code or the size of some parameter.
+//
+// Example:
+//
+//   CounterMetric<status_t> workCounter;
+//   workCounter("workCounterName", "result_status");
+//
+//   status_t err = DoWork();
+//
+//   // Increments the number of times called with the given error code.
+//   workCounter.Increment(err);
+//
+//   std::map<int, int64_t> values;
+//    metric.ExportValues(
+//        [&] (int attribute_value, int64_t value) {
+//             values[attribute_value] = value;
+//        });
+//
+//   // Do something with the exported stat.
+//
+template<typename AttributeType>
+class CounterMetric {
+ public:
+  // Instantiate the counter with the given metric name and
+  // attribute names. |attribute_names| must not be null.
+  CounterMetric(
+      const std::string& metric_name,
+      const std::string& attribute_name)
+          : metric_name_(metric_name),
+            attribute_name_(attribute_name) {}
+
+  // Increment the count of times the operation occurred with this
+  // combination of attributes.
+  void Increment(AttributeType attribute) {
+    if (values_.find(attribute) == values_.end()) {
+      values_[attribute] = 1;
+    } else {
+      values_[attribute] = values_[attribute] + 1;
+    }
+  };
+
+  // Export the metrics to the provided |function|. Each value for Attribute
+  // has a separate count. As such, |function| will be called once per value
+  // of Attribute.
+  void ExportValues(
+      std::function<void (const AttributeType&,
+                          const int64_t count)> function) {
+    for (auto it = values_.begin(); it != values_.end(); it++) {
+      function(it->first, it->second);
+    }
+  }
+
+  const std::string& metric_name() { return metric_name_; };
+
+ private:
+  const std::string metric_name_;
+  const std::string attribute_name_;
+  std::map<AttributeType, int64_t> values_;
+};
+
+}  // namespace android
+
+#endif  // ANDROID_COUNTER_METRIC_H_
diff --git a/media/libmedia/include/media/DrmHal.h b/media/libmedia/include/media/DrmHal.h
index 1a0553e..f2b25cd 100644
--- a/media/libmedia/include/media/DrmHal.h
+++ b/media/libmedia/include/media/DrmHal.h
@@ -23,6 +23,7 @@
 #include <android/hardware/drm/1.0/IDrmPluginListener.h>
 #include <android/hardware/drm/1.0/IDrmFactory.h>
 
+#include <media/CounterMetric.h>
 #include <media/IDrm.h>
 #include <media/IDrmClient.h>
 #include <media/MediaAnalyticsItem.h>
@@ -180,6 +181,8 @@
     sp<IDrmPlugin> mPlugin;
     sp<drm::V1_1::IDrmPlugin> mPluginV1_1;
 
+    CounterMetric<status_t> mOpenSessionCounter;
+
     Vector<Vector<uint8_t>> mOpenSessions;
     void closeOpenSessions();
 
diff --git a/media/libmedia/include/media/MediaBufferHolder.h b/media/libmedia/include/media/MediaBufferHolder.h
new file mode 100644
index 0000000..e8e2c4b
--- /dev/null
+++ b/media/libmedia/include/media/MediaBufferHolder.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef MEDIA_BUFFER_HOLDER_H_
+
+#define MEDIA_BUFFER_HOLDER_H_
+
+#include <media/stagefright/MediaBuffer.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct MediaBufferHolder : public RefBase {
+    MediaBufferHolder(MediaBuffer* buffer)
+        : mMediaBuffer(buffer) {
+        if (mMediaBuffer != nullptr) {
+            mMediaBuffer->add_ref();
+        }
+    }
+
+    virtual ~MediaBufferHolder() {
+        if (mMediaBuffer != nullptr) {
+            mMediaBuffer->release();
+        }
+    }
+
+    MediaBuffer* mediaBuffer() { return mMediaBuffer; }
+
+private:
+    MediaBuffer* const mMediaBuffer;
+};
+
+}  // android
+
+#endif  // MEDIA_BUFFER_HOLDER_H_
diff --git a/media/libmedia/include/media/MediaCodecBuffer.h b/media/libmedia/include/media/MediaCodecBuffer.h
index 501c00b..2c16fba 100644
--- a/media/libmedia/include/media/MediaCodecBuffer.h
+++ b/media/libmedia/include/media/MediaCodecBuffer.h
@@ -50,9 +50,6 @@
     size_t offset() const;
     // Default implementation calls ABuffer::setRange() and returns OK.
     virtual status_t setRange(size_t offset, size_t size);
-    // TODO: These can be removed if we finish replacing all MediaBuffer's.
-    MediaBufferBase *getMediaBufferBase();
-    void setMediaBufferBase(MediaBufferBase *mediaBuffer);
 
     // TODO: Specify each field for meta/format.
     sp<AMessage> meta();
@@ -66,7 +63,6 @@
     const sp<AMessage> mMeta;
     sp<AMessage> mFormat;
     const sp<ABuffer> mBuffer;
-    MediaBufferBase *mMediaBufferBase;
 };
 
 }  // namespace android
diff --git a/media/libmedia/nuplayer2/Android.bp b/media/libmedia/nuplayer2/Android.bp
index d609ba0..700c840 100644
--- a/media/libmedia/nuplayer2/Android.bp
+++ b/media/libmedia/nuplayer2/Android.bp
@@ -22,7 +22,6 @@
     ],
 
     include_dirs: [
-        "frameworks/av/media/libmedia/include",
         "frameworks/av/media/libstagefright",
         "frameworks/av/media/libstagefright/httplive",
         "frameworks/av/media/libstagefright/include",
@@ -54,6 +53,10 @@
         "libpowermanager",
     ],
 
+    static_libs: [
+        "libmedia_helper",
+    ],
+
     name: "libstagefright_nuplayer2",
 
     tags: ["eng"],
diff --git a/media/libmedia/nuplayer2/GenericSource.cpp b/media/libmedia/nuplayer2/GenericSource.cpp
index c0b81fb..6d5b14d 100644
--- a/media/libmedia/nuplayer2/GenericSource.cpp
+++ b/media/libmedia/nuplayer2/GenericSource.cpp
@@ -24,6 +24,7 @@
 #include <binder/IServiceManager.h>
 #include <cutils/properties.h>
 #include <media/DataSource.h>
+#include <media/MediaBufferHolder.h>
 #include <media/IMediaExtractorService.h>
 #include <media/MediaHTTPService.h>
 #include <media/MediaExtractor.h>
@@ -1167,8 +1168,7 @@
 
         // data is already provided in the buffer
         ab = new ABuffer(NULL, mb->range_length());
-        mb->add_ref();
-        ab->setMediaBufferBase(mb);
+        ab->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mb));
 
         // Modular DRM: Required b/c of the above add_ref.
         // If ref>0, there must be an observer, or it'll crash at release().
diff --git a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
index 715d6fc..a436592 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
@@ -28,6 +28,7 @@
 #include "NuPlayer2Source.h"
 
 #include <cutils/properties.h>
+#include <media/MediaBufferHolder.h>
 #include <media/MediaCodecBuffer.h>
 #include <media/NdkMediaCodec.h>
 #include <media/NdkWrapper.h>
@@ -1081,16 +1082,17 @@
                 memcpy(codecBuffer->data(), buffer->data(), buffer->size());
             } else { // No buffer->data()
                 //Modular DRM
-                mediaBuf = (MediaBuffer*)buffer->getMediaBufferBase();
+                sp<RefBase> holder;
+                if (buffer->meta()->findObject("mediaBufferHolder", &holder)) {
+                    mediaBuf = (holder != nullptr) ?
+                        static_cast<MediaBufferHolder*>(holder.get())->mediaBuffer() : nullptr;
+                }
                 if (mediaBuf != NULL) {
                     codecBuffer->setRange(0, mediaBuf->size());
                     memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->size());
 
                     sp<MetaData> meta_data = mediaBuf->meta_data();
                     cryptInfo = AMediaCodecCryptoInfoWrapper::Create(meta_data);
-
-                    // since getMediaBuffer() has incremented the refCount
-                    mediaBuf->release();
                 } else { // No mediaBuf
                     ALOGE("onInputBufferFetched: buffer->data()/mediaBuf are NULL for %p",
                             buffer.get());
diff --git a/media/libmediaextractor/Android.bp b/media/libmediaextractor/Android.bp
index 18573db..83fea39 100644
--- a/media/libmediaextractor/Android.bp
+++ b/media/libmediaextractor/Android.bp
@@ -15,6 +15,7 @@
     ],
 
     shared_libs: [
+        "libbinder",
         "libstagefright_foundation",
         "libutils",
         "libcutils",
@@ -23,6 +24,8 @@
 
     srcs: [
         "DataSource.cpp",
+        "MediaBuffer.cpp",
+        "MediaBufferGroup.cpp",
         "MediaSource.cpp",
         "MediaExtractor.cpp",
     ],
diff --git a/media/libstagefright/foundation/MediaBuffer.cpp b/media/libmediaextractor/MediaBuffer.cpp
similarity index 98%
rename from media/libstagefright/foundation/MediaBuffer.cpp
rename to media/libmediaextractor/MediaBuffer.cpp
index 95951dd..28fc760 100644
--- a/media/libstagefright/foundation/MediaBuffer.cpp
+++ b/media/libmediaextractor/MediaBuffer.cpp
@@ -26,8 +26,6 @@
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MetaData.h>
 
-#include <ui/GraphicBuffer.h>
-
 namespace android {
 
 /* static */
diff --git a/media/libstagefright/foundation/MediaBufferGroup.cpp b/media/libmediaextractor/MediaBufferGroup.cpp
similarity index 100%
rename from media/libstagefright/foundation/MediaBufferGroup.cpp
rename to media/libmediaextractor/MediaBufferGroup.cpp
diff --git a/media/libstagefright/include/media/stagefright/MediaBuffer.h b/media/libmediaextractor/include/media/stagefright/MediaBuffer.h
similarity index 97%
rename from media/libstagefright/include/media/stagefright/MediaBuffer.h
rename to media/libmediaextractor/include/media/stagefright/MediaBuffer.h
index 367a467..2b51081 100644
--- a/media/libstagefright/include/media/stagefright/MediaBuffer.h
+++ b/media/libmediaextractor/include/media/stagefright/MediaBuffer.h
@@ -20,7 +20,6 @@
 
 #include <atomic>
 #include <list>
-#include <media/stagefright/foundation/MediaBufferBase.h>
 
 #include <pthread.h>
 
@@ -48,7 +47,7 @@
     MediaBufferObserver &operator=(const MediaBufferObserver &);
 };
 
-class MediaBuffer : public MediaBufferBase {
+class MediaBuffer {
 public:
     // allocations larger than or equal to this will use shared memory.
     static const size_t kSharedMemThreshold = 64 * 1024;
@@ -72,11 +71,11 @@
     //
     // If no MediaBufferGroup is set, the local reference count must be zero
     // when called, whereupon the MediaBuffer is deleted.
-    virtual void release();
+    void release();
 
     // Increments the local reference count.
     // Use only when MediaBufferGroup is set.
-    virtual void add_ref();
+    void add_ref();
 
     void *data() const;
     size_t size() const;
@@ -144,7 +143,7 @@
         return mObserver != nullptr;
     }
 
-    virtual ~MediaBuffer();
+    ~MediaBuffer();
 
     sp<IMemory> mMemory;
 
diff --git a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h b/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
similarity index 100%
rename from media/libstagefright/include/media/stagefright/MediaBufferGroup.h
rename to media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
index 423dfb8..2e7efad 100644
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ b/media/libmediametrics/MediaAnalyticsItem.cpp
@@ -722,7 +722,7 @@
 
 
 std::string MediaAnalyticsItem::toString() {
-   return toString(-1);
+   return toString(PROTO_LAST);
 }
 
 std::string MediaAnalyticsItem::toString(int version) {
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index 33c3094..511f46f 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -24,6 +24,7 @@
 #include <binder/IServiceManager.h>
 #include <cutils/properties.h>
 #include <media/DataSource.h>
+#include <media/MediaBufferHolder.h>
 #include <media/MediaExtractor.h>
 #include <media/MediaSource.h>
 #include <media/IMediaHTTPService.h>
@@ -1160,14 +1161,12 @@
 
         // data is already provided in the buffer
         ab = new ABuffer(NULL, mb->range_length());
-        mb->add_ref();
-        ab->setMediaBufferBase(mb);
+        ab->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mb));
 
         // Modular DRM: Required b/c of the above add_ref.
         // If ref>0, there must be an observer, or it'll crash at release().
         // TODO: MediaBuffer might need to be revised to ease such need.
         mb->setObserver(this);
-        // setMediaBufferBase() interestingly doesn't increment the ref count on its own.
         // Extra increment (since we want to keep mb alive and attached to ab beyond this function
         // call. This is to counter the effect of mb->release() towards the end.
         mb->add_ref();
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index 1b02adb..1aca96c 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -29,6 +29,7 @@
 
 #include <cutils/properties.h>
 #include <media/ICrypto.h>
+#include <media/MediaBufferHolder.h>
 #include <media/MediaCodecBuffer.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
@@ -1061,16 +1062,17 @@
                 memcpy(codecBuffer->data(), buffer->data(), buffer->size());
             } else { // No buffer->data()
                 //Modular DRM
-                mediaBuf = (MediaBuffer*)buffer->getMediaBufferBase();
+                sp<RefBase> holder;
+                if (buffer->meta()->findObject("mediaBufferHolder", &holder)) {
+                    mediaBuf = (holder != nullptr) ?
+                        static_cast<MediaBufferHolder*>(holder.get())->mediaBuffer() : nullptr;
+                }
                 if (mediaBuf != NULL) {
                     codecBuffer->setRange(0, mediaBuf->size());
                     memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->size());
 
                     sp<MetaData> meta_data = mediaBuf->meta_data();
                     cryptInfo = NuPlayerDrm::getSampleCryptoInfo(meta_data);
-
-                    // since getMediaBuffer() has incremented the refCount
-                    mediaBuf->release();
                 } else { // No mediaBuf
                     ALOGE("onInputBufferFetched: buffer->data()/mediaBuf are NULL for %p",
                             buffer.get());
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 14ea2a8..0ded5be 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -43,6 +43,7 @@
 #include <media/stagefright/PersistentSurface.h>
 #include <media/stagefright/SurfaceUtils.h>
 #include <media/hardware/HardwareAPI.h>
+#include <media/MediaBufferHolder.h>
 #include <media/OMXBuffer.h>
 #include <media/omx/1.0/WOmxNode.h>
 
@@ -5606,7 +5607,7 @@
     // by this "MediaBuffer" object. Now that the OMX component has
     // told us that it's done with the input buffer, we can decrement
     // the mediaBuffer's reference count.
-    info->mData->setMediaBufferBase(NULL);
+    info->mData->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
 
     PortMode mode = getPortMode(kPortIndexInput);
 
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index eea9c78..ebc9b0a 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -665,16 +665,16 @@
     int32_t flags = 0;
     int32_t tmp = 0;
     if (buffer->meta()->findInt32("eos", &tmp) && tmp) {
-        flags |= C2BufferPack::FLAG_END_OF_STREAM;
+        flags |= C2FrameData::FLAG_END_OF_STREAM;
         ALOGV("input EOS");
     }
     if (buffer->meta()->findInt32("csd", &tmp) && tmp) {
-        flags |= C2BufferPack::FLAG_CODEC_CONFIG;
+        flags |= C2FrameData::FLAG_CODEC_CONFIG;
     }
     std::unique_ptr<C2Work> work(new C2Work);
-    work->input.flags = (C2BufferPack::flags_t)flags;
+    work->input.flags = (C2FrameData::flags_t)flags;
     work->input.ordinal.timestamp = timeUs;
-    work->input.ordinal.frame_index = mFrameIndex++;
+    work->input.ordinal.frameIndex = mFrameIndex++;
     work->input.buffers.clear();
     {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
@@ -724,7 +724,7 @@
         return OK;
     }
 
-    std::list<C2ConstGraphicBlock> blocks = c2Buffer->data().graphicBlocks();
+    std::vector<C2ConstGraphicBlock> blocks = c2Buffer->data().graphicBlocks();
     if (blocks.size() != 1u) {
         ALOGE("# of graphic blocks expected to be 1, but %zu", blocks.size());
         return UNKNOWN_ERROR;
@@ -881,7 +881,7 @@
         }
 
         const std::unique_ptr<C2Worklet> &worklet = work->worklets.front();
-        if (worklet->output.ordinal.frame_index < mFirstValidFrameIndex) {
+        if ((worklet->output.ordinal.frameIndex - mFirstValidFrameIndex.load()).peek() < 0) {
             // Discard frames from previous generation.
             continue;
         }
@@ -897,7 +897,7 @@
         if (buffer) {
             // TODO: transfer infos() into buffer metadata
         }
-        for (const auto &info : worklet->output.infos) {
+        for (const auto &info : worklet->output.configUpdate) {
             if (info->coreIndex() == C2StreamCsdInfo::output::CORE_INDEX) {
                 ALOGV("csd found");
                 csdInfo = static_cast<const C2StreamCsdInfo::output *>(info.get());
@@ -905,7 +905,7 @@
         }
 
         int32_t flags = 0;
-        if (worklet->output.flags & C2BufferPack::FLAG_END_OF_STREAM) {
+        if (worklet->output.flags & C2FrameData::FLAG_END_OF_STREAM) {
             flags |= MediaCodec::BUFFER_FLAG_EOS;
             ALOGV("output EOS");
         }
@@ -914,7 +914,7 @@
         if (csdInfo != nullptr) {
             Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
             if ((*buffers)->registerCsd(csdInfo, &index, &outBuffer)) {
-                outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp);
+                outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp.peek());
                 outBuffer->meta()->setInt32("flags", flags | MediaCodec::BUFFER_FLAG_CODECCONFIG);
                 ALOGV("csd index = %zu", index);
 
@@ -947,7 +947,7 @@
             }
         }
 
-        outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp);
+        outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp.peek());
         outBuffer->meta()->setInt32("flags", flags);
         ALOGV("index = %zu", index);
         mCallback->onOutputBufferAvailable(index, outBuffer);
diff --git a/media/libstagefright/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index 8bd0a51..04d83af 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -23,6 +23,7 @@
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/Surface.h>
 #include <media/ICrypto.h>
+#include <media/MediaBufferHolder.h>
 #include <media/MediaCodecBuffer.h>
 #include <media/MediaSource.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -739,7 +740,8 @@
             if (mIsVideo) {
                 // video encoder will release MediaBuffer when done
                 // with underlying data.
-                inbuf->setMediaBufferBase(mbuf);
+                inbuf->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mbuf));
+                mbuf->release();
             } else {
                 mbuf->release();
             }
diff --git a/media/libstagefright/codec2/SimpleC2Component.cpp b/media/libstagefright/codec2/SimpleC2Component.cpp
index 4d75a31..c3ae00e 100644
--- a/media/libstagefright/codec2/SimpleC2Component.cpp
+++ b/media/libstagefright/codec2/SimpleC2Component.cpp
@@ -342,7 +342,7 @@
             return;
         }
     }
-    if (work->worklets_processed != 0u) {
+    if (work->workletsProcessed != 0u) {
         Mutexed<ExecState>::Locked state(mExecState);
         ALOGV("returning this work");
         state->mListener->onWorkDone_nb(shared_from_this(), vec(work));
@@ -351,7 +351,7 @@
         std::unique_ptr<C2Work> unexpected;
         {
             Mutexed<PendingWork>::Locked pending(mPendingWork);
-            uint64_t frameIndex = work->input.ordinal.frame_index;
+            uint64_t frameIndex = work->input.ordinal.frameIndex.peeku();
             if (pending->count(frameIndex) != 0) {
                 unexpected = std::move(pending->at(frameIndex));
                 pending->erase(frameIndex);
diff --git a/media/libstagefright/codec2/include/C2.h b/media/libstagefright/codec2/include/C2.h
index fd99cce..442c89e 100644
--- a/media/libstagefright/codec2/include/C2.h
+++ b/media/libstagefright/codec2/include/C2.h
@@ -210,6 +210,219 @@
     } \
     DEFINE_OTHER_COMPARISON_OPERATORS(type)
 
+template<typename T, typename B>
+class C2_HIDE c2_cntr_t;
+
+/// \cond INTERNAL
+
+/// \defgroup utils_internal
+/// @{
+
+template<typename T>
+struct C2_HIDE _c2_cntr_compat_helper {
+    template<typename U, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+    __attribute__((no_sanitize("integer")))
+    inline static constexpr T get(const U &value) {
+        return T(value);
+    }
+
+    template<typename U, typename E=typename std::enable_if<(sizeof(U) >= sizeof(T))>::type>
+    __attribute__((no_sanitize("integer")))
+    inline static constexpr T get(const c2_cntr_t<U, void> &value) {
+        return T(value.mValue);
+    }
+};
+
+/// @}
+
+/// \endcond
+
+/**
+ * Integral counter type.
+ *
+ * This is basically an unsigned integral type that is NEVER checked for overflow/underflow - and
+ * comparison operators are redefined.
+ *
+ * \note Comparison of counter types is not fully transitive, e.g.
+ * it could be that a > b > c but a !> c.
+ * std::less<>, greater<>, less_equal<> and greater_equal<> specializations yield total ordering,
+ * but may not match semantic ordering of the values.
+ *
+ * Technically: counter types represent integer values: A * 2^N + value, where A can be arbitrary.
+ * This makes addition, subtraction, multiplication (as well as bitwise operations) well defined.
+ * However, division is in general not well defined, as the result may depend on A. This is also
+ * true for logical operators and boolean conversion.
+ *
+ * Even though well defined, bitwise operators are not implemented for counter types as they are not
+ * meaningful.
+ */
+template<typename T, typename B=typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value>::type>
+class C2_HIDE c2_cntr_t {
+    using compat = _c2_cntr_compat_helper<T>;
+
+    T mValue;
+    constexpr static T HALF_RANGE = T(~0) ^ (T(~0) >> 1);
+
+    template<typename U>
+    friend struct _c2_cntr_compat_helper;
+public:
+
+    /**
+     * Default constructor. Initialized counter to 0.
+     */
+    inline constexpr c2_cntr_t() : mValue(T(0)) {}
+
+    /**
+     * Construct from a compatible type.
+     */
+    template<typename U>
+    inline constexpr c2_cntr_t(const U &value) : mValue(compat::get(value)) {}
+
+    /**
+     * Peek as underlying signed type.
+     */
+    __attribute__((no_sanitize("integer")))
+    inline constexpr typename std::make_signed<T>::type peek() const {
+        return static_cast<typename std::make_signed<T>::type>(mValue);
+    }
+
+    /**
+     * Peek as underlying unsigned type.
+     */
+    inline constexpr T peeku() const {
+        return mValue;
+    }
+
+    /**
+     * Peek as long long - e.g. for printing.
+     */
+    __attribute__((no_sanitize("integer")))
+    inline constexpr long long peekll() const {
+        return (long long)mValue;
+    }
+
+    /**
+     * Peek as unsigned long long - e.g. for printing.
+     */
+    __attribute__((no_sanitize("integer")))
+    inline constexpr unsigned long long peekull() const {
+        return (unsigned long long)mValue;
+    }
+
+    /**
+     * Convert to a smaller counter type. This is always safe.
+     */
+    template<typename U, typename E=typename std::enable_if<sizeof(U) < sizeof(T)>::type>
+    inline operator c2_cntr_t<U>() {
+        return c2_cntr_t<U>(mValue);
+    }
+
+    /**
+     * Arithmetic operators
+     */
+
+#define DEFINE_C2_CNTR_BINARY_OP(attrib, op, op_assign) \
+    template<typename U> \
+    attrib inline c2_cntr_t<T>& operator op_assign(const U &value) { \
+        mValue op_assign compat::get(value); \
+        return *this; \
+    } \
+    \
+    template<typename U, typename E=decltype(compat::get(U(0)))> \
+    attrib inline constexpr c2_cntr_t<T> operator op(const U &value) const { \
+        return c2_cntr_t<T>(mValue op compat::get(value)); \
+    } \
+    \
+    template<typename U, typename E=typename std::enable_if<sizeof(U) < sizeof(T)>::type> \
+    attrib inline constexpr c2_cntr_t<U> operator op(const c2_cntr_t<U> &value) const { \
+        return c2_cntr_t<U>(U(mValue) op value.peeku()); \
+    }
+
+#define DEFINE_C2_CNTR_UNARY_OP(attrib, op) \
+    attrib inline constexpr c2_cntr_t<T> operator op() const { \
+        return c2_cntr_t<T>(op mValue); \
+    }
+
+#define DEFINE_C2_CNTR_CREMENT_OP(attrib, op) \
+    attrib inline c2_cntr_t<T> &operator op() { \
+        op mValue; \
+        return *this; \
+    } \
+    attrib inline c2_cntr_t<T> operator op(int) { \
+        return c2_cntr_t<T, void>(mValue op); \
+    }
+
+    DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), +, +=)
+    DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), -, -=)
+    DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), *, *=)
+
+    DEFINE_C2_CNTR_UNARY_OP(__attribute__((no_sanitize("integer"))), -)
+    DEFINE_C2_CNTR_UNARY_OP(__attribute__((no_sanitize("integer"))), +)
+
+    DEFINE_C2_CNTR_CREMENT_OP(__attribute__((no_sanitize("integer"))), ++)
+    DEFINE_C2_CNTR_CREMENT_OP(__attribute__((no_sanitize("integer"))), --)
+
+    template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type>
+    __attribute__((no_sanitize("integer")))
+    inline constexpr c2_cntr_t<T> operator<<(const U &value) const {
+        return c2_cntr_t<T>(mValue << value);
+    }
+
+    template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type>
+    __attribute__((no_sanitize("integer")))
+    inline c2_cntr_t<T> &operator<<=(const U &value) {
+        mValue <<= value;
+        return *this;
+    }
+
+    /**
+     * Comparison operators
+     */
+    __attribute__((no_sanitize("integer")))
+    inline constexpr bool operator<=(const c2_cntr_t<T> &other) const {
+        return T(other.mValue - mValue) < HALF_RANGE;
+    }
+
+    __attribute__((no_sanitize("integer")))
+    inline constexpr bool operator>=(const c2_cntr_t<T> &other) const {
+        return T(mValue - other.mValue) < HALF_RANGE;
+    }
+
+    inline constexpr bool operator==(const c2_cntr_t<T> &other) const {
+        return mValue == other.mValue;
+    }
+
+    inline constexpr bool operator!=(const c2_cntr_t<T> &other) const {
+        return !(*this == other);
+    }
+
+    inline constexpr bool operator<(const c2_cntr_t<T> &other) const {
+        return *this <= other && *this != other;
+    }
+
+    inline constexpr bool operator>(const c2_cntr_t<T> &other) const {
+        return *this >= other && *this != other;
+    }
+};
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator+(const U &a, const c2_cntr_t<T> &b) {
+    return b + a;
+}
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator-(const U &a, const c2_cntr_t<T> &b) {
+    return c2_cntr_t<T>(a) - b;
+}
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator*(const U &a, const c2_cntr_t<T> &b) {
+    return b * a;
+}
+
+typedef c2_cntr_t<uint32_t> c2_cntr32_t; /** 32-bit counter type */
+typedef c2_cntr_t<uint64_t> c2_cntr64_t; /** 64-bit counter type */
+
 /// \cond INTERNAL
 
 /// \defgroup utils_internal
@@ -326,4 +539,30 @@
 } // namespace android
 #endif
 
+#include <functional>
+template<typename T>
+struct std::less<::android::c2_cntr_t<T>> {
+    constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+        return lh.peeku() < rh.peeku();
+    }
+};
+template<typename T>
+struct std::less_equal<::android::c2_cntr_t<T>> {
+    constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+        return lh.peeku() <= rh.peeku();
+    }
+};
+template<typename T>
+struct std::greater<::android::c2_cntr_t<T>> {
+    constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+        return lh.peeku() > rh.peeku();
+    }
+};
+template<typename T>
+struct std::greater_equal<::android::c2_cntr_t<T>> {
+    constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+        return lh.peeku() >= rh.peeku();
+    }
+};
+
 #endif  // C2_H_
diff --git a/media/libstagefright/codec2/include/C2Buffer.h b/media/libstagefright/codec2/include/C2Buffer.h
index cd90978..2ca8222 100644
--- a/media/libstagefright/codec2/include/C2Buffer.h
+++ b/media/libstagefright/codec2/include/C2Buffer.h
@@ -679,14 +679,14 @@
      *
      * \param size    number of bytes to share
      * \param fence   fence to be used for the section
-     * \param blocks  list where the blocks of the section are appended to
+     * \param blocks  vector where the blocks of the section are appended to
      *
      * \retval C2_OK            the portion was successfully shared
      * \retval C2_NO_MEMORY     not enough memory to share the portion
      * \retval C2_TIMED_OUT     the operation timed out (unexpected)
      * \retval C2_CORRUPTED     some unknown error prevented sharing the data (unexpected)
      */
-    c2_status_t share(size_t size, C2Fence fence, std::list<C2ConstLinearBlock> &blocks);
+    c2_status_t share(size_t size, C2Fence fence, std::vector<C2ConstLinearBlock> &blocks);
 
     /**
      * Returns the beginning offset of this segment from the start of this circular block.
@@ -1203,14 +1203,14 @@
      * \return a constant list of const linear blocks of this buffer.
      * \retval empty list if this buffer does not contain linear block(s).
      */
-    const std::list<C2ConstLinearBlock> linearBlocks() const;
+    const std::vector<C2ConstLinearBlock> linearBlocks() const;
 
     /**
      * Gets the graphic blocks of this buffer.
      * \return a constant list of const graphic blocks of this buffer.
      * \retval empty list if this buffer does not contain graphic block(s).
      */
-    const std::list<C2ConstGraphicBlock> graphicBlocks() const;
+    const std::vector<C2ConstGraphicBlock> graphicBlocks() const;
 
 private:
     class Impl;
@@ -1218,8 +1218,8 @@
 
 protected:
     // no public constructor
-    explicit C2BufferData(const std::list<C2ConstLinearBlock> &blocks);
-    explicit C2BufferData(const std::list<C2ConstGraphicBlock> &blocks);
+    explicit C2BufferData(const std::vector<C2ConstLinearBlock> &blocks);
+    explicit C2BufferData(const std::vector<C2ConstGraphicBlock> &blocks);
 };
 
 /**
@@ -1301,7 +1301,7 @@
      *
      * \return a constant list of info objects associated with this buffer.
      */
-    const std::list<std::shared_ptr<const C2Info>> infos() const;
+    const std::vector<std::shared_ptr<const C2Info>> info() const;
 
     /**
      * Attaches (or updates) an (existing) metadata for this buffer.
@@ -1328,8 +1328,8 @@
 
 protected:
     // no public constructor
-    explicit C2Buffer(const std::list<C2ConstLinearBlock> &blocks);
-    explicit C2Buffer(const std::list<C2ConstGraphicBlock> &blocks);
+    explicit C2Buffer(const std::vector<C2ConstLinearBlock> &blocks);
+    explicit C2Buffer(const std::vector<C2ConstGraphicBlock> &blocks);
 
 private:
     class Impl;
diff --git a/media/libstagefright/codec2/include/C2Component.h b/media/libstagefright/codec2/include/C2Component.h
index a2168a0..e023db4 100644
--- a/media/libstagefright/codec2/include/C2Component.h
+++ b/media/libstagefright/codec2/include/C2Component.h
@@ -682,7 +682,7 @@
      */
     virtual c2_status_t reset() { return C2_OK; }
 
-    virtual c2_status_t parseFrame(C2BufferPack &frame);
+    virtual c2_status_t parseFrame(C2FrameData &frame);
 
     virtual ~C2FrameInfoParser() = default;
 };
diff --git a/media/libstagefright/codec2/include/C2Param.h b/media/libstagefright/codec2/include/C2Param.h
index 9785c87..e2df62d 100644
--- a/media/libstagefright/codec2/include/C2Param.h
+++ b/media/libstagefright/codec2/include/C2Param.h
@@ -721,6 +721,10 @@
 
     DEFINE_OTHER_COMPARISON_OPERATORS(C2ParamField)
 
+protected:
+    inline C2ParamField(C2Param::Index index, uint32_t offset, uint32_t size)
+        : _mIndex(index), _mFieldId(offset, size) {}
+
 private:
     friend struct _C2ParamInspector;
 
@@ -729,24 +733,39 @@
 };
 
 /**
+ * Structure uniquely specifying a field, an array element of a field, or a
+ * parameter in a configuration
+ */
+struct C2ParamOrField : public C2ParamField {
+//public:
+    template<typename S>
+    inline C2ParamOrField(S* param)
+        : C2ParamField(param->index(), 0u, param->size()) {}
+};
+
+/**
  * A shared (union) representation of numeric values
  */
 class C2Value {
 public:
     /// A union of supported primitive types.
     union Primitive {
-        int32_t  i32;   ///< int32_t value
-        uint32_t u32;   ///< uint32_t value
-        int64_t  i64;   ///< int64_t value
-        uint64_t u64;   ///< uint64_t value
-        float    fp;    ///< float value
+        int32_t     i32;   ///< int32_t value
+        uint32_t    u32;   ///< uint32_t value
+        c2_cntr32_t c32;   ///< c2_cntr32_t value
+        int64_t     i64;   ///< int64_t value
+        uint64_t    u64;   ///< uint64_t value
+        c2_cntr64_t c64;   ///< c2_cntr64_t value
+        float       fp;    ///< float value
 
         // constructors - implicit
-        Primitive(int32_t value)  : i32(value) { }
-        Primitive(uint32_t value) : u32(value) { }
-        Primitive(int64_t value)  : i64(value) { }
-        Primitive(uint64_t value) : u64(value) { }
-        Primitive(float value)    : fp(value)  { }
+        Primitive(int32_t value)     : i32(value) { }
+        Primitive(uint32_t value)    : u32(value) { }
+        Primitive(c2_cntr32_t value) : c32(value) { }
+        Primitive(int64_t value)     : i64(value) { }
+        Primitive(uint64_t value)    : u64(value) { }
+        Primitive(c2_cntr64_t value) : c64(value) { }
+        Primitive(float value)       : fp(value)  { }
 
         Primitive() : u64(0) { }
 
@@ -759,8 +778,10 @@
         NO_INIT,
         INT32,
         UINT32,
+        CNTR32,
         INT64,
         UINT64,
+        CNTR64,
         FLOAT,
     };
 
@@ -792,12 +813,16 @@
 template<> inline const int64_t &C2Value::Primitive::ref<int64_t>() const { return i64; }
 template<> inline const uint32_t &C2Value::Primitive::ref<uint32_t>() const { return u32; }
 template<> inline const uint64_t &C2Value::Primitive::ref<uint64_t>() const { return u64; }
+template<> inline const c2_cntr32_t &C2Value::Primitive::ref<c2_cntr32_t>() const { return c32; }
+template<> inline const c2_cntr64_t &C2Value::Primitive::ref<c2_cntr64_t>() const { return c64; }
 template<> inline const float &C2Value::Primitive::ref<float>() const { return fp; }
 
 template<> constexpr C2Value::type_t C2Value::typeFor<int32_t>() { return INT32; }
 template<> constexpr C2Value::type_t C2Value::typeFor<int64_t>() { return INT64; }
 template<> constexpr C2Value::type_t C2Value::typeFor<uint32_t>() { return UINT32; }
 template<> constexpr C2Value::type_t C2Value::typeFor<uint64_t>() { return UINT64; }
+template<> constexpr C2Value::type_t C2Value::typeFor<c2_cntr32_t>() { return CNTR32; }
+template<> constexpr C2Value::type_t C2Value::typeFor<c2_cntr64_t>() { return CNTR64; }
 template<> constexpr C2Value::type_t C2Value::typeFor<float>() { return FLOAT; }
 
 /**
@@ -817,8 +842,10 @@
         // primitive types
         INT32   = C2Value::INT32,  ///< 32-bit signed integer
         UINT32  = C2Value::UINT32, ///< 32-bit unsigned integer
+        CNTR32  = C2Value::CNTR32, ///< 32-bit counter
         INT64   = C2Value::INT64,  ///< 64-bit signed integer
         UINT64  = C2Value::UINT64, ///< 64-bit signed integer
+        CNTR64  = C2Value::CNTR64, ///< 64-bit counter
         FLOAT   = C2Value::FLOAT,  ///< 32-bit floating point
 
         // array types
@@ -903,13 +930,15 @@
     // 2) this is at parameter granularity.
 
     // type resolution
-    inline static type_t getType(int32_t*)  { return INT32; }
-    inline static type_t getType(uint32_t*) { return UINT32; }
-    inline static type_t getType(int64_t*)  { return INT64; }
-    inline static type_t getType(uint64_t*) { return UINT64; }
-    inline static type_t getType(float*)    { return FLOAT; }
-    inline static type_t getType(char*)     { return STRING; }
-    inline static type_t getType(uint8_t*)  { return BLOB; }
+    inline static type_t getType(int32_t*)     { return INT32; }
+    inline static type_t getType(uint32_t*)    { return UINT32; }
+    inline static type_t getType(c2_cntr32_t*) { return CNTR32; }
+    inline static type_t getType(int64_t*)     { return INT64; }
+    inline static type_t getType(uint64_t*)    { return UINT64; }
+    inline static type_t getType(c2_cntr64_t*) { return CNTR64; }
+    inline static type_t getType(float*)       { return FLOAT; }
+    inline static type_t getType(char*)        { return STRING; }
+    inline static type_t getType(uint8_t*)     { return BLOB; }
 
     template<typename T,
              class=typename std::enable_if<std::is_enum<T>::value>::type>
@@ -936,8 +965,10 @@
 // non-enumerated integral types.
 DEFINE_NO_NAMED_VALUES_FOR(int32_t)
 DEFINE_NO_NAMED_VALUES_FOR(uint32_t)
+DEFINE_NO_NAMED_VALUES_FOR(c2_cntr32_t)
 DEFINE_NO_NAMED_VALUES_FOR(int64_t)
 DEFINE_NO_NAMED_VALUES_FOR(uint64_t)
+DEFINE_NO_NAMED_VALUES_FOR(c2_cntr64_t)
 DEFINE_NO_NAMED_VALUES_FOR(uint8_t)
 DEFINE_NO_NAMED_VALUES_FOR(char)
 DEFINE_NO_NAMED_VALUES_FOR(float)
diff --git a/media/libstagefright/codec2/include/C2Work.h b/media/libstagefright/codec2/include/C2Work.h
index 105cf81..58a9174 100644
--- a/media/libstagefright/codec2/include/C2Work.h
+++ b/media/libstagefright/codec2/include/C2Work.h
@@ -83,32 +83,64 @@
     kParamIndexWorkOrdinal,
 };
 
+/**
+ * Information for ordering work items on a component port.
+ */
 struct C2WorkOrdinalStruct {
-    uint64_t timestamp;
-    uint64_t frame_index;    // submission ordinal on the initial component
-    uint64_t custom_ordinal; // can be given by the component, e.g. decode order
+//public:
+    c2_cntr64_t timestamp;     /** frame timestamp in microseconds */
+    c2_cntr64_t frameIndex;    /** submission ordinal on the initial component */
+    c2_cntr64_t customOrdinal; /** can be given by the component, e.g. decode order */
 
     DEFINE_AND_DESCRIBE_C2STRUCT(WorkOrdinal)
     C2FIELD(timestamp, "timestamp")
-    C2FIELD(frame_index, "frame-index")
-    C2FIELD(custom_ordinal, "custom-ordinal")
+    C2FIELD(frameIndex, "frame-index")
+    C2FIELD(customOrdinal, "custom-ordinal")
 };
 
-struct C2BufferPack {
+/**
+ * This structure represents a Codec 2.0 frame with its metadata.
+ *
+ * A frame basically consists of an ordered sets of buffers, configuration changes and info buffers
+ * along with some non-configuration metadata.
+ */
+struct C2FrameData {
 //public:
     enum flags_t : uint32_t {
-        FLAG_CODEC_CONFIG  = (1 << 0),
-        FLAG_DROP_FRAME    = (1 << 1),
-        FLAG_END_OF_STREAM = (1 << 2),
+        /**
+         * For input frames: no output frame shall be generated when processing this frame, but
+         * metadata shall still be processed.
+         * For output frames: this frame shall be discarded and but metadata is still valid.
+         */
+        FLAG_DROP_FRAME    = (1 << 0),
+        /**
+         * This frame is the last frame of the current stream. Further frames are part of a new
+         * stream.
+         */
+        FLAG_END_OF_STREAM = (1 << 1),
+        /**
+         * This frame shall be discarded with its metadata.
+         * This flag is only set by components - e.g. as a response to the flush command.
+         */
+        FLAG_DISCARD_FRAME = (1 << 2),
+        /**
+         * This frame contains only codec-specific configuration data, and no actual access unit.
+         *
+         * \deprecated pass codec configuration with using the \todo codec-specific configuration
+         * info together with the access unit.
+         */
+        FLAG_CODEC_CONFIG  = (1u << 31),
     };
 
+    /**
+     * Frame flags */
     flags_t  flags;
     C2WorkOrdinalStruct ordinal;
     std::vector<std::shared_ptr<C2Buffer>> buffers;
     //< for initial work item, these may also come from the parser - if provided
     //< for output buffers, these are the responses to requestedInfos
-    std::list<std::unique_ptr<C2Info>>       infos;
-    std::list<std::shared_ptr<C2InfoBuffer>> infoBuffers;
+    std::vector<std::unique_ptr<C2Param>>      configUpdate;
+    std::vector<std::shared_ptr<C2InfoBuffer>> infoBuffers;
 };
 
 struct C2Worklet {
@@ -116,59 +148,61 @@
     // IN
     c2_node_id_t component;
 
-    std::list<std::unique_ptr<C2Param>> tunings; //< tunings to be applied before processing this
-                                                 // worklet
-    std::list<C2Param::Type> requestedInfos;
-    std::vector<std::shared_ptr<C2BlockPool>> allocators; //< This vector shall be the same size as
-                                                          //< output.buffers. \deprecated
+    /** Configuration changes to be applied before processing this worklet. */
+    std::vector<std::unique_ptr<C2Tuning>> tunings;
+    std::vector<std::unique_ptr<C2SettingResult>> failures;
 
     // OUT
-    C2BufferPack output;
-    std::list<std::unique_ptr<C2SettingResult>> failures;
+    C2FrameData output;
 };
 
 /**
+ * Information about partial work-chains not part of the current work items.
+ *
+ * To be defined later.
+ */
+struct C2WorkChainInfo;
+
+/**
  * This structure holds information about all a single work item.
  *
  * This structure shall be passed by the client to the component for the first worklet. As such,
  * worklets must not be empty. The ownership of this object is passed.
- *
- * input:
- *      The input data to be processed. This is provided by the client with ownership. When the work
- *      is returned, the input buffer-pack's buffer vector shall contain nullptrs.
- *
- * worklets:
- *      The chain of components and associated allocators, tunings and info requests that the data
- *      must pass through. If this has more than a single element, the tunnels between successive
- *      components of the worklet chain must have been (successfully) pre-registered at the time
- *      the work is submitted. Allocating the output buffers in the worklets is the responsibility
- *      of each component. Upon work submission, each output buffer-pack shall be an appropriately
- *      sized vector containing nullptrs. When the work is completed/returned to the client,
- *
- * worklets_processed:
- *      It shall be initialized to 0 by the client when the work is submitted.
- *      It shall contain the number of worklets that were successfully processed when the work is
- *      returned. If this is less then the number of worklets, result must not be success.
- *      It must be in the range of [0, worklets.size()].
- *
- * result:
- *      The final outcome of the work. If 0 when work is returned, it is assumed that all worklets
- *      have been processed.
  */
 struct C2Work {
 //public:
-    // pre-chain infos (for portions of a tunneling chain that happend before this work-chain for
-    // this work item - due to framework facilitated (non-tunneled) work-chaining)
-    std::list<std::pair<std::unique_ptr<C2PortMimeConfig>, std::unique_ptr<C2Info>>> preChainInfos;
-    std::list<std::pair<std::unique_ptr<C2PortMimeConfig>, std::unique_ptr<C2Buffer>>> preChainInfoBlobs;
+    /// additional work chain info not part of this work
+    std::shared_ptr<C2WorkChainInfo> chainInfo;
 
-    C2BufferPack input;
+    /// The input data to be processed as part of this work/work-chain. This is provided by the
+    /// client with ownership. When the work is returned (via onWorkDone), the input buffer-pack's
+    /// buffer vector shall contain nullptrs.
+    C2FrameData input;
+
+    /// The chain of components, tunings (including output buffer pool IDs) and info requests that the
+    /// data must pass through. If this has more than a single element, the tunnels between successive
+    /// components of the worklet chain must have been (successfully) pre-registered at the time that
+    /// the work is submitted. Allocating the output buffers in the worklets is the responsibility of
+    /// each component. Upon work submission, each output buffer-pack shall be an appropriately sized
+    /// vector containing nullptrs. When the work is completed/returned to the client, output buffers
+    /// pointers from all but the final worklet shall be nullptrs.
     std::list<std::unique_ptr<C2Worklet>> worklets;
 
-    uint32_t worklets_processed;
+    /// Number of worklets successfully processed in this chain. This shall be initialized to 0 by the
+    /// client when the work is submitted. It shall contain the number of worklets that were
+    /// successfully processed when the work is returned to the client. If this is less then the number
+    /// of worklets, result must not be success. It must be in the range of [0, worklets.size()].
+    uint32_t workletsProcessed;
+
+    /// The final outcome of the work (corresponding to the current workletsProcessed). If 0 when
+    /// work is returned, it is assumed that all worklets have been processed.
     c2_status_t result;
 };
 
+/**
+ * Information about a future work to be submitted to the component. The information is used to
+ * reserve the work for work ordering purposes.
+ */
 struct C2WorkOutline {
 //public:
     C2WorkOrdinalStruct ordinal;
diff --git a/media/libstagefright/codec2/tests/C2_test.cpp b/media/libstagefright/codec2/tests/C2_test.cpp
index 92a3d91..46f545f 100644
--- a/media/libstagefright/codec2/tests/C2_test.cpp
+++ b/media/libstagefright/codec2/tests/C2_test.cpp
@@ -75,4 +75,88 @@
 static_assert(c2_const_checker<max_u32_u64>::num() == 3, "should be 3");
 static_assert(c2_const_checker<max_u32_u8>::num() == 0x7fffffff, "should be 0x7fffffff");
 
+/* ======================================= COUNTER TESTS ======================================= */
+
+void c2_cntr_static_test() {
+    // sanity checks for construction/assignment
+    constexpr c2_cntr32_t c32_a(123);
+    constexpr c2_cntr64_t c64_a(-456);
+    c2_cntr32_t c32_b __unused = c64_a;
+    // c32_b = 64.; // DISALLOWED
+    // c2_cntr64_t c64_b = c32_a; // DISALLOWED
+
+    // sanity checks for some constexpr operators
+    static_assert(std::is_same<decltype(c32_a + c64_a), decltype(c64_a + c32_a)>::value, "+ should result same type");
+    static_assert(c32_a + c64_a == c2_cntr32_t(-333), "123 + -456 = -333");
+    static_assert(c32_a + c32_a == c2_cntr32_t(246), "123 + 123 = 246");
+    static_assert(c64_a + c32_a == c2_cntr32_t(-333), "-456 + 123 = 579");
+    static_assert(std::is_same<decltype(c32_a + 1), decltype(1 + c32_a)>::value, "+ should result same type");
+    static_assert(c32_a + 456 == c2_cntr32_t(579), "123 + 456 = 579");
+    static_assert(456 + c64_a == c2_cntr64_t(0), "456 + -456 = 0");
+    static_assert(std::is_same<decltype(c32_a - c64_a), decltype(c64_a - c32_a)>::value, "- should result same type");
+    static_assert(c32_a - c64_a == c2_cntr32_t(579), "123 - -456 = 579");
+    static_assert(c32_a - c32_a == c2_cntr32_t(0), "123 - 123 = 0");
+    static_assert(c64_a - c32_a == c2_cntr32_t(-579), "-456 - 123 = -579");
+    static_assert(std::is_same<decltype(c32_a - 1), decltype(1 - c32_a)>::value, "- should result same type");
+    static_assert(c32_a - 456 == c2_cntr32_t(-333), "123 - 456 = -333");
+    static_assert(456 - c64_a == c2_cntr64_t(912), "456 - -456 = 912");
+    static_assert(std::is_same<decltype(c32_a * c64_a), decltype(c64_a * c32_a)>::value, "* should result same type");
+    static_assert(c32_a * c64_a == c2_cntr32_t(-56088), "123 * -456 = -56088");
+    static_assert(c32_a * c32_a == c2_cntr32_t(15129), "123 * 123 = 15129");
+    static_assert(c64_a * c32_a == c2_cntr32_t(-56088), "-456 * 123 = -56088");
+    static_assert(std::is_same<decltype(c32_a * 1), decltype(1 * c32_a)>::value, "* should result same type");
+    static_assert(c32_a * 456 == c2_cntr32_t(56088), "123 * 456 = 56088");
+    static_assert(456 * c64_a == c2_cntr64_t(-207936), "456 * -456 = -207936");
+
+    static_assert((c32_a << 26u) == c2_cntr32_t(0xEC000000), "123 << 26 = 0xEC000000");
+
+    // sanity checks for unary operators
+    static_assert(c2_cntr32_t(1) == +c2_cntr32_t(1), "1 == +1");
+    static_assert(c2_cntr32_t(1) == -c2_cntr32_t(-1), "1 == --1");
+
+    // sanity checks for comparison
+    using c8_t = c2_cntr_t<uint8_t>;
+    static_assert(c8_t(-0x80) > c8_t(0x7f), "80 > 7F");
+    static_assert(c8_t(-0x80) >= c8_t(0x7f), "80 >= 7F");
+    static_assert(c8_t(0x7f) > c8_t(0x7e), "7F > 7E");
+    static_assert(c8_t(0x7f) >= c8_t(0x7e), "7F >= 7E");
+    static_assert(!(c8_t(-0x80) > c8_t(0)), "80 !> 00");
+    static_assert(!(c8_t(-0x80) >= c8_t(0)), "80 !>= 00");
+    static_assert(!(c8_t(-0x80) > c8_t(-0x80)), "80 !> 80");
+    static_assert(c8_t(-0x80) >= c8_t(-0x80), "80 >= 80");
+
+    static_assert(c8_t(-0x80) == c8_t(0x80), "80 == 80");
+    static_assert(!(c8_t(-0x80) == c8_t(0)), "80 != 0");
+    static_assert(c8_t(-0x80) != c8_t(0x7f), "80 != 7F");
+    static_assert(!(c8_t(0x7f) != c8_t(0x7f)), "80 != 7F");
+
+    static_assert(c8_t(0x7f) < c8_t(-0x80), "7F < 80");
+    static_assert(c8_t(0x7f) <= c8_t(-0x80), "7F < 80");
+    static_assert(c8_t(0x7e) < c8_t(0x7f), "7E < 7F");
+    static_assert(c8_t(0x7e) <= c8_t(0x7f), "7E < 7F");
+    static_assert(!(c8_t(-0x40) < c8_t(0x40)), "-40 !< 40");
+    static_assert(!(c8_t(-0x40) <= c8_t(0x40)), "-40 !<= 40");
+    static_assert(!(c8_t(-0x40) < c8_t(-0x40)), "-40 !< -40");
+    static_assert(c8_t(-0x40) <= c8_t(-0x40), "-40 <= -40");
+
+    static_assert(c2_cntr32_t(-0x7fffffff - 1) > c2_cntr32_t(0x7fffffff), "80 > 7F");
+    static_assert(!(c2_cntr32_t(-0x7fffffff - 1) > c2_cntr32_t(0)), "80 !> 00");
+    static_assert(c2_cntr32_t(1) == c2_cntr32_t(c2_cntr64_t(0x100000001ul)), "1 == 1");
+}
+
+class C2Test : public ::testing::Test {
+};
+
+TEST_F(C2Test, CounterTest) {
+    c2_cntr32_t c32_a(123);
+    c2_cntr64_t c64_a(-456);
+    EXPECT_EQ(c32_a += 3, c2_cntr32_t(126));
+    EXPECT_EQ(c32_a += c64_a, c2_cntr32_t(-330));
+    EXPECT_EQ(c32_a <<= 2u, c2_cntr32_t(-1320));
+    EXPECT_EQ(c64_a *= 3, c2_cntr64_t(-1368));
+    EXPECT_EQ(c32_a -= c64_a, c2_cntr32_t(48));
+    EXPECT_EQ(c32_a -= 40, c2_cntr32_t(8));
+    EXPECT_EQ(c32_a *= c32_a, c2_cntr32_t(64));
+}
+
 } // namespace android
diff --git a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
index f6e6478..a310717 100644
--- a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
+++ b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
@@ -359,14 +359,14 @@
 
 class BufferData : public C2BufferData {
 public:
-    explicit BufferData(const std::list<C2ConstLinearBlock> &blocks) : C2BufferData(blocks) {}
-    explicit BufferData(const std::list<C2ConstGraphicBlock> &blocks) : C2BufferData(blocks) {}
+    explicit BufferData(const std::vector<C2ConstLinearBlock> &blocks) : C2BufferData(blocks) {}
+    explicit BufferData(const std::vector<C2ConstGraphicBlock> &blocks) : C2BufferData(blocks) {}
 };
 
 class Buffer : public C2Buffer {
 public:
-    explicit Buffer(const std::list<C2ConstLinearBlock> &blocks) : C2Buffer(blocks) {}
-    explicit Buffer(const std::list<C2ConstGraphicBlock> &blocks) : C2Buffer(blocks) {}
+    explicit Buffer(const std::vector<C2ConstLinearBlock> &blocks) : C2Buffer(blocks) {}
+    explicit Buffer(const std::vector<C2ConstGraphicBlock> &blocks) : C2Buffer(blocks) {}
 };
 
 TEST_F(C2BufferTest, BufferDataTest) {
@@ -487,45 +487,45 @@
     std::shared_ptr<C2Info> info1(new C2Number1Info(1));
     std::shared_ptr<C2Info> info2(new C2Number2Info(2));
     buffer.reset(new Buffer( { block->share(0, kCapacity, C2Fence()) }));
-    EXPECT_TRUE(buffer->infos().empty());
+    EXPECT_TRUE(buffer->info().empty());
     EXPECT_FALSE(buffer->hasInfo(info1->type()));
     EXPECT_FALSE(buffer->hasInfo(info2->type()));
 
     ASSERT_EQ(C2_OK, buffer->setInfo(info1));
-    EXPECT_EQ(1u, buffer->infos().size());
-    EXPECT_EQ(*info1, *buffer->infos().front());
+    EXPECT_EQ(1u, buffer->info().size());
+    EXPECT_EQ(*info1, *buffer->info().front());
     EXPECT_TRUE(buffer->hasInfo(info1->type()));
     EXPECT_FALSE(buffer->hasInfo(info2->type()));
 
     ASSERT_EQ(C2_OK, buffer->setInfo(info2));
-    EXPECT_EQ(2u, buffer->infos().size());
+    EXPECT_EQ(2u, buffer->info().size());
     EXPECT_TRUE(buffer->hasInfo(info1->type()));
     EXPECT_TRUE(buffer->hasInfo(info2->type()));
 
     std::shared_ptr<C2Info> removed = buffer->removeInfo(info1->type());
     ASSERT_TRUE(removed);
     EXPECT_EQ(*removed, *info1);
-    EXPECT_EQ(1u, buffer->infos().size());
-    EXPECT_EQ(*info2, *buffer->infos().front());
+    EXPECT_EQ(1u, buffer->info().size());
+    EXPECT_EQ(*info2, *buffer->info().front());
     EXPECT_FALSE(buffer->hasInfo(info1->type()));
     EXPECT_TRUE(buffer->hasInfo(info2->type()));
 
     removed = buffer->removeInfo(info1->type());
     ASSERT_FALSE(removed);
-    EXPECT_EQ(1u, buffer->infos().size());
+    EXPECT_EQ(1u, buffer->info().size());
     EXPECT_FALSE(buffer->hasInfo(info1->type()));
     EXPECT_TRUE(buffer->hasInfo(info2->type()));
 
     std::shared_ptr<C2Info> info3(new C2Number2Info(3));
     ASSERT_EQ(C2_OK, buffer->setInfo(info3));
-    EXPECT_EQ(1u, buffer->infos().size());
+    EXPECT_EQ(1u, buffer->info().size());
     EXPECT_FALSE(buffer->hasInfo(info1->type()));
     EXPECT_TRUE(buffer->hasInfo(info2->type()));
 
     removed = buffer->removeInfo(info2->type());
     ASSERT_TRUE(removed);
     EXPECT_EQ(*info3, *removed);
-    EXPECT_TRUE(buffer->infos().empty());
+    EXPECT_TRUE(buffer->info().empty());
     EXPECT_FALSE(buffer->hasInfo(info1->type()));
     EXPECT_FALSE(buffer->hasInfo(info2->type()));
 }
diff --git a/media/libstagefright/codec2/vndk/C2Buffer.cpp b/media/libstagefright/codec2/vndk/C2Buffer.cpp
index 65a271e..4ab3e05 100644
--- a/media/libstagefright/codec2/vndk/C2Buffer.cpp
+++ b/media/libstagefright/codec2/vndk/C2Buffer.cpp
@@ -602,44 +602,44 @@
 
 class C2BufferData::Impl {
 public:
-    explicit Impl(const std::list<C2ConstLinearBlock> &blocks)
+    explicit Impl(const std::vector<C2ConstLinearBlock> &blocks)
         : mType(blocks.size() == 1 ? LINEAR : LINEAR_CHUNKS),
           mLinearBlocks(blocks) {
     }
 
-    explicit Impl(const std::list<C2ConstGraphicBlock> &blocks)
+    explicit Impl(const std::vector<C2ConstGraphicBlock> &blocks)
         : mType(blocks.size() == 1 ? GRAPHIC : GRAPHIC_CHUNKS),
           mGraphicBlocks(blocks) {
     }
 
     Type type() const { return mType; }
-    const std::list<C2ConstLinearBlock> &linearBlocks() const { return mLinearBlocks; }
-    const std::list<C2ConstGraphicBlock> &graphicBlocks() const { return mGraphicBlocks; }
+    const std::vector<C2ConstLinearBlock> &linearBlocks() const { return mLinearBlocks; }
+    const std::vector<C2ConstGraphicBlock> &graphicBlocks() const { return mGraphicBlocks; }
 
 private:
     Type mType;
-    std::list<C2ConstLinearBlock> mLinearBlocks;
-    std::list<C2ConstGraphicBlock> mGraphicBlocks;
+    std::vector<C2ConstLinearBlock> mLinearBlocks;
+    std::vector<C2ConstGraphicBlock> mGraphicBlocks;
 };
 
-C2BufferData::C2BufferData(const std::list<C2ConstLinearBlock> &blocks) : mImpl(new Impl(blocks)) {}
-C2BufferData::C2BufferData(const std::list<C2ConstGraphicBlock> &blocks) : mImpl(new Impl(blocks)) {}
+C2BufferData::C2BufferData(const std::vector<C2ConstLinearBlock> &blocks) : mImpl(new Impl(blocks)) {}
+C2BufferData::C2BufferData(const std::vector<C2ConstGraphicBlock> &blocks) : mImpl(new Impl(blocks)) {}
 
 C2BufferData::Type C2BufferData::type() const { return mImpl->type(); }
 
-const std::list<C2ConstLinearBlock> C2BufferData::linearBlocks() const {
+const std::vector<C2ConstLinearBlock> C2BufferData::linearBlocks() const {
     return mImpl->linearBlocks();
 }
 
-const std::list<C2ConstGraphicBlock> C2BufferData::graphicBlocks() const {
+const std::vector<C2ConstGraphicBlock> C2BufferData::graphicBlocks() const {
     return mImpl->graphicBlocks();
 }
 
 class C2Buffer::Impl {
 public:
-    Impl(C2Buffer *thiz, const std::list<C2ConstLinearBlock> &blocks)
+    Impl(C2Buffer *thiz, const std::vector<C2ConstLinearBlock> &blocks)
         : mThis(thiz), mData(blocks) {}
-    Impl(C2Buffer *thiz, const std::list<C2ConstGraphicBlock> &blocks)
+    Impl(C2Buffer *thiz, const std::vector<C2ConstGraphicBlock> &blocks)
         : mThis(thiz), mData(blocks) {}
 
     ~Impl() {
@@ -676,8 +676,8 @@
         return C2_OK;
     }
 
-    std::list<std::shared_ptr<const C2Info>> infos() const {
-        std::list<std::shared_ptr<const C2Info>> result(mInfos.size());
+    std::vector<std::shared_ptr<const C2Info>> info() const {
+        std::vector<std::shared_ptr<const C2Info>> result(mInfos.size());
         std::transform(
                 mInfos.begin(), mInfos.end(), result.begin(),
                 [] (const auto &elem) { return elem.second; });
@@ -712,10 +712,10 @@
     std::list<std::pair<OnDestroyNotify, void *>> mNotify;
 };
 
-C2Buffer::C2Buffer(const std::list<C2ConstLinearBlock> &blocks)
+C2Buffer::C2Buffer(const std::vector<C2ConstLinearBlock> &blocks)
     : mImpl(new Impl(this, blocks)) {}
 
-C2Buffer::C2Buffer(const std::list<C2ConstGraphicBlock> &blocks)
+C2Buffer::C2Buffer(const std::vector<C2ConstGraphicBlock> &blocks)
     : mImpl(new Impl(this, blocks)) {}
 
 const C2BufferData C2Buffer::data() const { return mImpl->data(); }
@@ -728,8 +728,8 @@
     return mImpl->unregisterOnDestroyNotify(onDestroyNotify, arg);
 }
 
-const std::list<std::shared_ptr<const C2Info>> C2Buffer::infos() const {
-    return mImpl->infos();
+const std::vector<std::shared_ptr<const C2Info>> C2Buffer::info() const {
+    return mImpl->info();
 }
 
 c2_status_t C2Buffer::setInfo(const std::shared_ptr<C2Info> &info) {
diff --git a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
index 390f36c..5ddfc14 100644
--- a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
+++ b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
@@ -316,9 +316,9 @@
             work->worklets.front()->output.buffers.clear();
             work->worklets.front()->output.buffers.push_back(buffer);
             work->worklets.front()->output.ordinal = work->input.ordinal;
-            work->worklets_processed = 1u;
+            work->workletsProcessed = 1u;
         };
-        if (work && work->input.ordinal.frame_index == outInfo.frameIndex) {
+        if (work && work->input.ordinal.frameIndex == c2_cntr64_t(outInfo.frameIndex)) {
             fillWork(work);
         } else {
             finish(outInfo.frameIndex, fillWork);
@@ -332,7 +332,7 @@
 void C2SoftAac::process(
         const std::unique_ptr<C2Work> &work,
         const std::shared_ptr<C2BlockPool> &pool) {
-    work->worklets_processed = 0u;
+    work->workletsProcessed = 0u;
     if (mSignalledError) {
         return;
     }
@@ -346,8 +346,8 @@
     size_t offset = 0u;
     size_t size = view.capacity();
 
-    bool eos = (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) != 0;
-    bool codecConfig = (work->input.flags & C2BufferPack::FLAG_CODEC_CONFIG) != 0;
+    bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+    bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0;
 
     //TODO
 #if 0
@@ -381,8 +381,8 @@
     }
 
     Info inInfo;
-    inInfo.frameIndex = work->input.ordinal.frame_index;
-    inInfo.timestamp = work->input.ordinal.timestamp;
+    inInfo.frameIndex = work->input.ordinal.frameIndex.peeku();
+    inInfo.timestamp = work->input.ordinal.timestamp.peeku();
     inInfo.bufferSize = size;
     inInfo.decodedSizes.clear();
     while (size > 0u) {
@@ -604,13 +604,13 @@
             work->worklets.front()->output.buffers.clear();
             work->worklets.front()->output.buffers.emplace_back(nullptr);
             work->worklets.front()->output.ordinal = work->input.ordinal;
-            work->worklets_processed = 1u;
+            work->workletsProcessed = 1u;
         };
         while (mBuffersInfo.size() > 1u) {
             finish(mBuffersInfo.front().frameIndex, fillEmptyWork);
             mBuffersInfo.pop_front();
         }
-        if (work->worklets_processed == 0u) {
+        if (work->workletsProcessed == 0u) {
             fillEmptyWork(work);
         }
         mBuffersInfo.clear();
diff --git a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
index 94308c4..7bce21d 100644
--- a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
+++ b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
@@ -176,12 +176,12 @@
 void C2SoftAacEnc::process(
         const std::unique_ptr<C2Work> &work,
         const std::shared_ptr<C2BlockPool> &pool) {
-    work->worklets_processed = 0u;
+    work->workletsProcessed = 0u;
 
     if (mSignalledError) {
         return;
     }
-    bool eos = (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) != 0;
+    bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
 
     if (!mSentCodecSpecificData) {
         // The very first thing we want to output is the codec specific
@@ -215,7 +215,7 @@
 #if defined(LOG_NDEBUG) && !LOG_NDEBUG
         hexdump(csd->m.value, csd->flexCount());
 #endif
-        work->worklets.front()->output.infos.push_back(std::move(csd));
+        work->worklets.front()->output.configUpdate.push_back(std::move(csd));
 
         mOutBufferSize = encInfo.maxOutBufBytes;
         mNumBytesPerInputFrame = encInfo.frameLength * mNumChannels * sizeof(int16_t);
@@ -225,7 +225,7 @@
     }
 
     C2ReadView view = work->input.buffers[0]->data().linearBlocks().front().map().get();
-    uint64_t timestamp = mInputTimeUs;
+    uint64_t timestamp = mInputTimeUs.peeku();
 
     size_t numFrames = (view.capacity() + mInputSize + (eos ? mNumBytesPerInputFrame - 1 : 0))
             / mNumBytesPerInputFrame;
@@ -336,11 +336,11 @@
     }
 
     work->worklets.front()->output.flags =
-        (C2BufferPack::flags_t)(eos ? C2BufferPack::FLAG_END_OF_STREAM : 0);
+        (C2FrameData::flags_t)(eos ? C2FrameData::FLAG_END_OF_STREAM : 0);
     work->worklets.front()->output.buffers.clear();
     work->worklets.front()->output.ordinal = work->input.ordinal;
     work->worklets.front()->output.ordinal.timestamp = timestamp;
-    work->worklets_processed = 1u;
+    work->workletsProcessed = 1u;
     if (nOutputBytes) {
         work->worklets.front()->output.buffers.push_back(
                 createLinearBuffer(block, 0, nOutputBytes));
@@ -350,7 +350,7 @@
 
 #if 0
     ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)",
-          nOutputBytes, mInputTimeUs, outHeader->nFlags);
+          nOutputBytes, mInputTimeUs.peekll(), outHeader->nFlags);
 
     hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen);
 #endif
diff --git a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h
index 947c960..c9f440f 100644
--- a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h
+++ b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h
@@ -57,7 +57,7 @@
 
     bool mSentCodecSpecificData;
     size_t mInputSize;
-    int64_t mInputTimeUs;
+    c2_cntr64_t mInputTimeUs;
 
     bool mSignalledError;
 
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
index 0da9cc7..cc12d3c 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
@@ -206,14 +206,14 @@
 
 void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
     uint32_t flags = 0;
-    if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
-        flags |= C2BufferPack::FLAG_END_OF_STREAM;
+    if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
+        flags |= C2FrameData::FLAG_END_OF_STREAM;
     }
-    work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
+    work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
     work->worklets.front()->output.buffers.clear();
     work->worklets.front()->output.buffers.emplace_back(nullptr);
     work->worklets.front()->output.ordinal = work->input.ordinal;
-    work->worklets_processed = 1u;
+    work->workletsProcessed = 1u;
 }
 
 }  // namespace
@@ -1061,17 +1061,17 @@
     std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mAllocatedBlock));
     auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) {
         uint32_t flags = 0;
-        if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
-            flags |= C2BufferPack::FLAG_END_OF_STREAM;
+        if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+            flags |= C2FrameData::FLAG_END_OF_STREAM;
             ALOGV("EOS");
         }
-        work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
+        work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
         work->worklets.front()->output.buffers.clear();
         work->worklets.front()->output.buffers.push_back(buffer);
         work->worklets.front()->output.ordinal = work->input.ordinal;
-        work->worklets_processed = 1u;
+        work->workletsProcessed = 1u;
     };
-    if (work && index == work->input.ordinal.frame_index) {
+    if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
         fillWork(work);
     } else {
         finish(index, fillWork);
@@ -1084,25 +1084,25 @@
     bool eos = false;
 
     work->result = C2_OK;
-    work->worklets_processed = 0u;
+    work->workletsProcessed = 0u;
 
     const C2ConstLinearBlock &buffer =
         work->input.buffers[0]->data().linearBlocks().front();
     if (buffer.capacity() == 0) {
-        ALOGV("empty input: %llu", (long long)work->input.ordinal.frame_index);
+        ALOGV("empty input: %llu", work->input.ordinal.frameIndex.peekull());
         // TODO: result?
         fillEmptyWork(work);
-        if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
+        if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
             eos = true;
         }
         return;
-    } else if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
-        ALOGV("input EOS: %llu", (long long)work->input.ordinal.frame_index);
+    } else if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+        ALOGV("input EOS: %llu", work->input.ordinal.frameIndex.peekull());
         eos = true;
     }
 
     C2ReadView input = work->input.buffers[0]->data().linearBlocks().front().map().get();
-    uint32_t workIndex = work->input.ordinal.frame_index & 0xFFFFFFFF;
+    uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
     size_t inOffset = 0u;
 
     while (inOffset < input.capacity()) {
@@ -1266,7 +1266,7 @@
     }
 
     if (drainMode == DRAIN_COMPONENT_WITH_EOS
-            && work && work->worklets_processed == 0u) {
+            && work && work->workletsProcessed == 0u) {
         fillEmptyWork(work);
     }
 
diff --git a/media/libstagefright/codecs/cmds/Android.bp b/media/libstagefright/codecs/cmds/Android.bp
index 40f1a3d..6dba0a3 100644
--- a/media/libstagefright/codecs/cmds/Android.bp
+++ b/media/libstagefright/codecs/cmds/Android.bp
@@ -13,6 +13,7 @@
         "libcutils",
         "libgui",
         "liblog",
+        "libmediaextractor",
         "libstagefright",
         "libstagefright_codec2",
         "libstagefright_codec2_vndk",
diff --git a/media/libstagefright/codecs/cmds/codec2.cpp b/media/libstagefright/codecs/cmds/codec2.cpp
index 78fb527..ec05d7a 100644
--- a/media/libstagefright/codecs/cmds/codec2.cpp
+++ b/media/libstagefright/codecs/cmds/codec2.cpp
@@ -247,7 +247,7 @@
             }
             int slot;
             sp<Fence> fence;
-            ALOGV("Render: Frame #%" PRId64, work->worklets.front()->output.ordinal.frame_index);
+            ALOGV("Render: Frame #%lld", work->worklets.front()->output.ordinal.frameIndex.peekll());
             const std::shared_ptr<C2Buffer> &output = work->worklets.front()->output.buffers[0];
             if (output) {
                 const C2ConstGraphicBlock &block = output->data().graphicBlocks().front();
@@ -266,7 +266,7 @@
                 status_t err = igbp->attachBuffer(&slot, buffer);
 
                 IGraphicBufferProducer::QueueBufferInput qbi(
-                        work->worklets.front()->output.ordinal.timestamp * 1000ll,
+                        (work->worklets.front()->output.ordinal.timestamp * 1000ll).peekll(),
                         false,
                         HAL_DATASPACE_UNKNOWN,
                         Rect(block.width(), block.height()),
@@ -338,9 +338,9 @@
                 mQueueCondition.wait_for(l, 100ms);
             }
         }
-        work->input.flags = (C2BufferPack::flags_t)0;
+        work->input.flags = (C2FrameData::flags_t)0;
         work->input.ordinal.timestamp = timestamp;
-        work->input.ordinal.frame_index = numFrames;
+        work->input.ordinal.frameIndex = numFrames;
 
         std::shared_ptr<C2LinearBlock> block;
         mLinearPool->fetchLinearBlock(
diff --git a/media/libstagefright/foundation/ABuffer.cpp b/media/libstagefright/foundation/ABuffer.cpp
index 804046a..c8965d9 100644
--- a/media/libstagefright/foundation/ABuffer.cpp
+++ b/media/libstagefright/foundation/ABuffer.cpp
@@ -19,13 +19,11 @@
 #include "ADebug.h"
 #include "ALooper.h"
 #include "AMessage.h"
-#include "MediaBufferBase.h"
 
 namespace android {
 
 ABuffer::ABuffer(size_t capacity)
-    : mMediaBufferBase(NULL),
-      mRangeOffset(0),
+    : mRangeOffset(0),
       mInt32Data(0),
       mOwnsData(true) {
     mData = malloc(capacity);
@@ -39,8 +37,7 @@
 }
 
 ABuffer::ABuffer(void *data, size_t capacity)
-    : mMediaBufferBase(NULL),
-      mData(data),
+    : mData(data),
       mCapacity(capacity),
       mRangeOffset(0),
       mRangeLength(capacity),
@@ -66,8 +63,6 @@
             mData = NULL;
         }
     }
-
-    setMediaBufferBase(NULL);
 }
 
 void ABuffer::setRange(size_t offset, size_t size) {
@@ -85,19 +80,5 @@
     return mMeta;
 }
 
-MediaBufferBase *ABuffer::getMediaBufferBase() {
-    if (mMediaBufferBase != NULL) {
-        mMediaBufferBase->add_ref();
-    }
-    return mMediaBufferBase;
-}
-
-void ABuffer::setMediaBufferBase(MediaBufferBase *mediaBuffer) {
-    if (mMediaBufferBase != NULL) {
-        mMediaBufferBase->release();
-    }
-    mMediaBufferBase = mediaBuffer;
-}
-
 }  // namespace android
 
diff --git a/media/libstagefright/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index 4dfe5e5..2258e2c 100644
--- a/media/libstagefright/foundation/Android.bp
+++ b/media/libstagefright/foundation/Android.bp
@@ -62,8 +62,6 @@
         "AStringUtils.cpp",
         "ByteUtils.cpp",
         "ColorUtils.cpp",
-        "MediaBuffer.cpp",
-        "MediaBufferGroup.cpp",
         "MediaDefs.cpp",
         "MediaKeys.cpp",
         "MetaData.cpp",
diff --git a/media/libstagefright/foundation/MetaData.cpp b/media/libstagefright/foundation/MetaData.cpp
index a8965f0..2415c61 100644
--- a/media/libstagefright/foundation/MetaData.cpp
+++ b/media/libstagefright/foundation/MetaData.cpp
@@ -17,6 +17,7 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MetaData"
 #include <inttypes.h>
+#include <utils/KeyedVector.h>
 #include <utils/Log.h>
 
 #include <stdlib.h>
@@ -29,30 +30,81 @@
 
 namespace android {
 
-MetaData::MetaData() {
+struct MetaData::typed_data {
+    typed_data();
+    ~typed_data();
+
+    typed_data(const MetaData::typed_data &);
+    typed_data &operator=(const MetaData::typed_data &);
+
+    void clear();
+    void setData(uint32_t type, const void *data, size_t size);
+    void getData(uint32_t *type, const void **data, size_t *size) const;
+    // may include hexdump of binary data if verbose=true
+    String8 asString(bool verbose) const;
+
+private:
+    uint32_t mType;
+    size_t mSize;
+
+    union {
+        void *ext_data;
+        float reservoir;
+    } u;
+
+    bool usesReservoir() const {
+        return mSize <= sizeof(u.reservoir);
+    }
+
+    void *allocateStorage(size_t size);
+    void freeStorage();
+
+    void *storage() {
+        return usesReservoir() ? &u.reservoir : u.ext_data;
+    }
+
+    const void *storage() const {
+        return usesReservoir() ? &u.reservoir : u.ext_data;
+    }
+};
+
+struct MetaData::Rect {
+    int32_t mLeft, mTop, mRight, mBottom;
+};
+
+
+struct MetaData::MetaDataInternal {
+    KeyedVector<uint32_t, MetaData::typed_data> mItems;
+};
+
+
+MetaData::MetaData()
+    : mInternalData(new MetaDataInternal()) {
 }
 
 MetaData::MetaData(const MetaData &from)
     : RefBase(),
-      mItems(from.mItems) {
+      mInternalData(new MetaDataInternal()) {
+    mInternalData->mItems = from.mInternalData->mItems;
 }
 
 MetaData::~MetaData() {
     clear();
+    delete mInternalData;
 }
 
 void MetaData::clear() {
-    mItems.clear();
+    mInternalData->mItems.clear();
 }
 
 bool MetaData::remove(uint32_t key) {
-    ssize_t i = mItems.indexOfKey(key);
+    ssize_t i = mInternalData->mItems.indexOfKey(key);
 
     if (i < 0) {
         return false;
     }
 
-    mItems.removeItemsAt(i);
+    mInternalData->mItems.removeItemsAt(i);
 
     return true;
 }
@@ -192,15 +244,15 @@
         uint32_t key, uint32_t type, const void *data, size_t size) {
     bool overwrote_existing = true;
 
-    ssize_t i = mItems.indexOfKey(key);
+    ssize_t i = mInternalData->mItems.indexOfKey(key);
     if (i < 0) {
         typed_data item;
-        i = mItems.add(key, item);
+        i = mInternalData->mItems.add(key, item);
 
         overwrote_existing = false;
     }
 
-    typed_data &item = mItems.editValueAt(i);
+    typed_data &item = mInternalData->mItems.editValueAt(i);
 
     item.setData(type, data, size);
 
@@ -209,13 +261,13 @@
 
 bool MetaData::findData(uint32_t key, uint32_t *type,
                         const void **data, size_t *size) const {
-    ssize_t i = mItems.indexOfKey(key);
+    ssize_t i = mInternalData->mItems.indexOfKey(key);
 
     if (i < 0) {
         return false;
     }
 
-    const typed_data &item = mItems.valueAt(i);
+    const typed_data &item = mInternalData->mItems.valueAt(i);
 
     item.getData(type, data, size);
 
@@ -223,7 +275,7 @@
 }
 
 bool MetaData::hasData(uint32_t key) const {
-    ssize_t i = mItems.indexOfKey(key);
+    ssize_t i = mInternalData->mItems.indexOfKey(key);
 
     if (i < 0) {
         return false;
@@ -369,11 +421,11 @@
 
 String8 MetaData::toString() const {
     String8 s;
-    for (int i = mItems.size(); --i >= 0;) {
-        int32_t key = mItems.keyAt(i);
+    for (int i = mInternalData->mItems.size(); --i >= 0;) {
+        int32_t key = mInternalData->mItems.keyAt(i);
         char cc[5];
         MakeFourCCString(key, cc);
-        const typed_data &item = mItems.valueAt(i);
+        const typed_data &item = mInternalData->mItems.valueAt(i);
         s.appendFormat("%s: %s", cc, item.asString(false).string());
         if (i != 0) {
             s.append(", ");
@@ -382,25 +434,25 @@
     return s;
 }
 void MetaData::dumpToLog() const {
-    for (int i = mItems.size(); --i >= 0;) {
-        int32_t key = mItems.keyAt(i);
+    for (int i = mInternalData->mItems.size(); --i >= 0;) {
+        int32_t key = mInternalData->mItems.keyAt(i);
         char cc[5];
         MakeFourCCString(key, cc);
-        const typed_data &item = mItems.valueAt(i);
+        const typed_data &item = mInternalData->mItems.valueAt(i);
         ALOGI("%s: %s", cc, item.asString(true /* verbose */).string());
     }
 }
 
 status_t MetaData::writeToParcel(Parcel &parcel) {
     status_t ret;
-    size_t numItems = mItems.size();
+    size_t numItems = mInternalData->mItems.size();
     ret = parcel.writeUint32(uint32_t(numItems));
     if (ret) {
         return ret;
     }
     for (size_t i = 0; i < numItems; i++) {
-        int32_t key = mItems.keyAt(i);
-        const typed_data &item = mItems.valueAt(i);
+        int32_t key = mInternalData->mItems.keyAt(i);
+        const typed_data &item = mInternalData->mItems.valueAt(i);
         uint32_t type;
         const void *data;
         size_t size;
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h b/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
index ef11434..8fe9f8d 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
@@ -27,7 +27,6 @@
 namespace android {
 
 struct AMessage;
-class MediaBufferBase;
 
 struct ABuffer : public RefBase {
     explicit ABuffer(size_t capacity);
@@ -49,17 +48,12 @@
 
     sp<AMessage> meta();
 
-    MediaBufferBase *getMediaBufferBase();
-    void setMediaBufferBase(MediaBufferBase *mediaBuffer);
-
 protected:
     virtual ~ABuffer();
 
 private:
     sp<AMessage> mMeta;
 
-    MediaBufferBase *mMediaBufferBase;
-
     void *mData;
     size_t mCapacity;
     size_t mRangeOffset;
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h b/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h
deleted file mode 100644
index 99418fb..0000000
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2014 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 MEDIA_BUFFER_BASE_H_
-
-#define MEDIA_BUFFER_BASE_H_
-
-namespace android {
-
-class MediaBufferBase {
-public:
-    MediaBufferBase() {}
-
-    virtual void release() = 0;
-    virtual void add_ref() = 0;
-
-protected:
-    virtual ~MediaBufferBase() {}
-
-private:
-    MediaBufferBase(const MediaBufferBase &);
-    MediaBufferBase &operator=(const MediaBufferBase &);
-};
-
-}  // namespace android
-
-#endif  // MEDIA_BUFFER_BASE_H_
diff --git a/media/libstagefright/gbs/Android.bp b/media/libstagefright/gbs/Android.bp
new file mode 100644
index 0000000..a53b7b7
--- /dev/null
+++ b/media/libstagefright/gbs/Android.bp
@@ -0,0 +1,58 @@
+cc_library_shared {
+    name: "libstagefright_gbs",
+    vendor_available: true,
+    vndk: {
+        enabled: true,
+    },
+
+    srcs: [
+        "FrameDropper.cpp",
+        "GraphicBufferSource.cpp",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    header_libs: [
+        "media_plugin_headers",
+    ],
+
+    export_header_lib_headers: [
+        "media_plugin_headers",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libutils",
+        "liblog",
+        "libui",
+        "libgui",
+        "libcutils",
+        "libstagefright_foundation",
+        "libnativewindow", // TODO(b/62923479): use header library
+    ],
+
+    export_shared_lib_headers: [
+        "libstagefright_foundation",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-Wno-unused-parameter",
+        "-Wno-documentation",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+}
diff --git a/media/libstagefright/omx/FrameDropper.cpp b/media/libstagefright/gbs/FrameDropper.cpp
similarity index 97%
rename from media/libstagefright/omx/FrameDropper.cpp
rename to media/libstagefright/gbs/FrameDropper.cpp
index 0c50c58..9f0b8cc 100644
--- a/media/libstagefright/omx/FrameDropper.cpp
+++ b/media/libstagefright/gbs/FrameDropper.cpp
@@ -18,7 +18,7 @@
 #define LOG_TAG "FrameDropper"
 #include <utils/Log.h>
 
-#include <media/stagefright/omx/FrameDropper.h>
+#include <media/stagefright/gbs/FrameDropper.h>
 #include <media/stagefright/foundation/ADebug.h>
 
 namespace android {
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/gbs/GraphicBufferSource.cpp
similarity index 96%
rename from media/libstagefright/omx/GraphicBufferSource.cpp
rename to media/libstagefright/gbs/GraphicBufferSource.cpp
index f331dbb..139c916 100644
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ b/media/libstagefright/gbs/GraphicBufferSource.cpp
@@ -22,9 +22,8 @@
 
 #define STRINGIFY_ENUMS // for asString in HardwareAPI.h/VideoAPI.h
 
-#include <media/stagefright/omx/GraphicBufferSource.h>
-#include <media/stagefright/omx/FrameDropper.h>
-#include <media/stagefright/omx/OMXUtils.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/gbs/FrameDropper.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/foundation/ColorUtils.h>
@@ -34,9 +33,6 @@
 #include <ui/GraphicBuffer.h>
 #include <gui/BufferItem.h>
 #include <media/hardware/HardwareAPI.h>
-#include <media/openmax/OMX_Component.h>
-#include <media/openmax/OMX_IndexExt.h>
-#include <media/OMXBuffer.h>
 
 #include <inttypes.h>
 
@@ -361,9 +357,9 @@
     }
 }
 
-Status GraphicBufferSource::onOmxExecuting() {
+Status GraphicBufferSource::start() {
     Mutex::Autolock autoLock(mMutex);
-    ALOGV("--> executing; available=%zu, submittable=%zd",
+    ALOGV("--> start; available=%zu, submittable=%zd",
             mAvailableBuffers.size(), mFreeCodecBuffers.size());
     CHECK(!mExecuting);
     mExecuting = true;
@@ -411,8 +407,8 @@
     return Status::ok();
 }
 
-Status GraphicBufferSource::onOmxIdle() {
-    ALOGV("omxIdle");
+Status GraphicBufferSource::stop() {
+    ALOGV("stop");
 
     Mutex::Autolock autoLock(mMutex);
 
@@ -424,7 +420,7 @@
     return Status::ok();
 }
 
-Status GraphicBufferSource::onOmxLoaded(){
+Status GraphicBufferSource::release(){
     Mutex::Autolock autoLock(mMutex);
     if (mLooper != NULL) {
         mLooper->unregisterHandler(mReflector->id());
@@ -434,7 +430,7 @@
         mLooper.clear();
     }
 
-    ALOGV("--> loaded; available=%zu+%d eos=%d eosSent=%d acquired=%d",
+    ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
             mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
             mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
 
@@ -442,7 +438,7 @@
     mFreeCodecBuffers.clear();
     mSubmittedCodecBuffers.clear();
     mLatestBuffer.mBuffer.reset();
-    mOMXNode.clear();
+    mComponent.clear();
     mExecuting = false;
 
     return Status::ok();
@@ -537,7 +533,8 @@
     mLastDataspace = dataspace;
 
     if (ColorUtils::convertDataSpaceToV0(dataspace)) {
-        mOMXNode->dispatchDataSpaceChanged(mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
+        mComponent->dispatchDataSpaceChanged(
+                mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
     }
 }
 
@@ -631,7 +628,7 @@
                 default:
                     TRESPASS_DBG("Unknown action type");
                     // return true here because we did consume an available buffer, so the
-                    // loop in onOmxExecuting will eventually terminate even if we hit this.
+                    // loop in start will eventually terminate even if we hit this.
                     return false;
             }
         }
@@ -799,7 +796,7 @@
 
 status_t GraphicBufferSource::submitBuffer_l(const VideoBuffer &item) {
     CHECK(!mFreeCodecBuffers.empty());
-    IOMX::buffer_id codecBufferId = *mFreeCodecBuffers.begin();
+    uint32_t codecBufferId = *mFreeCodecBuffers.begin();
 
     ALOGV("submitBuffer_l [slot=%d, bufferId=%d]", item.mBuffer->getSlot(), codecBufferId);
 
@@ -815,15 +812,14 @@
     }
 
     std::shared_ptr<AcquiredBuffer> buffer = item.mBuffer;
-    // use a GraphicBuffer for now as OMXNodeInstance is using GraphicBuffers to hold references
+    // use a GraphicBuffer for now as component is using GraphicBuffers to hold references
     // and it requires this graphic buffer to be able to hold its reference
     // and thus we would need to create a new GraphicBuffer from an ANWBuffer separate from the
     // acquired GraphicBuffer.
     // TODO: this can be reworked globally to use ANWBuffer references
     sp<GraphicBuffer> graphicBuffer = buffer->getGraphicBuffer();
-    status_t err = mOMXNode->emptyBuffer(
-            codecBufferId, OMX_BUFFERFLAG_ENDOFFRAME, graphicBuffer, codecTimeUs,
-            buffer->getAcquireFenceFd());
+    status_t err = mComponent->submitBuffer(
+            codecBufferId, graphicBuffer, codecTimeUs, buffer->getAcquireFenceFd());
 
     if (err != OK) {
         ALOGW("WARNING: emptyGraphicBuffer failed: 0x%x", err);
@@ -849,11 +845,10 @@
         ALOGV("submitEndOfInputStream_l: no codec buffers available");
         return;
     }
-    IOMX::buffer_id codecBufferId = *mFreeCodecBuffers.begin();
+    uint32_t codecBufferId = *mFreeCodecBuffers.begin();
 
     // We reject any additional incoming graphic buffers. There is no acquired buffer used for EOS
-    status_t err = mOMXNode->emptyBuffer(
-            codecBufferId, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS);
+    status_t err = mComponent->submitEos(codecBufferId);
     if (err != OK) {
         ALOGW("emptyDirectBuffer EOS failed: 0x%x", err);
     } else {
@@ -959,7 +954,7 @@
 
 bool GraphicBufferSource::areWeDiscardingAvailableBuffers_l() {
     return mEndOfStreamSent // already sent EOS to codec
-            || mOMXNode == nullptr // there is no codec connected
+            || mComponent == nullptr // there is no codec connected
             || (mSuspended && mActionQueue.empty()) // we are suspended and not waiting for
                                                     // any further action
             || !mExecuting;
@@ -970,7 +965,7 @@
         // This should only be possible if a new buffer was queued after
         // EOS was signaled, i.e. the app is misbehaving.
         ALOGW("onFrameAvailable: EOS is sent, ignoring frame");
-    } else if (mOMXNode == NULL || (mSuspended && mActionQueue.empty())) {
+    } else if (mComponent == NULL || (mSuspended && mActionQueue.empty())) {
         // FIXME: if we are suspended but have a resume queued we will stop repeating the last
         // frame. Is that the desired behavior?
         ALOGV("onFrameAvailable: suspended, ignoring frame");
@@ -1064,13 +1059,13 @@
 }
 
 status_t GraphicBufferSource::configure(
-        const sp<IOmxNodeWrapper>& omxNode,
+        const sp<ComponentWrapper>& component,
         int32_t dataSpace,
         int32_t bufferCount,
         uint32_t frameWidth,
         uint32_t frameHeight,
         uint32_t consumerUsage) {
-    if (omxNode == NULL) {
+    if (component == NULL) {
         return BAD_VALUE;
     }
 
@@ -1088,7 +1083,7 @@
 
     {
         Mutex::Autolock autoLock(mMutex);
-        mOMXNode = omxNode;
+        mComponent = component;
 
         err = mConsumer->setDefaultBufferSize(frameWidth, frameHeight);
         if (err != NO_ERROR) {
@@ -1320,7 +1315,7 @@
     // Set the end-of-stream flag.  If no frames are pending from the
     // BufferQueue, and a codec buffer is available, and we're executing,
     // and there is no stop timestamp, we initiate the EOS from here.
-    // Otherwise, we'll let codecBufferEmptied() (or omxExecuting) do it.
+    // Otherwise, we'll let codecBufferEmptied() (or start) do it.
     //
     // Note: if there are no pending frames and all codec buffers are
     // available, we *must* submit the EOS from here or we'll just
diff --git a/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h b/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h
new file mode 100644
index 0000000..e27829b
--- /dev/null
+++ b/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#ifndef COMPONENT_WRAPPER_H_
+#define COMPONENT_WRAPPER_H_
+
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <ui/GraphicBuffer.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct ComponentWrapper : public RefBase {
+    virtual status_t submitBuffer(
+            int32_t bufferId, const sp<GraphicBuffer> &buffer = nullptr,
+            int64_t timestamp = 0, int fenceFd = -1) = 0;
+    virtual status_t submitEos(int32_t bufferId) = 0;
+    virtual void dispatchDataSpaceChanged(
+            int32_t dataSpace, int32_t aspects, int32_t pixelFormat) = 0;
+};
+
+}  // namespace android
+
+#endif  // COMPONENT_WRAPPER_H_
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/FrameDropper.h b/media/libstagefright/gbs/include/media/stagefright/gbs/FrameDropper.h
similarity index 100%
rename from media/libstagefright/omx/include/media/stagefright/omx/FrameDropper.h
rename to media/libstagefright/gbs/include/media/stagefright/gbs/FrameDropper.h
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h b/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
similarity index 93%
rename from media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h
rename to media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
index 84fee6f..89f6cf8 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h
+++ b/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
@@ -18,21 +18,16 @@
 
 #define GRAPHIC_BUFFER_SOURCE_H_
 
-#include <gui/IGraphicBufferProducer.h>
+#include <binder/Status.h>
 #include <gui/BufferQueue.h>
+#include <gui/IGraphicBufferProducer.h>
 #include <utils/RefBase.h>
 
 #include <media/hardware/VideoAPI.h>
-#include <media/IOMX.h>
-#include <media/OMXFenceParcelable.h>
 #include <media/stagefright/foundation/ABase.h>
 #include <media/stagefright/foundation/AHandlerReflector.h>
 #include <media/stagefright/foundation/ALooper.h>
-
-#include <android/BnGraphicBufferSource.h>
-#include <android/BnOMXBufferSource.h>
-
-#include "IOmxNodeWrapper.h"
+#include <media/stagefright/gbs/ComponentWrapper.h>
 
 namespace android {
 
@@ -41,7 +36,7 @@
 struct FrameDropper;
 
 /*
- * This class is used to feed OMX codecs from a Surface via BufferQueue or
+ * This class is used to feed codecs from a Surface via BufferQueue or
  * HW producer.
  *
  * Instances of the class don't run on a dedicated thread.  Instead,
@@ -49,7 +44,7 @@
  *
  *  - Availability of a new frame of data from the BufferQueue (notified
  *    via the onFrameAvailable callback).
- *  - The return of a codec buffer (via OnEmptyBufferDone).
+ *  - The return of a codec buffer.
  *  - Application signaling end-of-stream.
  *  - Transition to or from "executing" state.
  *
@@ -91,39 +86,37 @@
         return mProducer;
     }
 
-    // OmxBufferSource interface
-    // ------------------------------
-
-    // This is called when OMX transitions to OMX_StateExecuting, which means
+    // This is called when component transitions to running state, which means
     // we can start handing it buffers.  If we already have buffers of data
     // sitting in the BufferQueue, this will send them to the codec.
-    Status onOmxExecuting();
+    Status start();
 
-    // This is called when OMX transitions to OMX_StateIdle, indicating that
+    // This is called when component transitions to stopped, indicating that
     // the codec is meant to return all buffers back to the client for them
     // to be freed. Do NOT submit any more buffers to the component.
-    Status onOmxIdle();
+    Status stop();
 
-    // This is called when OMX transitions to OMX_StateLoaded, indicating that
+    // This is called when component transitions to released, indicating that
     // we are shutting down.
-    Status onOmxLoaded();
+    Status release();
 
     // A "codec buffer", i.e. a buffer that can be used to pass data into
     // the encoder, has been allocated.  (This call does not call back into
-    // OMXNodeInstance.)
+    // component.)
     Status onInputBufferAdded(int32_t bufferId);
 
-    // Called from OnEmptyBufferDone.  If we have a BQ buffer available,
-    // fill it with a new frame of data; otherwise, just mark it as available.
+    // Called when encoder is no longer using the buffer.  If we have a BQ
+    // buffer available, fill it with a new frame of data; otherwise, just mark
+    // it as available.
     Status onInputBufferEmptied(int32_t bufferId, int fenceFd);
 
     // IGraphicBufferSource interface
     // ------------------------------
 
-    // Configure the buffer source to be used with an OMX node with the default
+    // Configure the buffer source to be used with a component with the default
     // data space.
     status_t configure(
-        const sp<IOmxNodeWrapper> &omxNode,
+        const sp<ComponentWrapper> &component,
         int32_t dataSpace,
         int32_t bufferCount,
         uint32_t frameWidth,
@@ -335,10 +328,10 @@
     // called when the data space of the input buffer changes
     void onDataspaceChanged_l(android_dataspace dataspace, android_pixel_format pixelFormat);
 
-    // Pointer back to the Omx node that created us.  We send buffers here.
-    sp<IOmxNodeWrapper> mOMXNode;
+    // Pointer back to the component that created us.  We send buffers here.
+    sp<ComponentWrapper> mComponent;
 
-    // Set by omxExecuting() / omxIdling().
+    // Set by start() / stop().
     bool mExecuting;
 
     bool mSuspended;
diff --git a/media/libstagefright/gbs/tests/Android.bp b/media/libstagefright/gbs/tests/Android.bp
new file mode 100644
index 0000000..1463e73
--- /dev/null
+++ b/media/libstagefright/gbs/tests/Android.bp
@@ -0,0 +1,15 @@
+cc_test {
+    name: "FrameDropper_test",
+
+    srcs: ["FrameDropper_test.cpp"],
+
+    shared_libs: [
+        "libstagefright_gbs",
+        "libutils",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
diff --git a/media/libstagefright/omx/tests/FrameDropper_test.cpp b/media/libstagefright/gbs/tests/FrameDropper_test.cpp
similarity index 98%
rename from media/libstagefright/omx/tests/FrameDropper_test.cpp
rename to media/libstagefright/gbs/tests/FrameDropper_test.cpp
index a925da6..54a84d3 100644
--- a/media/libstagefright/omx/tests/FrameDropper_test.cpp
+++ b/media/libstagefright/gbs/tests/FrameDropper_test.cpp
@@ -20,7 +20,7 @@
 
 #include <gtest/gtest.h>
 
-#include <media/stagefright/omx/FrameDropper.h>
+#include <media/stagefright/gbs/FrameDropper.h>
 #include <media/stagefright/foundation/ADebug.h>
 
 namespace android {
diff --git a/media/libstagefright/include/media/stagefright/MediaBuffer.h b/media/libstagefright/include/media/stagefright/MediaBuffer.h
new file mode 120000
index 0000000..1d49c1a
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaBuffer.h
@@ -0,0 +1 @@
+../../../../libmediaextractor/include/media/stagefright/MediaBuffer.h
\ No newline at end of file
diff --git a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
new file mode 120000
index 0000000..009b3d9
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
@@ -0,0 +1 @@
+../../../../libmediaextractor/include/media/stagefright/MediaBufferGroup.h
\ No newline at end of file
diff --git a/media/libstagefright/include/media/stagefright/MetaData.h b/media/libstagefright/include/media/stagefright/MetaData.h
index 3438c56..5959e86 100644
--- a/media/libstagefright/include/media/stagefright/MetaData.h
+++ b/media/libstagefright/include/media/stagefright/MetaData.h
@@ -24,7 +24,6 @@
 
 #include <binder/Parcel.h>
 #include <utils/RefBase.h>
-#include <utils/KeyedVector.h>
 #include <utils/String8.h>
 
 namespace android {
@@ -229,7 +228,7 @@
     kTypeD263        = 'd263',
 };
 
-class MetaData : public RefBase {
+class MetaData final : public RefBase {
 public:
     MetaData();
     MetaData(const MetaData &from);
@@ -279,59 +278,22 @@
     String8 toString() const;
     void dumpToLog() const;
 
-    status_t writeToParcel(Parcel &parcel);
-    status_t updateFromParcel(const Parcel &parcel);
-    static sp<MetaData> createFromParcel(const Parcel &parcel);
-
 protected:
     virtual ~MetaData();
 
 private:
-    struct typed_data {
-        typed_data();
-        ~typed_data();
+    friend class BpMediaSource;
+    friend class BnMediaSource;
+    friend class BpMediaExtractor;
+    friend class BnMediaExtractor;
 
-        typed_data(const MetaData::typed_data &);
-        typed_data &operator=(const MetaData::typed_data &);
-
-        void clear();
-        void setData(uint32_t type, const void *data, size_t size);
-        void getData(uint32_t *type, const void **data, size_t *size) const;
-        // may include hexdump of binary data if verbose=true
-        String8 asString(bool verbose) const;
-
-    private:
-        uint32_t mType;
-        size_t mSize;
-
-        union {
-            void *ext_data;
-            float reservoir;
-        } u;
-
-        bool usesReservoir() const {
-            return mSize <= sizeof(u.reservoir);
-        }
-
-        void *allocateStorage(size_t size);
-        void freeStorage();
-
-        void *storage() {
-            return usesReservoir() ? &u.reservoir : u.ext_data;
-        }
-
-        const void *storage() const {
-            return usesReservoir() ? &u.reservoir : u.ext_data;
-        }
-    };
-
-    struct Rect {
-        int32_t mLeft, mTop, mRight, mBottom;
-    };
-
-    KeyedVector<uint32_t, typed_data> mItems;
-
-    // MetaData &operator=(const MetaData &);
+    status_t writeToParcel(Parcel &parcel);
+    status_t updateFromParcel(const Parcel &parcel);
+    static sp<MetaData> createFromParcel(const Parcel &parcel);
+    struct typed_data;
+    struct Rect;
+    struct MetaDataInternal;
+    MetaDataInternal *mInternalData;
 };
 
 }  // namespace android
diff --git a/media/libstagefright/omx/1.0/Omx.cpp b/media/libstagefright/omx/1.0/Omx.cpp
index fe50656..4e2d398 100644
--- a/media/libstagefright/omx/1.0/Omx.cpp
+++ b/media/libstagefright/omx/1.0/Omx.cpp
@@ -24,7 +24,7 @@
 
 #include <media/stagefright/omx/OMXUtils.h>
 #include <media/stagefright/omx/OMXMaster.h>
-#include <media/stagefright/omx/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
 
 #include <media/stagefright/omx/1.0/WOmxNode.h>
 #include <media/stagefright/omx/1.0/WOmxObserver.h>
@@ -148,7 +148,7 @@
 Return<void> Omx::createInputSurface(createInputSurface_cb _hidl_cb) {
     sp<::android::IGraphicBufferProducer> bufferProducer;
 
-    sp<GraphicBufferSource> graphicBufferSource = new GraphicBufferSource();
+    sp<OmxGraphicBufferSource> graphicBufferSource = new OmxGraphicBufferSource();
     status_t err = graphicBufferSource->initCheck();
     if (err != OK) {
         LOG(ERROR) << "Failed to create persistent input surface: "
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
index 3201c32..ed272bb 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
@@ -79,9 +79,9 @@
 };
 
 struct TWGraphicBufferSource::TWOmxBufferSource : public IOmxBufferSource {
-    sp<GraphicBufferSource> mSource;
+    sp<OmxGraphicBufferSource> mSource;
 
-    TWOmxBufferSource(const sp<GraphicBufferSource> &source): mSource(source) {
+    TWOmxBufferSource(const sp<OmxGraphicBufferSource> &source): mSource(source) {
     }
 
     Return<void> onOmxExecuting() override {
@@ -115,7 +115,7 @@
 
 // TWGraphicBufferSource
 TWGraphicBufferSource::TWGraphicBufferSource(
-        sp<GraphicBufferSource> const& base) :
+        sp<OmxGraphicBufferSource> const& base) :
     mBase(base),
     mOmxBufferSource(new TWOmxBufferSource(base)) {
 }
diff --git a/media/libstagefright/omx/Android.bp b/media/libstagefright/omx/Android.bp
index 8539864..306a8eb 100644
--- a/media/libstagefright/omx/Android.bp
+++ b/media/libstagefright/omx/Android.bp
@@ -6,12 +6,11 @@
     },
 
     srcs: [
-        "FrameDropper.cpp",
-        "GraphicBufferSource.cpp",
         "BWGraphicBufferSource.cpp",
         "OMXMaster.cpp",
         "OMXNodeInstance.cpp",
         "OMXUtils.cpp",
+        "OmxGraphicBufferSource.cpp",
         "SimpleSoftOMXComponent.cpp",
         "SoftOMXComponent.cpp",
         "SoftOMXPlugin.cpp",
@@ -49,6 +48,7 @@
         "libgui",
         "libcutils",
         "libstagefright_foundation",
+        "libstagefright_gbs",
         "libstagefright_xmlparser",
         "libdl",
         "libhidlbase",
diff --git a/media/libstagefright/omx/BWGraphicBufferSource.cpp b/media/libstagefright/omx/BWGraphicBufferSource.cpp
index 94ef598..fa30a46 100644
--- a/media/libstagefright/omx/BWGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/BWGraphicBufferSource.cpp
@@ -55,9 +55,9 @@
 };
 
 struct BWGraphicBufferSource::BWOMXBufferSource : public BnOMXBufferSource {
-    sp<GraphicBufferSource> mSource;
+    sp<OmxGraphicBufferSource> mSource;
 
-    BWOMXBufferSource(const sp<GraphicBufferSource> &source): mSource(source) {
+    BWOMXBufferSource(const sp<OmxGraphicBufferSource> &source): mSource(source) {
     }
 
     Status onOmxExecuting() override {
@@ -83,7 +83,7 @@
 };
 
 BWGraphicBufferSource::BWGraphicBufferSource(
-        sp<GraphicBufferSource> const& base) :
+        sp<OmxGraphicBufferSource> const& base) :
     mBase(base),
     mOMXBufferSource(new BWOMXBufferSource(base)) {
 }
diff --git a/media/libstagefright/omx/OmxGraphicBufferSource.cpp b/media/libstagefright/omx/OmxGraphicBufferSource.cpp
new file mode 100644
index 0000000..83feac8
--- /dev/null
+++ b/media/libstagefright/omx/OmxGraphicBufferSource.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "OmxGraphicBufferSource"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <media/stagefright/gbs/ComponentWrapper.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
+
+namespace android {
+
+namespace {
+
+class OmxComponentWrapper : public ComponentWrapper {
+public:
+    explicit OmxComponentWrapper(const sp<IOmxNodeWrapper> &node)
+        : mOmxNode(node) {}
+    virtual ~OmxComponentWrapper() = default;
+
+    status_t submitBuffer(
+            int32_t bufferId, const sp<GraphicBuffer> &buffer,
+            int64_t timestamp, int fenceFd) override {
+        return mOmxNode->emptyBuffer(
+                bufferId, OMX_BUFFERFLAG_ENDOFFRAME, buffer, timestamp, fenceFd);
+    }
+
+    status_t submitEos(int32_t bufferId) override {
+        return mOmxNode->emptyBuffer(bufferId, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS);
+    }
+
+    void dispatchDataSpaceChanged(
+            int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
+        mOmxNode->dispatchDataSpaceChanged(dataSpace, aspects, pixelFormat);
+    }
+
+private:
+    sp<IOmxNodeWrapper> mOmxNode;
+
+    DISALLOW_EVIL_CONSTRUCTORS(OmxComponentWrapper);
+};
+
+}  // namespace
+
+Status OmxGraphicBufferSource::onOmxExecuting() {
+    return start();
+}
+
+Status OmxGraphicBufferSource::onOmxIdle() {
+    return stop();
+}
+
+Status OmxGraphicBufferSource::onOmxLoaded(){
+    return release();
+}
+
+status_t OmxGraphicBufferSource::configure(
+        const sp<IOmxNodeWrapper>& omxNode,
+        int32_t dataSpace,
+        int32_t bufferCount,
+        uint32_t frameWidth,
+        uint32_t frameHeight,
+        uint32_t consumerUsage) {
+    if (omxNode == NULL) {
+        return BAD_VALUE;
+    }
+
+    return GraphicBufferSource::configure(
+            new OmxComponentWrapper(omxNode), dataSpace, bufferCount,
+            frameWidth, frameHeight, consumerUsage);
+}
+
+}  // namespace android
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
index b9f22ab..4e56c98 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
@@ -28,7 +28,7 @@
 
 #include <android/BnGraphicBufferSource.h>
 
-#include <media/stagefright/omx/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
 
 namespace android {
 namespace hardware {
@@ -37,7 +37,7 @@
 namespace V1_0 {
 namespace implementation {
 
-using ::android::GraphicBufferSource;
+using ::android::OmxGraphicBufferSource;
 using ::android::hardware::graphics::common::V1_0::Dataspace;
 using ::android::hardware::media::omx::V1_0::ColorAspects;
 using ::android::hardware::media::omx::V1_0::IGraphicBufferSource;
@@ -69,10 +69,10 @@
 struct TWGraphicBufferSource : public TGraphicBufferSource {
     struct TWOmxNodeWrapper;
     struct TWOmxBufferSource;
-    sp<GraphicBufferSource> mBase;
+    sp<OmxGraphicBufferSource> mBase;
     sp<IOmxBufferSource> mOmxBufferSource;
 
-    TWGraphicBufferSource(sp<GraphicBufferSource> const& base);
+    TWGraphicBufferSource(sp<OmxGraphicBufferSource> const& base);
     Return<Status> configure(
             const sp<IOmxNode>& omxNode, Dataspace dataspace) override;
     Return<Status> setSuspend(bool suspend, int64_t timeUs) override;
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
index 0f78eb6..0efff22 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
@@ -23,14 +23,14 @@
 #include <android/BnOMXBufferSource.h>
 #include <media/IOMX.h>
 
-#include "GraphicBufferSource.h"
+#include "OmxGraphicBufferSource.h"
 #include "IOmxNodeWrapper.h"
 
 namespace android {
 
 using ::android::binder::Status;
 using ::android::BnGraphicBufferSource;
-using ::android::GraphicBufferSource;
+using ::android::OmxGraphicBufferSource;
 using ::android::IOMXNode;
 using ::android::sp;
 
@@ -38,10 +38,10 @@
     struct BWOMXBufferSource;
     struct BWOmxNodeWrapper;
 
-    sp<GraphicBufferSource> mBase;
+    sp<OmxGraphicBufferSource> mBase;
     sp<IOMXBufferSource> mOMXBufferSource;
 
-    BWGraphicBufferSource(sp<GraphicBufferSource> const &base);
+    BWGraphicBufferSource(sp<OmxGraphicBufferSource> const &base);
 
     Status configure(
             const sp<IOMXNode>& omxNode, int32_t dataSpace) override;
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h
new file mode 100644
index 0000000..4b0f3d2
--- /dev/null
+++ b/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h
@@ -0,0 +1,84 @@
+/*
+ * 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 OMX_GRAPHIC_BUFFER_SOURCE_H_
+
+#define OMX_GRAPHIC_BUFFER_SOURCE_H_
+
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/foundation/ABase.h>
+
+#include <android/BnGraphicBufferSource.h>
+#include <android/BnOMXBufferSource.h>
+
+#include "IOmxNodeWrapper.h"
+
+namespace android {
+
+using ::android::binder::Status;
+
+/*
+ * This class is used to feed OMX codecs from a Surface via BufferQueue or
+ * HW producer.
+ *
+ * See media/stagefright/gbs/GraphicBufferSource.h for documentation.
+ */
+class OmxGraphicBufferSource : public GraphicBufferSource {
+public:
+    OmxGraphicBufferSource() = default;
+    virtual ~OmxGraphicBufferSource() = default;
+
+    // OmxBufferSource interface
+    // ------------------------------
+
+    // This is called when OMX transitions to OMX_StateExecuting, which means
+    // we can start handing it buffers.  If we already have buffers of data
+    // sitting in the BufferQueue, this will send them to the codec.
+    Status onOmxExecuting();
+
+    // This is called when OMX transitions to OMX_StateIdle, indicating that
+    // the codec is meant to return all buffers back to the client for them
+    // to be freed. Do NOT submit any more buffers to the component.
+    Status onOmxIdle();
+
+    // This is called when OMX transitions to OMX_StateLoaded, indicating that
+    // we are shutting down.
+    Status onOmxLoaded();
+
+    // Rest of the interface in GraphicBufferSource.
+
+    // IGraphicBufferSource interface
+    // ------------------------------
+
+    // Configure the buffer source to be used with an OMX node with the default
+    // data space.
+    status_t configure(
+        const sp<IOmxNodeWrapper> &omxNode,
+        int32_t dataSpace,
+        int32_t bufferCount,
+        uint32_t frameWidth,
+        uint32_t frameHeight,
+        uint32_t consumerUsage);
+
+    // Rest of the interface in GraphicBufferSource.
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(OmxGraphicBufferSource);
+};
+
+}  // namespace android
+
+#endif  // OMX_GRAPHIC_BUFFER_SOURCE_H_
diff --git a/media/libstagefright/omx/tests/Android.bp b/media/libstagefright/omx/tests/Android.bp
index 999d9d4..3b521ab 100644
--- a/media/libstagefright/omx/tests/Android.bp
+++ b/media/libstagefright/omx/tests/Android.bp
@@ -34,21 +34,3 @@
 
     compile_multilib: "32",
 }
-
-cc_test {
-    name: "FrameDropper_test",
-
-    srcs: ["FrameDropper_test.cpp"],
-
-    shared_libs: [
-        "libstagefright_omx",
-        "libutils",
-    ],
-
-    include_dirs: ["frameworks/av/media/libstagefright/omx"],
-
-    cflags: [
-        "-Werror",
-        "-Wall",
-    ],
-}
diff --git a/media/libstagefright/tests/Android.bp b/media/libstagefright/tests/Android.bp
index 35119c2..e67a949 100644
--- a/media/libstagefright/tests/Android.bp
+++ b/media/libstagefright/tests/Android.bp
@@ -15,6 +15,7 @@
         "libcutils",
         "libgui",
         "libmedia",
+        "libmediaextractor",
         "libstagefright",
         "libstagefright_foundation",
         "libstagefright_omx",
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
rename to packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
rename to packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
rename to packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
rename to packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml b/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml
new file mode 100644
index 0000000..c6228e6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_cast.xml b/packages/MediaComponents/res/drawable/ic_cast.xml
deleted file mode 100644
index ac22a4b..0000000
--- a/packages/MediaComponents/res/drawable/ic_cast.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_mute.xml b/packages/MediaComponents/res/drawable/ic_mute.xml
new file mode 100644
index 0000000..560aaec
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_mute.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_settings.xml b/packages/MediaComponents/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..a59ecc1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_settings.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_unmute.xml b/packages/MediaComponents/res/drawable/ic_unmute.xml
new file mode 100644
index 0000000..9dfb2b9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_unmute.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index ff4f12a..4d05546 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -46,14 +46,12 @@
             android:paddingStart="4dp"
             android:paddingEnd="4dp"
             android:textSize="20sp"
-            android:text="North by Northwest"
             android:textColor="#FFFFFFFF" />
 
-        <ImageButton
-            android:id="@+id/cast"
+        <view class="com.android.support.mediarouter.app.MediaRouteButton" android:id="@+id/cast"
             android:layout_alignParentEnd="true"
             android:layout_centerVertical="true"
-            style="@style/TitleBarButton.MediaRouteButton"/>
+            style="@style/TitleBarButton" />
 
     </RelativeLayout>
 
@@ -85,7 +83,6 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingLeft="15dp"
-        android:paddingRight="15dp"
         android:orientation="horizontal">
 
         <TextView
@@ -112,25 +109,50 @@
             android:textStyle="bold"
             android:textColor="#BBBBBB" />
 
-        <ImageButton
-            android:id="@+id/overflow"
-            android:layout_alignParentEnd="true"
+        <LinearLayout
+            android:id="@+id/basic_controls"
+            android:layout_alignParentRight="true"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:layout_centerVertical="true"
-            style="@style/BottomBarButton.Overflow"/>
+            android:orientation="horizontal" >
 
-        <ImageButton
-            android:id="@+id/fullscreen"
-            android:layout_toLeftOf="@id/overflow"
-            android:layout_centerVertical="true"
-            style="@style/BottomBarButton.FullScreen"/>
+            <ImageButton
+                android:id="@+id/subtitle"
+                android:scaleType="fitCenter"
+                style="@style/BottomBarButton.CC" />
+            <ImageButton
+                android:id="@+id/fullscreen"
+                style="@style/BottomBarButton.FullScreen"/>
+            <ImageButton
+                android:id="@+id/overflow_right"
+                style="@style/BottomBarButton.OverflowRight"/>
+        </LinearLayout>
 
-        <ImageButton
-            android:id="@+id/cc"
-            android:scaleType="fitCenter"
-            android:layout_toLeftOf="@id/fullscreen"
+        <LinearLayout
+            android:id="@+id/extra_controls"
+            android:layout_alignParentRight="true"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:layout_centerVertical="true"
-            style="@style/BottomBarButton.CC" />
+            android:visibility="gone"
+            android:orientation="horizontal" >
+
+            <ImageButton
+                android:id="@+id/mute"
+                style="@style/BottomBarButton.Mute" />
+            <ImageButton
+                android:id="@+id/aspect_ratio"
+                style="@style/BottomBarButton.AspectRatio" />
+            <ImageButton
+                android:id="@+id/settings"
+                style="@style/BottomBarButton.Settings" />
+            <ImageButton
+                android:id="@+id/overflow_left"
+                style="@style/BottomBarButton.OverflowLeft"/>
+        </LinearLayout>
 
     </RelativeLayout>
 
 </LinearLayout>
+
diff --git a/packages/MediaComponents/res/values/colors.xml b/packages/MediaComponents/res/values/colors.xml
new file mode 100644
index 0000000..9e071d7
--- /dev/null
+++ b/packages/MediaComponents/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?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 name="gray">0xff444444</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index d31b41d..c59380c 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -34,29 +34,42 @@
         <item name="android:layout_margin">10dp</item>
     </style>
 
-    <style name="TitleBarButton.MediaRouteButton">
-        <item name="android:src">@drawable/ic_cast</item>
-    </style>
-
-
     <style name="BottomBarButton">
         <item name="android:background">@null</item>
-        <item name="android:layout_width">24dp</item>
-        <item name="android:layout_height">24dp</item>
-        <item name="android:layout_margin">10dp</item>
+        <item name="android:layout_width">44dp</item>
+        <item name="android:layout_height">34dp</item>
+        <item name="android:layout_marginTop">5dp</item>
+        <item name="android:layout_marginBottom">5dp</item>
+        <item name="android:paddingLeft">5dp</item>
+        <item name="android:paddingRight">5dp</item>
     </style>
 
     <style name="BottomBarButton.CC">
-        <item name="android:src">@drawable/ic_media_cc_disabled</item>
+        <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
     </style>
 
     <style name="BottomBarButton.FullScreen">
         <item name="android:src">@drawable/ic_fullscreen</item>
     </style>
 
-    <style name="BottomBarButton.Overflow">
+    <style name="BottomBarButton.OverflowRight">
         <item name="android:src">@drawable/ic_chevron_right</item>
     </style>
 
-</resources>
+    <style name="BottomBarButton.OverflowLeft">
+        <item name="android:src">@drawable/ic_chevron_left</item>
+    </style>
 
+    <style name="BottomBarButton.Settings">
+        <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>
+    </style>
+
+</resources>
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
new file mode 100644
index 0000000..59d8366
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -0,0 +1,101 @@
+/*
+ * 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.IMediaSession2;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.CommandButton;
+import android.media.SessionToken;
+import android.media.update.MediaBrowser2Provider;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaBrowser2Impl extends MediaController2Impl implements MediaBrowser2Provider {
+    private final String TAG = "MediaBrowser2";
+    private final boolean DEBUG = true; // TODO(jaewan): change.
+
+    private final MediaBrowser2 mInstance;
+    private final MediaBrowser2.BrowserCallback mCallback;
+
+    public MediaBrowser2Impl(MediaBrowser2 instance, Context context, SessionToken token,
+            BrowserCallback callback, Executor executor) {
+        super(instance, context, token, callback, executor);
+        mInstance = instance;
+        mCallback = callback;
+    }
+
+    @Override
+    public void getBrowserRoot_impl(Bundle rootHints) {
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.getBrowserRoot(getControllerStub(), rootHints);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+    }
+
+    @Override
+    public void subscribe_impl(String parentId, Bundle options) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void unsubscribe_impl(String parentId, Bundle options) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void getItem_impl(String mediaId) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void getChildren_impl(String parentId, int page, int pageSize, Bundle options) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void search_impl(String query, int page, int pageSize, Bundle extras) {
+        // TODO(jaewan): Implement
+    }
+
+    public void onGetRootResult(
+            final Bundle rootHints, final String rootMediaId, final Bundle rootExtra) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onGetRootResult(rootHints, rootMediaId, rootExtra);
+        });
+    }
+
+    public void onCustomLayoutChanged(final List<CommandButton> layout) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onCustomLayoutChanged(layout);
+        });
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 1a27056..ac3f311 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -16,26 +16,35 @@
 
 package com.android.media;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.media.IMediaSession2;
 import android.media.IMediaSession2Callback;
+import android.media.MediaController2.PlaybackInfo;
+import android.media.MediaItem2;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaController2;
 import android.media.MediaController2.ControllerCallback;
 import android.media.MediaPlayerBase;
+import android.media.MediaSession2.PlaylistParam;
 import android.media.MediaSessionService2;
+import android.media.PlaybackState2;
+import android.media.Rating2;
 import android.media.SessionToken;
 import android.media.session.PlaybackState;
 import android.media.update.MediaController2Provider;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.support.annotation.GuardedBy;
 import android.util.Log;
 
@@ -105,7 +114,7 @@
         mCallback = callback;
         mCallbackExecutor = executor;
         mDeathRecipient = () -> {
-            mInstance.release();
+            mInstance.close();
         };
 
         mSessionBinder = null;
@@ -158,7 +167,10 @@
     }
 
     @Override
-    public void release_impl() {
+    public void close_impl() {
+        if (DEBUG) {
+            Log.d(TAG, "relese from " + mToken);
+        }
         final IMediaSession2 binder;
         synchronized (mLock) {
             if (mIsReleased) {
@@ -188,6 +200,18 @@
         });
     }
 
+    IMediaSession2 getSessionBinder() {
+        return mSessionBinder;
+    }
+
+    MediaSession2CallbackStub getControllerStub() {
+        return mSessionCallbackStub;
+    }
+
+    Executor getCallbackExecutor() {
+        return mCallbackExecutor;
+    }
+
     @Override
     public SessionToken getSessionToken_impl() {
         return mToken;
@@ -241,69 +265,128 @@
         }
     }
 
+    //////////////////////////////////////////////////////////////////////////////////////
+    // TODO(jaewan): Implement follows
+    //////////////////////////////////////////////////////////////////////////////////////
     @Override
-    public PlaybackState getPlaybackState_impl() {
-        final IMediaSession2 binder = mSessionBinder;
-        if (binder != null) {
-            try {
-                return binder.getPlaybackState();
-            } 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());
-        }
-        // TODO(jaewan): What to return for error case?
+    public PendingIntent getSessionActivity_impl() {
+        // TODO(jaewan): Implement
         return null;
     }
 
     @Override
-    public void addPlaybackListener_impl(
-            MediaPlayerBase.PlaybackListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener shouldn't be null");
-        }
-        if (handler == null) {
-            throw new IllegalArgumentException("handler shouldn't be null");
-        }
-        boolean registerCallback;
-        synchronized (mLock) {
-            if (PlaybackListenerHolder.contains(mPlaybackListeners, listener)) {
-                throw new IllegalArgumentException("listener is already added. Ignoring.");
-            }
-            registerCallback = mPlaybackListeners.isEmpty();
-            mPlaybackListeners.add(new PlaybackListenerHolder(listener, handler));
-        }
-        if (registerCallback) {
-            registerCallbackForPlaybackNotLocked();
-        }
+    public int getRatingType_impl() {
+        // TODO(jaewan): Implement
+        return 0;
     }
 
     @Override
-    public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener shouldn't be null");
-        }
-        boolean unregisterCallback;
-        synchronized (mLock) {
-            int idx = PlaybackListenerHolder.indexOf(mPlaybackListeners, listener);
-            if (idx >= 0) {
-                mPlaybackListeners.get(idx).removeCallbacksAndMessages(null);
-                mPlaybackListeners.remove(idx);
-            }
-            unregisterCallback = mPlaybackListeners.isEmpty();
-        }
-        if (unregisterCallback) {
-            final IMediaSession2 binder = mSessionBinder;
-            if (binder != null) {
-                // Lazy unregister
-                try {
-                    binder.unregisterCallback(mSessionCallbackStub, CALLBACK_FLAG_PLAYBACK);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Cannot connect to the service or the session is gone", e);
-                }
-            }
-        }
+    public void setVolumeTo_impl(int value, int flags) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void adjustVolume_impl(int direction, int flags) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public PlaybackInfo getPlaybackInfo_impl() {
+        // TODO(jaewan): Implement
+        return null;
+    }
+
+    @Override
+    public void prepareFromUri_impl(Uri uri, Bundle extras) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void prepareFromSearch_impl(String query, Bundle extras) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void prepareMediaId_impl(String mediaId, Bundle extras) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void playFromSearch_impl(String query, Bundle extras) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void playFromUri_impl(String uri, Bundle extras) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void playFromMediaId_impl(String mediaId, Bundle extras) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void setRating_impl(Rating2 rating) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public List<MediaItem2> getPlaylist_impl() {
+        // TODO(jaewan): Implement
+        return null;
+    }
+
+    @Override
+    public void prepare_impl() {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void fastForward_impl() {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void rewind_impl() {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void seekTo_impl(long pos) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void setCurrentPlaylistItem_impl(int index) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public PlaybackState2 getPlaybackState_impl() {
+        // TODO(jaewan): Implement
+        return null;
+    }
+
+    @Override
+    public void removePlaylistItem_impl(MediaItem2 index) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void addPlaylistItem_impl(int index, MediaItem2 item) {
+    // TODO(jaewan): Implement
+    }
+
+    @Override
+    public PlaylistParam getPlaylistParam_impl() {
+        // TODO(jaewan): Implement
+        return null;
     }
 
     ///////////////////////////////////////////////////
@@ -335,7 +418,8 @@
     private void onConnectionChangedNotLocked(IMediaSession2 sessionBinder,
             CommandGroup commandGroup) {
         if (DEBUG) {
-            Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder);
+            Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder
+                    + ", commands=" + commandGroup);
         }
         boolean release = false;
         try {
@@ -361,6 +445,9 @@
                     // so can be used without worrying about deadlock.
                     mSessionBinder.asBinder().linkToDeath(mDeathRecipient, 0);
                 } catch (RemoteException e) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Session died too early.", e);
+                    }
                     release = true;
                     return;
                 }
@@ -377,12 +464,14 @@
             if (release) {
                 // Trick to call release() without holding the lock, to prevent potential deadlock
                 // with the developer's custom lock within the ControllerCallback.onDisconnected().
-                mInstance.release();
+                mInstance.close();
             }
         }
     }
 
-    private static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
+    // TODO(jaewan): Pull out this from the controller2, and rename it to the MediaController2Stub
+    //               or MediaBrowser2Stub.
+    static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
         private final WeakReference<MediaController2Impl> mController;
 
         private MediaSession2CallbackStub(MediaController2Impl controller) {
@@ -397,6 +486,15 @@
             return controller;
         }
 
+        // TODO(jaewan): Refactor code to get rid of these pattern.
+        private MediaBrowser2Impl getBrowser() throws IllegalStateException {
+            final MediaController2Impl controller = getController();
+            if (controller instanceof MediaBrowser2Impl) {
+                return (MediaBrowser2Impl) controller;
+            }
+            return null;
+        }
+
         public void destroy() {
             mController.clear();
         }
@@ -420,6 +518,50 @@
             controller.onConnectionChangedNotLocked(
                     sessionBinder, CommandGroup.fromBundle(commandGroup));
         }
+
+        @Override
+        public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra)
+                throws RuntimeException {
+            final MediaBrowser2Impl browser;
+            try {
+                browser = getBrowser();
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+                return;
+            }
+            if (browser == null) {
+                // TODO(jaewan): Revisit here. Could be a bug
+                return;
+            }
+            browser.onGetRootResult(rootHints, rootMediaId, rootExtra);
+        }
+
+        @Override
+        public void onCustomLayoutChanged(List<Bundle> commandButtonlist) {
+            if (commandButtonlist == null) {
+                // Illegal call. Ignore
+                return;
+            }
+            final MediaBrowser2Impl browser;
+            try {
+                browser = getBrowser();
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+                return;
+            }
+            if (browser == null) {
+                // TODO(jaewan): Revisit here. Could be a bug
+                return;
+            }
+            List<CommandButton> layout = new ArrayList<>();
+            for (int i = 0; i < commandButtonlist.size(); i++) {
+                CommandButton button = CommandButton.fromBundle(commandButtonlist.get(i));
+                if (button != null) {
+                    layout.add(button);
+                }
+            }
+            browser.onCustomLayoutChanged(layout);
+        }
     }
 
     // This will be called on the main thread.
@@ -455,7 +597,7 @@
             // Permanent lose of the binding because of the service package update or removed.
             // This SessionServiceRecord will be removed accordingly, but forget session binder here
             // for sure.
-            mInstance.release();
+            mInstance.close();
         }
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
new file mode 100644
index 0000000..b177dda
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -0,0 +1,89 @@
+/*
+ * 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.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
+import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.MediaSessionService2;
+import android.media.VolumeProvider;
+import android.media.update.MediaLibraryService2Provider;
+import android.os.Bundle;
+
+public class MediaLibraryService2Impl extends MediaSessionService2Impl implements
+        MediaLibraryService2Provider {
+    private final MediaSessionService2 mInstance;
+    private MediaLibrarySession mLibrarySession;
+
+    public MediaLibraryService2Impl(MediaLibraryService2 instance) {
+        super(instance);
+        mInstance = instance;
+    }
+
+    @Override
+    public void onCreate_impl() {
+        super.onCreate_impl();
+
+        // Effectively final
+        MediaSession2 session = getSession();
+        if (!(session instanceof MediaLibrarySession)) {
+            throw new RuntimeException("Expected MediaLibrarySession, but returned MediaSession2");
+        }
+        mLibrarySession = (MediaLibrarySession) getSession();
+    }
+
+    @Override
+    Intent createServiceIntent() {
+        Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+        serviceIntent.setAction(MediaLibraryService2.SERVICE_INTERFACE);
+        return serviceIntent;
+    }
+
+    public static class MediaLibrarySessionImpl extends MediaSession2Impl
+            implements MediaLibrarySessionProvider {
+        private final MediaLibrarySession mInstance;
+        private final MediaLibrarySessionCallback mCallback;
+
+        public MediaLibrarySessionImpl(MediaLibrarySession instance, Context context,
+                MediaPlayerBase player, String id,
+                MediaLibrarySessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+                PendingIntent sessionActivity) {
+            super(instance, context, player, id, callback, volumeProvider, ratingType,
+                    sessionActivity);
+            mInstance = instance;
+            mCallback = callback;
+        }
+
+        @Override
+        public void notifyChildrenChanged_impl(ControllerInfo controller, String parentId,
+                Bundle options) {
+            // TODO(jaewan): Implements
+        }
+
+        @Override
+        public void notifyChildrenChanged_impl(String parentId, Bundle options) {
+            // TODO(jaewan): Implements
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 09d7adc..2e71641 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -17,23 +17,32 @@
 package com.android.media;
 
 import android.Manifest.permission;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.AudioAttributes;
 import android.media.IMediaSession2Callback;
-import android.media.MediaController2;
+import android.media.MediaItem2;
 import android.media.MediaPlayerBase;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
+import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParam;
 import android.media.MediaSession2.SessionCallback;
 import android.media.SessionToken;
+import android.media.VolumeProvider;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.media.update.MediaSession2Provider;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.ResultReceiver;
 import android.util.Log;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -48,7 +57,6 @@
     private final Context mContext;
     private final String mId;
     private final Handler mHandler;
-    private final SessionCallback mCallback;
     private final MediaSession2Stub mSessionStub;
     private final SessionToken mSessionToken;
 
@@ -60,24 +68,28 @@
 
     /**
      * Can be only called by the {@link Builder#build()}.
-     *
+     * 
      * @param instance
      * @param context
      * @param player
      * @param id
      * @param callback
+     * @param volumeProvider
+     * @param ratingType
+     * @param sessionActivity
      */
     public MediaSession2Impl(MediaSession2 instance, Context context, MediaPlayerBase player,
-            String id, SessionCallback callback) {
+            String id, SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity) {
         mInstance = instance;
+        // TODO(jaewan): Keep other params.
 
         // Argument checks are done by builder already.
         // Initialize finals first.
         mContext = context;
         mId = id;
         mHandler = new Handler(Looper.myLooper());
-        mCallback = callback;
-        mSessionStub = new MediaSession2Stub(this);
+        mSessionStub = new MediaSession2Stub(this, callback);
         // Ask server to create session token for following reasons.
         //   1. Make session ID unique per package.
         //      Server can only know if the package has another process and has another session
@@ -103,48 +115,44 @@
     //               setPlayer(null). Token can be available when player is null, and
     //               controller can also attach to session.
     @Override
-    public void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException {
+    public void setPlayer_impl(MediaPlayerBase player, VolumeProvider volumeProvider) throws IllegalArgumentException {
         ensureCallingThread();
-        // TODO(jaewan): Remove this when we don't inherits MediaPlayerBase.
-        if (player instanceof MediaSession2 || player instanceof MediaController2) {
-            throw new IllegalArgumentException("player doesn't accept MediaSession2 nor"
-                    + " MediaController2");
-        }
-        if (player != null && mPlayer == player) {
-            // Player didn't changed. No-op.
-            return;
+        if (player == null) {
+            throw new IllegalArgumentException("player shouldn't be null");
         }
         setPlayerInternal(player);
     }
 
     private void setPlayerInternal(MediaPlayerBase player) {
-        mHandler.removeCallbacksAndMessages(null);
-        if (mPlayer == null && player != null) {
-            if (DEBUG) {
-                Log.d(TAG, "session is ready to use, id=" + mId);
-            }
-        } else if (mPlayer != null && player == null) {
-            if (DEBUG) {
-                Log.d(TAG, "session is now unavailable, id=" + mId);
-            }
-            if (mSessionStub != null) {
-                // Invalidate previously published session stub.
-                mSessionStub.destroyNotLocked();
-            }
+        if (mPlayer == player) {
+            // Player didn't changed. No-op.
+            return;
         }
+        mHandler.removeCallbacksAndMessages(null);
         if (mPlayer != null && mListener != null) {
             // This might not work for a poorly implemented player.
             mPlayer.removePlaybackListener(mListener);
         }
-        if (player != null) {
-            mListener = new MyPlaybackListener(this, player);
-            player.addPlaybackListener(mListener, mHandler);
-            notifyPlaybackStateChanged(player.getPlaybackState());
-        }
+        mListener = new MyPlaybackListener(this, player);
+        player.addPlaybackListener(mListener, mHandler);
+        notifyPlaybackStateChanged(player.getPlaybackState());
         mPlayer = player;
     }
 
     @Override
+    public void close_impl() {
+        // Flush any pending messages.
+        mHandler.removeCallbacksAndMessages(null);
+        if (mSessionStub != null) {
+            if (DEBUG) {
+                Log.d(TAG, "session is now unavailable, id=" + mId);
+            }
+            // Invalidate previously published session stub.
+            mSessionStub.destroyNotLocked();
+        }
+    }
+
+    @Override
     public MediaPlayerBase getPlayer_impl() {
         return getPlayer();
     }
@@ -161,6 +169,16 @@
     }
 
     @Override
+    public void setAudioAttributes_impl(AudioAttributes attributes) {
+        // implement
+    }
+
+    @Override
+    public void setAudioFocusRequest_impl(int focusGain) {
+        // implement
+    }
+
+    @Override
     public void play_impl() {
         ensureCallingThread();
         ensurePlayer();
@@ -196,40 +214,74 @@
     }
 
     @Override
-    public PlaybackState getPlaybackState_impl() {
+    public void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout) {
         ensureCallingThread();
-        ensurePlayer();
-        return mPlayer.getPlaybackState();
+        if (controller == null) {
+            throw new IllegalArgumentException("controller shouldn't be null");
+        }
+        if (layout == null) {
+            throw new IllegalArgumentException("layout shouldn't be null");
+        }
+        mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////
+    // TODO(jaewan): Implement follows
+    //////////////////////////////////////////////////////////////////////////////////////
+    @Override
+    public void setPlayer_impl(MediaPlayerBase player) {
+        // TODO(jaewan): Implement
     }
 
     @Override
-    public void addPlaybackListener_impl(
-            MediaPlayerBase.PlaybackListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener shouldn't be null");
-        }
-        if (handler == null) {
-            throw new IllegalArgumentException("handler shouldn't be null");
-        }
-        ensureCallingThread();
-        if (PlaybackListenerHolder.contains(mListeners, listener)) {
-            Log.w(TAG, "listener is already added. Ignoring.");
-            return;
-        }
-        mListeners.add(new PlaybackListenerHolder(listener, handler));
+    public void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands) {
+        // TODO(jaewan): Implement
     }
 
     @Override
-    public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener shouldn't be null");
-        }
-        ensureCallingThread();
-        int idx = PlaybackListenerHolder.indexOf(mListeners, listener);
-        if (idx >= 0) {
-            mListeners.get(idx).removeCallbacksAndMessages(null);
-            mListeners.remove(idx);
-        }
+    public void notifyMetadataChanged_impl() {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
+            ResultReceiver receiver) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void sendCustomCommand_impl(Command command, Bundle args) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void setPlaylist_impl(List<MediaItem2> playlist, PlaylistParam param) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void prepare_impl() {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void fastForward_impl() {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void rewind_impl() {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void seekTo_impl(long pos) {
+        // TODO(jaewan): Implement
+    }
+
+    @Override
+    public void setCurrentPlaylistItem_impl(int index) {
+        // TODO(jaewan): Implement
     }
 
     ///////////////////////////////////////////////////
@@ -284,10 +336,6 @@
         return mInstance;
     }
 
-    SessionCallback getCallback() {
-        return mCallback;
-    }
-
     MediaPlayerBase getPlayer() {
         return mPlayer;
     }
@@ -420,5 +468,9 @@
         public void removeFlag(int flag) {
             mFlag &= ~flag;
         }
+
+        public static ControllerInfoImpl from(ControllerInfo controller) {
+            return (ControllerInfoImpl) controller.getProvider();
+        }
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index bae02e5..3f7a1b1 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -21,10 +21,14 @@
 import android.content.Context;
 import android.media.IMediaSession2;
 import android.media.IMediaSession2Callback;
+import android.media.MediaLibraryService2.BrowserRoot;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
 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.SessionCallback;
 import android.media.session.PlaybackState;
 import android.os.Binder;
 import android.os.Bundle;
@@ -41,9 +45,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-// TODO(jaewan): Add a hook for media apps to log which app requested specific command.
-// TODO(jaewan): Add a way to block specific command from a specific app. Also add supported
-// command per apps.
 public class MediaSession2Stub extends IMediaSession2.Stub {
     private static final String TAG = "MediaSession2Stub";
     private static final boolean DEBUG = true; // TODO(jaewan): Rename.
@@ -52,14 +53,20 @@
     private final CommandHandler mCommandHandler;
     private final WeakReference<MediaSession2Impl> mSession;
     private final Context mContext;
+    private final SessionCallback mSessionCallback;
+    private final MediaLibrarySessionCallback mLibraryCallback;
 
     @GuardedBy("mLock")
     private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
 
-    public MediaSession2Stub(MediaSession2Impl session) {
+    public MediaSession2Stub(MediaSession2Impl session, SessionCallback callback) {
         mSession = new WeakReference<>(session);
         mContext = session.getContext();
+        // TODO(jaewan): Should be executor from the session builder
         mCommandHandler = new CommandHandler(session.getHandler().getLooper());
+        mSessionCallback = callback;
+        mLibraryCallback = (callback instanceof MediaLibrarySessionCallback)
+                ? (MediaLibrarySessionCallback) callback : null;
     }
 
     public void destroyNotLocked() {
@@ -124,6 +131,25 @@
         mCommandHandler.postCommand(controller, Command.fromBundle(command), args);
     }
 
+    @Override
+    public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
+            throws RuntimeException {
+        if (mLibraryCallback == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Session cannot hand getBrowserRoot()");
+            }
+            return;
+        }
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "getBrowerRoot from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        mCommandHandler.postOnGetRoot(controller, rootHints);
+    }
+
     @Deprecated
     @Override
     public PlaybackState getPlaybackState() throws RemoteException {
@@ -142,7 +168,7 @@
             if (controllerInfo == null) {
                 return;
             }
-            ((ControllerInfoImpl) controllerInfo.getProvider()).addFlag(callbackFlag);
+            ControllerInfoImpl.from(controllerInfo).addFlag(callbackFlag);
         }
     }
 
@@ -156,9 +182,7 @@
             if (controllerInfo == null) {
                 return;
             }
-            ControllerInfoImpl impl =
-                    ((ControllerInfoImpl) controllerInfo.getProvider());
-            impl.removeFlag(callbackFlag);
+            ControllerInfoImpl.from(controllerInfo).removeFlag(callbackFlag);
         }
     }
 
@@ -183,7 +207,7 @@
         synchronized (mLock) {
             for (int i = 0; i < mControllers.size(); i++) {
                 ControllerInfo controllerInfo = mControllers.valueAt(i);
-                if (((ControllerInfoImpl) controllerInfo.getProvider()).containsFlag(flag)) {
+                if (ControllerInfoImpl.from(controllerInfo).containsFlag(flag)) {
                     controllers.add(controllerInfo);
                 }
             }
@@ -196,8 +220,7 @@
         final List<ControllerInfo> list = getControllersWithFlag(CALLBACK_FLAG_PLAYBACK);
         for (int i = 0; i < list.size(); i++) {
             IMediaSession2Callback callbackBinder =
-                    ((ControllerInfoImpl) list.get(i).getProvider())
-                            .getControllerBinder();
+                    ControllerInfoImpl.from(list.get(i)).getControllerBinder();
             try {
                 callbackBinder.onPlaybackStateChanged(state);
             } catch (RemoteException e) {
@@ -207,9 +230,31 @@
         }
     }
 
+    public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
+        // TODO(jaewan): It's OK to be called while it's connecting, but not OK if the connection
+        //               is rejected. Handle the case.
+        IMediaSession2Callback callbackBinder =
+                ControllerInfoImpl.from(controller).getControllerBinder();
+        try {
+            List<Bundle> layoutBundles = new ArrayList<>();
+            for (int i = 0; i < layout.size(); i++) {
+                Bundle bundle = layout.get(i).toBundle();
+                if (bundle != null) {
+                    layoutBundles.add(bundle);
+                }
+            }
+            callbackBinder.onCustomLayoutChanged(layoutBundles);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Controller is gone", e);
+            // TODO(jaewan): What to do when the controller is gone?
+        }
+    }
+
+    // TODO(jaewan): Remove this. We should use Executor given by the session builder.
     private class CommandHandler extends Handler {
         public static final int MSG_CONNECT = 1000;
         public static final int MSG_COMMAND = 1001;
+        public static final int MSG_ON_GET_ROOT = 2000;
 
         public CommandHandler(Looper looper) {
             super(looper);
@@ -223,18 +268,22 @@
             }
 
             switch (msg.what) {
-                case MSG_CONNECT:
+                case MSG_CONNECT: {
                     ControllerInfo request = (ControllerInfo) msg.obj;
-                    CommandGroup allowedCommands = session.getCallback().onConnect(request);
+                    CommandGroup allowedCommands = mSessionCallback.onConnect(request);
                     // Don't reject connection for the request from trusted app.
                     // Otherwise server will fail to retrieve session's information to dispatch
                     // media keys to.
                     boolean accept = allowedCommands != null || request.isTrusted();
-                    ControllerInfoImpl impl = (ControllerInfoImpl) request.getProvider();
+                    ControllerInfoImpl impl = ControllerInfoImpl.from(request);
                     if (accept) {
                         synchronized (mLock) {
                             mControllers.put(impl.getId(), request);
                         }
+                        if (allowedCommands == null) {
+                            // For trusted apps, send non-null allowed commands to keep connection.
+                            allowedCommands = new CommandGroup();
+                        }
                     }
                     if (DEBUG) {
                         Log.d(TAG, "onConnectResult, request=" + request
@@ -248,10 +297,11 @@
                         // Controller may be died prematurely.
                     }
                     break;
-                case MSG_COMMAND:
+                }
+                case MSG_COMMAND: {
                     CommandParam param = (CommandParam) msg.obj;
                     Command command = param.command;
-                    boolean accepted = session.getCallback().onCommandRequest(
+                    boolean accepted = mSessionCallback.onCommandRequest(
                             param.controller, command);
                     if (!accepted) {
                         // Don't run rejected command.
@@ -282,6 +332,21 @@
                             // TODO(jaewan): Handle custom command.
                     }
                     break;
+                }
+                case MSG_ON_GET_ROOT: {
+                    final CommandParam param = (CommandParam) msg.obj;
+                    final ControllerInfoImpl controller = ControllerInfoImpl.from(param.controller);
+                    BrowserRoot root = mLibraryCallback.onGetRoot(param.controller, param.args);
+                    try {
+                        controller.getControllerBinder().onGetRootResult(param.args,
+                                root == null ? null : root.getRootId(),
+                                root == null ? null : root.getExtras());
+                    } catch (RemoteException e) {
+                        // Controller may be died prematurely.
+                        // TODO(jaewan): Handle this.
+                    }
+                    break;
+                }
             }
         }
 
@@ -293,6 +358,11 @@
             CommandParam param = new CommandParam(controller, command, args);
             obtainMessage(MSG_COMMAND, param).sendToTarget();
         }
+
+        public void postOnGetRoot(ControllerInfo controller, Bundle rootHints) {
+            CommandParam param = new CommandParam(controller, null, rootHints);
+            obtainMessage(MSG_ON_GET_ROOT, param).sendToTarget();
+        }
     }
 
     private static class CommandParam {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 6a4760d..773a06f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -17,11 +17,7 @@
 package com.android.media;
 
 import static android.content.Context.NOTIFICATION_SERVICE;
-import static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID;
-import static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_ID;
 
-import android.app.Notification;
-import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -37,7 +33,7 @@
 import android.support.annotation.GuardedBy;
 import android.util.Log;
 
-// Need a test for session service itself.
+// TODO(jaewan): Need a test for session service itself.
 public class MediaSessionService2Impl implements MediaSessionService2Provider {
 
     private static final String TAG = "MPSessionService"; // to meet 23 char limit in Log tag
@@ -53,7 +49,6 @@
     private Intent mStartSelfIntent;
 
     private boolean mIsRunningForeground;
-    private NotificationChannel mDefaultNotificationChannel;
     private MediaSession2 mSession;
 
     public MediaSessionService2Impl(MediaSessionService2 instance) {
@@ -65,6 +60,10 @@
 
     @Override
     public MediaSession2 getSession_impl() {
+        return getSession();
+    }
+
+    MediaSession2 getSession() {
         synchronized (mLock) {
             return mSession;
         }
@@ -72,43 +71,23 @@
 
     @Override
     public MediaNotification onUpdateNotification_impl(PlaybackState state) {
-        return createDefaultNotification(state);
+        // Provide default notification UI later.
+        return null;
     }
 
-    // TODO(jaewan): Remove this for framework release.
-    private MediaNotification createDefaultNotification(PlaybackState state) {
-        // TODO(jaewan): Place better notification here.
-        if (mDefaultNotificationChannel == null) {
-            mDefaultNotificationChannel = new NotificationChannel(
-                    DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
-                    DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
-                    NotificationManager.IMPORTANCE_DEFAULT);
-            mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
-        }
-        Notification notification = new Notification.Builder(
-                mInstance, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
-                .setContentTitle(mInstance.getPackageName())
-                .setContentText("Playback state: " + state.getState())
-                .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
-        return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
-    }
-
-
     @Override
     public void onCreate_impl() {
         mNotificationManager = (NotificationManager) mInstance.getSystemService(
                 NOTIFICATION_SERVICE);
         mStartSelfIntent = new Intent(mInstance, mInstance.getClass());
 
-        Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
-        serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+        Intent serviceIntent = createServiceIntent();
         ResolveInfo resolveInfo = mInstance.getPackageManager()
-                .resolveService(serviceIntent,
-                        PackageManager.GET_META_DATA);
+                .resolveService(serviceIntent, PackageManager.GET_META_DATA);
         String id;
         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
             throw new IllegalArgumentException("service " + mInstance + " doesn't implement"
-                    + MediaSessionService2.SERVICE_INTERFACE);
+                    + serviceIntent.getAction());
         } else if (resolveInfo.serviceInfo.metaData == null) {
             if (DEBUG) {
                 Log.d(TAG, "Failed to resolve ID for " + mInstance + ". Using empty id");
@@ -122,6 +101,14 @@
         if (mSession == null || !id.equals(mSession.getToken().getId())) {
             throw new RuntimeException("Expected session with id " + id + ", but got " + mSession);
         }
+        // TODO(jaewan): Uncomment here.
+        // mSession.addPlaybackListener(mListener, mSession.getExecutor());
+    }
+
+    Intent createServiceIntent() {
+        Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+        serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+        return serviceIntent;
     }
 
     public IBinder onBind_impl(Intent intent) {
@@ -134,7 +121,7 @@
     private void updateNotification(PlaybackState state) {
         MediaNotification mediaNotification = mInstance.onUpdateNotification(state);
         if (mediaNotification == null) {
-            mediaNotification = createDefaultNotification(state);
+            return;
         }
         switch((int) state.getState()) {
             case PlaybackState.STATE_PLAYING:
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index cfef77f..43acaf9 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -16,10 +16,16 @@
 
 package com.android.media.update;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
 import android.media.MediaController2;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
 import android.media.MediaPlayerBase;
 import android.media.MediaSession2;
 import android.media.MediaSession2.ControllerInfo;
@@ -27,8 +33,11 @@
 import android.media.MediaSessionService2;
 import android.media.IMediaSession2Callback;
 import android.media.SessionToken;
+import android.media.VolumeProvider;
+import android.media.update.MediaBrowser2Provider;
 import android.media.update.MediaControlView2Provider;
 import android.media.update.MediaController2Provider;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
 import android.media.update.MediaSession2Provider;
 import android.media.update.MediaSessionService2Provider;
 import android.media.update.VideoView2Provider;
@@ -39,7 +48,10 @@
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
 
+import com.android.media.MediaBrowser2Impl;
 import com.android.media.MediaController2Impl;
+import com.android.media.MediaLibraryService2Impl;
+import com.android.media.MediaLibraryService2Impl.MediaLibrarySessionImpl;
 import com.android.media.MediaSession2Impl;
 import com.android.media.MediaSessionService2Impl;
 import com.android.widget.MediaControlView2Impl;
@@ -62,9 +74,18 @@
     }
 
     @Override
+    public MediaBrowser2Provider createMediaBrowser2(MediaBrowser2 instance, Context context,
+            SessionToken token, BrowserCallback callback, Executor executor) {
+        return new MediaBrowser2Impl(instance, context, token, callback, executor);
+    }
+
+    @Override
     public MediaSession2Provider createMediaSession2(MediaSession2 instance, Context context,
-            MediaPlayerBase player, String id, SessionCallback callback) {
-        return new MediaSession2Impl(instance, context, player, id, callback);
+            MediaPlayerBase player, String id, SessionCallback callback,
+            VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity) {
+        return new MediaSession2Impl(instance, context, player, id, callback,
+                volumeProvider, ratingType, sessionActivity);
     }
 
     @Override
@@ -82,6 +103,21 @@
     }
 
     @Override
+    public MediaSessionService2Provider createMediaLibraryService2(
+            MediaLibraryService2 instance) {
+        return new MediaLibraryService2Impl(instance);
+    }
+
+    @Override
+    public MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
+            MediaLibrarySession instance, Context context, MediaPlayerBase player, String id,
+            MediaLibrarySessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity) {
+        return new MediaLibrarySessionImpl(instance, context, player, id, callback, volumeProvider,
+                ratingType, sessionActivity);
+    }
+
+    @Override
     public MediaControlView2Provider createMediaControlView2(
             MediaControlView2 instance, ViewProvider superProvider) {
         return new MediaControlView2Impl(instance, superProvider);
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index a83e449..b0ca1bd 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -20,10 +20,13 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 
+import com.android.support.mediarouter.app.MediaRouteButton;
+
 public class ApiHelper {
     private static ApiHelper sInstance;
     private final Resources mLibResources;
@@ -55,7 +58,21 @@
     public static LayoutInflater getLayoutInflater(Context context) {
         LayoutInflater layoutInflater = LayoutInflater.from(context).cloneInContext(
                 new ContextThemeWrapper(context, getLibTheme()));
-        // TODO: call layoutInflater.setFactory2()
+        layoutInflater.setFactory2(new LayoutInflater.Factory2() {
+            @Override
+            public View onCreateView(
+                    View parent, String name, Context context, AttributeSet attrs) {
+                if (MediaRouteButton.class.getCanonicalName().equals(name)) {
+                    return new MediaRouteButton(context, attrs);
+                }
+                return null;
+            }
+
+            @Override
+            public View onCreateView(String name, Context context, AttributeSet attrs) {
+                return onCreateView(null, name, context, attrs);
+            }
+        });
         return layoutInflater;
     }
 
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
index 68e8d3a..65fc88c 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.AnimationDrawable;
@@ -36,6 +37,7 @@
 import android.view.SoundEffectConstants;
 import android.view.View;
 
+import com.android.media.update.ApiHelper;
 import com.android.media.update.R;
 import com.android.support.mediarouter.media.MediaRouteSelector;
 import com.android.support.mediarouter.media.MediaRouter;
@@ -128,8 +130,11 @@
         mRouter = MediaRouter.getInstance(context);
         mCallback = new MediaRouterCallback();
 
-        TypedArray a = context.obtainStyledAttributes(attrs,
+        Resources.Theme theme = ApiHelper.getLibResources().newTheme();
+        theme.applyStyle(MediaRouterThemeHelper.getRouterThemeId(context), true);
+        TypedArray a = theme.obtainStyledAttributes(attrs,
                 R.styleable.MediaRouteButton, defStyleAttr, 0);
+
         mButtonTint = a.getColorStateList(R.styleable.MediaRouteButton_mediaRouteButtonTint);
         mMinWidth = a.getDimensionPixelSize(
                 R.styleable.MediaRouteButton_android_minWidth, 0);
@@ -290,8 +295,9 @@
      * button when the button is long pressed.
      */
     void setCheatSheetEnabled(boolean enable) {
-        TooltipCompat.setTooltipText(this,
-                enable ? getContext().getString(R.string.mr_button_content_description) : null);
+        TooltipCompat.setTooltipText(this, enable
+                ? ApiHelper.getLibResources().getString(R.string.mr_button_content_description)
+                : null);
     }
 
     @Override
@@ -533,7 +539,7 @@
         } else {
             resId = R.string.mr_cast_button_disconnected;
         }
-        setContentDescription(getContext().getString(resId));
+        setContentDescription(ApiHelper.getLibResources().getString(resId));
     }
 
     private final class MediaRouterCallback extends MediaRouter.Callback {
@@ -590,7 +596,7 @@
 
         @Override
         protected Drawable doInBackground(Void... params) {
-            return getContext().getResources().getDrawable(mResId);
+            return ApiHelper.getLibResources().getDrawable(mResId);
         }
 
         @Override
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
index b4b49df..7440130 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
@@ -196,7 +196,7 @@
         return value.data;
     }
 
-    private static int getRouterThemeId(Context context) {
+    static int getRouterThemeId(Context context) {
         int themeId;
         if (isLightTheme(context)) {
             if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
index f5e1e61..33d92b4 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
@@ -27,6 +27,7 @@
 import android.support.annotation.RequiresApi;
 import android.view.Display;
 
+import com.android.media.update.ApiHelper;
 import com.android.media.update.R;
 
 import java.util.ArrayList;
@@ -266,7 +267,7 @@
             mCallbackObj = createCallbackObj();
             mVolumeCallbackObj = createVolumeCallbackObj();
 
-            Resources r = context.getResources();
+            Resources r = ApiHelper.getLibResources();
             mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
                     mRouterObj, r.getString(R.string.mr_user_route_category_name), false);
 
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index a814d5e..bc370d8 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 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.
@@ -22,11 +22,12 @@
 import android.media.session.PlaybackState;
 import android.media.update.MediaControlView2Provider;
 import android.media.update.ViewProvider;
+import android.os.Bundle;
 import android.util.Log;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.ImageButton;
 import android.widget.MediaControlView2;
@@ -47,8 +48,14 @@
     private final MediaControlView2 mInstance;
     private final ViewProvider mSuperProvider;
 
-    static final String ACTION_SHOW_SUBTITLE = "showSubtitle";
-    static final String ACTION_HIDE_SUBTITLE = "hideSubtitle";
+    static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
+    static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
+    static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+
+    static final String ARGUMENT_KEY_FULLSCREEN = "fullScreen";
+
+    static final String KEY_STATE_CONTAINS_SUBTITLE = "StateContainsSubtitle";
+    static final String EVENT_UPDATE_SUBTITLE_STATUS = "UpdateSubtitleStatus";
 
     private static final int MAX_PROGRESS = 1000;
     private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
@@ -57,7 +64,6 @@
     private static final int REWIND_TIME_MS = 10000;
     private static final int FORWARD_TIME_MS = 30000;
 
-    private final boolean mUseClosedCaption;
     private final AccessibilityManager mAccessibilityManager;
 
     private MediaController mController;
@@ -66,20 +72,36 @@
     private MediaMetadata mMetadata;
     private ProgressBar mProgress;
     private TextView mEndTime, mCurrentTime;
+    private TextView mTitleView;
     private int mDuration;
     private int mPrevState;
+    private long mPlaybackActions;
     private boolean mShowing;
     private boolean mDragging;
-    private boolean mListenersSet;
-    private boolean mCCIsEnabled;
+    private boolean mIsFullScreen;
+    private boolean mOverflowExpanded;
     private boolean mIsStopped;
+    private boolean mSubtitleIsEnabled;
+    private boolean mContainsSubtitle;
+    private boolean mSeekAvailable;
     private View.OnClickListener mNextListener, mPrevListener;
     private ImageButton mPlayPauseButton;
     private ImageButton mFfwdButton;
     private ImageButton mRewButton;
     private ImageButton mNextButton;
     private ImageButton mPrevButton;
-    private ImageButton mCCButton;
+
+    private ViewGroup mBasicControls;
+    private ImageButton mSubtitleButton;
+    private ImageButton mFullScreenButton;
+    private ImageButton mOverflowButtonRight;
+
+    private ViewGroup mExtraControls;
+    private ImageButton mOverflowButtonLeft;
+    private ImageButton mMuteButton;
+    private ImageButton mAspectRationButton;
+    private ImageButton mSettingsButton;
+
     private CharSequence mPlayDescription;
     private CharSequence mPauseDescription;
     private CharSequence mReplayDescription;
@@ -87,10 +109,10 @@
     private StringBuilder mFormatBuilder;
     private Formatter mFormatter;
 
-    public MediaControlView2Impl(MediaControlView2 instance, ViewProvider superProvider) {
+    public MediaControlView2Impl(
+            MediaControlView2 instance, ViewProvider superProvider) {
         mInstance = instance;
         mSuperProvider = superProvider;
-        mUseClosedCaption = true;
         mAccessibilityManager = AccessibilityManager.getInstance(mInstance.getContext());
 
         // Inflate MediaControlView2 from XML
@@ -108,6 +130,7 @@
             mPlaybackState = mController.getPlaybackState();
             mMetadata = mController.getMetadata();
             updateDuration();
+            updateTitle();
 
             mController.registerCallback(new MediaControllerCallback());
         }
@@ -161,79 +184,104 @@
     }
 
     @Override
-    public void showCCButton_impl() {
-        if (mCCButton != null) {
-            mCCButton.setVisibility(View.VISIBLE);
-        }
-    }
-
-    @Override
-    public boolean isPlaying_impl() {
-        if (mPlaybackState != null) {
-            return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
-        }
-        return false;
-    }
-
-    @Override
-    public int getCurrentPosition_impl() {
-        mPlaybackState = mController.getPlaybackState();
-        if (mPlaybackState != null) {
-            return (int) mPlaybackState.getPosition();
-        }
-        return 0;
-    }
-
-    @Override
-    public int getBufferPercentage_impl() {
-        if (mDuration == 0) {
-            return 0;
-        }
-        mPlaybackState = mController.getPlaybackState();
-        if (mPlaybackState != null) {
-            return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
-        }
-        return 0;
-    }
-
-    @Override
-    public boolean canPause_impl() {
-        if (mPlaybackState != null) {
-            return (mPlaybackState.getActions() & PlaybackState.ACTION_PAUSE) != 0;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean canSeekBackward_impl() {
-        if (mPlaybackState != null) {
-            return (mPlaybackState.getActions() & PlaybackState.ACTION_REWIND) != 0;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean canSeekForward_impl() {
-        if (mPlaybackState != null) {
-            return (mPlaybackState.getActions() & PlaybackState.ACTION_FAST_FORWARD) != 0;
-        }
-        return true;
-    }
-
-    @Override
     public void showSubtitle_impl() {
-        mController.sendCommand(ACTION_SHOW_SUBTITLE, null, null);
+        mController.sendCommand(COMMAND_SHOW_SUBTITLE, null, null);
     }
 
     @Override
     public void hideSubtitle_impl() {
-        mController.sendCommand(ACTION_HIDE_SUBTITLE, null, null);
+        mController.sendCommand(COMMAND_HIDE_SUBTITLE, null, null);
+    }
+
+    @Override
+    public void setPrevNextListeners_impl(View.OnClickListener next, View.OnClickListener prev) {
+        mNextListener = next;
+        mPrevListener = prev;
+
+        if (mNextButton != null) {
+            mNextButton.setOnClickListener(mNextListener);
+            mNextButton.setEnabled(mNextListener != null);
+            mNextButton.setVisibility(View.VISIBLE);
+        }
+        if (mPrevButton != null) {
+            mPrevButton.setOnClickListener(mPrevListener);
+            mPrevButton.setEnabled(mPrevListener != null);
+            mPrevButton.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void setButtonVisibility_impl(int button, boolean visible) {
+        switch (button) {
+            case MediaControlView2.BUTTON_PLAY_PAUSE:
+                if (mPlayPauseButton != null && canPause()) {
+                    mPlayPauseButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            case MediaControlView2.BUTTON_FFWD:
+                if (mFfwdButton != null && canSeekForward()) {
+                    mFfwdButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            case MediaControlView2.BUTTON_REW:
+                if (mRewButton != null && canSeekBackward()) {
+                    mRewButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                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((visible) ? View.VISIBLE : View.GONE);
+                }
+                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((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            case MediaControlView2.BUTTON_SUBTITLE:
+                if (mSubtitleButton != null && mContainsSubtitle) {
+                    mSubtitleButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            case MediaControlView2.BUTTON_FULL_SCREEN:
+                if (mFullScreenButton != null) {
+                    mFullScreenButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            case MediaControlView2.BUTTON_OVERFLOW:
+                if (mOverflowButtonRight != null) {
+                    mOverflowButtonRight.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            case MediaControlView2.BUTTON_MUTE:
+                if (mMuteButton != null) {
+                    mMuteButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            case MediaControlView2.BUTTON_ASPECT_RATIO:
+                if (mAspectRationButton != null) {
+                    mAspectRationButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            case MediaControlView2.BUTTON_SETTINGS:
+                if (mSettingsButton != null) {
+                    mSettingsButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+                }
+                break;
+            default:
+                break;
+        }
     }
 
     @Override
     public void onAttachedToWindow_impl() {
         mSuperProvider.onAttachedToWindow_impl();
     }
+
     @Override
     public void onDetachedFromWindow_impl() {
         mSuperProvider.onDetachedFromWindow_impl();
@@ -283,14 +331,14 @@
             }
             return true;
         } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
-            if (uniqueDown && !mInstance.isPlaying()) {
+            if (uniqueDown && !isPlaying()) {
                 togglePausePlayState();
                 mInstance.show(DEFAULT_TIMEOUT_MS);
             }
             return true;
         } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
                 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
-            if (uniqueDown && mInstance.isPlaying()) {
+            if (uniqueDown && isPlaying()) {
                 togglePausePlayState();
                 mInstance.show(DEFAULT_TIMEOUT_MS);
             }
@@ -324,10 +372,10 @@
             mRewButton.setEnabled(enabled);
         }
         if (mNextButton != null) {
-            mNextButton.setEnabled(enabled && mNextListener != null);
+            mNextButton.setEnabled(enabled);
         }
         if (mPrevButton != null) {
-            mPrevButton.setEnabled(enabled && mPrevListener != null);
+            mPrevButton.setEnabled(enabled);
         }
         if (mProgress != null) {
             mProgress.setEnabled(enabled);
@@ -340,6 +388,53 @@
     // Protected or private methods
     ///////////////////////////////////////////////////
 
+    private boolean isPlaying() {
+        if (mPlaybackState != null) {
+            return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
+        }
+        return false;
+    }
+
+    private int getCurrentPosition() {
+        mPlaybackState = mController.getPlaybackState();
+        if (mPlaybackState != null) {
+            return (int) mPlaybackState.getPosition();
+        }
+        return 0;
+    }
+
+    private int getBufferPercentage() {
+        if (mDuration == 0) {
+            return 0;
+        }
+        mPlaybackState = mController.getPlaybackState();
+        if (mPlaybackState != null) {
+            return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+        }
+        return 0;
+    }
+
+    private boolean canPause() {
+        if (mPlaybackState != null) {
+            return (mPlaybackState.getActions() & PlaybackState.ACTION_PAUSE) != 0;
+        }
+        return true;
+    }
+
+    private boolean canSeekBackward() {
+        if (mPlaybackState != null) {
+            return (mPlaybackState.getActions() & PlaybackState.ACTION_REWIND) != 0;
+        }
+        return true;
+    }
+
+    private boolean canSeekForward() {
+        if (mPlaybackState != null) {
+            return (mPlaybackState.getActions() & PlaybackState.ACTION_FAST_FORWARD) != 0;
+        }
+        return true;
+    }
+
     /**
      * Create the view that holds the widgets that control playback.
      * Derived classes can override this to create their own.
@@ -358,42 +453,63 @@
         mPlayDescription = res.getText(R.string.lockscreen_play_button_content_description);
         mPauseDescription = res.getText(R.string.lockscreen_pause_button_content_description);
         mReplayDescription = res.getText(R.string.lockscreen_replay_button_content_description);
+
         mPlayPauseButton = v.findViewById(R.id.pause);
         if (mPlayPauseButton != null) {
             mPlayPauseButton.requestFocus();
             mPlayPauseButton.setOnClickListener(mPlayPauseListener);
+            mPlayPauseButton.setColorFilter(R.integer.gray);
+            mPlayPauseButton.setEnabled(false);
         }
-
-        // TODO: make the following buttons visible based upon whether they are supported for
-        // individual media files
         mFfwdButton = v.findViewById(R.id.ffwd);
         if (mFfwdButton != null) {
             mFfwdButton.setOnClickListener(mFfwdListener);
-            mFfwdButton.setVisibility(View.GONE);
+            mFfwdButton.setColorFilter(R.integer.gray);
+            mFfwdButton.setEnabled(false);
         }
         mRewButton = v.findViewById(R.id.rew);
         if (mRewButton != null) {
             mRewButton.setOnClickListener(mRewListener);
-            mRewButton.setVisibility(View.GONE);
+            mRewButton.setColorFilter(R.integer.gray);
+            mRewButton.setEnabled(false);
         }
         mNextButton = v.findViewById(R.id.next);
-        if (mNextButton != null && !mListenersSet) {
+        if (mNextButton != null) {
             mNextButton.setVisibility(View.GONE);
         }
         mPrevButton = v.findViewById(R.id.prev);
-        if (mPrevButton != null && !mListenersSet) {
+        if (mPrevButton != null) {
             mPrevButton.setVisibility(View.GONE);
         }
 
-        // TODO: make CC button visible if the media file has a subtitle track
-        mCCButton = v.findViewById(R.id.cc);
-        if (mCCButton != null) {
-            mCCButton.setOnClickListener(mCCListener);
-            mCCButton.setVisibility(mUseClosedCaption ? View.VISIBLE : View.GONE);
+        mBasicControls = v.findViewById(R.id.basic_controls);
+        mSubtitleButton = v.findViewById(R.id.subtitle);
+        if (mSubtitleButton != null) {
+            mSubtitleButton.setOnClickListener(mSubtitleListener);
+            mSubtitleButton.setColorFilter(R.integer.gray);
+            mSubtitleButton.setEnabled(false);
+        }
+        mFullScreenButton = v.findViewById(R.id.fullscreen);
+        if (mFullScreenButton != null) {
+            mFullScreenButton.setOnClickListener(mFullScreenListener);
+            // TODO: Show Fullscreen button when only it is possible.
+        }
+        mOverflowButtonRight = v.findViewById(R.id.overflow_right);
+        if (mOverflowButtonRight != null) {
+            mOverflowButtonRight.setOnClickListener(mOverflowRightListener);
         }
 
-        mProgress =
-                v.findViewById(R.id.mediacontroller_progress);
+        // TODO: should these buttons be shown as default?
+        mExtraControls = v.findViewById(R.id.extra_controls);
+        mOverflowButtonLeft = v.findViewById(R.id.overflow_left);
+        if (mOverflowButtonLeft != null) {
+            mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
+        }
+        mMuteButton = v.findViewById(R.id.mute);
+        mAspectRationButton = v.findViewById(R.id.aspect_ratio);
+        mSettingsButton = v.findViewById(R.id.settings);
+
+        mProgress = v.findViewById(R.id.mediacontroller_progress);
         if (mProgress != null) {
             if (mProgress instanceof SeekBar) {
                 SeekBar seeker = (SeekBar) mProgress;
@@ -402,12 +518,12 @@
             mProgress.setMax(MAX_PROGRESS);
         }
 
+        mTitleView = v.findViewById(R.id.title_text);
+
         mEndTime = v.findViewById(R.id.time);
         mCurrentTime = v.findViewById(R.id.time_current);
         mFormatBuilder = new StringBuilder();
         mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
-
-        installPrevNextListeners();
     }
 
     /**
@@ -416,13 +532,13 @@
      */
     private void disableUnsupportedButtons() {
         try {
-            if (mPlayPauseButton != null && !mInstance.canPause()) {
+            if (mPlayPauseButton != null && !canPause()) {
                 mPlayPauseButton.setEnabled(false);
             }
-            if (mRewButton != null && !mInstance.canSeekBackward()) {
+            if (mRewButton != null && !canSeekBackward()) {
                 mRewButton.setEnabled(false);
             }
-            if (mFfwdButton != null && !mInstance.canSeekForward()) {
+            if (mFfwdButton != null && !canSeekForward()) {
                 mFfwdButton.setEnabled(false);
             }
             // TODO What we really should do is add a canSeek to the MediaPlayerControl interface;
@@ -432,7 +548,7 @@
             // However, currently the flags SEEK_BACKWARD_AVAILABLE, SEEK_FORWARD_AVAILABLE,
             // and SEEK_AVAILABLE are all (un)set together; as such the aforementioned issue
             // shouldn't arise in existing applications.
-            if (mProgress != null && !mInstance.canSeekBackward() && !mInstance.canSeekForward()) {
+            if (mProgress != null && !canSeekBackward() && !canSeekForward()) {
                 mProgress.setEnabled(false);
             }
         } catch (IncompatibleClassChangeError ex) {
@@ -446,7 +562,9 @@
     private final Runnable mFadeOut = new Runnable() {
         @Override
         public void run() {
-            mInstance.hide();
+            if (isPlaying()) {
+                mInstance.hide();
+            }
         }
     };
 
@@ -454,7 +572,7 @@
         @Override
         public void run() {
             int pos = setProgress();
-            if (!mDragging && mShowing && mInstance.isPlaying()) {
+            if (!mDragging && mShowing && isPlaying()) {
                 mInstance.postDelayed(mShowProgress,
                         DEFAULT_PROGRESS_UPDATE_TIME_MS - (pos % DEFAULT_PROGRESS_UPDATE_TIME_MS));
             }
@@ -481,13 +599,13 @@
             return 0;
         }
         int positionOnProgressBar = 0;
-        int currentPosition = mInstance.getCurrentPosition();
+        int currentPosition = getCurrentPosition();
         if (mDuration > 0) {
             positionOnProgressBar = (int) (MAX_PROGRESS * (long) currentPosition / mDuration);
         }
         if (mProgress != null && currentPosition != mDuration) {
             mProgress.setProgress(positionOnProgressBar);
-            mProgress.setSecondaryProgress(mInstance.getBufferPercentage() * 10);
+            mProgress.setSecondaryProgress(getBufferPercentage() * 10);
         }
 
         if (mEndTime != null) {
@@ -502,13 +620,17 @@
     }
 
     private void togglePausePlayState() {
-        if (mInstance.isPlaying()) {
+        if (isPlaying()) {
             mControls.pause();
-            mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+            mPlayPauseButton.setImageDrawable(
+                    ApiHelper.getLibResources().getDrawable(
+                            R.drawable.ic_play_circle_filled, null));
             mPlayPauseButton.setContentDescription(mPlayDescription);
         } else {
             mControls.play();
-            mPlayPauseButton.setImageResource(R.drawable.ic_pause_circle_filled);
+            mPlayPauseButton.setImageDrawable(
+                    ApiHelper.getLibResources().getDrawable(
+                            R.drawable.ic_pause_circle_filled, null));
             mPlayPauseButton.setContentDescription(mPauseDescription);
         }
     }
@@ -527,6 +649,9 @@
     private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
         @Override
         public void onStartTrackingTouch(SeekBar bar) {
+            if (!mSeekAvailable) {
+                return;
+            }
             mInstance.show(3600000);
 
             mDragging = true;
@@ -538,10 +663,12 @@
             // this ensures that there will be exactly one message queued up.
             mInstance.removeCallbacks(mShowProgress);
 
-            // Check if playback is currently stopped. In this case, update the pause button to show
-            // the play image instead of the replay image.
+            // Check if playback is currently stopped. In this case, update the pause button to
+            // show the play image instead of the replay image.
             if (mIsStopped) {
-                mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+                mPlayPauseButton.setImageDrawable(
+                        ApiHelper.getLibResources().getDrawable(
+                                R.drawable.ic_play_circle_filled, null));
                 mPlayPauseButton.setContentDescription(mPlayDescription);
                 mIsStopped = false;
             }
@@ -549,6 +676,9 @@
 
         @Override
         public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) {
+            if (!mSeekAvailable) {
+                return;
+            }
             if (!fromUser) {
                 // We're not interested in programmatically generated changes to
                 // the progress bar's position.
@@ -566,6 +696,9 @@
 
         @Override
         public void onStopTrackingTouch(SeekBar bar) {
+            if (!mSeekAvailable) {
+                return;
+            }
             mDragging = false;
 
             setProgress();
@@ -589,7 +722,7 @@
     private final View.OnClickListener mRewListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            int pos = mInstance.getCurrentPosition() - REWIND_TIME_MS;
+            int pos = getCurrentPosition() - REWIND_TIME_MS;
             mControls.seekTo(pos);
             setProgress();
 
@@ -600,7 +733,7 @@
     private final View.OnClickListener mFfwdListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            int pos = mInstance.getCurrentPosition() + FORWARD_TIME_MS;
+            int pos = getCurrentPosition() + FORWARD_TIME_MS;
             mControls.seekTo(pos);
             setProgress();
 
@@ -608,32 +741,64 @@
         }
     };
 
-    private final View.OnClickListener mCCListener = new View.OnClickListener() {
+    private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            if (!mCCIsEnabled) {
-                mCCButton.setImageResource(R.drawable.ic_media_cc_enabled);
-                mCCIsEnabled = true;
+            if (!mSubtitleIsEnabled) {
+                mSubtitleButton.setImageDrawable(
+                        ApiHelper.getLibResources().getDrawable(
+                                R.drawable.ic_media_subtitle_enabled, null));
                 mInstance.showSubtitle();
+                mSubtitleIsEnabled = true;
             } else {
-                mCCButton.setImageResource(R.drawable.ic_media_cc_disabled);
-                mCCIsEnabled = false;
+                mSubtitleButton.setImageDrawable(
+                        ApiHelper.getLibResources().getDrawable(
+                                R.drawable.ic_media_subtitle_disabled, null));
                 mInstance.hideSubtitle();
+                mSubtitleIsEnabled = false;
             }
+            mInstance.show(DEFAULT_TIMEOUT_MS);
         }
     };
 
-    private void installPrevNextListeners() {
-        if (mNextButton != null) {
-            mNextButton.setOnClickListener(mNextListener);
-            mNextButton.setEnabled(mNextListener != null);
-        }
+    private final View.OnClickListener mFullScreenListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final boolean isEnteringFullScreen = !mIsFullScreen;
+            // TODO: Re-arrange the button layouts according to the UX.
+            if (isEnteringFullScreen) {
+                mFullScreenButton.setImageDrawable(
+                        ApiHelper.getLibResources().getDrawable(
+                                R.drawable.ic_fullscreen_exit, null));
+            } else {
+                mFullScreenButton.setImageDrawable(
+                        ApiHelper.getLibResources().getDrawable(R.drawable.ic_fullscreen, null));
+            }
+            Bundle args = new Bundle();
+            args.putBoolean(ARGUMENT_KEY_FULLSCREEN, isEnteringFullScreen);
+            mController.sendCommand(COMMAND_SET_FULLSCREEN, args, null);
 
-        if (mPrevButton != null) {
-            mPrevButton.setOnClickListener(mPrevListener);
-            mPrevButton.setEnabled(mPrevListener != null);
+            mIsFullScreen = isEnteringFullScreen;
+            mInstance.show(DEFAULT_TIMEOUT_MS);
         }
-    }
+    };
+
+    private final View.OnClickListener mOverflowRightListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mBasicControls.setVisibility(View.GONE);
+            mExtraControls.setVisibility(View.VISIBLE);
+            mInstance.show(DEFAULT_TIMEOUT_MS);
+        }
+    };
+
+    private final View.OnClickListener mOverflowLeftListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mBasicControls.setVisibility(View.VISIBLE);
+            mExtraControls.setVisibility(View.GONE);
+        }
+    };
 
     private void updateDuration() {
         if (mMetadata != null) {
@@ -645,6 +810,14 @@
         }
     }
 
+    private void updateTitle() {
+        if (mMetadata != null) {
+            if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
+                mTitleView.setText(mMetadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+            }
+        }
+    }
+
     private class MediaControllerCallback extends MediaController.Callback {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -657,15 +830,21 @@
             if (mPlaybackState.getState() != mPrevState) {
                 switch (mPlaybackState.getState()) {
                     case PlaybackState.STATE_PLAYING:
-                        mPlayPauseButton.setImageResource(R.drawable.ic_pause_circle_filled);
+                        mPlayPauseButton.setImageDrawable(
+                                ApiHelper.getLibResources().getDrawable(
+                                        R.drawable.ic_pause_circle_filled, null));
                         mPlayPauseButton.setContentDescription(mPauseDescription);
                         break;
                     case PlaybackState.STATE_PAUSED:
-                        mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+                        mPlayPauseButton.setImageDrawable(
+                                ApiHelper.getLibResources().getDrawable(
+                                        R.drawable.ic_play_circle_filled, null));
                         mPlayPauseButton.setContentDescription(mPlayDescription);
                         break;
                     case PlaybackState.STATE_STOPPED:
-                        mPlayPauseButton.setImageResource(R.drawable.ic_replay);
+                        mPlayPauseButton.setImageDrawable(
+                                ApiHelper.getLibResources().getDrawable(
+                                        R.drawable.ic_replay, null));
                         mPlayPauseButton.setContentDescription(mReplayDescription);
                         mIsStopped = true;
                         break;
@@ -674,12 +853,52 @@
                 }
                 mPrevState = mPlaybackState.getState();
             }
+
+            if (mPlaybackActions != mPlaybackState.getActions()) {
+                long newActions = mPlaybackState.getActions();
+                if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
+                    mPlayPauseButton.clearColorFilter();
+                    mPlayPauseButton.setEnabled(true);
+                }
+                if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
+                    mRewButton.clearColorFilter();
+                    mRewButton.setEnabled(true);
+                }
+                if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+                    mFfwdButton.clearColorFilter();
+                    mFfwdButton.setEnabled(true);
+                }
+                if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
+                    mSeekAvailable = true;
+                } else {
+                    mSeekAvailable = false;
+                }
+                mPlaybackActions = newActions;
+            }
         }
 
         @Override
         public void onMetadataChanged(MediaMetadata metadata) {
             mMetadata = metadata;
             updateDuration();
+            updateTitle();
+        }
+
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            if (event.equals(EVENT_UPDATE_SUBTITLE_STATUS)) {
+                boolean newSubtitleStatus = extras.getBoolean(KEY_STATE_CONTAINS_SUBTITLE);
+                if (newSubtitleStatus != mContainsSubtitle) {
+                    if (newSubtitleStatus) {
+                        mSubtitleButton.clearColorFilter();
+                        mSubtitleButton.setEnabled(true);
+                    } else {
+                        mSubtitleButton.setColorFilter(R.integer.gray);
+                        mSubtitleButton.setEnabled(false);
+                    }
+                    mContainsSubtitle = newSubtitleStatus;
+                }
+            }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 57534f1..4c312f8 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -25,15 +25,18 @@
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.MediaPlayer;
+import android.media.MediaPlayerBase;
 import android.media.Cea708CaptionRenderer;
 import android.media.ClosedCaptionRenderer;
+import android.media.MediaMetadata;
+import android.media.MediaPlayer;
 import android.media.Metadata;
 import android.media.PlaybackParams;
 import android.media.SubtitleController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
 import android.media.TtmlRenderer;
 import android.media.WebVttRenderer;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
 import android.media.update.VideoView2Provider;
 import android.media.update.ViewProvider;
 import android.net.Uri;
@@ -50,14 +53,14 @@
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
 
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-import com.android.media.update.ApiHelper;
-import com.android.media.update.R;
-
 public class VideoView2Impl implements VideoView2Provider, VideoViewInterface.SurfaceListener {
     private static final String TAG = "VideoView2";
     private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
@@ -83,6 +86,7 @@
     private VideoView2.OnErrorListener mOnErrorListener;
     private VideoView2.OnInfoListener mOnInfoListener;
     private VideoView2.OnViewTypeChangedListener mOnViewTypeChangedListener;
+    private VideoView2.OnFullScreenChangedListener mOnFullScreenChangedListener;
 
     private VideoViewInterface mCurrentView;
     private VideoTextureView mTextureView;
@@ -91,6 +95,8 @@
     private MediaPlayer mMediaPlayer;
     private MediaControlView2 mMediaControlView;
     private MediaSession mMediaSession;
+    private Metadata mMetadata;
+    private String mTitle;
 
     private PlaybackState.Builder mStateBuilder;
     private int mTargetState = STATE_IDLE;
@@ -110,8 +116,7 @@
     // Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
     private float mFallbackSpeed;  // keep the original speed before 'pause' is called.
 
-    public VideoView2Impl(
-            VideoView2 instance, ViewProvider superProvider,
+    public VideoView2Impl(VideoView2 instance, ViewProvider superProvider,
             @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         mInstance = instance;
         mSuperProvider = superProvider;
@@ -285,6 +290,13 @@
     }
 
     @Override
+    public void setFullScreen_impl(boolean fullScreen) {
+        if (mOnFullScreenChangedListener != null) {
+            mOnFullScreenChangedListener.onFullScreenChanged(fullScreen);
+        }
+    }
+
+    @Override
     public void setSpeed_impl(float speed) {
         if (speed <= 0.0f) {
             Log.e(TAG, "Unsupported speed (" + speed + ") is ignored.");
@@ -331,6 +343,11 @@
     }
 
     @Override
+    public void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerBase player) {
+        // TODO: implement this.
+    }
+
+    @Override
     public void setVideoPath_impl(String path) {
         mInstance.setVideoURI(Uri.parse(path));
     }
@@ -402,6 +419,11 @@
     }
 
     @Override
+    public void setFullScreenChangedListener_impl(VideoView2.OnFullScreenChangedListener l) {
+        mOnFullScreenChangedListener = l;
+    }
+
+    @Override
     public void onAttachedToWindow_impl() {
         mSuperProvider.onAttachedToWindow_impl();
 
@@ -419,7 +441,6 @@
         mMediaSession = null;
     }
 
-
     @Override
     public CharSequence getAccessibilityClassName_impl() {
         return VideoView2.class.getName();
@@ -559,9 +580,6 @@
     ///////////////////////////////////////////////////
 
     private void attachMediaControlView() {
-        // TODO: change this so that the CC button appears only where there is a subtitle track.
-        // mMediaControlView.showCCButton();
-
         // Get MediaController from MediaSession and set it inside MediaControlView
         mMediaControlView.setController(mMediaSession.getController());
 
@@ -632,6 +650,13 @@
             mCurrentState = STATE_PREPARING;
             mMediaPlayer.prepareAsync();
 
+            // Save file name as title since the file may not have a title Metadata.
+            mTitle = uri.getPath();
+            String scheme = uri.getScheme();
+            if (scheme != null && scheme.equals("file")) {
+                mTitle = uri.getLastPathSegment();
+            }
+
             if (DEBUG) {
                 Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
                         + ", mTargetState=" + mTargetState);
@@ -680,26 +705,26 @@
     private void updatePlaybackState() {
         if (mStateBuilder == null) {
             // Get the capabilities of the player for this stream
-            Metadata data = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
+            mMetadata = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
                     MediaPlayer.BYPASS_METADATA_FILTER);
 
             // Add Play action as default
             long playbackActions = PlaybackState.ACTION_PLAY;
-            if (data != null) {
-                if (!data.has(Metadata.PAUSE_AVAILABLE)
-                        || data.getBoolean(Metadata.PAUSE_AVAILABLE)) {
+            if (mMetadata != null) {
+                if (!mMetadata.has(Metadata.PAUSE_AVAILABLE)
+                        || mMetadata.getBoolean(Metadata.PAUSE_AVAILABLE)) {
                     playbackActions |= PlaybackState.ACTION_PAUSE;
                 }
-                if (!data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
-                        || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
+                if (!mMetadata.has(Metadata.SEEK_BACKWARD_AVAILABLE)
+                        || mMetadata.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
                     playbackActions |= PlaybackState.ACTION_REWIND;
                 }
-                if (!data.has(Metadata.SEEK_FORWARD_AVAILABLE)
-                        || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
+                if (!mMetadata.has(Metadata.SEEK_FORWARD_AVAILABLE)
+                        || mMetadata.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
                     playbackActions |= PlaybackState.ACTION_FAST_FORWARD;
                 }
-                if (!data.has(Metadata.SEEK_AVAILABLE)
-                        || data.getBoolean(Metadata.SEEK_AVAILABLE)) {
+                if (!mMetadata.has(Metadata.SEEK_AVAILABLE)
+                        || mMetadata.getBoolean(Metadata.SEEK_AVAILABLE)) {
                     playbackActions |= PlaybackState.ACTION_SEEK_TO;
                 }
             } else {
@@ -709,8 +734,8 @@
             }
             mStateBuilder = new PlaybackState.Builder();
             mStateBuilder.setActions(playbackActions);
-            mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_SHOW_SUBTITLE, null, -1);
-            mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_HIDE_SUBTITLE, null, -1);
+            mStateBuilder.addCustomAction(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, -1);
+            mStateBuilder.addCustomAction(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, -1);
         }
         mStateBuilder.setState(getCorrespondingPlaybackState(),
                 mInstance.getCurrentPosition(), 1.0f);
@@ -733,7 +758,7 @@
             case STATE_PREPARING:
                 return PlaybackState.STATE_CONNECTING;
             case STATE_PREPARED:
-                return PlaybackState.STATE_STOPPED;
+                return PlaybackState.STATE_PAUSED;
             case STATE_PLAYING:
                 return PlaybackState.STATE_PLAYING;
             case STATE_PAUSED:
@@ -817,16 +842,6 @@
                 mInstance.seekTo(seekToPosition);
             }
 
-            // Create and set playback state for MediaControlView2
-            updatePlaybackState();
-
-            // Get and set duration value as MediaMetadata for MediaControlView2
-            MediaMetadata.Builder builder = new MediaMetadata.Builder();
-            builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mInstance.getDuration());
-            if (mMediaSession != null) {
-                mMediaSession.setMetadata(builder.build());
-            }
-
             if (videoWidth != 0 && videoHeight != 0) {
                 if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
                     if (DEBUG) {
@@ -842,6 +857,7 @@
                     mVideoHeight = videoHeight;
                     mInstance.requestLayout();
                 }
+
                 if (needToStart()) {
                     mInstance.start();
                     if (mMediaControlView != null) {
@@ -862,6 +878,20 @@
                     mInstance.start();
                 }
             }
+            // Create and set playback state for MediaControlView2
+            updatePlaybackState();
+
+            // Get and set duration and title values as MediaMetadata for MediaControlView2
+            MediaMetadata.Builder builder = new MediaMetadata.Builder();
+            if (mMetadata != null && mMetadata.has(Metadata.TITLE)) {
+                mTitle = mMetadata.getString(Metadata.TITLE);
+            }
+            builder.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle);
+            builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mInstance.getDuration());
+
+            if (mMediaSession != null) {
+                mMediaSession.setMetadata(builder.build());
+            }
         }
     };
 
@@ -872,9 +902,6 @@
                     mTargetState = STATE_PLAYBACK_COMPLETED;
                     updatePlaybackState();
 
-                    if (mMediaControlView != null) {
-                        mMediaControlView.hide();
-                    }
                     if (mOnCompletionListener != null) {
                         mOnCompletionListener.onCompletion();
                     }
@@ -936,7 +963,7 @@
                                 .setPositiveButton(res.getString(R.string.VideoView2_error_button),
                                         new DialogInterface.OnClickListener() {
                                             public void onClick(DialogInterface dialog,
-                                                                int whichButton) {
+                                                    int whichButton) {
                                                 /* If we get here, there is no onError listener, so
                                                 * at least inform them that the video is over.
                                                 */
@@ -964,12 +991,16 @@
         @Override
         public void onCommand(String command, Bundle args, ResultReceiver receiver) {
             switch (command) {
-                case MediaControlView2Impl.ACTION_SHOW_SUBTITLE:
+                case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
                     mInstance.showSubtitle();
                     break;
-                case MediaControlView2Impl.ACTION_HIDE_SUBTITLE:
+                case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
                     mInstance.hideSubtitle();
                     break;
+                case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
+                    mInstance.setFullScreen(
+                            args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
+                    break;
             }
         }
 
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
index fe16583..30bac87 100644
--- a/packages/MediaComponents/test/AndroidManifest.xml
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -32,10 +32,17 @@
         <!-- Keep the test services synced together with the TestUtils.java -->
         <service android:name="android.media.MockMediaSessionService2">
             <intent-filter>
-                <action android:name="android.media.session.MediaSessionService2" />
+                <action android:name="android.media.MediaSessionService2" />
             </intent-filter>
             <meta-data android:name="android.media.session" android:value="TestSession" />
         </service>
+        <!-- Keep the test services synced together with the MockMediaLibraryService -->
+        <service android:name="android.media.MockMediaLibraryService2">
+            <intent-filter>
+                <action android:name="android.media.MediaLibraryService2" />
+            </intent-filter>
+            <meta-data android:name="android.media.session" android:value="TestBrowser" />
+        </service>
     </application>
 
     <instrumentation
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
new file mode 100644
index 0000000..fe8aeb9
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link MediaBrowser2}.
+ * <p>
+ * This test inherits {@link MediaController2Test} to ensure that inherited APIs from
+ * {@link MediaController2} works cleanly.
+ */
+// TODO(jaewan): Implement host-side test so browser and service can run in different processes.
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaBrowser2Test extends MediaController2Test {
+    private static final String TAG = "MediaBrowser2Test";
+
+    @Override
+    TestControllerInterface onCreateController(@NonNull SessionToken token,
+            @NonNull TestControllerCallbackInterface callback) {
+        return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
+    }
+
+    @Test
+    public void testGetBrowserRoot() throws InterruptedException {
+        final Bundle param = new Bundle();
+        param.putString(TAG, TAG);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+            @Override
+            public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+                assertTrue(TestUtils.equals(param, rootHints));
+                assertEquals(MockMediaLibraryService2.ROOT_ID, rootMediaId);
+                assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRA, rootExtra));
+                latch.countDown();
+            }
+        };
+
+        final SessionToken token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser =
+                (MediaBrowser2) createController(token,true, callback);
+        browser.getBrowserRoot(param);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    public static class TestBrowserCallback extends BrowserCallback
+            implements WaitForConnectionInterface {
+        private final TestControllerCallbackInterface mCallbackProxy;
+        public final CountDownLatch connectLatch = new CountDownLatch(1);
+        public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+
+        TestBrowserCallback(TestControllerCallbackInterface callbackProxy) {
+            mCallbackProxy = callbackProxy;
+        }
+
+        @CallSuper
+        @Override
+        public void onConnected(CommandGroup commands) {
+            super.onConnected(commands);
+            connectLatch.countDown();
+        }
+
+        @CallSuper
+        @Override
+        public void onDisconnected() {
+            super.onDisconnected();
+            disconnectLatch.countDown();
+        }
+
+        @Override
+        public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+            mCallbackProxy.onGetRootResult(rootHints, rootMediaId, rootExtra);
+        }
+
+        @Override
+        public void waitForConnect(boolean expect) throws InterruptedException {
+            if (expect) {
+                assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            } else {
+                assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            }
+        }
+
+        @Override
+        public void waitForDisconnect(boolean expect) throws InterruptedException {
+            if (expect) {
+                assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            } else {
+                assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            }
+        }
+    }
+
+    public class TestMediaBrowser extends MediaBrowser2 implements TestControllerInterface {
+        private final BrowserCallback mCallback;
+
+        public TestMediaBrowser(@NonNull Context context, @NonNull SessionToken token,
+                @NonNull ControllerCallback callback) {
+            super(context, token, (BrowserCallback) callback, sHandlerExecutor);
+            mCallback = (BrowserCallback) callback;
+        }
+
+        @Override
+        public BrowserCallback getCallback() {
+            return mCallback;
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 38d34cc..f99935a 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -51,9 +51,9 @@
 public class MediaController2Test extends MediaSession2TestBase {
     private static final String TAG = "MediaController2Test";
 
-    private MediaSession2 mSession;
-    private MediaController2Wrapper mController;
-    private MockPlayer mPlayer;
+    MediaSession2 mSession;
+    MediaController2 mController;
+    MockPlayer mPlayer;
 
     @Before
     @Override
@@ -75,7 +75,7 @@
         super.cleanUp();
         sHandler.postAndSync(() -> {
             if (mSession != null) {
-                mSession.setPlayer(null);
+                mSession.close();
             }
         });
         TestServiceRegistry.getInstance().cleanUp();
@@ -144,6 +144,8 @@
 
     @Test
     public void testGetPlaybackState() throws InterruptedException {
+        // TODO(jaewan): add equivalent test later
+        /*
         final CountDownLatch latch = new CountDownLatch(1);
         final MediaPlayerBase.PlaybackListener listener = (state) -> {
             assertEquals(PlaybackState.STATE_BUFFERING, state.getState());
@@ -155,8 +157,11 @@
         mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_BUFFERING));
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
         assertEquals(PlaybackState.STATE_BUFFERING, mController.getPlaybackState().getState());
+        */
     }
 
+    // TODO(jaewan): add equivalent test later
+    /*
     @Test
     public void testAddPlaybackListener() throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(2);
@@ -192,6 +197,7 @@
         mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
         assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
+    */
 
     @Test
     public void testControllerCallback_onConnected() throws InterruptedException {
@@ -210,35 +216,36 @@
             }
         };
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
             mSession = new MediaSession2.Builder(mContext, mPlayer)
                     .setSessionCallback(sessionCallback).build();
         });
-        MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+        MediaController2 controller =
+                createController(mSession.getToken(), false, null);
         assertNotNull(controller);
-        controller.waitForConnect(false);
-        controller.waitForDisconnect(true);
+        waitForConnect(controller, false);
+        waitForDisconnect(controller, true);
     }
 
     @Test
     public void testControllerCallback_releaseSession() throws InterruptedException {
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
         });
-        mController.waitForDisconnect(true);
+        waitForDisconnect(mController, true);
     }
 
     @Test
     public void testControllerCallback_release() throws InterruptedException {
-        mController.release();
-        mController.waitForDisconnect(true);
+        mController.close();
+        waitForDisconnect(mController, true);
     }
 
     @Test
     public void testIsConnected() throws InterruptedException {
         assertTrue(mController.isConnected());
         sHandler.postAndSync(()->{
-            mSession.setPlayer(null);
+            mSession.close();
         });
         // postAndSync() to wait until the disconnection is propagated.
         sHandler.postAndSync(()->{
@@ -252,7 +259,7 @@
     @Test
     public void testDeadlock() throws InterruptedException {
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
             mSession = null;
         });
 
@@ -272,9 +279,6 @@
             });
             final MediaController2 controller = createController(mSession.getToken());
             testHandler.post(() -> {
-                controller.addPlaybackListener((state) -> {
-                    // no-op. Just to set a binder call path from session to controller.
-                }, sessionHandler);
                 final PlaybackState state = createPlaybackState(PlaybackState.STATE_ERROR);
                 for (int i = 0; i < 100; i++) {
                     // triggers call from session to controller.
@@ -300,7 +304,7 @@
             if (mSession != null) {
                 sessionHandler.postAndSync(() -> {
                     // Clean up here because sessionHandler will be removed afterwards.
-                    mSession.setPlayer(null);
+                    mSession.close();
                     mSession = null;
                 });
             }
@@ -330,11 +334,23 @@
         mPlayer = (MockPlayer) mSession.getPlayer();
     }
 
+    // TODO(jaewan): Reenable when session manager detects app installs
     @Ignore
     @Test
-    public void testConnectToService() throws InterruptedException {
+    public void testConnectToService_sessionService() throws InterruptedException {
         connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+        testConnectToService();
+    }
 
+    // TODO(jaewan): Reenable when session manager detects app installs
+    @Ignore
+    @Test
+    public void testConnectToService_libraryService() throws InterruptedException {
+        connectToService(TestUtils.getServiceToken(mContext, MockMediaLibraryService2.ID));
+        testConnectToService();
+    }
+
+    public void testConnectToService() throws InterruptedException {
         TestServiceRegistry serviceInfo = TestServiceRegistry.getInstance();
         ControllerInfo info = serviceInfo.getOnConnectControllerInfo();
         assertEquals(mContext.getPackageName(), info.getPackageName());
@@ -347,6 +363,8 @@
         assertTrue(mPlayer.mPlayCalled);
 
         // Test command from session service to controller
+        // TODO(jaewan): Add equivalent tests again
+        /*
         final CountDownLatch latch = new CountDownLatch(1);
         mController.addPlaybackListener((state) -> {
             assertNotNull(state);
@@ -356,6 +374,7 @@
         mPlayer.notifyPlaybackState(
                 TestUtils.createPlaybackState(PlaybackState.STATE_REWINDING));
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        */
     }
 
     @Test
@@ -371,34 +390,48 @@
     }
 
     @Test
-    public void testRelease_beforeConnected() throws InterruptedException {
+    public void testClose_beforeConnected() throws InterruptedException {
         MediaController2 controller =
                 createController(mSession.getToken(), false, null);
-        controller.release();
+        controller.close();
     }
 
     @Test
-    public void testRelease_twice() throws InterruptedException {
-        mController.release();
-        mController.release();
+    public void testClose_twice() throws InterruptedException {
+        mController.close();
+        mController.close();
     }
 
     @Test
-    public void testRelease_session() throws InterruptedException {
+    public void testClose_session() throws InterruptedException {
         final String id = mSession.getToken().getId();
-        mController.release();
-        // Release is done immediately for session.
+        mController.close();
+        // close is done immediately for session.
         testNoInteraction();
 
-        // Test whether the controller is notified about later release of the session or
+        // Test whether the controller is notified about later close of the session or
         // re-creation.
         testControllerAfterSessionIsGone(id);
     }
 
+    // TODO(jaewan): Reenable when session manager detects app installs
     @Ignore
     @Test
-    public void testRelease_sessionService() throws InterruptedException {
+    public void testClose_sessionService() throws InterruptedException {
         connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+        testCloseFromService();
+    }
+
+    // TODO(jaewan): Reenable when session manager detects app installs
+    @Ignore
+    @Test
+    public void testClose_libraryService() throws InterruptedException {
+        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+        testCloseFromService();
+    }
+
+    private void testCloseFromService() throws InterruptedException {
+        final String id = mController.getSessionToken().getId();
         final CountDownLatch latch = new CountDownLatch(1);
         TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
             if (service == null) {
@@ -406,23 +439,23 @@
                 latch.countDown();
             }
         });
-        mController.release();
-        // Wait until release triggers onDestroy() of the session service.
+        mController.close();
+        // Wait until close triggers onDestroy() of the session service.
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
         assertNull(TestServiceRegistry.getInstance().getServiceInstance());
         testNoInteraction();
 
-        // Test whether the controller is notified about later release of the session or
+        // Test whether the controller is notified about later close of the session or
         // re-creation.
-        testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
+        testControllerAfterSessionIsGone(id);
     }
 
     private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
         sHandler.postAndSync(() -> {
-            // TODO(jaewan): Use Session.release later when we add the API.
-            mSession.setPlayer(null);
+            // TODO(jaewan): Use Session.close later when we add the API.
+            mSession.close();
         });
-        mController.waitForDisconnect(true);
+        waitForDisconnect(mController, true);
         testNoInteraction();
 
         // Test with the newly created session.
@@ -434,17 +467,19 @@
         testNoInteraction();
     }
 
-
     private void testNoInteraction() throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
         final PlaybackListener playbackListener = (state) -> {
-            fail("Controller shouldn't be notified about change in session after the release.");
+            fail("Controller shouldn't be notified about change in session after the close.");
             latch.countDown();
         };
+        // TODO(jaewan): Add equivalent tests again
+        /*
         mController.addPlaybackListener(playbackListener, sHandler);
         mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
         assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         mController.removePlaybackListener(playbackListener);
+        */
     }
 
     // TODO(jaewan): Add  test for service connect rejection, when we differentiate session
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index f7224ea..7b58f0e 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -65,7 +65,7 @@
     public void cleanUp() throws Exception {
         super.cleanUp();
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
         });
     }
 
@@ -93,7 +93,7 @@
             // Test if setPlayer doesn't crash with various situations.
             mSession.setPlayer(mPlayer);
             mSession.setPlayer(player);
-            mSession.setPlayer(null);
+            mSession.close();
         });
     }
 
@@ -139,6 +139,8 @@
 
     @Test
     public void testPlaybackStateChangedListener() throws InterruptedException {
+        // TODO(jaewan): Add equivalent tests again
+        /*
         final CountDownLatch latch = new CountDownLatch(2);
         final MockPlayer player = new MockPlayer(0);
         final PlaybackListener listener = (state) -> {
@@ -164,10 +166,13 @@
         });
         player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        */
     }
 
     @Test
     public void testBadPlayer() throws InterruptedException {
+        // TODO(jaewan): Add equivalent tests again
+        /*
         final CountDownLatch latch = new CountDownLatch(3); // expected call + 1
         final BadPlayer player = new BadPlayer(0);
         sHandler.postAndSync(() -> {
@@ -181,6 +186,7 @@
         });
         player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
         assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        */
     }
 
     private static class BadPlayer extends MockPlayer {
@@ -199,7 +205,7 @@
     public void testOnCommandCallback() throws InterruptedException {
         final MockOnCommandCallback callback = new MockOnCommandCallback();
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
             mPlayer = new MockPlayer(1);
             mSession = new MediaSession2.Builder(mContext, mPlayer)
                     .setSessionCallback(callback).build();
@@ -224,14 +230,15 @@
     public void testOnConnectCallback() throws InterruptedException {
         final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
             mSession = new MediaSession2.Builder(mContext, mPlayer)
                     .setSessionCallback(sessionCallback).build();
         });
-        MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+        MediaController2 controller =
+                createController(mSession.getToken(), false, null);
         assertNotNull(controller);
-        controller.waitForConnect(false);
-        controller.waitForDisconnect(true);
+        waitForConnect(controller, false);
+        waitForDisconnect(controller, true);
     }
 
     public class MockOnConnectCallback extends SessionCallback {
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
index ab842c4..2965c82 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -20,7 +20,9 @@
 import static junit.framework.Assert.assertTrue;
 
 import android.content.Context;
+import android.media.MediaController2.ControllerCallback;
 import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
 import android.os.HandlerThread;
 import android.support.annotation.CallSuper;
 import android.support.annotation.NonNull;
@@ -50,6 +52,22 @@
     Context mContext;
     private List<MediaController2> mControllers = new ArrayList<>();
 
+    interface TestControllerInterface {
+        ControllerCallback getCallback();
+    }
+
+    interface TestControllerCallbackInterface {
+        // Currently empty. Add methods in ControllerCallback/BrowserCallback that you want to test.
+
+        // Browser specific callbacks
+        default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
+    }
+
+    interface WaitForConnectionInterface {
+        void waitForConnect(boolean expect) throws InterruptedException;
+        void waitForDisconnect(boolean expect) throws InterruptedException;
+    }
+
     @BeforeClass
     public static void setUpThread() {
         if (sHandler == null) {
@@ -79,32 +97,69 @@
     @CallSuper
     public void cleanUp() throws Exception {
         for (int i = 0; i < mControllers.size(); i++) {
-            mControllers.get(i).release();
+            mControllers.get(i).close();
         }
     }
 
-    MediaController2Wrapper createController(SessionToken token) throws InterruptedException {
+    final MediaController2 createController(SessionToken token) throws InterruptedException {
         return createController(token, true, null);
     }
 
-    MediaController2Wrapper createController(@NonNull SessionToken token, boolean waitForConnect,
-            @Nullable TestControllerCallback callback)
+    final MediaController2 createController(@NonNull SessionToken token,
+            boolean waitForConnect, @Nullable TestControllerCallbackInterface callback)
             throws InterruptedException {
-        if (callback == null) {
-            callback = new TestControllerCallback();
+        TestControllerInterface instance = onCreateController(token, callback);
+        if (!(instance instanceof MediaController2)) {
+            throw new RuntimeException("Test has a bug. Expected MediaController2 but returned "
+                    + instance);
         }
-        MediaController2Wrapper controller = new MediaController2Wrapper(mContext, token, callback);
+        MediaController2 controller = (MediaController2) instance;
         mControllers.add(controller);
         if (waitForConnect) {
-            controller.waitForConnect(true);
+            waitForConnect(controller, true);
         }
         return controller;
     }
 
-    public static class TestControllerCallback extends MediaController2.ControllerCallback {
+    private static WaitForConnectionInterface getWaitForConnectionInterface(
+            MediaController2 controller) {
+        if (!(controller instanceof TestControllerInterface)) {
+            throw new RuntimeException("Test has a bug. Expected controller implemented"
+                    + " TestControllerInterface but got " + controller);
+        }
+        ControllerCallback callback = ((TestControllerInterface) controller).getCallback();
+        if (!(callback instanceof WaitForConnectionInterface)) {
+            throw new RuntimeException("Test has a bug. Expected controller with callback "
+                    + " implemented WaitForConnectionInterface but got " + controller);
+        }
+        return (WaitForConnectionInterface) callback;
+    }
+
+    public static void waitForConnect(MediaController2 controller, boolean expected)
+            throws InterruptedException {
+        getWaitForConnectionInterface(controller).waitForConnect(expected);
+    }
+
+    public static void waitForDisconnect(MediaController2 controller, boolean expected)
+            throws InterruptedException {
+        getWaitForConnectionInterface(controller).waitForDisconnect(expected);
+    }
+
+    TestControllerInterface onCreateController(@NonNull SessionToken token,
+            @NonNull TestControllerCallbackInterface callback) {
+        return new TestMediaController(mContext, token, new TestControllerCallback(callback));
+    }
+
+    public static class TestControllerCallback extends MediaController2.ControllerCallback
+            implements WaitForConnectionInterface {
+        public final TestControllerCallbackInterface mCallbackProxy;
         public final CountDownLatch connectLatch = new CountDownLatch(1);
         public final CountDownLatch disconnectLatch = new CountDownLatch(1);
 
+        TestControllerCallback(TestControllerCallbackInterface callbackProxy) {
+            mCallbackProxy = callbackProxy;
+        }
+
         @CallSuper
         @Override
         public void onConnected(CommandGroup commands) {
@@ -118,31 +173,38 @@
             super.onDisconnected();
             disconnectLatch.countDown();
         }
+
+        @Override
+        public void waitForConnect(boolean expect) throws InterruptedException {
+            if (expect) {
+                assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            } else {
+                assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            }
+        }
+
+        @Override
+        public void waitForDisconnect(boolean expect) throws InterruptedException {
+            if (expect) {
+                assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            } else {
+                assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            }
+        }
     }
 
-    public class MediaController2Wrapper extends MediaController2 {
-        private final TestControllerCallback mCallback;
+    public class TestMediaController extends MediaController2 implements TestControllerInterface {
+        private final ControllerCallback mCallback;
 
-        public MediaController2Wrapper(@NonNull Context context, @NonNull SessionToken token,
-                @NonNull TestControllerCallback callback) {
+        public TestMediaController(@NonNull Context context, @NonNull SessionToken token,
+                @NonNull ControllerCallback callback) {
             super(context, token, callback, sHandlerExecutor);
             mCallback = callback;
         }
 
-        public void waitForConnect(boolean expect) throws InterruptedException {
-            if (expect) {
-                assertTrue(mCallback.connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            } else {
-                assertFalse(mCallback.connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-
-        public void waitForDisconnect(boolean expect) throws InterruptedException {
-            if (expect) {
-                assertTrue(mCallback.disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            } else {
-                assertFalse(mCallback.disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
+        @Override
+        public ControllerCallback getCallback() {
+            return mCallback;
         }
     }
 }
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
index 0ee37b1..bfed7d0 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -71,7 +71,7 @@
         super.cleanUp();
         sHandler.removeCallbacksAndMessages(null);
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
         });
     }
 
@@ -111,7 +111,7 @@
     @Test
     public void testGetSessionTokens_sessionRejected() throws InterruptedException {
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
             mSession = new MediaSession2.Builder(mContext, new MockPlayer(0)).setId(TAG)
                     .setSessionCallback(new SessionCallback() {
                         @Override
@@ -141,7 +141,7 @@
     public void testGetMediaSession2Tokens_playerRemoved() throws InterruptedException {
         // Release
         sHandler.postAndSync(() -> {
-            mSession.setPlayer(null);
+            mSession.close();
         });
         ensureChangeInSession();
 
@@ -158,6 +158,7 @@
     @Test
     public void testGetMediaSessionService2Token() throws InterruptedException {
         boolean foundTestSessionService = false;
+        boolean foundTestLibraryService = false;
         List<SessionToken> tokens = mManager.getSessionServiceTokens();
         for (int i = 0; i < tokens.size(); i++) {
             SessionToken token = tokens.get(i);
@@ -167,15 +168,23 @@
                 assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
                 assertNull(token.getSessionBinder());
                 foundTestSessionService = true;
+            } else if (mContext.getPackageName().equals(token.getPackageName())
+                    && MockMediaLibraryService2.ID.equals(token.getId())) {
+                assertFalse(foundTestLibraryService);
+                assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
+                assertNull(token.getSessionBinder());
+                foundTestLibraryService = true;
             }
         }
         assertTrue(foundTestSessionService);
+        assertTrue(foundTestLibraryService);
     }
 
     @Test
     public void testGetAllSessionTokens() throws InterruptedException {
         boolean foundTestSession = false;
         boolean foundTestSessionService = false;
+        boolean foundTestLibraryService = false;
         List<SessionToken> tokens = mManager.getAllSessionTokens();
         for (int i = 0; i < tokens.size(); i++) {
             SessionToken token = tokens.get(i);
@@ -192,12 +201,18 @@
                     foundTestSessionService = true;
                     assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
                     break;
+                case MockMediaLibraryService2.ID:
+                    assertFalse(foundTestLibraryService);
+                    assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
+                    foundTestLibraryService = true;
+                    break;
                 default:
                     fail("Unexpected session " + token + " exists in the package");
             }
         }
         assertTrue(foundTestSession);
         assertTrue(foundTestSessionService);
+        assertTrue(foundTestLibraryService);
     }
 
     // Ensures if the session creation/release is notified to the server.
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
new file mode 100644
index 0000000..acf733f
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -0,0 +1,98 @@
+/*
+* Copyright 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package android.media;
+
+import static junit.framework.Assert.fail;
+
+import android.content.Context;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.TestUtils.SyncHandler;
+import android.os.Bundle;
+import android.os.Process;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Mock implementation of {@link MediaLibraryService2} for testing.
+ */
+public class MockMediaLibraryService2 extends MediaLibraryService2 {
+    // Keep in sync with the AndroidManifest.xml
+    public static final String ID = "TestLibrary";
+
+    public static final String ROOT_ID = "rootId";
+    public static final Bundle EXTRA = new Bundle();
+    static {
+        EXTRA.putString(ROOT_ID, ROOT_ID);
+    }
+    @GuardedBy("MockMediaLibraryService2.class")
+    private static SessionToken sToken;
+
+    private MediaLibrarySession mSession;
+
+    @Override
+    public MediaLibrarySession onCreateSession(String sessionId) {
+        final MockPlayer player = new MockPlayer(1);
+        SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+        try {
+            handler.postAndSync(() -> {
+                TestLibrarySessionCallback callback = new TestLibrarySessionCallback();
+                mSession = new MediaLibrarySessionBuilder(
+                        MockMediaLibraryService2.this, player, callback)
+                        .setId(sessionId).build();
+            });
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+        return mSession;
+    }
+
+    @Override
+    public void onDestroy() {
+        TestServiceRegistry.getInstance().cleanUp();
+        super.onDestroy();
+    }
+
+    public static SessionToken getToken(Context context) {
+        synchronized (MockMediaLibraryService2.class) {
+            if (sToken == null) {
+                sToken = new SessionToken(SessionToken.TYPE_LIBRARY_SERVICE,
+                        context.getPackageName(), ID,
+                        MockMediaLibraryService2.class.getName(), null);
+            }
+            return sToken;
+        }
+    }
+
+    private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
+        @Override
+        public CommandGroup onConnect(ControllerInfo controller) {
+            if (Process.myUid() != controller.getUid()) {
+                // It's system app wants to listen changes. Ignore.
+                return super.onConnect(controller);
+            }
+            TestServiceRegistry.getInstance().setServiceInstance(
+                    MockMediaLibraryService2.this, controller);
+            return super.onConnect(controller);
+        }
+
+        @Override
+        public BrowserRoot onGetRoot(ControllerInfo controller, Bundle rootHints) {
+            return new BrowserRoot(ROOT_ID, EXTRA);
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index e4a7485..9cf4911 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -18,9 +18,14 @@
 
 import static junit.framework.Assert.fail;
 
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSession2.SessionCallback;
 import android.media.TestUtils.SyncHandler;
+import android.media.session.PlaybackState;
 import android.os.Process;
 
 /**
@@ -29,7 +34,13 @@
 public class MockMediaSessionService2 extends MediaSessionService2 {
     // Keep in sync with the AndroidManifest.xml
     public static final String ID = "TestSession";
-    public MediaSession2 mSession;
+
+    private static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
+    private static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
+
+    private NotificationChannel mDefaultNotificationChannel;
+    private MediaSession2 mSession;
+    private NotificationManager mNotificationManager;
 
     @Override
     public MediaSession2 onCreateSession(String sessionId) {
@@ -49,6 +60,7 @@
     @Override
     public void onCreate() {
         super.onCreate();
+        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
     @Override
@@ -57,6 +69,23 @@
         super.onDestroy();
     }
 
+    @Override
+    public MediaNotification onUpdateNotification(PlaybackState state) {
+        if (mDefaultNotificationChannel == null) {
+            mDefaultNotificationChannel = new NotificationChannel(
+                    DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+                    DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+                    NotificationManager.IMPORTANCE_DEFAULT);
+            mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
+        }
+        Notification notification = new Notification.Builder(
+                this, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
+                .setContentTitle(getPackageName())
+                .setContentText("Playback state: " + state.getState())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+        return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
+    }
+
     private class MySessionCallback extends SessionCallback {
         @Override
         public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
index 378a6c4..6f5512e 100644
--- a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
+++ b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
@@ -106,17 +106,17 @@
             if (mService != null) {
                 try {
                     if (mHandler.getLooper() == Looper.myLooper()) {
-                        mService.getSession().setPlayer(null);
+                        mService.getSession().close();
                     } else {
                         mHandler.postAndSync(() -> {
-                            mService.getSession().setPlayer(null);
+                            mService.getSession().close();
                         });
                     }
                 } catch (InterruptedException e) {
                     // No-op. Service containing session will die, but shouldn't be a huge issue.
                 }
                 // stopSelf() would not kill service while the binder connection established by
-                // bindService() exists, and setPlayer(null) above will do the job instead.
+                // bindService() exists, and close() above will do the job instead.
                 // So stopSelf() isn't really needed, but just for sure.
                 mService.stopSelf();
                 mService = null;
diff --git a/packages/MediaComponents/test/src/android/media/TestUtils.java b/packages/MediaComponents/test/src/android/media/TestUtils.java
index 0cca12c..1372f01 100644
--- a/packages/MediaComponents/test/src/android/media/TestUtils.java
+++ b/packages/MediaComponents/test/src/android/media/TestUtils.java
@@ -19,11 +19,13 @@
 import android.content.Context;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
+import android.os.Bundle;
 import android.os.Handler;
 
 import android.os.Looper;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -47,6 +49,14 @@
         return new PlaybackState.Builder().setState(state, 0, 1.0f).build();
     }
 
+    /**
+     * Finds the session with id in this test package.
+     *
+     * @param context
+     * @param id
+     * @return
+     */
+    // TODO(jaewan): Currently not working.
     public static SessionToken getServiceToken(Context context, String id) {
         MediaSessionManager manager =
                 (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -63,6 +73,33 @@
     }
 
     /**
+     * Compares contents of two bundles.
+     *
+     * @param a a bundle
+     * @param b another bundle
+     * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
+     *     incorrect if any bundle contains a bundle.
+     */
+    public static boolean equals(Bundle a, Bundle b) {
+        if (a == b) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        if (!a.keySet().containsAll(b.keySet())
+                || !b.keySet().containsAll(a.keySet())) {
+            return false;
+        }
+        for (String key : a.keySet()) {
+            if (!Objects.equals(a.get(key), b.get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Handler that always waits until the Runnable finishes.
      */
     public static class SyncHandler extends Handler {
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 686e3e3..e362530 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -155,6 +155,8 @@
       mBtNrecIsOff(false),
       mIsLowRamDevice(true),
       mIsDeviceTypeKnown(false),
+      mTotalMemory(0),
+      mClientSharedHeapSize(kMinimumClientSharedHeapSizeBytes),
       mGlobalEffectEnableTime(0),
       mSystemReady(false)
 {
@@ -1512,17 +1514,9 @@
         mAudioFlinger(audioFlinger),
         mPid(pid)
 {
-    size_t heapSize = property_get_int32("ro.af.client_heap_size_kbyte", 0);
-    heapSize *= 1024;
-    if (!heapSize) {
-        heapSize = kClientSharedHeapSizeBytes;
-        // Increase heap size on non low ram devices to limit risk of reconnection failure for
-        // invalidated tracks
-        if (!audioFlinger->isLowRamDevice()) {
-            heapSize *= kClientSharedHeapSizeMultiplier;
-        }
-    }
-    mMemoryDealer = new MemoryDealer(heapSize, "AudioFlinger::Client");
+    mMemoryDealer = new MemoryDealer(
+            audioFlinger->getClientSharedHeapSize(),
+            (std::string("AudioFlinger::Client(") + std::to_string(pid) + ")").c_str());
 }
 
 // Client destructor must be called with AudioFlinger::mClientLock held
@@ -1860,7 +1854,7 @@
 
 // ----------------------------------------------------------------------------
 
-status_t AudioFlinger::setLowRamDevice(bool isLowRamDevice)
+status_t AudioFlinger::setLowRamDevice(bool isLowRamDevice, int64_t totalMemory)
 {
     uid_t uid = IPCThreadState::self()->getCallingUid();
     if (uid != AID_SYSTEM) {
@@ -1871,10 +1865,43 @@
         return INVALID_OPERATION;
     }
     mIsLowRamDevice = isLowRamDevice;
+    mTotalMemory = totalMemory;
+    // mIsLowRamDevice and mTotalMemory are obtained through ActivityManager;
+    // see ActivityManager.isLowRamDevice() and ActivityManager.getMemoryInfo().
+    // mIsLowRamDevice generally represent devices with less than 1GB of memory,
+    // though actual setting is determined through device configuration.
+    constexpr int64_t GB = 1024 * 1024 * 1024;
+    mClientSharedHeapSize =
+            isLowRamDevice ? kMinimumClientSharedHeapSizeBytes
+                    : mTotalMemory < 2 * GB ? 4 * kMinimumClientSharedHeapSizeBytes
+                    : mTotalMemory < 3 * GB ? 8 * kMinimumClientSharedHeapSizeBytes
+                    : mTotalMemory < 4 * GB ? 16 * kMinimumClientSharedHeapSizeBytes
+                    : 32 * kMinimumClientSharedHeapSizeBytes;
     mIsDeviceTypeKnown = true;
+
+    // TODO: Cache the client shared heap size in a persistent property.
+    // It's possible that a native process or Java service or app accesses audioserver
+    // after it is registered by system server, but before AudioService updates
+    // the memory info.  This would occur immediately after boot or an audioserver
+    // crash and restore. Before update from AudioService, the client would get the
+    // minimum heap size.
+
+    ALOGD("isLowRamDevice:%s totalMemory:%lld mClientSharedHeapSize:%zu",
+            (isLowRamDevice ? "true" : "false"),
+            (long long)mTotalMemory,
+            mClientSharedHeapSize.load());
     return NO_ERROR;
 }
 
+size_t AudioFlinger::getClientSharedHeapSize() const
+{
+    size_t heapSizeInBytes = property_get_int32("ro.af.client_heap_size_kbyte", 0) * 1024;
+    if (heapSizeInBytes != 0) { // read-only property overrides all.
+        return heapSizeInBytes;
+    }
+    return mClientSharedHeapSize;
+}
+
 audio_hw_sync_t AudioFlinger::getAudioHwSyncForSession(audio_session_t sessionId)
 {
     Mutex::Autolock _l(mLock);
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index a1c3f36..296f092 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -94,12 +94,6 @@
 
 static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);
 
-
-// Max shared memory size for audio tracks and audio records per client process
-static const size_t kClientSharedHeapSizeBytes = 1024*1024;
-// Shared memory size multiplier for non low ram devices
-static const size_t kClientSharedHeapSizeMultiplier = 4;
-
 #define INCLUDING_FROM_AUDIOFLINGER_H
 
 class AudioFlinger :
@@ -227,7 +221,7 @@
     virtual uint32_t getPrimaryOutputSamplingRate();
     virtual size_t getPrimaryOutputFrameCount();
 
-    virtual status_t setLowRamDevice(bool isLowRamDevice);
+    virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) override;
 
     /* List available audio ports and their attributes */
     virtual status_t listAudioPorts(unsigned int *num_ports,
@@ -829,15 +823,18 @@
     static const size_t kTeeSinkTrackFramesDefault = 0x200000;
 #endif
 
-    // This method reads from a variable without mLock, but the variable is updated under mLock.  So
-    // we might read a stale value, or a value that's inconsistent with respect to other variables.
-    // In this case, it's safe because the return value isn't used for making an important decision.
-    // The reason we don't want to take mLock is because it could block the caller for a long time.
+    // These methods read variables atomically without mLock,
+    // though the variables are updated with mLock.
     bool    isLowRamDevice() const { return mIsLowRamDevice; }
+    size_t getClientSharedHeapSize() const;
 
 private:
-    bool    mIsLowRamDevice;
+    std::atomic<bool> mIsLowRamDevice;
     bool    mIsDeviceTypeKnown;
+    int64_t mTotalMemory;
+    std::atomic<size_t> mClientSharedHeapSize;
+    static constexpr size_t kMinimumClientSharedHeapSizeBytes = 1024 * 1024; // 1MB
+
     nsecs_t mGlobalEffectEnableTime;  // when a global effect was last enabled
 
     sp<PatchPanel> mPatchPanel;
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 2803ec1..cd2174d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -187,6 +187,15 @@
     bool isStreamActiveRemotely(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
 
     /**
+     * return whether a stream is playing, but not on a "remote" device.
+     * Override to change the definition of a local/remote playback.
+     * Used for instance by policy manager to alter the speaker playback ("speaker safe" behavior)
+     * when media plays or not locally.
+     * For the base implementation, "remotely" means playing during screen mirroring.
+     */
+    bool isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
+
+    /**
      * returns the A2DP output handle if it is open or 0 otherwise
      */
     audio_io_handle_t getA2dpOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index d5e8e1b..17fc272 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -579,6 +579,19 @@
     return false;
 }
 
+bool SwAudioOutputCollection::isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs) const
+{
+    nsecs_t sysTime = systemTime();
+    for (size_t i = 0; i < this->size(); i++) {
+        const sp<SwAudioOutputDescriptor> outputDesc = this->valueAt(i);
+        if (outputDesc->isStreamActive(stream, inPastMs, sysTime)
+                && ((outputDesc->device() & APM_AUDIO_OUT_DEVICE_REMOTE_ALL) == 0)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 bool SwAudioOutputCollection::isStreamActiveRemotely(audio_stream_type_t stream,
                                                    uint32_t inPastMs) const
 {
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 9bdb98c..5ec0475 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -238,18 +238,19 @@
     const SwAudioOutputCollection &outputs = mApmObserver->getOutputs();
 
     return getDeviceForStrategyInt(strategy, availableOutputDevices,
-                                   availableInputDevices, outputs);
+                                   availableInputDevices, outputs, (uint32_t)AUDIO_DEVICE_NONE);
 }
 
 
-
 audio_devices_t Engine::getDeviceForStrategyInt(routing_strategy strategy,
-                                                DeviceVector availableOutputDevices,
-                                                DeviceVector availableInputDevices,
-                                                const SwAudioOutputCollection &outputs) const
+        DeviceVector availableOutputDevices,
+        DeviceVector availableInputDevices,
+        const SwAudioOutputCollection &outputs,
+        uint32_t outputDeviceTypesToIgnore) const
 {
     uint32_t device = AUDIO_DEVICE_NONE;
-    uint32_t availableOutputDevicesType = availableOutputDevices.types();
+    uint32_t availableOutputDevicesType =
+            availableOutputDevices.types() & ~outputDeviceTypesToIgnore;
 
     switch (strategy) {
 
@@ -260,38 +261,24 @@
     case STRATEGY_SONIFICATION_RESPECTFUL:
         if (isInCall()) {
             device = getDeviceForStrategyInt(
-                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
-        } else if (outputs.isStreamActiveRemotely(AUDIO_STREAM_MUSIC,
-                SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)) {
-            // while media is playing on a remote device, use the the sonification behavior.
-            // Note that we test this usecase before testing if media is playing because
-            //   the isStreamActive() method only informs about the activity of a stream, not
-            //   if it's for local playback. Note also that we use the same delay between both tests
-            device = getDeviceForStrategyInt(
-                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
-            //user "safe" speaker if available instead of normal speaker to avoid triggering
-            //other acoustic safety mechanisms for notification
-            if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
-                    (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
-                device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
-                device &= ~AUDIO_DEVICE_OUT_SPEAKER;
-            }
-        } else if (outputs.isStreamActive(
-                                AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
-                    || outputs.isStreamActive(
-                            AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY))
-        {
-            // while media/a11y is playing (or has recently played), use the same device
-            device = getDeviceForStrategyInt(
-                    STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
         } else {
-            // when media is not playing anymore, fall back on the sonification behavior
-            device = getDeviceForStrategyInt(
-                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
-            //user "safe" speaker if available instead of normal speaker to avoid triggering
-            //other acoustic safety mechanisms for notification
-            if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
-                    (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
+            bool media_active_locally =
+                    outputs.isStreamActiveLocally(
+                            AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
+                    || outputs.isStreamActiveLocally(
+                            AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY);
+            // routing is same as media without the "remote" device
+            device = getDeviceForStrategyInt(STRATEGY_MEDIA,
+                    availableOutputDevices,
+                    availableInputDevices, outputs,
+                    AUDIO_DEVICE_OUT_REMOTE_SUBMIX | outputDeviceTypesToIgnore);
+            // if no media is playing on the device, check for mandatory use of "safe" speaker
+            // when media would have played on speaker, and the safe speaker path is available
+            if (!media_active_locally
+                    && (device & AUDIO_DEVICE_OUT_SPEAKER)
+                    && (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
                 device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
                 device &= ~AUDIO_DEVICE_OUT_SPEAKER;
             }
@@ -302,7 +289,8 @@
         if (!isInCall()) {
             // when off call, DTMF strategy follows the same rules as MEDIA strategy
             device = getDeviceForStrategyInt(
-                    STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
             break;
         }
         // when in call, DTMF and PHONE strategies follow the same rules
@@ -408,7 +396,8 @@
         // handleIncallSonification().
         if (isInCall()) {
             device = getDeviceForStrategyInt(
-                    STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
             break;
         }
         // FALL THROUGH
@@ -463,11 +452,13 @@
             if (outputs.isStreamActive(AUDIO_STREAM_RING) ||
                     outputs.isStreamActive(AUDIO_STREAM_ALARM)) {
                 return getDeviceForStrategyInt(
-                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
             }
             if (isInCall()) {
                 return getDeviceForStrategyInt(
-                        STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+                        STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+                        outputDeviceTypesToIgnore);
             }
         }
         // For other cases, STRATEGY_ACCESSIBILITY behaves like STRATEGY_MEDIA
@@ -486,7 +477,8 @@
         }
         if (isInCall() && (strategy == STRATEGY_MEDIA)) {
             device = getDeviceForStrategyInt(
-                    STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
             break;
         }
         if ((device2 == AUDIO_DEVICE_NONE) &&
diff --git a/services/audiopolicy/enginedefault/src/Engine.h b/services/audiopolicy/enginedefault/src/Engine.h
index 57538c4..06186c1 100644
--- a/services/audiopolicy/enginedefault/src/Engine.h
+++ b/services/audiopolicy/enginedefault/src/Engine.h
@@ -126,9 +126,10 @@
     routing_strategy getStrategyForUsage(audio_usage_t usage);
     audio_devices_t getDeviceForStrategy(routing_strategy strategy) const;
     audio_devices_t getDeviceForStrategyInt(routing_strategy strategy,
-                                            DeviceVector availableOutputDevices,
-                                            DeviceVector availableInputDevices,
-                                            const SwAudioOutputCollection &outputs) const;
+            DeviceVector availableOutputDevices,
+            DeviceVector availableInputDevices,
+            const SwAudioOutputCollection &outputs,
+            uint32_t outputDeviceTypesToIgnore) const;
     audio_devices_t getDeviceForInputSource(audio_source_t inputSource) const;
     audio_mode_t mPhoneState;  /**< current phone state. */
 
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index e058dc8..7343601 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -2939,7 +2939,7 @@
                 sinkDeviceDesc->toAudioPortConfig(&newPatch.sinks[i], &patch->sinks[i]);
 
                 // create a software bridge in PatchPanel if:
-                // - source and sink devices are on differnt HW modules OR
+                // - source and sink devices are on different HW modules OR
                 // - audio HAL version is < 3.0
                 if (!srcDeviceDesc->hasSameHwModuleAs(sinkDeviceDesc) ||
                         (srcDeviceDesc->mModule->getHalVersionMajor() < 3)) {
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 7e71c3b..999e258 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -55,8 +55,6 @@
 #include "device3/Camera3SharedOutputStream.h"
 #include "CameraService.h"
 
-#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
-
 using namespace android::camera3;
 using namespace android::hardware::camera;
 using namespace android::hardware::camera::device::V3_2;
@@ -669,13 +667,15 @@
     }
 
     if (dumpTemplates) {
-        const char *templateNames[] = {
+        const char *templateNames[CAMERA3_TEMPLATE_COUNT] = {
             "TEMPLATE_PREVIEW",
             "TEMPLATE_STILL_CAPTURE",
             "TEMPLATE_VIDEO_RECORD",
             "TEMPLATE_VIDEO_SNAPSHOT",
             "TEMPLATE_ZERO_SHUTTER_LAG",
-            "TEMPLATE_MANUAL"
+            "TEMPLATE_MANUAL",
+            "TEMPLATE_MOTION_TRACKING_PREVIEW",
+            "TEMPALTE_MOTION_TRACKING_BEST"
         };
 
         for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; i++) {
@@ -1668,10 +1668,16 @@
     mStatusChanged.broadcast();
 }
 
+void Camera3Device::pauseStateNotify(bool enable) {
+    Mutex::Autolock il(mInterfaceLock);
+    Mutex::Autolock l(mLock);
+
+    mPauseStateNotify = enable;
+}
+
 // Pause to reconfigure
 status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration) {
     mRequestThread->setPaused(true);
-    mPauseStateNotify = true;
 
     ALOGV("%s: Camera %s: Internal wait until idle (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
           maxExpectedDuration);
@@ -1690,6 +1696,8 @@
 
     mRequestThread->setPaused(false);
 
+    ALOGV("%s: Camera %s: Internal wait until active (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
+            kActiveTimeout);
     res = waitUntilStateThenRelock(/*active*/ true, kActiveTimeout);
     if (res != OK) {
         SET_ERR_L("Can't transition to active in %f seconds!",
@@ -1970,8 +1978,8 @@
         if (mStatus != STATUS_ACTIVE && mStatus != STATUS_CONFIGURED) {
             return;
         }
-        ALOGV("%s: Camera %s: Now %s", __FUNCTION__, mId.string(),
-                idle ? "idle" : "active");
+        ALOGV("%s: Camera %s: Now %s, pauseState: %s", __FUNCTION__, mId.string(),
+                idle ? "idle" : "active", mPauseStateNotify ? "true" : "false");
         internalUpdateStatusLocked(idle ? STATUS_CONFIGURED : STATUS_ACTIVE);
 
         // Skip notifying listener if we're doing some user-transparent
@@ -3238,7 +3246,18 @@
             sp<ICameraDeviceSession> &session,
             std::shared_ptr<RequestMetadataQueue> queue) :
         mHidlSession(session),
-        mRequestMetadataQueue(queue) {}
+        mRequestMetadataQueue(queue) {
+    // Check with hardware service manager if we can downcast these interfaces
+    // Somewhat expensive, so cache the results at startup
+    auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
+    if (castResult_3_4.isOk()) {
+        mHidlSession_3_4 = castResult_3_4;
+    }
+    auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
+    if (castResult_3_3.isOk()) {
+        mHidlSession_3_3 = castResult_3_3;
+    }
+}
 
 Camera3Device::HalInterface::HalInterface() {}
 
@@ -3266,52 +3285,89 @@
     status_t res = OK;
 
     common::V1_0::Status status;
-    RequestTemplate id;
-    switch (templateId) {
-        case CAMERA3_TEMPLATE_PREVIEW:
-            id = RequestTemplate::PREVIEW;
-            break;
-        case CAMERA3_TEMPLATE_STILL_CAPTURE:
-            id = RequestTemplate::STILL_CAPTURE;
-            break;
-        case CAMERA3_TEMPLATE_VIDEO_RECORD:
-            id = RequestTemplate::VIDEO_RECORD;
-            break;
-        case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
-            id = RequestTemplate::VIDEO_SNAPSHOT;
-            break;
-        case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
-            id = RequestTemplate::ZERO_SHUTTER_LAG;
-            break;
-        case CAMERA3_TEMPLATE_MANUAL:
-            id = RequestTemplate::MANUAL;
-            break;
-        default:
-            // Unknown template ID
-            return BAD_VALUE;
-    }
-    auto err = mHidlSession->constructDefaultRequestSettings(id,
-            [&status, &requestTemplate]
+
+    auto requestCallback = [&status, &requestTemplate]
             (common::V1_0::Status s, const device::V3_2::CameraMetadata& request) {
-                status = s;
-                if (status == common::V1_0::Status::OK) {
-                    const camera_metadata *r =
-                            reinterpret_cast<const camera_metadata_t*>(request.data());
-                    size_t expectedSize = request.size();
-                    int ret = validate_camera_metadata_structure(r, &expectedSize);
-                    if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
-                        *requestTemplate = clone_camera_metadata(r);
-                        if (*requestTemplate == nullptr) {
-                            ALOGE("%s: Unable to clone camera metadata received from HAL",
-                                    __FUNCTION__);
-                            status = common::V1_0::Status::INTERNAL_ERROR;
-                        }
-                    } else {
-                        ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+            status = s;
+            if (status == common::V1_0::Status::OK) {
+                const camera_metadata *r =
+                        reinterpret_cast<const camera_metadata_t*>(request.data());
+                size_t expectedSize = request.size();
+                int ret = validate_camera_metadata_structure(r, &expectedSize);
+                if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
+                    *requestTemplate = clone_camera_metadata(r);
+                    if (*requestTemplate == nullptr) {
+                        ALOGE("%s: Unable to clone camera metadata received from HAL",
+                                __FUNCTION__);
                         status = common::V1_0::Status::INTERNAL_ERROR;
                     }
+                } else {
+                    ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+                    status = common::V1_0::Status::INTERNAL_ERROR;
                 }
-            });
+            }
+        };
+    hardware::Return<void> err;
+    if (mHidlSession_3_4 != nullptr) {
+        device::V3_4::RequestTemplate id;
+        switch (templateId) {
+            case CAMERA3_TEMPLATE_PREVIEW:
+                id = device::V3_4::RequestTemplate::PREVIEW;
+                break;
+            case CAMERA3_TEMPLATE_STILL_CAPTURE:
+                id = device::V3_4::RequestTemplate::STILL_CAPTURE;
+                break;
+            case CAMERA3_TEMPLATE_VIDEO_RECORD:
+                id = device::V3_4::RequestTemplate::VIDEO_RECORD;
+                break;
+            case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+                id = device::V3_4::RequestTemplate::VIDEO_SNAPSHOT;
+                break;
+            case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+                id = device::V3_4::RequestTemplate::ZERO_SHUTTER_LAG;
+                break;
+            case CAMERA3_TEMPLATE_MANUAL:
+                id = device::V3_4::RequestTemplate::MANUAL;
+                break;
+            case CAMERA3_TEMPLATE_MOTION_TRACKING_PREVIEW:
+                id = device::V3_4::RequestTemplate::MOTION_TRACKING_PREVIEW;
+                break;
+            case CAMERA3_TEMPLATE_MOTION_TRACKING_BEST:
+                id = device::V3_4::RequestTemplate::MOTION_TRACKING_BEST;
+                break;
+            default:
+                // Unknown template ID
+                return BAD_VALUE;
+        }
+        err = mHidlSession_3_4->constructDefaultRequestSettings_3_4(id, requestCallback);
+    } else {
+        RequestTemplate id;
+        switch (templateId) {
+            case CAMERA3_TEMPLATE_PREVIEW:
+                id = RequestTemplate::PREVIEW;
+                break;
+            case CAMERA3_TEMPLATE_STILL_CAPTURE:
+                id = RequestTemplate::STILL_CAPTURE;
+                break;
+            case CAMERA3_TEMPLATE_VIDEO_RECORD:
+                id = RequestTemplate::VIDEO_RECORD;
+                break;
+            case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+                id = RequestTemplate::VIDEO_SNAPSHOT;
+                break;
+            case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+                id = RequestTemplate::ZERO_SHUTTER_LAG;
+                break;
+            case CAMERA3_TEMPLATE_MANUAL:
+                id = RequestTemplate::MANUAL;
+                break;
+            default:
+                // Unknown template ID, or this HAL is too old to support it
+                return BAD_VALUE;
+        }
+        err = mHidlSession->constructDefaultRequestSettings(id, requestCallback);
+    }
+
     if (!err.isOk()) {
         ALOGE("%s: Transaction error: %s", __FUNCTION__, err.description().c_str());
         res = DEAD_OBJECT;
@@ -3403,24 +3459,11 @@
     common::V1_0::Status status;
 
     // See if we have v3.4 or v3.3 HAL
-    sp<device::V3_4::ICameraDeviceSession> hidlSession_3_4;
-    sp<device::V3_3::ICameraDeviceSession> hidlSession_3_3;
-    auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
-    if (castResult_3_4.isOk()) {
-        hidlSession_3_4 = castResult_3_4;
-    } else {
-        auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
-        if (castResult_3_3.isOk()) {
-            hidlSession_3_3 = castResult_3_3;
-        }
-    }
-
-    if (hidlSession_3_4 != nullptr) {
-        // We do; use v3.4 for the call, and construct a v3.4
-        // HalStreamConfiguration
+    if (mHidlSession_3_4 != nullptr) {
+        // We do; use v3.4 for the call
         ALOGV("%s: v3.4 device found", __FUNCTION__);
         device::V3_4::HalStreamConfiguration finalConfiguration3_4;
-        auto err = hidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
+        auto err = mHidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
             [&status, &finalConfiguration3_4]
             (common::V1_0::Status s, const device::V3_4::HalStreamConfiguration& halConfiguration) {
                 finalConfiguration3_4 = halConfiguration;
@@ -3434,10 +3477,10 @@
         for (size_t i = 0; i < finalConfiguration3_4.streams.size(); i++) {
             finalConfiguration.streams[i] = finalConfiguration3_4.streams[i].v3_3;
         }
-    } else if (hidlSession_3_3 != nullptr) {
+    } else if (mHidlSession_3_3 != nullptr) {
         // We do; use v3.3 for the call
         ALOGV("%s: v3.3 device found", __FUNCTION__);
-        auto err = hidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
+        auto err = mHidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
             [&status, &finalConfiguration]
             (common::V1_0::Status s, const device::V3_3::HalStreamConfiguration& halConfiguration) {
                 finalConfiguration = halConfiguration;
@@ -4433,9 +4476,13 @@
         if (res == OK) {
             sp<StatusTracker> statusTracker = mStatusTracker.promote();
             if (statusTracker != 0) {
+                sp<Camera3Device> parent = mParent.promote();
+                if (parent != nullptr) {
+                    parent->pauseStateNotify(true);
+                }
+
                 statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
 
-                sp<Camera3Device> parent = mParent.promote();
                 if (parent != nullptr) {
                     mReconfigured |= parent->reconfigureCamera(mLatestSessionParams);
                 }
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 63e6219..bf2a577 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -31,6 +31,7 @@
 #include <android/hardware/camera/device/3.2/ICameraDevice.h>
 #include <android/hardware/camera/device/3.2/ICameraDeviceSession.h>
 #include <android/hardware/camera/device/3.3/ICameraDeviceSession.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
 #include <android/hardware/camera/device/3.2/ICameraDeviceCallback.h>
 #include <fmq/MessageQueue.h>
 #include <hardware/camera3.h>
@@ -297,7 +298,13 @@
         void getInflightBufferKeys(std::vector<std::pair<int32_t, int32_t>>* out);
 
       private:
+        // Always valid
         sp<hardware::camera::device::V3_2::ICameraDeviceSession> mHidlSession;
+        // Valid if ICameraDeviceSession is @3.3 or newer
+        sp<hardware::camera::device::V3_3::ICameraDeviceSession> mHidlSession_3_3;
+        // Valid if ICameraDeviceSession is @3.4 or newer
+        sp<hardware::camera::device::V3_4::ICameraDeviceSession> mHidlSession_3_4;
+
         std::shared_ptr<RequestMetadataQueue> mRequestMetadataQueue;
 
         std::mutex mInflightLock;
@@ -554,8 +561,15 @@
                                             const SurfaceMap &surfaceMap);
 
     /**
+     * Pause state updates to the client application.  Needed to mask out idle/active
+     * transitions during internal reconfigure
+     */
+    void pauseStateNotify(bool enable);
+
+    /**
      * Internally re-configure camera device using new session parameters.
-     * This will get triggered by the request thread.
+     * This will get triggered by the request thread. Be sure to call
+     * pauseStateNotify(true) before going idle in the requesting location.
      */
     bool reconfigureCamera(const CameraMetadata& sessionParams);
 
diff --git a/services/mediacodec/Android.mk b/services/mediacodec/Android.mk
index 8e5b260..97eaf77 100644
--- a/services/mediacodec/Android.mk
+++ b/services/mediacodec/Android.mk
@@ -1,5 +1,28 @@
 LOCAL_PATH := $(call my-dir)
 
+_software_codecs := \
+    libstagefright_soft_aacdec \
+    libstagefright_soft_aacenc \
+    libstagefright_soft_amrdec \
+    libstagefright_soft_amrnbenc \
+    libstagefright_soft_amrwbenc \
+    libstagefright_soft_avcdec \
+    libstagefright_soft_avcenc \
+    libstagefright_soft_flacdec \
+    libstagefright_soft_flacenc \
+    libstagefright_soft_g711dec \
+    libstagefright_soft_gsmdec \
+    libstagefright_soft_hevcdec \
+    libstagefright_soft_mp3dec \
+    libstagefright_soft_mpeg2dec \
+    libstagefright_soft_mpeg4dec \
+    libstagefright_soft_mpeg4enc \
+    libstagefright_soft_opusdec \
+    libstagefright_soft_rawdec \
+    libstagefright_soft_vorbisdec \
+    libstagefright_soft_vpxdec \
+    libstagefright_soft_vpxenc \
+
 # service executable
 include $(CLEAR_VARS)
 # seccomp is not required for coverage build.
@@ -27,6 +50,15 @@
 LOCAL_MODULE_RELATIVE_PATH := hw
 LOCAL_VENDOR_MODULE := true
 LOCAL_32_BIT_ONLY := true
+# Since this is 32-bit-only module, only 32-bit version of the codecs are installed.
+# TODO(b/72343507): eliminate the need for manually adding .vendor suffix. This should be done
+# by the build system.
+LOCAL_REQUIRED_MODULES := \
+$(foreach codec,$(_software_codecs),\
+  $(eval _vendor_suffix := $(if $(BOARD_VNDK_VERSION),.vendor))\
+  $(codec)$(_vendor_suffix)\
+)
+_software_codecs :=
 LOCAL_INIT_RC := android.hardware.media.omx@1.0-service.rc
 
 include $(BUILD_EXECUTABLE)
diff --git a/services/mediaextractor/main_extractorservice.cpp b/services/mediaextractor/main_extractorservice.cpp
index 0dc5e29..8d3359a 100644
--- a/services/mediaextractor/main_extractorservice.cpp
+++ b/services/mediaextractor/main_extractorservice.cpp
@@ -25,6 +25,7 @@
 #include <string>
 
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <utils/misc.h>
 
 // from LOCAL_C_INCLUDES
@@ -65,14 +66,10 @@
     sp<IServiceManager> sm = defaultServiceManager();
     MediaExtractorService::instantiate();
 
-    // TODO: Uncomment below once sepolicy change is landed.
-    /*
-    char value[PROPERTY_VALUE_MAX];
-    property_get("ro.build.type", value, "unknown");
-    if (strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0) {
+    std::string value = base::GetProperty("ro.build.type", "unknown");
+    if (value == "userdebug" || value == "eng") {
         media::MediaExtractorUpdateService::instantiate();
     }
-    */
 
     ProcessState::self()->startThreadPool();
     IPCThreadState::self()->joinThreadPool();
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index 864fc35..e8c9e41 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -271,11 +271,11 @@
 }
 
 aaudio_result_t AAudioServiceStreamBase::flush() {
-    if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
-        ALOGE("flush() stream not paused, state = %s",
-              AAudio_convertStreamStateToText(mState));
-        return AAUDIO_ERROR_INVALID_STATE;
+    aaudio_result_t result = AAudio_isFlushAllowed(getState());
+    if (result != AAUDIO_OK) {
+        return result;
     }
+
     // Data will get flushed when the client receives the FLUSHED event.
     sendServiceEvent(AAUDIO_SERVICE_EVENT_FLUSHED);
     setState(AAUDIO_STREAM_STATE_FLUSHED);