Merge "Move CCodec and c2.google.* components out of framework" into pi-dev
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 940ac5f..346761c 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -2257,7 +2257,7 @@
      * from the main sensor along the +X axis (to the right from the user's perspective) will
      * report <code>(0.03, 0, 0)</code>.</p>
      * <p>To transform a pixel coordinates between two cameras facing the same direction, first
-     * the source camera ACAMERA_LENS_RADIAL_DISTORTION must be corrected for.  Then the source
+     * the source camera ACAMERA_LENS_DISTORTION must be corrected for.  Then the source
      * camera ACAMERA_LENS_INTRINSIC_CALIBRATION needs to be applied, followed by the
      * ACAMERA_LENS_POSE_ROTATION of the source camera, the translation of the source camera
      * relative to the destination camera, the ACAMERA_LENS_POSE_ROTATION of the destination
@@ -2269,10 +2269,10 @@
      * <p>When ACAMERA_LENS_POSE_REFERENCE is GYROSCOPE, then this position is relative to
      * the center of the primary gyroscope on the device.</p>
      *
+     * @see ACAMERA_LENS_DISTORTION
      * @see ACAMERA_LENS_INTRINSIC_CALIBRATION
      * @see ACAMERA_LENS_POSE_REFERENCE
      * @see ACAMERA_LENS_POSE_ROTATION
-     * @see ACAMERA_LENS_RADIAL_DISTORTION
      */
     ACAMERA_LENS_POSE_TRANSLATION =                             // float[3]
             ACAMERA_LENS_START + 7,
@@ -2382,7 +2382,7 @@
      * where <code>(0,0)</code> is the top-left of the
      * preCorrectionActiveArraySize rectangle. Once the pose and
      * intrinsic calibration transforms have been applied to a
-     * world point, then the ACAMERA_LENS_RADIAL_DISTORTION
+     * world point, then the ACAMERA_LENS_DISTORTION
      * transform needs to be applied, and the result adjusted to
      * be in the ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE coordinate
      * system (where <code>(0, 0)</code> is the top-left of the
@@ -2390,56 +2390,15 @@
      * coordinate of the world point for processed (non-RAW)
      * output buffers.</p>
      *
+     * @see ACAMERA_LENS_DISTORTION
      * @see ACAMERA_LENS_POSE_ROTATION
      * @see ACAMERA_LENS_POSE_TRANSLATION
-     * @see ACAMERA_LENS_RADIAL_DISTORTION
      * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
      * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     ACAMERA_LENS_INTRINSIC_CALIBRATION =                        // float[5]
             ACAMERA_LENS_START + 10,
-    /**
-     * <p>The correction coefficients to correct for this camera device's
-     * radial and tangential lens distortion.</p>
-     *
-     * <p>Type: float[6]</p>
-     *
-     * <p>This tag may appear in:
-     * <ul>
-     *   <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
-     *   <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
-     * </ul></p>
-     *
-     * <p>Four radial distortion coefficients <code>[kappa_0, kappa_1, kappa_2,
-     * kappa_3]</code> and two tangential distortion coefficients
-     * <code>[kappa_4, kappa_5]</code> that can be used to correct the
-     * lens's geometric distortion with the mapping equations:</p>
-     * <pre><code> x_c = x_i * ( kappa_0 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) +
-     *        kappa_4 * (2 * x_i * y_i) + kappa_5 * ( r^2 + 2 * x_i^2 )
-     *  y_c = y_i * ( kappa_0 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) +
-     *        kappa_5 * (2 * x_i * y_i) + kappa_4 * ( r^2 + 2 * y_i^2 )
-     * </code></pre>
-     * <p>Here, <code>[x_c, y_c]</code> are the coordinates to sample in the
-     * input image that correspond to the pixel values in the
-     * corrected image at the coordinate <code>[x_i, y_i]</code>:</p>
-     * <pre><code> correctedImage(x_i, y_i) = sample_at(x_c, y_c, inputImage)
-     * </code></pre>
-     * <p>The pixel coordinates are defined in a normalized
-     * coordinate system related to the
-     * ACAMERA_LENS_INTRINSIC_CALIBRATION calibration fields.
-     * Both <code>[x_i, y_i]</code> and <code>[x_c, y_c]</code> have <code>(0,0)</code> at the
-     * lens optical center <code>[c_x, c_y]</code>. The maximum magnitudes
-     * of both x and y coordinates are normalized to be 1 at the
-     * edge further from the optical center, so the range
-     * for both dimensions is <code>-1 &lt;= x &lt;= 1</code>.</p>
-     * <p>Finally, <code>r</code> represents the radial distance from the
-     * optical center, <code>r^2 = x_i^2 + y_i^2</code>, and its magnitude
-     * is therefore no larger than <code>|r| &lt;= sqrt(2)</code>.</p>
-     * <p>The distortion model used is the Brown-Conrady model.</p>
-     *
-     * @see ACAMERA_LENS_INTRINSIC_CALIBRATION
-     */
-    ACAMERA_LENS_RADIAL_DISTORTION =                            // float[6]
+    ACAMERA_LENS_RADIAL_DISTORTION =                            // Deprecated! DO NOT USE
             ACAMERA_LENS_START + 11,
     /**
      * <p>The origin for ACAMERA_LENS_POSE_TRANSLATION.</p>
@@ -2458,6 +2417,51 @@
      */
     ACAMERA_LENS_POSE_REFERENCE =                               // byte (acamera_metadata_enum_android_lens_pose_reference_t)
             ACAMERA_LENS_START + 12,
+    /**
+     * <p>The correction coefficients to correct for this camera device's
+     * radial and tangential lens distortion.</p>
+     * <p>Replaces the deprecated ACAMERA_LENS_RADIAL_DISTORTION field, which was
+     * inconsistently defined.</p>
+     *
+     * @see ACAMERA_LENS_RADIAL_DISTORTION
+     *
+     * <p>Type: float[5]</p>
+     *
+     * <p>This tag may appear in:
+     * <ul>
+     *   <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+     *   <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+     * </ul></p>
+     *
+     * <p>Three radial distortion coefficients <code>[kappa_1, kappa_2,
+     * kappa_3]</code> and two tangential distortion coefficients
+     * <code>[kappa_4, kappa_5]</code> that can be used to correct the
+     * lens's geometric distortion with the mapping equations:</p>
+     * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) +
+     *        kappa_4 * (2 * x_i * y_i) + kappa_5 * ( r^2 + 2 * x_i^2 )
+     *  y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) +
+     *        kappa_5 * (2 * x_i * y_i) + kappa_4 * ( r^2 + 2 * y_i^2 )
+     * </code></pre>
+     * <p>Here, <code>[x_c, y_c]</code> are the coordinates to sample in the
+     * input image that correspond to the pixel values in the
+     * corrected image at the coordinate <code>[x_i, y_i]</code>:</p>
+     * <pre><code> correctedImage(x_i, y_i) = sample_at(x_c, y_c, inputImage)
+     * </code></pre>
+     * <p>The pixel coordinates are defined in a coordinate system
+     * related to the ACAMERA_LENS_INTRINSIC_CALIBRATION
+     * calibration fields; see that entry for details of the mapping stages.
+     * Both <code>[x_i, y_i]</code> and <code>[x_c, y_c]</code>
+     * have <code>(0,0)</code> at the lens optical center <code>[c_x, c_y]</code>, and
+     * the range of the coordinates depends on the focal length
+     * terms of the intrinsic calibration.</p>
+     * <p>Finally, <code>r</code> represents the radial distance from the
+     * optical center, <code>r^2 = x_i^2 + y_i^2</code>.</p>
+     * <p>The distortion model used is the Brown-Conrady model.</p>
+     *
+     * @see ACAMERA_LENS_INTRINSIC_CALIBRATION
+     */
+    ACAMERA_LENS_DISTORTION =                                   // float[5]
+            ACAMERA_LENS_START + 13,
     ACAMERA_LENS_END,
 
     /**
@@ -4212,7 +4216,7 @@
      * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.</p>
      * <p>The currently supported fields that correct for geometric distortion are:</p>
      * <ol>
-     * <li>ACAMERA_LENS_RADIAL_DISTORTION.</li>
+     * <li>ACAMERA_LENS_DISTORTION.</li>
      * </ol>
      * <p>If all of the geometric distortion fields are no-ops, this rectangle will be the same
      * as the post-distortion-corrected rectangle given in
@@ -4224,7 +4228,7 @@
      * full array may include black calibration pixels or other inactive regions.</p>
      * <p>The data representation is <code>int[4]</code>, which maps to <code>(left, top, width, height)</code>.</p>
      *
-     * @see ACAMERA_LENS_RADIAL_DISTORTION
+     * @see ACAMERA_LENS_DISTORTION
      * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
      * @see ACAMERA_SENSOR_INFO_PIXEL_ARRAY_SIZE
      * @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
@@ -6941,7 +6945,7 @@
      * <li>ACAMERA_LENS_POSE_TRANSLATION</li>
      * <li>ACAMERA_LENS_POSE_ROTATION</li>
      * <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li>
-     * <li>ACAMERA_LENS_RADIAL_DISTORTION</li>
+     * <li>ACAMERA_LENS_DISTORTION</li>
      * </ul>
      * </li>
      * <li>The ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE entry is listed by this device.</li>
@@ -6959,12 +6963,12 @@
      * rate, including depth stall time.</p>
      *
      * @see ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE
+     * @see ACAMERA_LENS_DISTORTION
      * @see ACAMERA_LENS_FACING
      * @see ACAMERA_LENS_INTRINSIC_CALIBRATION
      * @see ACAMERA_LENS_POSE_REFERENCE
      * @see ACAMERA_LENS_POSE_ROTATION
      * @see ACAMERA_LENS_POSE_TRANSLATION
-     * @see ACAMERA_LENS_RADIAL_DISTORTION
      */
     ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT              = 8,
 
@@ -6994,7 +6998,7 @@
      * <li>ACAMERA_LENS_POSE_ROTATION</li>
      * <li>ACAMERA_LENS_POSE_TRANSLATION</li>
      * <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li>
-     * <li>ACAMERA_LENS_RADIAL_DISTORTION</li>
+     * <li>ACAMERA_LENS_DISTORTION</li>
      * </ul>
      * </li>
      * <li>The SENSOR_INFO_TIMESTAMP_SOURCE of the logical device and physical devices must be
@@ -7020,11 +7024,11 @@
      * not slow down the frame rate of the capture, as long as the minimum frame duration
      * of the physical and logical streams are the same.</p>
      *
+     * @see ACAMERA_LENS_DISTORTION
      * @see ACAMERA_LENS_INTRINSIC_CALIBRATION
      * @see ACAMERA_LENS_POSE_REFERENCE
      * @see ACAMERA_LENS_POSE_ROTATION
      * @see ACAMERA_LENS_POSE_TRANSLATION
-     * @see ACAMERA_LENS_RADIAL_DISTORTION
      * @see ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
      */
     ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA      = 11,
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
index 6b0201a..300c688 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
+++ b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
@@ -19,6 +19,7 @@
 #include <utils/Log.h>
 
 #include <stdio.h>
+#include <inttypes.h>
 
 #include "DrmPlugin.h"
 #include "ClearKeyDrmProperties.h"
@@ -26,6 +27,7 @@
 #include "TypeConvert.h"
 
 namespace {
+const int kSecureStopIdStart = 100;
 const std::string kStreaming("Streaming");
 const std::string kOffline("Offline");
 const std::string kTrue("True");
@@ -36,7 +38,18 @@
     // Value: "True" or "False"
 const std::string kQueryKeyRenewAllowed("RenewAllowed");
     // Value: "True" or "False"
-};
+
+const int kSecureStopIdSize = 10;
+
+std::vector<uint8_t> uint32ToVector(uint32_t value) {
+    // 10 bytes to display max value 4294967295 + one byte null terminator
+    char buffer[kSecureStopIdSize];
+    memset(buffer, 0, kSecureStopIdSize);
+    snprintf(buffer, kSecureStopIdSize, "%" PRIu32, value);
+    return std::vector<uint8_t>(buffer, buffer + sizeof(buffer));
+}
+
+}; // unnamed namespace
 
 namespace android {
 namespace hardware {
@@ -48,9 +61,11 @@
         : mSessionLibrary(sessionLibrary),
           mOpenSessionOkCount(0),
           mCloseSessionOkCount(0),
-          mCloseSessionNotOpenedCount(0) {
+          mCloseSessionNotOpenedCount(0),
+          mNextSecureStopId(kSecureStopIdStart) {
     mPlayPolicy.clear();
     initProperties();
+    mSecureStops.clear();
 }
 
 void DrmPlugin::initProperties() {
@@ -73,6 +88,18 @@
     mByteArrayProperties[kMetricsKey] = valueVector;
 }
 
+// The secure stop in ClearKey implementation is not installed securely.
+// This function merely creates a test environment for testing secure stops APIs.
+// The content in this secure stop is implementation dependent, the clearkey
+// secureStop does not serve as a reference implementation.
+void DrmPlugin::installSecureStop(const hidl_vec<uint8_t>& sessionId) {
+    ClearkeySecureStop clearkeySecureStop;
+    clearkeySecureStop.id = uint32ToVector(++mNextSecureStopId);
+    clearkeySecureStop.data.assign(sessionId.begin(), sessionId.end());
+
+    mSecureStops.insert(std::pair<std::vector<uint8_t>, ClearkeySecureStop>(
+            clearkeySecureStop.id, clearkeySecureStop));
+}
 
 Return<void> DrmPlugin::openSession(openSession_cb _hidl_cb) {
     sp<Session> session = mSessionLibrary->createSession();
@@ -209,6 +236,7 @@
         _hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>());
         return Void();
     }
+
     sp<Session> session = mSessionLibrary->findSession(toVector(scope));
     if (!session.get()) {
         _hidl_cb(Status::ERROR_DRM_SESSION_NOT_OPENED, hidl_vec<uint8_t>());
@@ -224,6 +252,8 @@
         keySetId.clear();
     }
 
+    installSecureStop(scope);
+
     // Returns status and empty keySetId
     _hidl_cb(status, toHidlVec(keySetId));
     return Void();
@@ -435,7 +465,106 @@
     return Void();
 }
 
+Return<void> DrmPlugin::getSecureStops(getSecureStops_cb _hidl_cb) {
+    std::vector<SecureStop> stops;
+    for (auto itr = mSecureStops.begin(); itr != mSecureStops.end(); ++itr) {
+        ClearkeySecureStop clearkeyStop = itr->second;
+        std::vector<uint8_t> stopVec;
+        stopVec.insert(stopVec.end(), clearkeyStop.id.begin(), clearkeyStop.id.end());
+        stopVec.insert(stopVec.end(), clearkeyStop.data.begin(), clearkeyStop.data.end());
 
+        SecureStop stop;
+        stop.opaqueData = toHidlVec(stopVec);
+        stops.push_back(stop);
+    }
+    _hidl_cb(Status::OK, stops);
+    return Void();
+}
+
+Return<void> DrmPlugin::getSecureStop(const hidl_vec<uint8_t>& secureStopId,
+        getSecureStop_cb _hidl_cb) {
+    SecureStop stop;
+    auto itr = mSecureStops.find(toVector(secureStopId));
+    if (itr != mSecureStops.end()) {
+        ClearkeySecureStop clearkeyStop = itr->second;
+        std::vector<uint8_t> stopVec;
+        stopVec.insert(stopVec.end(), clearkeyStop.id.begin(), clearkeyStop.id.end());
+        stopVec.insert(stopVec.end(), clearkeyStop.data.begin(), clearkeyStop.data.end());
+
+        stop.opaqueData = toHidlVec(stopVec);
+        _hidl_cb(Status::OK, stop);
+    } else {
+        _hidl_cb(Status::BAD_VALUE, stop);
+    }
+
+    return Void();
+}
+
+Return<Status> DrmPlugin::releaseSecureStop(const hidl_vec<uint8_t>& secureStopId) {
+    return removeSecureStop(secureStopId);
+}
+
+Return<Status> DrmPlugin::releaseAllSecureStops() {
+    return removeAllSecureStops();
+}
+
+Return<void> DrmPlugin::getSecureStopIds(getSecureStopIds_cb _hidl_cb) {
+    std::vector<SecureStopId> ids;
+    for (auto itr = mSecureStops.begin(); itr != mSecureStops.end(); ++itr) {
+        ids.push_back(itr->first);
+    }
+
+    _hidl_cb(Status::OK, toHidlVec(ids));
+    return Void();
+}
+
+Return<Status> DrmPlugin::releaseSecureStops(const SecureStopRelease& ssRelease) {
+    if (ssRelease.opaqueData.size() == 0) {
+        return Status::BAD_VALUE;
+    }
+
+    Status status = Status::OK;
+    std::vector<uint8_t> input = toVector(ssRelease.opaqueData);
+
+    // The format of opaqueData is shared between the server
+    // and the drm service. The clearkey implementation consists of:
+    //    count - number of secure stops
+    //    list of fixed length secure stops
+    size_t countBufferSize = sizeof(uint32_t);
+    uint32_t count = 0;
+    sscanf(reinterpret_cast<char*>(input.data()), "%04" PRIu32, &count);
+
+    // Avoid divide by 0 below.
+    if (count == 0) {
+        return Status::BAD_VALUE;
+    }
+
+    size_t secureStopSize = (input.size() - countBufferSize) / count;
+    uint8_t buffer[secureStopSize];
+    size_t offset = countBufferSize; // skip the count
+    for (size_t i = 0; i < count; ++i, offset += secureStopSize) {
+        memcpy(buffer, input.data() + offset, secureStopSize);
+        std::vector<uint8_t> id(buffer, buffer + kSecureStopIdSize);
+
+        status = removeSecureStop(toHidlVec(id));
+        if (Status::OK != status) break;
+    }
+
+    return status;
+}
+
+Return<Status> DrmPlugin::removeSecureStop(const hidl_vec<uint8_t>& secureStopId) {
+    if (1 != mSecureStops.erase(toVector(secureStopId))) {
+        return Status::BAD_VALUE;
+    }
+    return Status::OK;
+}
+
+Return<Status> DrmPlugin::removeAllSecureStops() {
+    mSecureStops.clear();
+    mNextSecureStopId = kSecureStopIdStart;
+    return Status::OK;
+}
 
 }  // namespace clearkey
 }  // namespace V1_1
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
index 19baf0b..fb0695a 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
+++ b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
@@ -17,9 +17,11 @@
 #ifndef CLEARKEY_DRM_PLUGIN_H_
 #define CLEARKEY_DRM_PLUGIN_H_
 
-
 #include <android/hardware/drm/1.1/IDrmPlugin.h>
 
+#include <stdio.h>
+#include <map>
+
 #include "SessionLibrary.h"
 #include "Utils.h"
 
@@ -36,6 +38,7 @@
 using ::android::hardware::drm::V1_0::KeyValue;
 using ::android::hardware::drm::V1_0::SecureStop;
 using ::android::hardware::drm::V1_0::SecureStopId;
+using ::android::hardware::drm::V1_0::SessionId;
 using ::android::hardware::drm::V1_0::Status;
 using ::android::hardware::drm::V1_1::DrmMetricGroup;
 using ::android::hardware::drm::V1_1::IDrmPlugin;
@@ -124,34 +127,6 @@
         return Void();
     }
 
-    Return<void> getSecureStops(getSecureStops_cb _hidl_cb) {
-        _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<SecureStop>());
-        return Void();
-    }
-
-    Return<void> getSecureStop(
-        const hidl_vec<uint8_t>& secureStopId,
-        getSecureStop_cb _hidl_cb) {
-
-        if (secureStopId.size() == 0) {
-            _hidl_cb(Status::BAD_VALUE, SecureStop());
-            return Void();
-        }
-        _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, SecureStop());
-        return Void();
-    }
-
-    Return<Status> releaseSecureStop(const hidl_vec<uint8_t>& ssRelease) {
-        if (ssRelease.size() == 0) {
-            return Status::BAD_VALUE;
-        }
-        return Status::ERROR_DRM_CANNOT_HANDLE;
-    }
-
-    Return<Status> releaseAllSecureStops() {
-        return Status::ERROR_DRM_CANNOT_HANDLE;
-    }
-
     Return<void> getHdcpLevels(getHdcpLevels_cb _hidl_cb) {
         HdcpLevel connectedLevel = HdcpLevel::HDCP_NONE;
         HdcpLevel maxLevel = HdcpLevel::HDCP_NO_OUTPUT;
@@ -305,31 +280,26 @@
         return Void();
     }
 
-    Return<void> getSecureStopIds(getSecureStopIds_cb _hidl_cb) {
-        _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<SecureStopId>());
-        return Void();
-    }
+    Return<void> getSecureStops(getSecureStops_cb _hidl_cb);
 
-    Return<Status> releaseSecureStops(const SecureStopRelease& ssRelease) {
-        if (ssRelease.opaqueData.size() == 0) {
-            return Status::BAD_VALUE;
-        }
-        return Status::ERROR_DRM_CANNOT_HANDLE;
-    }
+    Return<void> getSecureStop(const hidl_vec<uint8_t>& secureStopId,
+            getSecureStop_cb _hidl_cb);
 
-    Return<Status> removeSecureStop(const hidl_vec<uint8_t>& secureStopId) {
-        if (secureStopId.size() == 0) {
-            return Status::BAD_VALUE;
-        }
-        return Status::ERROR_DRM_CANNOT_HANDLE;
-    }
+    Return<Status> releaseSecureStop(const hidl_vec<uint8_t>& ssRelease);
 
-    Return<Status> removeAllSecureStops() {
-        return Status::ERROR_DRM_CANNOT_HANDLE;
-    }
+    Return<Status> releaseAllSecureStops();
+
+    Return<void> getSecureStopIds(getSecureStopIds_cb _hidl_cb);
+
+    Return<Status> releaseSecureStops(const SecureStopRelease& ssRelease);
+
+    Return<Status> removeSecureStop(const hidl_vec<uint8_t>& secureStopId);
+
+    Return<Status> removeAllSecureStops();
 
 private:
     void initProperties();
+    void installSecureStop(const hidl_vec<uint8_t>& sessionId);
     void setPlayPolicy();
 
     Return<Status> setSecurityLevel(const hidl_vec<uint8_t>& sessionId,
@@ -344,6 +314,12 @@
             KeyRequestType *getKeyRequestType,
             std::string *defaultUrl);
 
+    struct ClearkeySecureStop {
+        std::vector<uint8_t> id;
+        std::vector<uint8_t> data;
+    };
+
+    std::map<std::vector<uint8_t>, ClearkeySecureStop> mSecureStops;
     std::vector<KeyValue> mPlayPolicy;
     std::map<std::string, std::string> mStringProperties;
     std::map<std::string, std::vector<uint8_t> > mByteArrayProperties;
@@ -353,6 +329,7 @@
     int64_t mOpenSessionOkCount;
     int64_t mCloseSessionOkCount;
     int64_t mCloseSessionNotOpenedCount;
+    uint32_t mNextSecureStopId;
 
     CLEARKEY_DISALLOW_COPY_AND_ASSIGN_AND_NEW(DrmPlugin);
 };
diff --git a/media/extractors/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp
index c082fc1..666d68a 100644
--- a/media/extractors/mp4/ItemTable.cpp
+++ b/media/extractors/mp4/ItemTable.cpp
@@ -1471,8 +1471,8 @@
 
         // point image to the first tile for grid size and HVCC
         image = &mItemIdToItemMap.editValueAt(tileItemIndex);
-        meta->setInt32(kKeyGridWidth, image->width);
-        meta->setInt32(kKeyGridHeight, image->height);
+        meta->setInt32(kKeyTileWidth, image->width);
+        meta->setInt32(kKeyTileHeight, image->height);
         meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5);
     }
 
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 40b31b9..4a2a0a8 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -597,16 +597,21 @@
 }
 
 int32_t AAudioConvert_framesToBytes(int32_t numFrames,
-                                            int32_t bytesPerFrame,
-                                            int32_t *sizeInBytes) {
-    // TODO implement more elegantly
-    const int32_t maxChannels = 256; // ridiculously large
-    const int32_t maxBytesPerFrame = maxChannels * sizeof(float);
-    // Prevent overflow by limiting multiplicands.
-    if (bytesPerFrame > maxBytesPerFrame || numFrames > (0x3FFFFFFF / maxBytesPerFrame)) {
+                                    int32_t bytesPerFrame,
+                                    int32_t *sizeInBytes) {
+    *sizeInBytes = 0;
+
+    if (numFrames < 0 || bytesPerFrame < 0) {
+        ALOGE("negative size, numFrames = %d, frameSize = %d", numFrames, bytesPerFrame);
+        return AAUDIO_ERROR_OUT_OF_RANGE;
+    }
+
+    // Prevent numeric overflow.
+    if (numFrames > (INT32_MAX / bytesPerFrame)) {
         ALOGE("size overflow, numFrames = %d, frameSize = %d", numFrames, bytesPerFrame);
         return AAUDIO_ERROR_OUT_OF_RANGE;
     }
+
     *sizeInBytes = numFrames * bytesPerFrame;
     return AAUDIO_OK;
 }
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index cea88fb..4b975e8 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -196,14 +196,16 @@
 
 /**
  * Calculate the number of bytes and prevent numeric overflow.
+ * The *sizeInBytes will be set to zero if there is an error.
+ *
  * @param numFrames frame count
  * @param bytesPerFrame size of a frame in bytes
- * @param sizeInBytes total size in bytes
+ * @param sizeInBytes pointer to a variable to receive total size in bytes
  * @return AAUDIO_OK or negative error, eg. AAUDIO_ERROR_OUT_OF_RANGE
  */
 int32_t AAudioConvert_framesToBytes(int32_t numFrames,
-                                            int32_t bytesPerFrame,
-                                            int32_t *sizeInBytes);
+                                    int32_t bytesPerFrame,
+                                    int32_t *sizeInBytes);
 
 audio_format_t AAudioConvert_aaudioToAndroidDataFormat(aaudio_format_t aaudio_format);
 
diff --git a/media/libmedia/NdkWrapper.cpp b/media/libmedia/NdkWrapper.cpp
index 2cdb44e..6f56d0c 100644
--- a/media/libmedia/NdkWrapper.cpp
+++ b/media/libmedia/NdkWrapper.cpp
@@ -56,10 +56,8 @@
     AMEDIAFORMAT_KEY_COLOR_TRANSFER,
     AMEDIAFORMAT_KEY_COMPLEXITY,
     AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL,
-    AMEDIAFORMAT_KEY_GRID_COLS,
-    AMEDIAFORMAT_KEY_GRID_HEIGHT,
+    AMEDIAFORMAT_KEY_GRID_COLUMNS,
     AMEDIAFORMAT_KEY_GRID_ROWS,
-    AMEDIAFORMAT_KEY_GRID_WIDTH,
     AMEDIAFORMAT_KEY_HEIGHT,
     AMEDIAFORMAT_KEY_INTRA_REFRESH_PERIOD,
     AMEDIAFORMAT_KEY_IS_ADTS,
@@ -84,6 +82,8 @@
     AMEDIAFORMAT_KEY_DISPLAY_HEIGHT,
     AMEDIAFORMAT_KEY_DISPLAY_WIDTH,
     AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID,
+    AMEDIAFORMAT_KEY_TILE_HEIGHT,
+    AMEDIAFORMAT_KEY_TILE_WIDTH,
     AMEDIAFORMAT_KEY_TRACK_INDEX,
 };
 
diff --git a/media/libmedia/TypeConverter.cpp b/media/libmedia/TypeConverter.cpp
index 9323d6d..03700bf 100644
--- a/media/libmedia/TypeConverter.cpp
+++ b/media/libmedia/TypeConverter.cpp
@@ -226,8 +226,12 @@
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1_BACK),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1_SIDE),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1POINT4),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_6POINT1),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_7POINT1),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_7POINT1POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_7POINT1POINT4),
     TERMINATOR
 };
 
diff --git a/media/libmediaextractor/include/media/stagefright/MetaDataBase.h b/media/libmediaextractor/include/media/stagefright/MetaDataBase.h
index 6010af4..18e63f2 100644
--- a/media/libmediaextractor/include/media/stagefright/MetaDataBase.h
+++ b/media/libmediaextractor/include/media/stagefright/MetaDataBase.h
@@ -211,11 +211,11 @@
     kKeyTemporalLayerId  = 'iLyr', // int32_t, temporal layer-id. 0-based (0 => base layer)
     kKeyTemporalLayerCount = 'cLyr', // int32_t, number of temporal layers encoded
 
-    kKeyGridWidth        = 'grdW', // int32_t, HEIF grid width
-    kKeyGridHeight       = 'grdH', // int32_t, HEIF grid height
+    kKeyTileWidth        = 'tilW', // int32_t, HEIF tile width
+    kKeyTileHeight       = 'tilH', // int32_t, HEIF tile height
     kKeyGridRows         = 'grdR', // int32_t, HEIF grid rows
     kKeyGridCols         = 'grdC', // int32_t, HEIF grid columns
-    kKeyIccProfile       = 'prof', // raw data, ICC prifile data
+    kKeyIccProfile       = 'prof', // raw data, ICC profile data
     kKeyIsPrimaryImage   = 'prim', // bool (int32_t), image track is the primary image
     kKeyFrameCount       = 'nfrm', // int32_t, total number of frame in video track
 };
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 3570aaf..a8c6d15 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -4384,9 +4384,9 @@
 
 status_t ACodec::configureImageGrid(
         const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
-    int32_t gridWidth, gridHeight, gridRows, gridCols;
-    if (!msg->findInt32("grid-width", &gridWidth) ||
-        !msg->findInt32("grid-height", &gridHeight) ||
+    int32_t tileWidth, tileHeight, gridRows, gridCols;
+    if (!msg->findInt32("tile-width", &tileWidth) ||
+        !msg->findInt32("tile-height", &tileHeight) ||
         !msg->findInt32("grid-rows", &gridRows) ||
         !msg->findInt32("grid-cols", &gridCols)) {
         return OK;
@@ -4396,8 +4396,8 @@
     InitOMXParams(&gridType);
     gridType.nPortIndex = kPortIndexOutput;
     gridType.bEnabled = OMX_TRUE;
-    gridType.nGridWidth = gridWidth;
-    gridType.nGridHeight = gridHeight;
+    gridType.nTileWidth = tileWidth;
+    gridType.nTileHeight = tileHeight;
     gridType.nGridRows = gridRows;
     gridType.nGridCols = gridCols;
 
@@ -4422,8 +4422,8 @@
             &gridType, sizeof(gridType));
 
     if (err == OK && gridType.bEnabled) {
-        outputFormat->setInt32("grid-width", gridType.nGridWidth);
-        outputFormat->setInt32("grid-height", gridType.nGridHeight);
+        outputFormat->setInt32("tile-width", gridType.nTileWidth);
+        outputFormat->setInt32("tile-height", gridType.nTileHeight);
         outputFormat->setInt32("grid-rows", gridType.nGridRows);
         outputFormat->setInt32("grid-cols", gridType.nGridCols);
     }
diff --git a/media/libstagefright/FrameDecoder.cpp b/media/libstagefright/FrameDecoder.cpp
index 9748a8b..3d0aad1 100644
--- a/media/libstagefright/FrameDecoder.cpp
+++ b/media/libstagefright/FrameDecoder.cpp
@@ -495,27 +495,27 @@
     mGridRows = mGridCols = 1;
     if (overrideMeta == NULL) {
         // check if we're dealing with a tiled heif
-        int32_t gridWidth, gridHeight, gridRows, gridCols;
-        if (trackMeta()->findInt32(kKeyGridWidth, &gridWidth) && gridWidth > 0
-         && trackMeta()->findInt32(kKeyGridHeight, &gridHeight) && gridHeight > 0
+        int32_t tileWidth, tileHeight, gridRows, gridCols;
+        if (trackMeta()->findInt32(kKeyTileWidth, &tileWidth) && tileWidth > 0
+         && trackMeta()->findInt32(kKeyTileHeight, &tileHeight) && tileHeight > 0
          && trackMeta()->findInt32(kKeyGridRows, &gridRows) && gridRows > 0
          && trackMeta()->findInt32(kKeyGridCols, &gridCols) && gridCols > 0) {
             int32_t width, height;
             CHECK(trackMeta()->findInt32(kKeyWidth, &width));
             CHECK(trackMeta()->findInt32(kKeyHeight, &height));
 
-            if (width <= gridWidth * gridCols && height <= gridHeight * gridRows) {
-                ALOGV("grid: %dx%d, size: %dx%d, picture size: %dx%d",
-                        gridCols, gridRows, gridWidth, gridHeight, width, height);
+            if (width <= tileWidth * gridCols && height <= tileHeight * gridRows) {
+                ALOGV("grid: %dx%d, tile size: %dx%d, picture size: %dx%d",
+                        gridCols, gridRows, tileWidth, tileHeight, width, height);
 
                 overrideMeta = new MetaData(*(trackMeta()));
-                overrideMeta->setInt32(kKeyWidth, gridWidth);
-                overrideMeta->setInt32(kKeyHeight, gridHeight);
+                overrideMeta->setInt32(kKeyWidth, tileWidth);
+                overrideMeta->setInt32(kKeyHeight, tileHeight);
                 mGridCols = gridCols;
                 mGridRows = gridRows;
             } else {
-                ALOGE("bad grid: %dx%d, size: %dx%d, picture size: %dx%d",
-                        gridCols, gridRows, gridWidth, gridHeight, width, height);
+                ALOGE("bad grid: %dx%d, tile size: %dx%d, picture size: %dx%d",
+                        gridCols, gridRows, tileWidth, tileHeight, width, height);
             }
         }
         if (overrideMeta == NULL) {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 768df26..a3261d7 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -358,7 +358,7 @@
     Vector<uint16_t> mDimgRefs;
     int32_t mIsPrimary;
     int32_t mWidth, mHeight;
-    int32_t mGridWidth, mGridHeight;
+    int32_t mTileWidth, mTileHeight;
     int32_t mGridRows, mGridCols;
     size_t mNumTiles, mTileIndex;
 
@@ -1770,8 +1770,8 @@
       mIsPrimary(0),
       mWidth(0),
       mHeight(0),
-      mGridWidth(0),
-      mGridHeight(0),
+      mTileWidth(0),
+      mTileHeight(0),
       mGridRows(0),
       mGridCols(0),
       mNumTiles(1),
@@ -1802,13 +1802,13 @@
         CHECK(mMeta->findInt32(kKeyWidth, &mWidth) && (mWidth > 0));
         CHECK(mMeta->findInt32(kKeyHeight, &mHeight) && (mHeight > 0));
 
-        int32_t gridWidth, gridHeight, gridRows, gridCols;
-        if (mMeta->findInt32(kKeyGridWidth, &gridWidth) && (gridWidth > 0) &&
-            mMeta->findInt32(kKeyGridHeight, &gridHeight) && (gridHeight > 0) &&
+        int32_t tileWidth, tileHeight, gridRows, gridCols;
+        if (mMeta->findInt32(kKeyTileWidth, &tileWidth) && (tileWidth > 0) &&
+            mMeta->findInt32(kKeyTileHeight, &tileHeight) && (tileHeight > 0) &&
             mMeta->findInt32(kKeyGridRows, &gridRows) && (gridRows > 0) &&
             mMeta->findInt32(kKeyGridCols, &gridCols) && (gridCols > 0)) {
-            mGridWidth = gridWidth;
-            mGridHeight = gridHeight;
+            mTileWidth = tileWidth;
+            mTileHeight = tileHeight;
             mGridRows = gridRows;
             mGridCols = gridCols;
             mNumTiles = gridRows * gridCols;
@@ -1978,8 +1978,8 @@
 
         mProperties.push_back(mOwner->addProperty_l({
             .type = FOURCC('i', 's', 'p', 'e'),
-            .width = hasGrid ? mGridWidth : mWidth,
-            .height = hasGrid ? mGridHeight : mHeight,
+            .width = hasGrid ? mTileWidth : mWidth,
+            .height = hasGrid ? mTileHeight : mHeight,
         }));
 
         if (!hasGrid && heifRotation > 0) {
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 4a93051..0c6e988 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -665,13 +665,13 @@
         }
 
         if (!strncasecmp("image/", mime, 6)) {
-            int32_t gridWidth, gridHeight, gridRows, gridCols;
-            if (meta->findInt32(kKeyGridWidth, &gridWidth)
-                    && meta->findInt32(kKeyGridHeight, &gridHeight)
+            int32_t tileWidth, tileHeight, gridRows, gridCols;
+            if (meta->findInt32(kKeyTileWidth, &tileWidth)
+                    && meta->findInt32(kKeyTileHeight, &tileHeight)
                     && meta->findInt32(kKeyGridRows, &gridRows)
                     && meta->findInt32(kKeyGridCols, &gridCols)) {
-                msg->setInt32("grid-width", gridWidth);
-                msg->setInt32("grid-height", gridHeight);
+                msg->setInt32("tile-width", tileWidth);
+                msg->setInt32("tile-height", tileHeight);
                 msg->setInt32("grid-rows", gridRows);
                 msg->setInt32("grid-cols", gridCols);
             }
@@ -1341,12 +1341,12 @@
             if (msg->findInt32("is-default", &isPrimary) && isPrimary) {
                 meta->setInt32(kKeyTrackIsDefault, 1);
             }
-            int32_t gridWidth, gridHeight, gridRows, gridCols;
-            if (msg->findInt32("grid-width", &gridWidth)) {
-                meta->setInt32(kKeyGridWidth, gridWidth);
+            int32_t tileWidth, tileHeight, gridRows, gridCols;
+            if (msg->findInt32("tile-width", &tileWidth)) {
+                meta->setInt32(kKeyTileWidth, tileWidth);
             }
-            if (msg->findInt32("grid-height", &gridHeight)) {
-                meta->setInt32(kKeyGridHeight, gridHeight);
+            if (msg->findInt32("tile-height", &tileHeight)) {
+                meta->setInt32(kKeyTileHeight, tileHeight);
             }
             if (msg->findInt32("grid-rows", &gridRows)) {
                 meta->setInt32(kKeyGridRows, gridRows);
diff --git a/media/libstagefright/bqhelper/Android.bp b/media/libstagefright/bqhelper/Android.bp
index 388ed6b..9febe12 100644
--- a/media/libstagefright/bqhelper/Android.bp
+++ b/media/libstagefright/bqhelper/Android.bp
@@ -42,8 +42,9 @@
     ],
 
     export_shared_lib_headers: [
-        "libstagefright_foundation",
+        "libgui",
         "libhidlmemory",
+        "libstagefright_foundation",
     ],
 
     cflags: [
diff --git a/media/libstagefright/codec2/include/C2Buffer.h b/media/libstagefright/codec2/include/C2Buffer.h
index 4bdd20f..0e35f11 100644
--- a/media/libstagefright/codec2/include/C2Buffer.h
+++ b/media/libstagefright/codec2/include/C2Buffer.h
@@ -655,7 +655,8 @@
      * (Re)creates a 1D allocation from a native |handle|. If successful, the allocation is stored
      * in |allocation|. Otherwise, |allocation| is set to 'nullptr'.
      *
-     * \param handle      the handle for the existing allocation
+     * \param handle      the handle for the existing allocation. On success, the allocation will
+     *                    take ownership of |handle|.
      * \param allocation  pointer to where the allocation shall be stored on success. nullptr
      *                    will be stored here on failure
      *
@@ -712,7 +713,8 @@
      * (Re)creates a 2D allocation from a native handle.  If successful, the allocation is stored
      * in |allocation|. Otherwise, |allocation| is set to 'nullptr'.
      *
-     * \param handle      the handle for the existing allocation
+     * \param handle      the handle for the existing allocation. On success, the allocation will
+     *                    take ownership of |handle|.
      * \param allocation  pointer to where the allocation shall be stored on success. nullptr
      *                    will be stored here on failure
      *
diff --git a/media/libstagefright/codec2/vndk/Android.bp b/media/libstagefright/codec2/vndk/Android.bp
index 95e7c39..8966933 100644
--- a/media/libstagefright/codec2/vndk/Android.bp
+++ b/media/libstagefright/codec2/vndk/Android.bp
@@ -5,10 +5,12 @@
         "internal",
     ],
 
-    vendor_available: false,
     vndk: {
         enabled: true,
     },
+
+    // TODO: Remove this when this module is moved back to frameworks/av.
+    vendor_available: true,
 }
 
 cc_library_shared {
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
index a90e094..96b52a1 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
@@ -255,6 +255,7 @@
       mHidlHandle(std::move(hidlHandle)),
       mHandle(handle),
       mBuffer(nullptr),
+      mLockedHandle(nullptr),
       mLocked(false),
       mAllocatorId(allocatorId) {
 }
@@ -269,6 +270,8 @@
         unmap(addr, C2Rect(), nullptr);
     }
     mMapper->freeBuffer(const_cast<native_handle_t *>(mBuffer));
+    native_handle_delete(const_cast<native_handle_t*>(
+            reinterpret_cast<const native_handle_t*>(mHandle)));
 }
 
 c2_status_t C2AllocationGralloc::map(
@@ -608,8 +611,8 @@
         return C2_BAD_VALUE;
     }
 
-    hidl_handle hidlHandle = C2HandleGralloc::UnwrapNativeHandle(grallocHandle);
-
+    hidl_handle hidlHandle;
+    hidlHandle.setTo(C2HandleGralloc::UnwrapNativeHandle(grallocHandle), true);
     allocation->reset(new C2AllocationGralloc(info, mMapper, hidlHandle, grallocHandle, mTraits->id));
     return C2_OK;
 }
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp b/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
index 664c09e..c5fb8d9 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
@@ -150,7 +150,6 @@
      * so that we can capture the error.
      *
      * \param ionFd     ion client (ownership transferred to created object)
-     * \param owned     whehter native_handle_t is owned by an allocation or not.
      * \param capacity  size of allocation
      * \param bufferFd  buffer handle (ownership transferred to created object). Must be
      *                  invalid if err is not 0.
@@ -158,9 +157,8 @@
      *                  invalid if err is not 0.
      * \param err       errno during buffer allocation or import
      */
-    Impl(int ionFd, bool owned, size_t capacity, int bufferFd, ion_user_handle_t buffer, C2Allocator::id_t id, int err)
+    Impl(int ionFd, size_t capacity, int bufferFd, ion_user_handle_t buffer, C2Allocator::id_t id, int err)
         : mIonFd(ionFd),
-          mHandleOwned(owned),
           mHandle(bufferFd, capacity),
           mBuffer(buffer),
           mId(id),
@@ -191,7 +189,7 @@
     static Impl *Import(int ionFd, size_t capacity, int bufferFd, C2Allocator::id_t id) {
         ion_user_handle_t buffer = -1;
         int ret = ion_import(ionFd, bufferFd, &buffer);
-        return new Impl(ionFd, false, capacity, bufferFd, buffer, id, ret);
+        return new Impl(ionFd, capacity, bufferFd, buffer, id, ret);
     }
 
     /**
@@ -218,7 +216,7 @@
                 buffer = -1;
             }
         }
-        return new Impl(ionFd, true, size, bufferFd, buffer, id, ret);
+        return new Impl(ionFd, size, bufferFd, buffer, id, ret);
     }
 
     c2_status_t map(size_t offset, size_t size, C2MemoryUsage usage, C2Fence *fence, void **addr) {
@@ -311,13 +309,11 @@
         }
         if (mInit == C2_OK) {
             (void)ion_free(mIonFd, mBuffer);
+            native_handle_close(&mHandle);
         }
         if (mIonFd >= 0) {
             close(mIonFd);
         }
-        if (mHandleOwned) {
-            native_handle_close(&mHandle);
-        }
     }
 
     c2_status_t status() const {
@@ -338,7 +334,6 @@
 
 private:
     int mIonFd;
-    bool mHandleOwned;
     C2HandleIon mHandle;
     ion_user_handle_t mBuffer;
     C2Allocator::id_t mId;
@@ -481,6 +476,8 @@
     c2_status_t ret = alloc->status();
     if (ret == C2_OK) {
         *allocation = alloc;
+        native_handle_delete(const_cast<native_handle_t*>(
+                reinterpret_cast<const native_handle_t*>(handle)));
     }
     return ret;
 }
diff --git a/media/libstagefright/codec2/vndk/bufferpool/BufferPoolClient.cpp b/media/libstagefright/codec2/vndk/bufferpool/BufferPoolClient.cpp
index 7cb4ba9..dda8e39 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/BufferPoolClient.cpp
+++ b/media/libstagefright/codec2/vndk/bufferpool/BufferPoolClient.cpp
@@ -135,7 +135,8 @@
     bool mInvalidated; // TODO: implement
     int64_t mExpireUs;
     bool mHasCache;
-    _C2BlockPoolData mBuffer;
+    BufferId mId;
+    native_handle_t *mHandle;
     std::weak_ptr<_C2BlockPoolData> mCache;
 
     void updateExpire() {
@@ -144,11 +145,18 @@
 
 public:
     ClientBuffer(BufferId id, native_handle_t *handle)
-            : mInvalidated(false), mHasCache(false), mBuffer(id, handle) {
+            : mInvalidated(false), mHasCache(false), mId(id), mHandle(handle) {
         (void)mInvalidated;
         mExpireUs = getTimestampNow() + kCacheTtlUs;
     }
 
+    ~ClientBuffer() {
+        if (mHandle) {
+            native_handle_close(mHandle);
+            native_handle_delete(mHandle);
+        }
+    }
+
     bool expire() const {
         int64_t now = getTimestampNow();
         return now >= mExpireUs;
@@ -175,7 +183,7 @@
             // Allocates a raw ptr in order to avoid sending #postBufferRelease
             // from deleter, in case of native_handle_clone failure.
             _C2BlockPoolData *ptr = new _C2BlockPoolData(
-                    mBuffer.mId, native_handle_clone(mBuffer.mHandle));
+                    mId, native_handle_clone(mHandle));
             if (ptr && ptr->mHandle != NULL) {
                 std::shared_ptr<_C2BlockPoolData>
                         cache(ptr, BlockPoolDataDtor(impl));
diff --git a/media/libstagefright/codec2/vndk/bufferpool/include/BufferPoolTypes.h b/media/libstagefright/codec2/vndk/bufferpool/include/BufferPoolTypes.h
index 0cf023c..7f4223c 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/include/BufferPoolTypes.h
+++ b/media/libstagefright/codec2/vndk/bufferpool/include/BufferPoolTypes.h
@@ -25,6 +25,7 @@
 
 struct __attribute__((visibility("hidden"))) _C2BlockPoolData {
     uint32_t mId; //BufferId
+    // Handle should be copied to somewhere else, and should be managed there.
     native_handle_t *mHandle;
 
     _C2BlockPoolData() : mId(0), mHandle(NULL) {}
@@ -33,10 +34,6 @@
             : mId(id), mHandle(handle) {}
 
     ~_C2BlockPoolData() {
-        if (mHandle != NULL) {
-            native_handle_close(mHandle);
-            native_handle_delete(mHandle);
-        }
     }
 };
 
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index 9bf450c..f32b83e 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -296,10 +296,8 @@
 EXPORT const char* AMEDIAFORMAT_KEY_DURATION = "durationUs";
 EXPORT const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
 EXPORT const char* AMEDIAFORMAT_KEY_FRAME_RATE = "frame-rate";
-EXPORT const char* AMEDIAFORMAT_KEY_GRID_COLS = "grid-cols";
-EXPORT const char* AMEDIAFORMAT_KEY_GRID_HEIGHT = "grid-height";
+EXPORT const char* AMEDIAFORMAT_KEY_GRID_COLUMNS = "grid-cols";
 EXPORT const char* AMEDIAFORMAT_KEY_GRID_ROWS = "grid-rows";
-EXPORT const char* AMEDIAFORMAT_KEY_GRID_WIDTH = "grid-width";
 EXPORT const char* AMEDIAFORMAT_KEY_HDR_STATIC_INFO = "hdr-static-info";
 EXPORT const char* AMEDIAFORMAT_KEY_HEIGHT = "height";
 EXPORT const char* AMEDIAFORMAT_KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
@@ -329,6 +327,8 @@
 EXPORT const char* AMEDIAFORMAT_KEY_STRIDE = "stride";
 EXPORT const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID = "temporal-layer-id";
 EXPORT const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYERING = "ts-schema";
+EXPORT const char* AMEDIAFORMAT_KEY_TILE_HEIGHT = "tile-height";
+EXPORT const char* AMEDIAFORMAT_KEY_TILE_WIDTH = "tile-width";
 EXPORT const char* AMEDIAFORMAT_KEY_TIME_US = "timeUs";
 EXPORT const char* AMEDIAFORMAT_KEY_TRACK_ID = "track-id";
 EXPORT const char* AMEDIAFORMAT_KEY_TRACK_INDEX = "track-index";
diff --git a/media/ndk/include/media/NdkMediaFormat.h b/media/ndk/include/media/NdkMediaFormat.h
index 1da9197..687054e 100644
--- a/media/ndk/include/media/NdkMediaFormat.h
+++ b/media/ndk/include/media/NdkMediaFormat.h
@@ -110,10 +110,8 @@
 extern const char* AMEDIAFORMAT_KEY_DURATION;
 extern const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL;
 extern const char* AMEDIAFORMAT_KEY_FRAME_RATE;
-extern const char* AMEDIAFORMAT_KEY_GRID_COLS;
-extern const char* AMEDIAFORMAT_KEY_GRID_HEIGHT;
+extern const char* AMEDIAFORMAT_KEY_GRID_COLUMNS;
 extern const char* AMEDIAFORMAT_KEY_GRID_ROWS;
-extern const char* AMEDIAFORMAT_KEY_GRID_WIDTH;
 extern const char* AMEDIAFORMAT_KEY_HDR_STATIC_INFO;
 extern const char* AMEDIAFORMAT_KEY_HEIGHT;
 extern const char* AMEDIAFORMAT_KEY_INTRA_REFRESH_PERIOD;
@@ -143,6 +141,8 @@
 extern const char* AMEDIAFORMAT_KEY_STRIDE;
 extern const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID;
 extern const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYERING;
+extern const char* AMEDIAFORMAT_KEY_TILE_HEIGHT;
+extern const char* AMEDIAFORMAT_KEY_TILE_WIDTH;
 extern const char* AMEDIAFORMAT_KEY_TIME_US;
 extern const char* AMEDIAFORMAT_KEY_TRACK_ID;
 extern const char* AMEDIAFORMAT_KEY_TRACK_INDEX;
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index b0d8e7d..b6480a2 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -40,6 +40,9 @@
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
 
+# TODO: Enable proguard (b/74090741)
+LOCAL_PROGUARD_ENABLED := disabled
+
 LOCAL_MULTILIB := first
 
 LOCAL_JAVA_LIBRARIES += android-support-annotations
diff --git a/packages/MediaComponents/proguard.cfg b/packages/MediaComponents/proguard.cfg
index 43f2e63..d7bf730 100644
--- a/packages/MediaComponents/proguard.cfg
+++ b/packages/MediaComponents/proguard.cfg
@@ -16,5 +16,5 @@
 
 # Keep entry point for updatable Java classes
 -keep public class com.android.media.update.ApiFactory {
-   public static java.lang.Object initialize(android.content.res.Resources, android.content.res.Resources$Theme);
+   public static com.android.media.update.ApiFactory initialize(android.content.pm.ApplicationInfo);
 }
diff --git a/packages/MediaComponents/res/layout/settings_list_item.xml b/packages/MediaComponents/res/layout/embedded_settings_list_item.xml
similarity index 63%
copy from packages/MediaComponents/res/layout/settings_list_item.xml
copy to packages/MediaComponents/res/layout/embedded_settings_list_item.xml
index 81b3275..1156dca 100644
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/embedded_settings_list_item.xml
@@ -15,49 +15,49 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/MediaControlView2_settings_width"
-    android:layout_height="@dimen/MediaControlView2_settings_height"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/mcv2_embedded_settings_height"
     android:orientation="horizontal"
-    android:background="@color/black_transparent_70">
+    android:background="@color/black_opacity_70">
 
     <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
-        android:paddingRight="2dp"
+        android:layout_height="@dimen/mcv2_embedded_settings_height"
         android:gravity="center"
         android:orientation="horizontal">
 
         <ImageView
             android:id="@+id/icon"
-            android:layout_width="@dimen/MediaControlView2_settings_icon_size"
-            android:layout_height="@dimen/MediaControlView2_settings_icon_size"
-            android:gravity="center"
-            android:paddingLeft="2dp"/>
+            android:layout_width="@dimen/mcv2_embedded_settings_icon_size"
+            android:layout_height="@dimen/mcv2_embedded_settings_icon_size"
+            android:layout_margin="8dp"
+            android:gravity="center" />
     </LinearLayout>
 
     <RelativeLayout
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
+        android:layout_height="@dimen/mcv2_embedded_settings_height"
         android:gravity="center"
         android:orientation="vertical">
 
         <TextView
             android:id="@+id/main_text"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/MediaControlView2_text_width"
+            android:layout_height="@dimen/mcv2_embedded_settings_text_height"
+            android:gravity="center"
             android:paddingLeft="2dp"
             android:textColor="@color/white"
-            android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+            android:textSize="@dimen/mcv2_embedded_settings_main_text_size"/>
 
         <TextView
             android:id="@+id/sub_text"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/MediaControlView2_text_width"
+            android:layout_height="@dimen/mcv2_embedded_settings_text_height"
             android:layout_below="@id/main_text"
+            android:gravity="center"
             android:paddingLeft="2dp"
-            android:textColor="@color/white_transparent_70"
-            android:textSize="@dimen/MediaControlView2_settings_sub_text_size"/>
+            android:textColor="@color/white_opacity_70"
+            android:textSize="@dimen/mcv2_embedded_settings_sub_text_size"/>
     </RelativeLayout>
-
 </LinearLayout>
 
diff --git a/packages/MediaComponents/res/layout/sub_settings_list_item.xml b/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
similarity index 66%
copy from packages/MediaComponents/res/layout/sub_settings_list_item.xml
copy to packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
index 9de7f2b..5947a72 100644
--- a/packages/MediaComponents/res/layout/sub_settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
@@ -15,40 +15,39 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/MediaControlView2_settings_width"
-    android:layout_height="@dimen/MediaControlView2_settings_height"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/mcv2_embedded_settings_height"
     android:orientation="horizontal"
-    android:background="@color/black_transparent_70">
+    android:background="@color/black_opacity_70">
 
     <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
-        android:paddingRight="2dp"
+        android:layout_height="@dimen/mcv2_embedded_settings_height"
         android:gravity="center"
         android:orientation="horizontal">
 
         <ImageView
             android:id="@+id/check"
-            android:layout_width="@dimen/MediaControlView2_settings_icon_size"
-            android:layout_height="@dimen/MediaControlView2_settings_icon_size"
+            android:layout_width="@dimen/mcv2_embedded_settings_icon_size"
+            android:layout_height="@dimen/mcv2_embedded_settings_icon_size"
+            android:layout_margin="8dp"
             android:gravity="center"
-            android:paddingLeft="2dp"
             android:src="@drawable/ic_check"/>
     </LinearLayout>
 
     <RelativeLayout
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
+        android:layout_height="@dimen/mcv2_embedded_settings_height"
         android:gravity="center"
         android:orientation="vertical">
 
         <TextView
             android:id="@+id/text"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/MediaControlView2_text_width"
+            android:layout_height="@dimen/mcv2_embedded_settings_text_height"
+            android:gravity="center"
             android:paddingLeft="2dp"
             android:textColor="@color/white"
-            android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+            android:textSize="@dimen/mcv2_embedded_settings_main_text_size"/>
     </RelativeLayout>
-
 </LinearLayout>
diff --git a/packages/MediaComponents/res/layout/embedded_transport_controls.xml b/packages/MediaComponents/res/layout/embedded_transport_controls.xml
new file mode 100644
index 0000000..a3a5957
--- /dev/null
+++ b/packages/MediaComponents/res/layout/embedded_transport_controls.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="horizontal"
+    android:paddingLeft="@dimen/mcv2_transport_controls_padding"
+    android:paddingRight="@dimen/mcv2_transport_controls_padding"
+    android:visibility="visible">
+
+    <ImageButton android:id="@+id/prev" style="@style/EmbeddedTransportControlsButton.Previous" />
+    <ImageButton android:id="@+id/rew" style="@style/EmbeddedTransportControlsButton.Rew" />
+    <ImageButton android:id="@+id/pause" style="@style/EmbeddedTransportControlsButton.Pause" />
+    <ImageButton android:id="@+id/ffwd" style="@style/EmbeddedTransportControlsButton.Ffwd" />
+    <ImageButton android:id="@+id/next" style="@style/EmbeddedTransportControlsButton.Next" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/settings_list_item.xml b/packages/MediaComponents/res/layout/full_settings_list_item.xml
similarity index 63%
rename from packages/MediaComponents/res/layout/settings_list_item.xml
rename to packages/MediaComponents/res/layout/full_settings_list_item.xml
index 81b3275..f92ea5e 100644
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/full_settings_list_item.xml
@@ -15,49 +15,48 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/MediaControlView2_settings_width"
-    android:layout_height="@dimen/MediaControlView2_settings_height"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/mcv2_full_settings_height"
     android:orientation="horizontal"
-    android:background="@color/black_transparent_70">
+    android:background="@color/black_opacity_70">
 
     <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
-        android:paddingRight="2dp"
+        android:layout_height="@dimen/mcv2_full_settings_height"
         android:gravity="center"
         android:orientation="horizontal">
 
         <ImageView
             android:id="@+id/icon"
-            android:layout_width="@dimen/MediaControlView2_settings_icon_size"
-            android:layout_height="@dimen/MediaControlView2_settings_icon_size"
-            android:gravity="center"
-            android:paddingLeft="2dp"/>
+            android:layout_width="@dimen/mcv2_full_settings_icon_size"
+            android:layout_height="@dimen/mcv2_full_settings_icon_size"
+            android:layout_margin="8dp"
+            android:gravity="center"/>
     </LinearLayout>
 
     <RelativeLayout
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
+        android:layout_height="@dimen/mcv2_full_settings_height"
         android:gravity="center"
         android:orientation="vertical">
 
         <TextView
             android:id="@+id/main_text"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/MediaControlView2_text_width"
+            android:layout_height="@dimen/mcv2_full_settings_text_height"
             android:paddingLeft="2dp"
+            android:gravity="center"
             android:textColor="@color/white"
-            android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+            android:textSize="@dimen/mcv2_full_settings_main_text_size"/>
 
         <TextView
             android:id="@+id/sub_text"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/MediaControlView2_text_width"
+            android:layout_height="@dimen/mcv2_full_settings_text_height"
             android:layout_below="@id/main_text"
+            android:gravity="center"
             android:paddingLeft="2dp"
-            android:textColor="@color/white_transparent_70"
-            android:textSize="@dimen/MediaControlView2_settings_sub_text_size"/>
+            android:textColor="@color/white_opacity_70"
+            android:textSize="@dimen/mcv2_full_settings_sub_text_size"/>
     </RelativeLayout>
-
 </LinearLayout>
-
diff --git a/packages/MediaComponents/res/layout/sub_settings_list_item.xml b/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
similarity index 66%
rename from packages/MediaComponents/res/layout/sub_settings_list_item.xml
rename to packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
index 9de7f2b..49128d0 100644
--- a/packages/MediaComponents/res/layout/sub_settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
@@ -15,40 +15,39 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/MediaControlView2_settings_width"
-    android:layout_height="@dimen/MediaControlView2_settings_height"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/mcv2_full_settings_height"
     android:orientation="horizontal"
-    android:background="@color/black_transparent_70">
+    android:background="@color/black_opacity_70">
 
     <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
-        android:paddingRight="2dp"
+        android:layout_height="@dimen/mcv2_full_settings_height"
         android:gravity="center"
         android:orientation="horizontal">
 
         <ImageView
             android:id="@+id/check"
-            android:layout_width="@dimen/MediaControlView2_settings_icon_size"
-            android:layout_height="@dimen/MediaControlView2_settings_icon_size"
+            android:layout_width="@dimen/mcv2_full_settings_icon_size"
+            android:layout_height="@dimen/mcv2_full_settings_icon_size"
+            android:layout_margin="8dp"
             android:gravity="center"
-            android:paddingLeft="2dp"
             android:src="@drawable/ic_check"/>
     </LinearLayout>
 
     <RelativeLayout
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
+        android:layout_height="@dimen/mcv2_full_settings_height"
         android:gravity="center"
         android:orientation="vertical">
 
         <TextView
             android:id="@+id/text"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/MediaControlView2_text_width"
+            android:layout_height="@dimen/mcv2_full_settings_text_height"
+            android:gravity="center"
             android:paddingLeft="2dp"
             android:textColor="@color/white"
-            android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+            android:textSize="@dimen/mcv2_full_settings_main_text_size"/>
     </RelativeLayout>
-
-</LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/full_transport_controls.xml b/packages/MediaComponents/res/layout/full_transport_controls.xml
new file mode 100644
index 0000000..dd41a1f
--- /dev/null
+++ b/packages/MediaComponents/res/layout/full_transport_controls.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="horizontal"
+    android:paddingLeft="@dimen/mcv2_transport_controls_padding"
+    android:paddingRight="@dimen/mcv2_transport_controls_padding"
+    android:visibility="visible">
+
+    <ImageButton android:id="@+id/prev" style="@style/FullTransportControlsButton.Previous" />
+    <ImageButton android:id="@+id/rew" style="@style/FullTransportControlsButton.Rew" />
+    <ImageButton android:id="@+id/pause" style="@style/FullTransportControlsButton.Pause" />
+    <ImageButton android:id="@+id/ffwd" style="@style/FullTransportControlsButton.Ffwd" />
+    <ImageButton android:id="@+id/next" style="@style/FullTransportControlsButton.Next" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index eab2309..c5fbee8 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -111,19 +111,11 @@
     </RelativeLayout>
 
     <LinearLayout
+        android:id="@+id/center_view"
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
-        android:gravity="center"
-        android:paddingTop="4dp"
-        android:orientation="horizontal">
-
-        <ImageButton android:id="@+id/prev" style="@style/TransportControlsButton.Previous" />
-        <ImageButton android:id="@+id/rew" style="@style/TransportControlsButton.Rew" />
-        <ImageButton android:id="@+id/pause" style="@style/TransportControlsButton.Pause" />
-        <ImageButton android:id="@+id/ffwd" style="@style/TransportControlsButton.Ffwd" />
-        <ImageButton android:id="@+id/next" style="@style/TransportControlsButton.Next" />
-
+        android:gravity="center">
     </LinearLayout>
 
     <SeekBar
@@ -137,103 +129,108 @@
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="44dp"
-        android:paddingLeft="15dp"
         android:orientation="horizontal">
 
-        <TextView
-            android:id="@+id/time_current"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:layout_alignParentStart="true"
-            android:paddingEnd="4dp"
-            android:paddingStart="4dp"
-            android:textSize="14sp"
-            android:textStyle="bold"
-            android:textColor="#FFFFFF" />
-
-        <TextView
-            android:id="@+id/time"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:layout_toRightOf="@id/time_current"
-            android:paddingStart="4dp"
-            android:paddingEnd="4dp"
-            android:textSize="14sp"
-            android:textStyle="bold"
-            android:textColor="#BBBBBB" />
-
-        <TextView
-            android:id="@+id/ad_skip_time"
-            android:gravity="center"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:textSize="12sp"
-            android:textColor="#FFFFFF"
-            android:visibility="gone" />
-
         <LinearLayout
-            android:id="@+id/basic_controls"
-            android:gravity="center"
-            android:layout_alignParentRight="true"
+            android:id="@+id/bottom_bar_left"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:orientation="horizontal" >
+            android:layout_height="match_parent"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true">
 
             <TextView
-                android:id="@+id/ad_remaining"
+                android:id="@+id/ad_skip_time"
                 android:gravity="center"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerVertical="true"
+                android:layout_height="match_parent"
+                android:layout_marginLeft="4dp"
                 android:textSize="12sp"
                 android:textColor="#FFFFFF"
                 android:visibility="gone" />
-
-            <ImageButton
-                android:id="@+id/mute"
-                style="@style/BottomBarButton.Mute" />
-            <ImageButton
-                android:id="@+id/subtitle"
-                android:scaleType="fitCenter"
-                android:visibility="gone"
-                style="@style/BottomBarButton.CC" />
-            <ImageButton
-                android:id="@+id/fullscreen"
-                style="@style/BottomBarButton.FullScreen"/>
-            <ImageButton
-                android:id="@+id/overflow_right"
-                style="@style/BottomBarButton.OverflowRight"/>
         </LinearLayout>
 
         <LinearLayout
-            android:id="@+id/extra_controls"
-            android:layout_alignParentRight="true"
+            android:id="@+id/time"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:visibility="gone"
-            android:orientation="horizontal"
-            android:gravity="center">
+            android:layout_height="match_parent"
+            android:layout_toRightOf="@id/bottom_bar_left"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:gravity="center" >
 
-            <LinearLayout
-                android:id="@+id/custom_buttons"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-
-            <ImageButton
-                android:id="@+id/video_quality"
-                style="@style/BottomBarButton.VideoQuality" />
-            <ImageButton
-                android:id="@+id/settings"
-                style="@style/BottomBarButton.Settings" />
-            <ImageButton
-                android:id="@+id/overflow_left"
-                style="@style/BottomBarButton.OverflowLeft"/>
+            <TextView
+                android:id="@+id/time_current"
+                style="@style/TimeText.Current"/>
+            <TextView
+                android:id="@+id/time_interpunct"
+                style="@style/TimeText.Interpunct"/>
+            <TextView
+                android:id="@+id/time_end"
+                style="@style/TimeText.End"/>
         </LinearLayout>
 
+        <LinearLayout
+            android:id="@+id/bottom_bar_right"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:gravity="right">
+
+            <LinearLayout
+                android:id="@+id/basic_controls"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="horizontal" >
+
+                <TextView
+                    android:id="@+id/ad_remaining"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:textSize="12sp"
+                    android:textColor="#FFFFFF"
+                    android:visibility="gone" />
+
+                <ImageButton
+                    android:id="@+id/mute"
+                    style="@style/BottomBarButton.Mute" />
+                <ImageButton
+                    android:id="@+id/subtitle"
+                    android:scaleType="fitCenter"
+                    android:visibility="gone"
+                    style="@style/BottomBarButton.CC" />
+                <ImageButton
+                    android:id="@+id/fullscreen"
+                    style="@style/BottomBarButton.FullScreen"/>
+                <ImageButton
+                    android:id="@+id/overflow_right"
+                    style="@style/BottomBarButton.OverflowRight"/>
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/extra_controls"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                android:orientation="horizontal"
+                android:gravity="center_vertical">
+
+                <LinearLayout
+                    android:id="@+id/custom_buttons"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <ImageButton
+                    android:id="@+id/video_quality"
+                    style="@style/BottomBarButton.VideoQuality" />
+                <ImageButton
+                    android:id="@+id/settings"
+                    style="@style/BottomBarButton.Settings" />
+                <ImageButton
+                    android:id="@+id/overflow_left"
+                    style="@style/BottomBarButton.OverflowLeft"/>
+            </LinearLayout>
+        </LinearLayout>
     </RelativeLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/minimal_transport_controls.xml b/packages/MediaComponents/res/layout/minimal_transport_controls.xml
new file mode 100644
index 0000000..9ca3721
--- /dev/null
+++ b/packages/MediaComponents/res/layout/minimal_transport_controls.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="horizontal"
+    android:visibility="visible">
+
+    <ImageButton android:id="@+id/pause" style="@style/MinimalTransportControlsButton.Pause" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/settings_list.xml b/packages/MediaComponents/res/layout/settings_list.xml
index 37a3a60..ea30538 100644
--- a/packages/MediaComponents/res/layout/settings_list.xml
+++ b/packages/MediaComponents/res/layout/settings_list.xml
@@ -15,8 +15,8 @@
 -->
 
 <ListView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/MediaControlView2_settings_width"
-    android:layout_height="@dimen/MediaControlView2_settings_height"
+    android:layout_width="@dimen/mcv2_embedded_settings_width"
+    android:layout_height="@dimen/mcv2_embedded_settings_height"
     android:divider="@null"
     android:dividerHeight="0dp">
 </ListView>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/colors.xml b/packages/MediaComponents/res/values/colors.xml
index 8ba069c..6a65ef5 100644
--- a/packages/MediaComponents/res/values/colors.xml
+++ b/packages/MediaComponents/res/values/colors.xml
@@ -17,6 +17,6 @@
 <resources>
     <color name="gray">#808080</color>
     <color name="white">#ffffff</color>
-    <color name="white_transparent_70">#B3ffffff</color>
-    <color name="black_transparent_70">#B3000000</color>
+    <color name="white_opacity_70">#B3ffffff</color>
+    <color name="black_opacity_70">#B3000000</color>
 </resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/dimens.xml b/packages/MediaComponents/res/values/dimens.xml
index b5ef626..8d72d40 100644
--- a/packages/MediaComponents/res/values/dimens.xml
+++ b/packages/MediaComponents/res/values/dimens.xml
@@ -41,12 +41,26 @@
     <!-- Group list fade out animation duration. -->
     <integer name="mr_controller_volume_group_list_fade_out_duration_ms">200</integer>
 
-    <dimen name="MediaControlView2_settings_width">200dp</dimen>
-    <dimen name="MediaControlView2_settings_height">36dp</dimen>
-    <dimen name="MediaControlView2_settings_icon_size">28dp</dimen>
-    <dimen name="MediaControlView2_settings_offset">8dp</dimen>
-    <dimen name="MediaControlView2_text_width">18dp</dimen>
+    <dimen name="mcv2_embedded_settings_width">150dp</dimen>
+    <dimen name="mcv2_embedded_settings_height">36dp</dimen>
+    <dimen name="mcv2_embedded_settings_icon_size">20dp</dimen>
+    <dimen name="mcv2_embedded_settings_text_height">18dp</dimen>
+    <dimen name="mcv2_embedded_settings_main_text_size">12sp</dimen>
+    <dimen name="mcv2_embedded_settings_sub_text_size">10sp</dimen>
+    <dimen name="mcv2_full_settings_width">225dp</dimen>
+    <dimen name="mcv2_full_settings_height">54dp</dimen>
+    <dimen name="mcv2_full_settings_icon_size">30dp</dimen>
+    <dimen name="mcv2_full_settings_text_height">27dp</dimen>
+    <dimen name="mcv2_full_settings_main_text_size">16sp</dimen>
+    <dimen name="mcv2_full_settings_sub_text_size">13sp</dimen>
+    <dimen name="mcv2_settings_offset">8dp</dimen>
 
-    <dimen name="MediaControlView2_settings_main_text_size">12sp</dimen>
-    <dimen name="MediaControlView2_settings_sub_text_size">10sp</dimen>
+    <dimen name="mcv2_transport_controls_padding">4dp</dimen>
+    <dimen name="mcv2_pause_icon_size">36dp</dimen>
+    <dimen name="mcv2_full_icon_size">28dp</dimen>
+    <dimen name="mcv2_embedded_icon_size">24dp</dimen>
+    <dimen name="mcv2_minimal_icon_size">24dp</dimen>
+    <dimen name="mcv2_icon_margin">10dp</dimen>
+
+    <!-- TODO: adjust bottom bar view -->
 </resources>
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index c80aaf3..69e9dff 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -122,6 +122,8 @@
         <item>1.5x</item>
         <item>2x</item>
     </string-array>
+    <!-- Placeholder text for displaying time. Used to calculate which size layout to use. -->
+    <string name="MediaControlView2_time_placeholder">00:00:00</string>
 
     <!-- Text for displaying subtitle track number. -->
     <string name="MediaControlView2_subtitle_track_number_text">
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index e6ed039..b1da137 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -1,30 +1,86 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <style name="TransportControlsButton">
+    <style name="FullTransportControlsButton">
         <item name="android:background">@null</item>
-        <item name="android:layout_width">70dp</item>
-        <item name="android:layout_height">40dp</item>
-        <item name="android:visibility">gone</item>
+        <item name="android:layout_margin">@dimen/mcv2_icon_margin</item>
+        <item name="android:scaleType">fitXY</item>
     </style>
 
-    <style name="TransportControlsButton.Previous">
+    <style name="FullTransportControlsButton.Previous">
         <item name="android:src">@drawable/ic_skip_previous</item>
+        <item name="android:layout_width">@dimen/mcv2_full_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_full_icon_size</item>
     </style>
 
-    <style name="TransportControlsButton.Next">
+    <style name="FullTransportControlsButton.Next">
         <item name="android:src">@drawable/ic_skip_next</item>
+        <item name="android:layout_width">@dimen/mcv2_full_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_full_icon_size</item>
     </style>
 
-    <style name="TransportControlsButton.Pause">
+    <style name="FullTransportControlsButton.Pause">
         <item name="android:src">@drawable/ic_pause_circle_filled</item>
+        <item name="android:layout_width">@dimen/mcv2_pause_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_pause_icon_size</item>
     </style>
 
-    <style name="TransportControlsButton.Ffwd">
+    <style name="FullTransportControlsButton.Ffwd">
         <item name="android:src">@drawable/ic_forward_30</item>
+        <item name="android:layout_width">@dimen/mcv2_full_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_full_icon_size</item>
     </style>
 
-    <style name="TransportControlsButton.Rew">
+    <style name="FullTransportControlsButton.Rew">
         <item name="android:src">@drawable/ic_rewind_10</item>
+        <item name="android:layout_width">@dimen/mcv2_full_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_full_icon_size</item>
+    </style>
+
+    <style name="EmbeddedTransportControlsButton">
+        <item name="android:background">@null</item>
+        <item name="android:layout_margin">@dimen/mcv2_icon_margin</item>
+        <item name="android:scaleType">fitXY</item>
+    </style>
+
+    <style name="EmbeddedTransportControlsButton.Previous">
+        <item name="android:src">@drawable/ic_skip_previous</item>
+        <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+    </style>
+
+    <style name="EmbeddedTransportControlsButton.Next">
+        <item name="android:src">@drawable/ic_skip_next</item>
+        <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+    </style>
+
+    <style name="EmbeddedTransportControlsButton.Pause">
+        <item name="android:src">@drawable/ic_pause_circle_filled</item>
+        <item name="android:layout_width">@dimen/mcv2_pause_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_pause_icon_size</item>
+    </style>
+
+    <style name="EmbeddedTransportControlsButton.Ffwd">
+        <item name="android:src">@drawable/ic_forward_30</item>
+        <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+    </style>
+
+    <style name="EmbeddedTransportControlsButton.Rew">
+        <item name="android:src">@drawable/ic_rewind_10</item>
+        <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+    </style>
+
+    <style name="MinimalTransportControlsButton">
+        <item name="android:background">@null</item>
+        <item name="android:scaleType">fitXY</item>
+    </style>
+
+    <style name="MinimalTransportControlsButton.Pause">
+        <item name="android:src">@drawable/ic_pause_circle_filled</item>
+        <item name="android:layout_width">@dimen/mcv2_minimal_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_minimal_icon_size</item>
     </style>
 
     <style name="TitleBar">
@@ -45,14 +101,38 @@
         <item name="android:src">@drawable/ic_launch</item>
     </style>
 
+    <style name="TimeText">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingStart">4dp</item>
+        <item name="android:paddingEnd">4dp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:gravity">center</item>
+    </style>
+
+    <style name="TimeText.Current">
+        <item name="android:textColor">@color/white</item>
+        <item name="android:text">@string/MediaControlView2_time_placeholder</item>
+    </style>
+
+    <style name="TimeText.Interpunct">
+        <item name="android:textColor">@color/white_opacity_70</item>
+        <item name="android:text">·</item>
+    </style>
+
+    <style name="TimeText.End">
+        <item name="android:textColor">@color/white_opacity_70</item>
+        <item name="android:text">@string/MediaControlView2_time_placeholder</item>
+    </style>
+
     <style name="BottomBarButton">
         <item name="android:background">@null</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>
+        <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+        <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+        <item name="android:layout_margin">@dimen/mcv2_icon_margin</item>
+        <item name="android:gravity">center_horizontal</item>
+        <item name="android:scaleType">fitXY</item>
     </style>
 
     <style name="BottomBarButton.CC">
diff --git a/packages/MediaComponents/src/com/android/media/IMediaController2.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
index d6c8e21..0488b70 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
@@ -37,12 +37,14 @@
     void onBufferedPositionChanged(long bufferedPositionMs);
     void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
     void onPlaylistMetadataChanged(in Bundle metadata);
-    void onPlaylistParamsChanged(in Bundle params);
     void onPlaybackInfoChanged(in Bundle playbackInfo);
+    void onRepeatModeChanged(int repeatMode);
+    void onShuffleModeChanged(int shuffleMode);
+    void onError(int errorCode, in Bundle extras);
 
     void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
             int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
-            long bufferedPositionMs, in Bundle playbackInfo, in Bundle params,
+            long bufferedPositionMs, in Bundle playbackInfo, int repeatMode, int shuffleMode,
             in List<Bundle> playlist, in PendingIntent sessionActivity);
     void onDisconnected();
 
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index 7317b44..664467d 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -67,6 +67,8 @@
     void skipToPlaylistItem(IMediaController2 caller, in Bundle mediaItem);
     void skipToPreviousItem(IMediaController2 caller);
     void skipToNextItem(IMediaController2 caller);
+    void setRepeatMode(IMediaController2 caller, int repeatMode);
+    void setShuffleMode(IMediaController2 caller, int shuffleMode);
 
     //////////////////////////////////////////////////////////////////////////////////////////////
     // library service specific
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 15922f7..0091816 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -22,6 +22,8 @@
 import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
 import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST;
 import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE;
 import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID;
 import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH;
 import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_URI;
@@ -40,11 +42,12 @@
 import android.media.MediaController2.PlaybackInfo;
 import android.media.MediaItem2;
 import android.media.MediaMetadata2;
+import android.media.MediaPlaylistAgent.RepeatMode;
+import android.media.MediaPlaylistAgent.ShuffleMode;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.PlaylistParams;
 import android.media.MediaSessionService2;
 import android.media.Rating2;
 import android.media.SessionToken2;
@@ -87,7 +90,9 @@
     @GuardedBy("mLock")
     private MediaMetadata2 mPlaylistMetadata;
     @GuardedBy("mLock")
-    private PlaylistParams mPlaylistParams;
+    private @RepeatMode int mRepeatMode;
+    @GuardedBy("mLock")
+    private @ShuffleMode int mShuffleMode;
     @GuardedBy("mLock")
     private int mPlayerState;
     @GuardedBy("mLock")
@@ -706,9 +711,41 @@
     }
 
     @Override
-    public PlaylistParams getPlaylistParams_impl() {
-        synchronized (mLock) {
-            return mPlaylistParams;
+    public int getShuffleMode_impl() {
+        return mShuffleMode;
+    }
+
+    @Override
+    public void setShuffleMode_impl(int shuffleMode) {
+        final IMediaSession2 binder = getSessionBinderIfAble(
+                COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE);
+        if (binder != null) {
+            try {
+                binder.setShuffleMode(mControllerStub, shuffleMode);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+    }
+
+    @Override
+    public int getRepeatMode_impl() {
+        return mRepeatMode;
+    }
+
+    @Override
+    public void setRepeatMode_impl(int repeatMode) {
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE);
+        if (binder != null) {
+            try {
+                binder.setRepeatMode(mControllerStub, repeatMode);
+            } 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());
         }
     }
 
@@ -719,19 +756,6 @@
         }
     }
 
-    // TODO(jaewan): Remove (b/74116823)
-    @Override
-    public void setPlaylistParams_impl(PlaylistParams params) {
-        if (params == null) {
-            throw new IllegalArgumentException("params shouldn't be null");
-        }
-        /*
-        Bundle args = new Bundle();
-        args.putBundle(MediaSession2Stub.ARGUMENT_KEY_PLAYLIST_PARAMS, params.toBundle());
-        sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS, args);
-        */
-    }
-
     @Override
     public int getPlayerState_impl() {
         synchronized (mLock) {
@@ -817,18 +841,6 @@
         });
     }
 
-    void pushPlaylistParamsChanges(final PlaylistParams params) {
-        synchronized (mLock) {
-            mPlaylistParams = params;
-        }
-        mCallbackExecutor.execute(() -> {
-            if (!mInstance.isConnected()) {
-                return;
-            }
-            mCallback.onPlaylistParamsChanged(mInstance, params);
-        });
-    }
-
     void pushPlaybackInfoChanges(final PlaybackInfo info) {
         synchronized (mLock) {
             mPlaybackInfo = info;
@@ -855,7 +867,7 @@
         });
     }
 
-    public void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
+    void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
         synchronized (mLock) {
             mPlaylistMetadata = metadata;
         }
@@ -868,6 +880,41 @@
         });
     }
 
+    void pushShuffleModeChanges(int shuffleMode) {
+        synchronized (mLock) {
+            mShuffleMode = shuffleMode;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            // TODO(jaewan): Fix public API not to take playlistAgent.
+            mCallback.onShuffleModeChanged(mInstance, null, shuffleMode);
+        });
+    }
+
+    void pushRepeatModeChanges(int repeatMode) {
+        synchronized (mLock) {
+            mRepeatMode = repeatMode;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            // TODO(jaewan): Fix public API not to take playlistAgent.
+            mCallback.onRepeatModeChanged(mInstance, null, repeatMode);
+        });
+    }
+
+    void pushError(int errorCode, Bundle extras) {
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            mCallback.onError(mInstance, errorCode, extras);
+        });
+    }
+
     // Should be used without a lock to prevent potential deadlock.
     void onConnectedNotLocked(IMediaSession2 sessionBinder,
             final CommandGroup allowedCommands,
@@ -877,7 +924,9 @@
             final float playbackSpeed,
             final long bufferedPositionMs,
             final PlaybackInfo info,
-            final PlaylistParams params, final List<MediaItem2> playlist,
+            final int repeatMode,
+            final int shuffleMode,
+            final List<MediaItem2> playlist,
             final PendingIntent sessionActivity) {
         if (DEBUG) {
             Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
@@ -907,7 +956,8 @@
                 mPlaybackSpeed = playbackSpeed;
                 mBufferedPositionMs = bufferedPositionMs;
                 mPlaybackInfo = info;
-                mPlaylistParams = params;
+                mRepeatMode = repeatMode;
+                mShuffleMode = shuffleMode;
                 mPlaylist = playlist;
                 mSessionActivity = sessionActivity;
                 mSessionBinder = sessionBinder;
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Stub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
index 99bdfce..721c963 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
@@ -24,7 +24,6 @@
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.PlaylistParams;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.text.TextUtils;
@@ -169,7 +168,7 @@
     }
 
     @Override
-    public void onPlaylistParamsChanged(Bundle paramsBundle) throws RuntimeException {
+    public void onRepeatModeChanged(int repeatMode) {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -177,12 +176,7 @@
             Log.w(TAG, "Don't fail silently here. Highly likely a bug");
             return;
         }
-        PlaylistParams params = PlaylistParams.fromBundle(controller.getContext(), paramsBundle);
-        if (params == null) {
-            Log.w(TAG, "onPlaylistParamsChanged(): Ignoring null playlistParams");
-            return;
-        }
-        controller.pushPlaylistParamsChanges(params);
+        controller.pushRepeatModeChanges(repeatMode);
     }
 
     @Override
@@ -208,9 +202,33 @@
     }
 
     @Override
+    public void onShuffleModeChanged(int shuffleMode) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        controller.pushShuffleModeChanges(shuffleMode);
+    }
+
+    @Override
+    public void onError(int errorCode, Bundle extras) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        controller.pushError(errorCode, extras);
+    }
+
+    @Override
     public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
             int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
-            long bufferedPositionMs, Bundle playbackInfo, Bundle playlistParams,
+            long bufferedPositionMs, Bundle playbackInfo, int shuffleMode, int repeatMode,
             List<Bundle> itemBundleList, PendingIntent sessionActivity) {
         final MediaController2Impl controller = mController.get();
         if (controller == null) {
@@ -233,8 +251,7 @@
         controller.onConnectedNotLocked(sessionBinder,
                 CommandGroup.fromBundle(context, commandGroup),
                 playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
-                PlaybackInfoImpl.fromBundle(context, playbackInfo),
-                PlaylistParams.fromBundle(context, playlistParams),
+                PlaybackInfoImpl.fromBundle(context, playbackInfo), repeatMode, shuffleMode,
                 itemList, sessionActivity);
     }
 
diff --git a/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
index bab29b7..ab6b167 100644
--- a/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.media.DataSourceDesc;
 import android.media.MediaItem2;
 import android.media.MediaMetadata2;
 import android.media.MediaPlaylistAgent;
@@ -48,6 +49,7 @@
         mInstance = instance;
     }
 
+    @Override
     final public void registerPlaylistEventCallback_impl(
             @NonNull @CallbackExecutor Executor executor, @NonNull PlaylistEventCallback callback) {
         if (executor == null) {
@@ -66,6 +68,7 @@
         }
     }
 
+    @Override
     final public void unregisterPlaylistEventCallback_impl(
             @NonNull PlaylistEventCallback callback) {
         if (callback == null) {
@@ -76,6 +79,7 @@
         }
     }
 
+    @Override
     final public void notifyPlaylistChanged_impl() {
         ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
         List<MediaItem2> playlist= mInstance.getPlaylist();
@@ -88,6 +92,7 @@
         }
     }
 
+    @Override
     final public void notifyPlaylistMetadataChanged_impl() {
         ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
         for (int i = 0; i < callbacks.size(); i++) {
@@ -98,6 +103,7 @@
         }
     }
 
+    @Override
     final public void notifyShuffleModeChanged_impl() {
         ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
         for (int i = 0; i < callbacks.size(); i++) {
@@ -108,6 +114,7 @@
         }
     }
 
+    @Override
     final public void notifyRepeatModeChanged_impl() {
         ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
         for (int i = 0; i < callbacks.size(); i++) {
@@ -118,66 +125,98 @@
         }
     }
 
+    @Override
     public @Nullable List<MediaItem2> getPlaylist_impl() {
         // empty implementation
         return null;
     }
 
+    @Override
     public void setPlaylist_impl(@NonNull List<MediaItem2> list,
             @Nullable MediaMetadata2 metadata) {
         // empty implementation
     }
 
+    @Override
     public @Nullable MediaMetadata2 getPlaylistMetadata_impl() {
         // empty implementation
         return null;
     }
 
+    @Override
     public void updatePlaylistMetadata_impl(@Nullable MediaMetadata2 metadata) {
         // empty implementation
     }
 
+    @Override
     public void addPlaylistItem_impl(int index, @NonNull MediaItem2 item) {
         // empty implementation
     }
 
+    @Override
     public void removePlaylistItem_impl(@NonNull MediaItem2 item) {
         // empty implementation
     }
 
+    @Override
     public void replacePlaylistItem_impl(int index, @NonNull MediaItem2 item) {
         // empty implementation
     }
 
+    @Override
     public void skipToPlaylistItem_impl(@NonNull MediaItem2 item) {
         // empty implementation
     }
 
+    @Override
     public void skipToPreviousItem_impl() {
         // empty implementation
     }
 
+    @Override
     public void skipToNextItem_impl() {
         // empty implementation
     }
 
+    @Override
     public int getRepeatMode_impl() {
         return MediaPlaylistAgent.REPEAT_MODE_NONE;
     }
 
+    @Override
     public void setRepeatMode_impl(int repeatMode) {
         // empty implementation
     }
 
+    @Override
     public int getShuffleMode_impl() {
         // empty implementation
         return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
     }
 
+    @Override
     public void setShuffleMode_impl(int shuffleMode) {
         // empty implementation
     }
 
+    @Override
+    public @Nullable MediaItem2 getMediaItem_impl(@NonNull DataSourceDesc dsd) {
+        if (dsd == null) {
+            throw new IllegalArgumentException("dsd shouldn't be null");
+        }
+        List<MediaItem2> itemList = mInstance.getPlaylist();
+        if (itemList == null) {
+            return null;
+        }
+        for (int i = 0; i < itemList.size(); i++) {
+            MediaItem2 item = itemList.get(i);
+            if (item != null && item.getDataSourceDesc() == dsd) {
+                return item;
+            }
+        }
+        return null;
+    }
+
     private ArrayMap<PlaylistEventCallback, Executor> getCallbacks() {
         ArrayMap<PlaylistEventCallback, Executor> callbacks = new ArrayMap<>();
         synchronized (mLock) {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 8957ea8..2b7c72a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -39,6 +39,7 @@
 import android.media.MediaMetadata2;
 import android.media.MediaPlayerBase;
 import android.media.MediaPlayerBase.PlayerEventCallback;
+import android.media.MediaPlayerBase.PlayerState;
 import android.media.MediaPlaylistAgent;
 import android.media.MediaPlaylistAgent.PlaylistEventCallback;
 import android.media.MediaSession2;
@@ -47,9 +48,6 @@
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.MediaSession2.PlaylistParams.RepeatMode;
-import android.media.MediaSession2.PlaylistParams.ShuffleMode;
 import android.media.MediaSession2.SessionCallback;
 import android.media.MediaSessionService2;
 import android.media.SessionToken2;
@@ -71,6 +69,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.concurrent.Executor;
 
 public class MediaSession2Impl implements MediaSession2Provider {
@@ -87,7 +86,6 @@
     private final MediaSession2Stub mSessionStub;
     private final SessionToken2 mSessionToken;
     private final AudioManager mAudioManager;
-    private final ArrayMap<PlayerEventCallback, Executor> mCallbacks = new ArrayMap<>();
     private final PendingIntent mSessionActivity;
     private final PlayerEventCallback mPlayerEventCallback;
     private final PlaylistEventCallback mPlaylistEventCallback;
@@ -254,6 +252,7 @@
             mSessionStub.notifyPlaybackInfoChanged(info);
             notifyPlayerUpdatedNotLocked(oldPlayer);
         }
+        // TODO(jaewan): Repeat the same thing for the playlist agent.
     }
 
     private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -430,41 +429,6 @@
         mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
     }
 
-    @Override
-    public void setPlaylistParams_impl(PlaylistParams params) {
-        if (params == null) {
-            throw new IllegalArgumentException("params shouldn't be null");
-        }
-        ensureCallingThread();
-        // TODO: Uncomment or remove
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-            // TODO implement
-            //player.setPlaylistParams(params);
-            mSessionStub.notifyPlaylistParamsChanged(params);
-        }
-        */
-    }
-
-    @Override
-    public PlaylistParams getPlaylistParams_impl() {
-        // TODO: Uncomment or remove
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-            // TODO(jaewan): Is it safe to be called on any thread?
-            //               Otherwise MediaSession2 should cache parameter of setPlaylistParams.
-            // TODO implement
-            //return player.getPlaylistParams();
-            return null;
-        } else if (DEBUG) {
-            Log.d(TAG, "API calls after the close()", new IllegalStateException());
-        }
-        */
-        return null;
-    }
-
     //////////////////////////////////////////////////////////////////////////////////////
     // TODO(jaewan): Implement follows
     //////////////////////////////////////////////////////////////////////////////////////
@@ -598,6 +562,48 @@
     }
 
     @Override
+    public int getRepeatMode_impl() {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            return agent.getRepeatMode();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
+        return MediaPlaylistAgent.REPEAT_MODE_NONE;
+    }
+
+    @Override
+    public void setRepeatMode_impl(int repeatMode) {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.setRepeatMode(repeatMode);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
+    }
+
+    @Override
+    public int getShuffleMode_impl() {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            return agent.getShuffleMode();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
+        return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
+    }
+
+    @Override
+    public void setShuffleMode_impl(int shuffleMode) {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.setShuffleMode(shuffleMode);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
+    }
+
+    @Override
     public void prepare_impl() {
         ensureCallingThread();
         final MediaPlayerBase player = mPlayer;
@@ -611,31 +617,23 @@
     @Override
     public void fastForward_impl() {
         ensureCallingThread();
-        // TODO: Uncomment or remove
-        /*
         final MediaPlayerBase player = mPlayer;
         if (player != null) {
-            // TODO implement
-            //player.fastForward();
+            player.fastForward();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
     }
 
     @Override
     public void rewind_impl() {
         ensureCallingThread();
-        // TODO: Uncomment or remove
-        /*
         final MediaPlayerBase player = mPlayer;
         if (player != null) {
-            // TODO implement
-            //player.rewind();
+            player.rewind();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
     }
 
     @Override
@@ -650,33 +648,41 @@
     }
 
     @Override
-    public void registerPlayerEventCallback_impl(Executor executor, PlayerEventCallback callback) {
-        if (executor == null) {
-            throw new IllegalArgumentException("executor shouldn't be null");
+    public @PlayerState int getPlayerState_impl() {
+        final MediaPlayerBase player = mPlayer;
+        if (player != null) {
+            return mPlayer.getPlayerState();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        if (callback == null) {
-            throw new IllegalArgumentException("callback shouldn't be null");
-        }
-        ensureCallingThread();
-        if (mCallbacks.get(callback) != null) {
-            Log.w(TAG, "callback is already added. Ignoring.");
-            return;
-        }
-        mCallbacks.put(callback, executor);
+        return MediaPlayerBase.PLAYER_STATE_ERROR;
     }
 
     @Override
-    public void unregisterPlayerEventCallback_impl(PlayerEventCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback shouldn't be null");
+    public long getPosition_impl() {
+        final MediaPlayerBase player = mPlayer;
+        if (player != null) {
+            return mPlayer.getCurrentPosition();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        ensureCallingThread();
-        mCallbacks.remove(callback);
+        return MediaPlayerBase.UNKNOWN_TIME;
+    }
+
+    @Override
+    public long getBufferedPosition_impl() {
+        final MediaPlayerBase player = mPlayer;
+        if (player != null) {
+            return mPlayer.getBufferedPosition();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
+        return MediaPlayerBase.UNKNOWN_TIME;
     }
 
     @Override
     public void notifyError_impl(int errorCode, Bundle extras) {
-        // TODO(jaewan): Implement
+        mSessionStub.notifyError(errorCode, extras);
     }
 
     ///////////////////////////////////////////////////
@@ -706,7 +712,7 @@
     private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
             List<MediaItem2> list, MediaMetadata2 metadata) {
         if (playlistAgent != mPlaylistAgent) {
-            // Ignore calls from the old agent. Ignore.
+            // Ignore calls from the old agent.
             return;
         }
         mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
@@ -716,27 +722,39 @@
     private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
             MediaMetadata2 metadata) {
         if (playlistAgent != mPlaylistAgent) {
-            // Ignore calls from the old agent. Ignore.
+            // Ignore calls from the old agent.
             return;
         }
         mCallback.onPlaylistMetadataChanged(mInstance, playlistAgent, metadata);
         mSessionStub.notifyPlaylistMetadataChangedNotLocked(metadata);
     }
 
+    private void notifyRepeatModeChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+            int repeatMode) {
+        if (playlistAgent != mPlaylistAgent) {
+            // Ignore calls from the old agent.
+            return;
+        }
+        mCallback.onRepeatModeChanged(mInstance, playlistAgent, repeatMode);
+        mSessionStub.notifyRepeatModeChangedNotLocked(repeatMode);
+    }
+
+    private void notifyShuffleModeChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+            int shuffleMode) {
+        if (playlistAgent != mPlaylistAgent) {
+            // Ignore calls from the old agent.
+            return;
+        }
+        mCallback.onShuffleModeChanged(mInstance, playlistAgent, shuffleMode);
+        mSessionStub.notifyShuffleModeChangedNotLocked(shuffleMode);
+    }
+
     private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) {
-        ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
-        MediaPlayerBase player;
-        synchronized (mLock) {
-            player = mPlayer;
-            callbacks.putAll(mCallbacks);
-        }
-        // Notify to callbacks added directly to this session
-        for (int i = 0; i < callbacks.size(); i++) {
-            final PlayerEventCallback callback = callbacks.keyAt(i);
-            final Executor executor = callbacks.valueAt(i);
-            // TODO: Uncomment or remove
-            //executor.execute(() -> callback.onPlaybackStateChanged(state));
-        }
+        final MediaPlayerBase player = mPlayer;
+        // TODO(jaewan): (Can be post-P) Find better way for player.getPlayerState() //
+        //               In theory, Session.getXXX() may not be the same as Player.getXXX()
+        //               and we should notify information of the session.getXXX() instead of
+        //               player.getXXX()
         // Notify to controllers as well.
         final int state = player.getPlayerState();
         if (state != oldPlayer.getPlayerState()) {
@@ -760,21 +778,6 @@
         }
     }
 
-    private void notifyErrorNotLocked(String mediaId, int what, int extra) {
-        ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
-        synchronized (mLock) {
-            callbacks.putAll(mCallbacks);
-        }
-        // Notify to callbacks added directly to this session
-        for (int i = 0; i < callbacks.size(); i++) {
-            final PlayerEventCallback callback = callbacks.keyAt(i);
-            final Executor executor = callbacks.valueAt(i);
-            // TODO: Uncomment or remove
-            //executor.execute(() -> callback.onError(mediaId, what, extra));
-        }
-        // TODO(jaewan): Notify to controllers as well.
-    }
-
     Context getContext() {
         return mContext;
     }
@@ -827,28 +830,32 @@
         @Override
         public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
             MediaSession2Impl session = getSession();
-            if (session == null) {
+            if (session == null || dsd == null) {
                 return;
             }
             session.getCallbackExecutor().execute(() -> {
-                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+                MediaItem2 item = getMediaItem(session, dsd);
+                if (item == null) {
+                    return;
+                }
+                session.getCallback().onCurrentMediaItemChanged(session.getInstance(), mpb, item);
                 // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
-                session.getCallback().onCurrentMediaItemChanged(
-                        session.getInstance(), mpb, null /* MediaItem */);
             });
         }
 
         @Override
         public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
             MediaSession2Impl session = getSession();
-            if (session == null) {
+            if (session == null || dsd == null) {
                 return;
             }
             session.getCallbackExecutor().execute(() -> {
-                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+                MediaItem2 item = getMediaItem(session, dsd);
+                if (item == null) {
+                    return;
+                }
+                session.getCallback().onMediaPrepared(session.getInstance(), mpb, item);
                 // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
-                session.getCallback().onMediaPrepared(
-                        session.getInstance(), mpb, null /* MediaItem */);
             });
         }
 
@@ -867,14 +874,17 @@
         @Override
         public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
             MediaSession2Impl session = getSession();
-            if (session == null) {
+            if (session == null || dsd == null) {
                 return;
             }
             session.getCallbackExecutor().execute(() -> {
-                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
-                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+                MediaItem2 item = getMediaItem(session, dsd);
+                if (item == null) {
+                    return;
+                }
                 session.getCallback().onBufferingStateChanged(
-                        session.getInstance(), mpb, null /* MediaItem */, state);
+                        session.getInstance(), mpb, item, state);
+                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
             });
         }
 
@@ -885,6 +895,24 @@
             }
             return session;
         }
+
+        private MediaItem2 getMediaItem(MediaSession2Impl session, DataSourceDesc dsd) {
+            MediaPlaylistAgent agent = session.getPlaylistAgent();
+            if (agent == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "Session is closed", new IllegalStateException());
+                }
+                return null;
+            }
+            MediaItem2 item = agent.getMediaItem(dsd);
+            if (item == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "Could not find matching item for dsd=" + dsd,
+                            new NoSuchElementException());
+                }
+            }
+            return item;
+        }
     }
 
     private static class MyPlaylistEventCallback extends PlaylistEventCallback {
@@ -915,15 +943,21 @@
         }
 
         @Override
-        public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
-            super.onShuffleModeChanged(playlistAgent, shuffleMode);
-            // TODO(jaewan): Handle this (b/74118768)
+        public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            session.notifyRepeatModeChangedOnExecutor(playlistAgent, repeatMode);
         }
 
         @Override
-        public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
-            super.onRepeatModeChanged(playlistAgent, repeatMode);
-            // TODO(jaewan): Handle this (b/74118768)
+        public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            session.notifyShuffleModeChangedOnExecutor(playlistAgent, shuffleMode);
         }
     }
 
@@ -1268,76 +1302,6 @@
         }
     }
 
-    public static class PlaylistParamsImpl implements PlaylistParamsProvider {
-        /**
-         * Keys used for converting a PlaylistParams object to a bundle object and vice versa.
-         */
-        private static final String KEY_REPEAT_MODE =
-                "android.media.session2.playlistparams2.repeat_mode";
-        private static final String KEY_SHUFFLE_MODE =
-                "android.media.session2.playlistparams2.shuffle_mode";
-        private static final String KEY_MEDIA_METADATA2_BUNDLE =
-                "android.media.session2.playlistparams2.metadata2_bundle";
-
-        private Context mContext;
-        private PlaylistParams mInstance;
-        private @RepeatMode int mRepeatMode;
-        private @ShuffleMode int mShuffleMode;
-        private MediaMetadata2 mPlaylistMetadata;
-
-        public PlaylistParamsImpl(Context context, PlaylistParams instance,
-                @RepeatMode int repeatMode, @ShuffleMode int shuffleMode,
-                MediaMetadata2 playlistMetadata) {
-            // TODO(jaewan): Sanity check
-            mContext = context;
-            mInstance = instance;
-            mRepeatMode = repeatMode;
-            mShuffleMode = shuffleMode;
-            mPlaylistMetadata = playlistMetadata;
-        }
-
-        public @RepeatMode int getRepeatMode_impl() {
-            return mRepeatMode;
-        }
-
-        public @ShuffleMode int getShuffleMode_impl() {
-            return mShuffleMode;
-        }
-
-        public MediaMetadata2 getPlaylistMetadata_impl() {
-            return mPlaylistMetadata;
-        }
-
-        @Override
-        public Bundle toBundle_impl() {
-            Bundle bundle = new Bundle();
-            bundle.putInt(KEY_REPEAT_MODE, mRepeatMode);
-            bundle.putInt(KEY_SHUFFLE_MODE, mShuffleMode);
-            if (mPlaylistMetadata != null) {
-                bundle.putBundle(KEY_MEDIA_METADATA2_BUNDLE, mPlaylistMetadata.toBundle());
-            }
-            return bundle;
-        }
-
-        public static PlaylistParams fromBundle(Context context, Bundle bundle) {
-            if (bundle == null) {
-                return null;
-            }
-            if (!bundle.containsKey(KEY_REPEAT_MODE) || !bundle.containsKey(KEY_SHUFFLE_MODE)) {
-                return null;
-            }
-
-            Bundle metadataBundle = bundle.getBundle(KEY_MEDIA_METADATA2_BUNDLE);
-            MediaMetadata2 metadata = metadataBundle == null
-                    ? null : MediaMetadata2.fromBundle(context, metadataBundle);
-
-            return new PlaylistParams(context,
-                    bundle.getInt(KEY_REPEAT_MODE),
-                    bundle.getInt(KEY_SHUFFLE_MODE),
-                    metadata);
-        }
-    }
-
     public static class CommandButtonImpl implements CommandButtonProvider {
         private static final String KEY_COMMAND
                 = "android.media.media_session2.command_button.command";
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index 4ec0da9..caf834a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -22,13 +22,11 @@
 import android.media.MediaItem2;
 import android.media.MediaLibraryService2.LibraryRoot;
 import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParams;
 import android.media.Rating2;
 import android.media.VolumeProvider2;
 import android.net.Uri;
@@ -391,13 +389,13 @@
                 //       use thread poll for incoming calls.
                 final int playerState = session.getInstance().getPlayerState();
                 final long positionEventTimeMs = System.currentTimeMillis();
-                final long positionMs = session.getInstance().getCurrentPosition();
+                final long positionMs = session.getInstance().getPosition();
                 final float playbackSpeed = session.getInstance().getPlaybackSpeed();
                 final long bufferedPositionMs = session.getInstance().getBufferedPosition();
                 final Bundle playbackInfoBundle = ((MediaController2Impl.PlaybackInfoImpl)
                         session.getPlaybackInfo().getProvider()).toBundle();
-                final PlaylistParams params = session.getInstance().getPlaylistParams();
-                final Bundle paramsBundle = (params != null) ? params.toBundle() : null;
+                final int repeatMode = session.getInstance().getRepeatMode();
+                final int shuffleMode = session.getInstance().getShuffleMode();
                 final PendingIntent sessionActivity = session.getSessionActivity();
                 final List<MediaItem2> playlist =
                         allowedCommands.hasCommand(MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST)
@@ -427,8 +425,8 @@
                 try {
                     caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
                             playerState, positionEventTimeMs, positionMs, playbackSpeed,
-                            bufferedPositionMs, playbackInfoBundle, paramsBundle, playlistBundle,
-                            sessionActivity);
+                            bufferedPositionMs, playbackInfoBundle, repeatMode, shuffleMode,
+                            playlistBundle, sessionActivity);
                 } catch (RemoteException e) {
                     // Controller may be died prematurely.
                     // TODO(jaewan): Handle here.
@@ -507,14 +505,6 @@
                 case MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO:
                     session.getInstance().seekTo(args.getLong(ARGUMENT_KEY_POSITION));
                     break;
-                    // TODO(jaewan): Remove (b/74116823)
-                    /*
-                case MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS:
-                    session.getInstance().setPlaylistParams(
-                            PlaylistParams.fromBundle(session.getContext(),
-                                    args.getBundle(ARGUMENT_KEY_PLAYLIST_PARAMS)));
-                    break;
-                    */
                 default:
                     // TODO(jaewan): Resend unknown (new) commands through the custom command.
             }
@@ -741,6 +731,21 @@
                 });
     }
 
+    @Override
+    public void setRepeatMode(IMediaController2 caller, int repeatMode) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE,
+                (session, controller) -> {
+                    session.getInstance().setRepeatMode(repeatMode);
+                });
+    }
+
+    @Override
+    public void setShuffleMode(IMediaController2 caller, int shuffleMode) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE,
+                (session, controller) -> {
+                    session.getInstance().setShuffleMode(shuffleMode);
+                });
+    }
 
     //////////////////////////////////////////////////////////////////////////////////////////////
     // AIDL methods for LibrarySession overrides
@@ -985,6 +990,18 @@
                 });
     }
 
+    public void notifyRepeatModeChangedNotLocked(int repeatMode) {
+        notifyAll((unused, iController) -> {
+            iController.onRepeatModeChanged(repeatMode);
+        });
+    }
+
+    public void notifyShuffleModeChangedNotLocked(int shuffleMode) {
+        notifyAll((unused, iController) -> {
+            iController.onShuffleModeChanged(shuffleMode);
+        });
+    }
+
     public void notifyPlaybackInfoChanged(MediaController2.PlaybackInfo playbackInfo) {
         final Bundle playbackInfoBundle =
                 ((MediaController2Impl.PlaybackInfoImpl) playbackInfo.getProvider()).toBundle();
@@ -1027,6 +1044,12 @@
         });
     }
 
+    public void notifyError(int errorCode, Bundle extras) {
+        notifyAll((unused, iController) -> {
+            iController.onError(errorCode, extras);
+        });
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////////////
     // APIs for MediaLibrarySessionImpl
     //////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java b/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java
new file mode 100644
index 0000000..6805dad
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java
@@ -0,0 +1,433 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.DataSourceDesc;
+import android.media.MediaItem2;
+import android.media.MediaMetadata2;
+import android.media.MediaPlayerBase;
+import android.media.MediaPlaylistAgent;
+import android.media.MediaSession2;
+import android.media.MediaSession2.OnDataSourceMissingHelper;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class SessionPlaylistAgent extends MediaPlaylistAgent {
+    private static final String TAG = "SessionPlaylistAgent";
+    @VisibleForTesting
+    static final int END_OF_PLAYLIST = -1;
+    @VisibleForTesting
+    static final int NO_VALID_ITEMS = -2;
+
+    private final PlayItem mEopPlayItem = new PlayItem(END_OF_PLAYLIST, null);
+
+    private final Object mLock = new Object();
+    private final MediaSession2 mSession;
+
+    // TODO: Set data sources properly into mPlayer (b/74090741)
+    @GuardedBy("mLock")
+    private MediaPlayerBase mPlayer;
+    @GuardedBy("mLock")
+    private OnDataSourceMissingHelper mDsdHelper;
+
+    // TODO: Check if having the same item is okay (b/74090741)
+    @GuardedBy("mLock")
+    private ArrayList<MediaItem2> mPlaylist = new ArrayList<>();
+    @GuardedBy("mLock")
+    private ArrayList<MediaItem2> mShuffledList = new ArrayList<>();
+    @GuardedBy("mLock")
+    private Map<MediaItem2, DataSourceDesc> mItemDsdMap = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private MediaMetadata2 mMetadata;
+    @GuardedBy("mLock")
+    private int mRepeatMode;
+    @GuardedBy("mLock")
+    private int mShuffleMode;
+    @GuardedBy("mLock")
+    private PlayItem mCurrent;
+
+    private class PlayItem {
+        int shuffledIdx;
+        DataSourceDesc dsd;
+        MediaItem2 mediaItem;
+
+        PlayItem(int shuffledIdx) {
+            this(shuffledIdx, null);
+        }
+
+        PlayItem(int shuffledIdx, DataSourceDesc dsd) {
+            this.shuffledIdx = shuffledIdx;
+            if (shuffledIdx >= 0) {
+                this.mediaItem = mShuffledList.get(shuffledIdx);
+                if (dsd == null) {
+                    synchronized (mLock) {
+                        this.dsd = retrieveDataSourceDescLocked(this.mediaItem);
+                    }
+                } else {
+                    this.dsd = dsd;
+                }
+            }
+        }
+
+        boolean isValid() {
+            if (this == mEopPlayItem) {
+                return true;
+            }
+            if (mediaItem == null) {
+                return false;
+            }
+            if (dsd == null) {
+                return false;
+            }
+            if (shuffledIdx >= mShuffledList.size()) {
+                return false;
+            }
+            if (mediaItem != mShuffledList.get(shuffledIdx)) {
+                return false;
+            }
+            if (mediaItem.getDataSourceDesc() != null
+                    && !mediaItem.getDataSourceDesc().equals(dsd)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    public SessionPlaylistAgent(@NonNull Context context, @NonNull MediaSession2 session,
+            @NonNull MediaPlayerBase player) {
+        super(context);
+        if (session == null) {
+            throw new IllegalArgumentException("session shouldn't be null");
+        }
+        if (player == null) {
+            throw new IllegalArgumentException("player shouldn't be null");
+        }
+        mSession = session;
+        mPlayer = player;
+    }
+
+    public void setPlayer(MediaPlayerBase player) {
+        if (player == null) {
+            throw new IllegalArgumentException("player shouldn't be null");
+        }
+        synchronized (mLock) {
+            mPlayer = player;
+        }
+    }
+
+    public void setOnDataSourceMissingHelper(OnDataSourceMissingHelper helper) {
+        synchronized (mLock) {
+            mDsdHelper = helper;
+        }
+    }
+
+    @Override
+    public @Nullable List<MediaItem2> getPlaylist() {
+        synchronized (mLock) {
+            return Collections.unmodifiableList(mPlaylist);
+        }
+    }
+
+    @Override
+    public void setPlaylist(@NonNull List<MediaItem2> list,
+            @Nullable MediaMetadata2 metadata) {
+        if (list == null) {
+            throw new IllegalArgumentException("list shouldn't be null");
+        }
+
+        synchronized (mLock) {
+            mItemDsdMap.clear();
+
+            mPlaylist.clear();
+            mPlaylist.addAll(list);
+            applyShuffleModeLocked();
+
+            mMetadata = metadata;
+            mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
+        }
+        notifyPlaylistChanged();
+    }
+
+    @Override
+    public @Nullable MediaMetadata2 getPlaylistMetadata() {
+        return mMetadata;
+    }
+
+    @Override
+    public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
+        synchronized (mLock) {
+            if (metadata == mMetadata) {
+                return;
+            }
+            mMetadata = metadata;
+        }
+        notifyPlaylistMetadataChanged();
+    }
+
+    @Override
+    public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        synchronized (mLock) {
+            index = clamp(index, mPlaylist.size());
+            int shuffledIdx = index;
+            mPlaylist.add(index, item);
+            if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
+                mShuffledList.add(index, item);
+            } else {
+                // Add the item in random position of mShuffledList.
+                shuffledIdx = ThreadLocalRandom.current().nextInt(mShuffledList.size() + 1);
+                mShuffledList.add(shuffledIdx, item);
+            }
+            if (!hasValidItem()) {
+                mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
+            } else {
+                updateCurrentIfNeededLocked();
+            }
+        }
+        notifyPlaylistChanged();
+    }
+
+    @Override
+    public void removePlaylistItem(@NonNull MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        synchronized (mLock) {
+            if (!mPlaylist.remove(item)) {
+                return;
+            }
+            mShuffledList.remove(item);
+            mItemDsdMap.remove(item);
+            updateCurrentIfNeededLocked();
+        }
+        notifyPlaylistChanged();
+    }
+
+    @Override
+    public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        synchronized (mLock) {
+            if (mPlaylist.size() <= 0) {
+                return;
+            }
+            index = clamp(index, mPlaylist.size() - 1);
+            int shuffledIdx = mShuffledList.indexOf(mPlaylist.get(index));
+            mItemDsdMap.remove(mShuffledList.get(shuffledIdx));
+            mShuffledList.set(shuffledIdx, item);
+            mPlaylist.set(index, item);
+            if (!hasValidItem()) {
+                mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
+            } else {
+                updateCurrentIfNeededLocked();
+            }
+        }
+        notifyPlaylistChanged();
+    }
+
+    @Override
+    public void skipToPlaylistItem(@NonNull MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        synchronized (mLock) {
+            if (!hasValidItem() || item.equals(mCurrent.mediaItem)) {
+                return;
+            }
+            int shuffledIdx = mShuffledList.indexOf(item);
+            if (shuffledIdx < 0) {
+                return;
+            }
+            mCurrent = new PlayItem(shuffledIdx);
+            updateCurrentIfNeededLocked();
+        }
+    }
+
+    @Override
+    public void skipToPreviousItem() {
+        synchronized (mLock) {
+            if (!hasValidItem()) {
+                return;
+            }
+            PlayItem prev = getNextValidPlayItemLocked(mCurrent.shuffledIdx, -1);
+            if (prev != mEopPlayItem) {
+                mCurrent = prev;
+            }
+            updateCurrentIfNeededLocked();
+       }
+    }
+
+    @Override
+    public void skipToNextItem() {
+        synchronized (mLock) {
+            if (!hasValidItem()) {
+                return;
+            }
+            PlayItem next = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
+            if (next != mEopPlayItem) {
+                mCurrent = next;
+            }
+            updateCurrentIfNeededLocked();
+        }
+    }
+
+    @Override
+    public int getRepeatMode() {
+        return mRepeatMode;
+    }
+
+    @Override
+    public void setRepeatMode(int repeatMode) {
+        if (repeatMode < MediaPlaylistAgent.REPEAT_MODE_NONE
+                || repeatMode > MediaPlaylistAgent.REPEAT_MODE_GROUP) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mRepeatMode == repeatMode) {
+                return;
+            }
+            mRepeatMode = repeatMode;
+        }
+        notifyRepeatModeChanged();
+    }
+
+    @Override
+    public int getShuffleMode() {
+        return mShuffleMode;
+    }
+
+    @Override
+    public void setShuffleMode(int shuffleMode) {
+        if (shuffleMode < MediaPlaylistAgent.SHUFFLE_MODE_NONE
+                || shuffleMode > MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mShuffleMode == shuffleMode) {
+                return;
+            }
+            mShuffleMode = shuffleMode;
+            applyShuffleModeLocked();
+        }
+        notifyShuffleModeChanged();
+    }
+
+    @VisibleForTesting
+    int getCurShuffledIndex() {
+        return hasValidItem() ? mCurrent.shuffledIdx : NO_VALID_ITEMS;
+    }
+
+    private boolean hasValidItem() {
+        return mCurrent != null;
+    }
+
+    private DataSourceDesc retrieveDataSourceDescLocked(MediaItem2 item) {
+        DataSourceDesc dsd = item.getDataSourceDesc();
+        if (dsd != null) {
+            mItemDsdMap.put(item, dsd);
+            return dsd;
+        }
+        dsd = mItemDsdMap.get(item);
+        if (dsd != null) {
+            return dsd;
+        }
+        OnDataSourceMissingHelper helper = mDsdHelper;
+        if (helper != null) {
+            // TODO: Do not call onDataSourceMissing with the lock (b/74090741).
+            dsd = helper.onDataSourceMissing(mSession, item);
+            if (dsd != null) {
+                mItemDsdMap.put(item, dsd);
+            }
+        }
+        return dsd;
+    }
+
+    private PlayItem getNextValidPlayItemLocked(int curShuffledIdx, int direction) {
+        int size = mPlaylist.size();
+        if (curShuffledIdx == END_OF_PLAYLIST) {
+            curShuffledIdx = (direction > 0) ? -1 : size;
+        }
+        for (int i = 0; i < size; i++) {
+            curShuffledIdx += direction;
+            if (curShuffledIdx < 0 || curShuffledIdx >= mPlaylist.size()) {
+                if (mRepeatMode == REPEAT_MODE_NONE) {
+                    return (i == size - 1) ? null : mEopPlayItem;
+                } else {
+                    curShuffledIdx = curShuffledIdx < 0 ? mPlaylist.size() - 1 : 0;
+                }
+            }
+            DataSourceDesc dsd = retrieveDataSourceDescLocked(mShuffledList.get(curShuffledIdx));
+            if (dsd != null) {
+                return new PlayItem(curShuffledIdx, dsd);
+            }
+        }
+        return null;
+    }
+
+    private void updateCurrentIfNeededLocked() {
+        if (!hasValidItem() || mCurrent.isValid()) {
+            return;
+        }
+        int shuffledIdx = mShuffledList.indexOf(mCurrent.mediaItem);
+        if (shuffledIdx >= 0) {
+            // Added an item.
+            mCurrent.shuffledIdx = shuffledIdx;
+            return;
+        }
+
+        if (mCurrent.shuffledIdx >= mShuffledList.size()) {
+            mCurrent = getNextValidPlayItemLocked(mShuffledList.size() - 1, 1);
+        } else {
+            mCurrent.mediaItem = mShuffledList.get(mCurrent.shuffledIdx);
+            if (retrieveDataSourceDescLocked(mCurrent.mediaItem) == null) {
+                mCurrent = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
+            }
+        }
+        return;
+    }
+
+    private void applyShuffleModeLocked() {
+        mShuffledList.clear();
+        mShuffledList.addAll(mPlaylist);
+        if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_ALL
+                || mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
+            Collections.shuffle(mShuffledList);
+        }
+    }
+
+    // Clamps value to [0, size]
+    private static int clamp(int value, int size) {
+        if (value < 0) {
+            return 0;
+        }
+        return (value > size) ? size : value;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 7f225de..05a54d5 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -18,8 +18,7 @@
 
 import android.app.Notification;
 import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
+import android.content.pm.ApplicationInfo;
 import android.media.MediaBrowser2;
 import android.media.MediaBrowser2.BrowserCallback;
 import android.media.MediaController2;
@@ -35,7 +34,6 @@
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParams;
 import android.media.MediaSession2.SessionCallback;
 import android.media.MediaSessionService2;
 import android.media.MediaSessionService2.MediaNotification;
@@ -52,7 +50,6 @@
 import android.media.update.MediaSession2Provider;
 import android.media.update.MediaSession2Provider.BuilderBaseProvider;
 import android.media.update.MediaSession2Provider.CommandButtonProvider.BuilderProvider;
-import android.media.update.MediaSession2Provider.PlaylistParamsProvider;
 import android.media.update.MediaSessionService2Provider;
 import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
 import android.media.update.SessionToken2Provider;
@@ -76,7 +73,6 @@
 import com.android.media.MediaMetadata2Impl;
 import com.android.media.MediaPlaylistAgentImpl;
 import com.android.media.MediaSession2Impl;
-import com.android.media.MediaSession2Impl.PlaylistParamsImpl;
 import com.android.media.MediaSessionService2Impl;
 import com.android.media.Rating2Impl;
 import com.android.media.SessionToken2Impl;
@@ -86,10 +82,11 @@
 
 import java.util.concurrent.Executor;
 
-public class ApiFactory implements StaticProvider {
-    public static Object initialize(Resources libResources, Theme libTheme)
-            throws ReflectiveOperationException {
-        ApiHelper.initialize(libResources, libTheme);
+public final class ApiFactory implements StaticProvider {
+    private ApiFactory() { }
+
+    public static ApiFactory initialize(ApplicationInfo updatableInfo) {
+        ApiHelper.initialize(updatableInfo);
         return new ApiFactory();
     }
 
@@ -141,19 +138,6 @@
     }
 
     @Override
-    public PlaylistParamsProvider createMediaSession2PlaylistParams(Context context,
-            PlaylistParams playlistParams, int repeatMode, int shuffleMode,
-            MediaMetadata2 playlistMetadata) {
-        return new PlaylistParamsImpl(context, playlistParams, repeatMode, shuffleMode,
-                playlistMetadata);
-    }
-
-    @Override
-    public PlaylistParams fromBundle_PlaylistParams(Context context, Bundle bundle) {
-        return PlaylistParamsImpl.fromBundle(context, bundle);
-    }
-
-    @Override
     public BuilderProvider createMediaSession2CommandButtonBuilder(Context context,
             MediaSession2.CommandButton.Builder instance) {
         return new MediaSession2Impl.CommandButtonImpl.BuilderImpl(context, instance);
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index 7018844..ad8bb48 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -19,9 +19,12 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.XmlResourceParser;
+import android.support.annotation.GuardedBy;
 import android.support.v4.widget.Space;
 import android.support.v7.widget.ButtonBarLayout;
 import android.util.AttributeSet;
@@ -35,45 +38,47 @@
 import com.android.support.mediarouter.app.MediaRouteVolumeSlider;
 import com.android.support.mediarouter.app.OverlayListView;
 
-public class ApiHelper {
-    private static ApiHelper sInstance;
-    private final Resources mLibResources;
-    private final Theme mLibTheme;
+public final class ApiHelper {
+    private static ApplicationInfo sUpdatableInfo;
 
-    public static ApiHelper getInstance() {
-        return sInstance;
-    }
+    @GuardedBy("this")
+    private static Theme sLibTheme;
 
-    static void initialize(Resources libResources, Theme libTheme) {
-        if (sInstance == null) {
-            sInstance = new ApiHelper(libResources, libTheme);
+    private ApiHelper() { }
+
+    static void initialize(ApplicationInfo updatableInfo) {
+        if (sUpdatableInfo != null) {
+            throw new IllegalStateException("initialize should only be called once");
         }
+
+        sUpdatableInfo = updatableInfo;
     }
 
-    private ApiHelper(Resources libResources, Theme libTheme) {
-        mLibResources = libResources;
-        mLibTheme = libTheme;
+    public static Resources getLibResources(Context context) {
+        return getLibTheme(context).getResources();
     }
 
-    public static Resources getLibResources() {
-        return sInstance.mLibResources;
+    public static Theme getLibTheme(Context context) {
+        if (sLibTheme != null) return sLibTheme;
+
+        return getLibThemeSynchronized(context);
     }
 
-    public static Theme getLibTheme() {
-        return sInstance.mLibTheme;
-    }
-
-    public static Theme getLibTheme(int themeId) {
-        Theme theme = sInstance.mLibResources.newTheme();
+    public static Theme getLibTheme(Context context, int themeId) {
+        Theme theme = getLibResources(context).newTheme();
         theme.applyStyle(themeId, true);
         return theme;
     }
 
     public static LayoutInflater getLayoutInflater(Context context) {
-        return getLayoutInflater(context, getLibTheme());
+        return getLayoutInflater(context, null);
     }
 
     public static LayoutInflater getLayoutInflater(Context context, Theme theme) {
+        if (theme == null) {
+            theme = getLibTheme(context);
+        }
+
         // TODO (b/72975976): Avoid to use ContextThemeWrapper with app context and lib theme.
         LayoutInflater layoutInflater = LayoutInflater.from(context).cloneInContext(
                 new ContextThemeWrapper(context, theme));
@@ -106,7 +111,7 @@
     }
 
     public static View inflateLibLayout(Context context, int libResId) {
-        return inflateLibLayout(context, getLibTheme(), libResId, null, false);
+        return inflateLibLayout(context, getLibTheme(context), libResId, null, false);
     }
 
     public static View inflateLibLayout(Context context, Theme theme, int libResId) {
@@ -115,8 +120,23 @@
 
     public static View inflateLibLayout(Context context, Theme theme, int libResId,
             @Nullable ViewGroup root, boolean attachToRoot) {
-        try (XmlResourceParser parser = getLibResources().getLayout(libResId)) {
+        try (XmlResourceParser parser = getLibResources(context).getLayout(libResId)) {
             return getLayoutInflater(context, theme).inflate(parser, root, attachToRoot);
         }
     }
+
+    private static synchronized Theme getLibThemeSynchronized(Context context) {
+        if (sLibTheme != null) return sLibTheme;
+
+        if (sUpdatableInfo == null) {
+            throw new IllegalStateException("initialize hasn't been called yet");
+        }
+
+        try {
+            return sLibTheme = context.getPackageManager()
+                    .getResourcesForApplication(sUpdatableInfo).newTheme();
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
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 fa94a81..fde8a63 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
@@ -130,7 +130,7 @@
         mRouter = MediaRouter.getInstance(context);
         mCallback = new MediaRouterCallback();
 
-        Resources.Theme theme = ApiHelper.getLibResources().newTheme();
+        Resources.Theme theme = ApiHelper.getLibResources(context).newTheme();
         theme.applyStyle(MediaRouterThemeHelper.getRouterThemeId(context), true);
         TypedArray a = theme.obtainStyledAttributes(attrs,
                 R.styleable.MediaRouteButton, defStyleAttr, 0);
@@ -304,7 +304,8 @@
      */
     void setCheatSheetEnabled(boolean enable) {
         TooltipCompat.setTooltipText(this, enable
-                ? ApiHelper.getLibResources().getString(R.string.mr_button_content_description)
+                ? ApiHelper.getLibResources(getContext())
+                    .getString(R.string.mr_button_content_description)
                 : null);
     }
 
@@ -547,7 +548,7 @@
         } else {
             resId = R.string.mr_cast_button_disconnected;
         }
-        setContentDescription(ApiHelper.getLibResources().getString(resId));
+        setContentDescription(ApiHelper.getLibResources(getContext()).getString(resId));
     }
 
     private final class MediaRouterCallback extends MediaRouter.Callback {
@@ -604,7 +605,7 @@
 
         @Override
         protected Drawable doInBackground(Void... params) {
-            return ApiHelper.getLibResources().getDrawable(mResId);
+            return ApiHelper.getLibResources(getContext()).getDrawable(mResId);
         }
 
         @Override
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
index 6e70eaf..cac64d9 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
@@ -99,8 +99,8 @@
 
     public MediaRouteChooserDialog(Context context, int theme) {
         // TODO (b/72975976): Avoid to use ContextThemeWrapper with app context and lib theme.
-        super(new ContextThemeWrapper(context,
-                ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(context))), theme);
+        super(new ContextThemeWrapper(context, ApiHelper.getLibTheme(context,
+                MediaRouterThemeHelper.getRouterThemeId(context))), theme);
         context = getContext();
 
         mRouter = MediaRouter.getInstance(context);
@@ -187,8 +187,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        setContentView(ApiHelper.inflateLibLayout(getContext(),
-                ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(getContext())),
+        setContentView(ApiHelper.inflateLibLayout(getContext(), ApiHelper.getLibTheme(getContext(),
+                MediaRouterThemeHelper.getRouterThemeId(getContext())),
                 R.layout.mr_chooser_dialog));
 
         mRoutes = new ArrayList<>();
@@ -206,7 +206,7 @@
      * Sets the width of the dialog. Also called when configuration changes.
      */
     void updateLayout() {
-        getWindow().setLayout(MediaRouteDialogHelper.getDialogWidth(),
+        getWindow().setLayout(MediaRouteDialogHelper.getDialogWidth(getContext()),
                 ViewGroup.LayoutParams.WRAP_CONTENT);
     }
 
@@ -263,7 +263,7 @@
         public RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes) {
             super(context, 0, routes);
 
-            TypedArray styledAttributes = ApiHelper.getLibTheme(
+            TypedArray styledAttributes = ApiHelper.getLibTheme(context,
                     MediaRouterThemeHelper.getRouterThemeId(context)).obtainStyledAttributes(
                             new int[] {
                                 R.attr.mediaRouteDefaultIconDrawable,
@@ -294,7 +294,7 @@
             View view = convertView;
             if (view == null) {
                 view = ApiHelper.inflateLibLayout(getContext(),
-                        ApiHelper.getLibTheme(
+                        ApiHelper.getLibTheme(getContext(),
                                 MediaRouterThemeHelper.getRouterThemeId(getContext())),
                         R.layout.mr_chooser_list_item, parent, false);
             }
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
index 269a6e9..060cfca 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
@@ -206,8 +206,8 @@
 
     public MediaRouteControllerDialog(Context context, int theme) {
         // TODO (b/72975976): Avoid to use ContextThemeWrapper with app context and lib theme.
-        super(new ContextThemeWrapper(context,
-                ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(context))), theme);
+        super(new ContextThemeWrapper(context, ApiHelper.getLibTheme(context,
+                MediaRouterThemeHelper.getRouterThemeId(context))), theme);
         mContext = getContext();
 
         mControllerCallback = new MediaControllerCallback();
@@ -215,7 +215,7 @@
         mCallback = new MediaRouterCallback();
         mRoute = mRouter.getSelectedRoute();
         setMediaSession(mRouter.getMediaSessionToken());
-        mVolumeGroupListPaddingTop = ApiHelper.getLibResources().getDimensionPixelSize(
+        mVolumeGroupListPaddingTop = ApiHelper.getLibResources(context).getDimensionPixelSize(
                 R.dimen.mr_controller_volume_group_list_padding_top);
         mAccessibilityManager =
                 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -334,7 +334,7 @@
         getWindow().setBackgroundDrawableResource(android.R.color.transparent);
 
         setContentView(ApiHelper.inflateLibLayout(mContext,
-                ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(mContext)),
+                ApiHelper.getLibTheme(mContext, MediaRouterThemeHelper.getRouterThemeId(mContext)),
                 R.layout.mr_controller_material_dialog_b));
 
         // Remove the neutral button.
@@ -359,13 +359,13 @@
         int color = MediaRouterThemeHelper.getButtonTextColor(mContext);
         mDisconnectButton = findViewById(BUTTON_DISCONNECT_RES_ID);
         mDisconnectButton.setText(
-                ApiHelper.getLibResources().getString(R.string.mr_controller_disconnect));
+                ApiHelper.getLibResources(mContext).getString(R.string.mr_controller_disconnect));
         mDisconnectButton.setTextColor(color);
         mDisconnectButton.setOnClickListener(listener);
 
         mStopCastingButton = findViewById(BUTTON_STOP_RES_ID);
         mStopCastingButton.setText(
-                ApiHelper.getLibResources().getString(R.string.mr_controller_stop_casting));
+                ApiHelper.getLibResources(mContext).getString(R.string.mr_controller_stop_casting));
         mStopCastingButton.setTextColor(color);
         mStopCastingButton.setOnClickListener(listener);
 
@@ -440,11 +440,11 @@
             }
         });
         loadInterpolator();
-        mGroupListAnimationDurationMs = ApiHelper.getLibResources().getInteger(
+        mGroupListAnimationDurationMs = ApiHelper.getLibResources(mContext).getInteger(
                 R.integer.mr_controller_volume_group_list_animation_duration_ms);
-        mGroupListFadeInDurationMs = ApiHelper.getLibResources().getInteger(
+        mGroupListFadeInDurationMs = ApiHelper.getLibResources(mContext).getInteger(
                 R.integer.mr_controller_volume_group_list_fade_in_duration_ms);
-        mGroupListFadeOutDurationMs = ApiHelper.getLibResources().getInteger(
+        mGroupListFadeOutDurationMs = ApiHelper.getLibResources(mContext).getInteger(
                 R.integer.mr_controller_volume_group_list_fade_out_duration_ms);
 
         mCustomControlView = onCreateMediaControlView(savedInstanceState);
@@ -460,13 +460,13 @@
      * Sets the width of the dialog. Also called when configuration changes.
      */
     void updateLayout() {
-        int width = MediaRouteDialogHelper.getDialogWidth();
+        int width = MediaRouteDialogHelper.getDialogWidth(mContext);
         getWindow().setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT);
 
         View decorView = getWindow().getDecorView();
         mDialogContentWidth = width - decorView.getPaddingLeft() - decorView.getPaddingRight();
 
-        Resources res = ApiHelper.getLibResources();
+        Resources res = ApiHelper.getLibResources(mContext);
         mVolumeGroupListItemIconSize = res.getDimensionPixelSize(
                 R.dimen.mr_controller_volume_group_list_item_icon_size);
         mVolumeGroupListItemHeight = res.getDimensionPixelSize(
@@ -991,16 +991,16 @@
             if (mRoute.getPresentationDisplayId()
                     != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
                 // The user is currently casting screen.
-                mTitleView.setText(ApiHelper.getLibResources().getString(
+                mTitleView.setText(ApiHelper.getLibResources(mContext).getString(
                         R.string.mr_controller_casting_screen));
                 showTitle = true;
             } else if (mState == null || mState.getState() == PlaybackStateCompat.STATE_NONE) {
                 // Show "No media selected" as we don't yet know the playback state.
-                mTitleView.setText(ApiHelper.getLibResources().getString(
+                mTitleView.setText(ApiHelper.getLibResources(mContext).getString(
                         R.string.mr_controller_no_media_selected));
                 showTitle = true;
             } else if (!hasTitle && !hasSubtitle) {
-                mTitleView.setText(ApiHelper.getLibResources().getString(
+                mTitleView.setText(ApiHelper.getLibResources(mContext).getString(
                         R.string.mr_controller_no_info_available));
                 showTitle = true;
             } else {
@@ -1223,7 +1223,8 @@
                                 AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
                         event.setPackageName(mContext.getPackageName());
                         event.setClassName(getClass().getName());
-                        event.getText().add(ApiHelper.getLibResources().getString(actionDescResId));
+                        event.getText().add(
+                                ApiHelper.getLibResources(mContext).getString(actionDescResId));
                         mAccessibilityManager.sendAccessibilityEvent(event);
                     }
                 }
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
index 62c050b..9aabf7b 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
@@ -41,12 +41,13 @@
      * The framework should set the dialog width properly, but somehow it doesn't work, hence
      * duplicating a similar logic here to determine the appropriate dialog width.
      */
-    public static int getDialogWidth() {
-        DisplayMetrics metrics = ApiHelper.getLibResources().getDisplayMetrics();
+    public static int getDialogWidth(Context context) {
+        DisplayMetrics metrics = ApiHelper.getLibResources(context).getDisplayMetrics();
         boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
 
         TypedValue value = new TypedValue();
-        ApiHelper.getLibResources().getValue(isPortrait ? R.dimen.mr_dialog_fixed_width_minor
+        ApiHelper.getLibResources(context).getValue(isPortrait
+                ? R.dimen.mr_dialog_fixed_width_minor
                 : R.dimen.mr_dialog_fixed_width_major, value, true);
         if (value.type == TypedValue.TYPE_DIMENSION) {
             return (int) value.getDimension(metrics);
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
index defeedb..6a0a95a 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
@@ -51,9 +51,9 @@
     public MediaRouteExpandCollapseButton(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mExpandAnimationDrawable = (AnimationDrawable)
-                ApiHelper.getLibResources().getDrawable(R.drawable.mr_group_expand);
+                ApiHelper.getLibResources(context).getDrawable(R.drawable.mr_group_expand);
         mCollapseAnimationDrawable = (AnimationDrawable)
-                ApiHelper.getLibResources().getDrawable(R.drawable.mr_group_collapse);
+                ApiHelper.getLibResources(context).getDrawable(R.drawable.mr_group_collapse);
 
         ColorFilter filter = new PorterDuffColorFilter(
                 MediaRouterThemeHelper.getControllerColor(context, defStyleAttr),
@@ -62,9 +62,9 @@
         mCollapseAnimationDrawable.setColorFilter(filter);
 
         mExpandGroupDescription =
-                ApiHelper.getLibResources().getString(R.string.mr_controller_expand_group);
+                ApiHelper.getLibResources(context).getString(R.string.mr_controller_expand_group);
         mCollapseGroupDescription =
-                ApiHelper.getLibResources().getString(R.string.mr_controller_collapse_group);
+                ApiHelper.getLibResources(context).getString(R.string.mr_controller_collapse_group);
 
         setImageDrawable(mExpandAnimationDrawable.getFrame(0));
         setContentDescription(mExpandGroupDescription);
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 33d92b4..a38491f 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
@@ -267,7 +267,7 @@
             mCallbackObj = createCallbackObj();
             mVolumeCallbackObj = createVolumeCallbackObj();
 
-            Resources r = ApiHelper.getLibResources();
+            Resources r = ApiHelper.getLibResources(context);
             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 16707c6..9ae75b0 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -101,6 +101,12 @@
     private static final int SETTINGS_MODE_VIDEO_QUALITY = 4;
     private static final int SETTINGS_MODE_MAIN = 5;
     private static final int PLAYBACK_SPEED_1x_INDEX = 3;
+
+    private static final int SIZE_TYPE_EMBEDDED = 0;
+    private static final int SIZE_TYPE_FULL = 1;
+    // TODO: add support for Minimal size type.
+    private static final int SIZE_TYPE_MINIMAL = 2;
+
     private static final int MAX_PROGRESS = 1000;
     private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
     private static final int REWIND_TIME_MS = 10000;
@@ -114,16 +120,10 @@
     private MediaController.TransportControls mControls;
     private PlaybackState mPlaybackState;
     private MediaMetadata mMetadata;
-    private ProgressBar mProgress;
-    private TextView mEndTime, mCurrentTime;
-    private TextView mTitleView;
-    private TextView mAdSkipView, mAdRemainingView;
-    private View mAdExternalLink;
-    private View mTitleBar;
-    private View mRoot;
     private int mDuration;
     private int mPrevState;
-    private int mPrevLeftBarWidth;
+    private int mPrevWidth;
+    private int mOriginalLeftBarWidth;
     private int mVideoTrackCount;
     private int mAudioTrackCount;
     private int mSubtitleTrackCount;
@@ -132,8 +132,13 @@
     private int mSelectedAudioTrackIndex;
     private int mSelectedVideoQualityIndex;
     private int mSelectedSpeedIndex;
-    private int mSettingsItemHeight;
+    private int mEmbeddedSettingsItemWidth;
+    private int mFullSettingsItemWidth;
+    private int mEmbeddedSettingsItemHeight;
+    private int mFullSettingsItemHeight;
     private int mSettingsWindowMargin;
+    private int mSizeType;
+    private int mOrientation;
     private long mPlaybackActions;
     private boolean mDragging;
     private boolean mIsFullScreen;
@@ -143,30 +148,56 @@
     private boolean mSeekAvailable;
     private boolean mIsAdvertisement;
     private boolean mIsMute;
+
+    // Relating to Title Bar View
+    private View mRoot;
+    private View mTitleBar;
+    private TextView mTitleView;
+    private View mAdExternalLink;
+    private ImageButton mBackButton;
+    private MediaRouteButton mRouteButton;
+    private MediaRouteSelector mRouteSelector;
+
+    // Relating to Center View
+    private ViewGroup mCenterView;
+    private View mTransportControls;
     private ImageButton mPlayPauseButton;
     private ImageButton mFfwdButton;
     private ImageButton mRewButton;
     private ImageButton mNextButton;
     private ImageButton mPrevButton;
-    private ImageButton mBackButton;
 
+    // Relating to Progress Bar View
+    private ProgressBar mProgress;
+
+    // Relating to Bottom Bar Left View
+    private ViewGroup mBottomBarLeftView;
+    private ViewGroup mTimeView;
+    private TextView mEndTime;
+    private TextView mCurrentTime;
+    private TextView mAdSkipView;
+    private StringBuilder mFormatBuilder;
+    private Formatter mFormatter;
+
+    // Relating to Bottom Bar Right View
+    private ViewGroup mBottomBarRightView;
     private ViewGroup mBasicControls;
+    private ViewGroup mExtraControls;
+    private ViewGroup mCustomButtons;
     private ImageButton mSubtitleButton;
     private ImageButton mFullScreenButton;
     private ImageButton mOverflowButtonRight;
-
-    private ViewGroup mExtraControls;
-    private ViewGroup mCustomButtons;
     private ImageButton mOverflowButtonLeft;
     private ImageButton mMuteButton;
     private ImageButton mVideoQualityButton;
     private ImageButton mSettingsButton;
+    private TextView mAdRemainingView;
 
+    // Relating to Settings List View
     private ListView mSettingsListView;
     private PopupWindow mSettingsWindow;
     private SettingsAdapter mSettingsAdapter;
     private SubSettingsAdapter mSubSettingsAdapter;
-
     private List<String> mSettingsMainTextsList;
     private List<String> mSettingsSubTextsList;
     private List<Integer> mSettingsIconIdsList;
@@ -180,12 +211,6 @@
     private CharSequence mPauseDescription;
     private CharSequence mReplayDescription;
 
-    private StringBuilder mFormatBuilder;
-    private Formatter mFormatter;
-
-    private MediaRouteButton mRouteButton;
-    private MediaRouteSelector mRouteSelector;
-
     public MediaControlView2Impl(MediaControlView2 instance,
             ViewGroupProvider superProvider, ViewGroupProvider privateProvider) {
         super(instance, superProvider, privateProvider);
@@ -194,7 +219,7 @@
 
     @Override
     public void initialize(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        mResources = ApiHelper.getLibResources();
+        mResources = ApiHelper.getLibResources(mInstance.getContext());
         // Inflate MediaControlView2 from XML
         mRoot = makeControllerView();
         mInstance.addView(mRoot);
@@ -310,6 +335,35 @@
     }
 
     @Override
+    public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure_impl(widthMeasureSpec, heightMeasureSpec);
+
+        if (mPrevWidth != mInstance.getMeasuredWidth()) {
+            int currWidth = mInstance.getMeasuredWidth();
+
+            int iconSize = mResources.getDimensionPixelSize(R.dimen.mcv2_full_icon_size);
+            int marginSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_margin);
+            int bottomBarRightWidthMax = iconSize * 4 + marginSize * 8;
+
+            int fullWidth = mTransportControls.getWidth() + mTimeView.getWidth()
+                    + bottomBarRightWidthMax;
+            // These views may not have been initialized yet.
+            if (mTransportControls.getWidth() == 0 || mTimeView.getWidth() == 0) {
+                return;
+            }
+            if (mSizeType == SIZE_TYPE_EMBEDDED && fullWidth <= currWidth) {
+                updateLayoutForSizeChange(SIZE_TYPE_FULL);
+            } else if (mSizeType == SIZE_TYPE_FULL && fullWidth > currWidth) {
+                updateLayoutForSizeChange(SIZE_TYPE_EMBEDDED);
+            }
+            // Dismiss SettingsWindow if it is showing.
+            mSettingsWindow.dismiss();
+            mPrevWidth = currWidth;
+        }
+        updateTitleBarLayout();
+    }
+
+    @Override
     public void setEnabled_impl(boolean enabled) {
         super.setEnabled_impl(enabled);
 
@@ -431,35 +485,47 @@
         mReplayDescription =
                 mResources.getText(R.string.lockscreen_replay_button_content_description);
 
-        mRouteButton = v.findViewById(R.id.cast);
-
-        mPlayPauseButton = v.findViewById(R.id.pause);
-        if (mPlayPauseButton != null) {
-            mPlayPauseButton.requestFocus();
-            mPlayPauseButton.setOnClickListener(mPlayPauseListener);
-        }
-        mFfwdButton = v.findViewById(R.id.ffwd);
-        if (mFfwdButton != null) {
-            mFfwdButton.setOnClickListener(mFfwdListener);
-        }
-        mRewButton = v.findViewById(R.id.rew);
-        if (mRewButton != null) {
-            mRewButton.setOnClickListener(mRewListener);
-        }
-        mNextButton = v.findViewById(R.id.next);
-        if (mNextButton != null) {
-            mNextButton.setOnClickListener(mNextListener);
-        }
-        mPrevButton = v.findViewById(R.id.prev);
-        if (mPrevButton != null) {
-            mPrevButton.setOnClickListener(mPrevListener);
-        }
+        // Relating to Title Bar View
+        mTitleBar = v.findViewById(R.id.title_bar);
+        mTitleView = v.findViewById(R.id.title_text);
+        mAdExternalLink = v.findViewById(R.id.ad_external_link);
         mBackButton = v.findViewById(R.id.back);
         if (mBackButton != null) {
             mBackButton.setOnClickListener(mBackListener);
         }
+        mRouteButton = v.findViewById(R.id.cast);
 
+        // Relating to Center View
+        mCenterView = v.findViewById(R.id.center_view);
+        mTransportControls = inflateTransportControls(R.layout.embedded_transport_controls);
+        mCenterView.addView(mTransportControls);
+
+        // Relating to Progress Bar View
+        mProgress = v.findViewById(R.id.mediacontroller_progress);
+        if (mProgress != null) {
+            if (mProgress instanceof SeekBar) {
+                SeekBar seeker = (SeekBar) mProgress;
+                seeker.setOnSeekBarChangeListener(mSeekListener);
+                seeker.setProgressDrawable(mResources.getDrawable(R.drawable.custom_progress));
+                seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
+            }
+            mProgress.setMax(MAX_PROGRESS);
+        }
+
+        // Relating to Bottom Bar Left View
+        mBottomBarLeftView = v.findViewById(R.id.bottom_bar_left);
+        mTimeView = v.findViewById(R.id.time);
+        mEndTime = v.findViewById(R.id.time_end);
+        mCurrentTime = v.findViewById(R.id.time_current);
+        mAdSkipView = v.findViewById(R.id.ad_skip_time);
+        mFormatBuilder = new StringBuilder();
+        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+
+        // Relating to Bottom Bar Right View
+        mBottomBarRightView = v.findViewById(R.id.bottom_bar_right);
         mBasicControls = v.findViewById(R.id.basic_controls);
+        mExtraControls = v.findViewById(R.id.extra_controls);
+        mCustomButtons = v.findViewById(R.id.custom_buttons);
         mSubtitleButton = v.findViewById(R.id.subtitle);
         if (mSubtitleButton != null) {
             mSubtitleButton.setOnClickListener(mSubtitleListener);
@@ -473,10 +539,6 @@
         if (mOverflowButtonRight != null) {
             mOverflowButtonRight.setOnClickListener(mOverflowRightListener);
         }
-
-        // TODO: should these buttons be shown as default?
-        mExtraControls = v.findViewById(R.id.extra_controls);
-        mCustomButtons = v.findViewById(R.id.custom_buttons);
         mOverflowButtonLeft = v.findViewById(R.id.overflow_left);
         if (mOverflowButtonLeft != null) {
             mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
@@ -493,33 +555,9 @@
         if (mVideoQualityButton != null) {
             mVideoQualityButton.setOnClickListener(mVideoQualityListener);
         }
-
-        mProgress = v.findViewById(R.id.mediacontroller_progress);
-        if (mProgress != null) {
-            if (mProgress instanceof SeekBar) {
-                SeekBar seeker = (SeekBar) mProgress;
-                seeker.setOnSeekBarChangeListener(mSeekListener);
-                seeker.setProgressDrawable(mResources.getDrawable(R.drawable.custom_progress));
-                seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
-            }
-            mProgress.setMax(MAX_PROGRESS);
-        }
-
-        mTitleBar = v.findViewById(R.id.title_bar);
-        if (mTitleBar != null) {
-            mTitleBar.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
-        }
-        mTitleView = v.findViewById(R.id.title_text);
-
-        mEndTime = v.findViewById(R.id.time);
-        mCurrentTime = v.findViewById(R.id.time_current);
-        mFormatBuilder = new StringBuilder();
-        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
-
-        mAdSkipView = v.findViewById(R.id.ad_skip_time);
         mAdRemainingView = v.findViewById(R.id.ad_remaining);
-        mAdExternalLink = v.findViewById(R.id.ad_external_link);
 
+        // Relating to Settings List View
         initializeSettingsLists();
         mSettingsListView = (ListView) ApiHelper.inflateLibLayout(mInstance.getContext(),
                 R.layout.settings_list);
@@ -530,12 +568,16 @@
         mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
 
-        mSettingsItemHeight = mResources.getDimensionPixelSize(
-                R.dimen.MediaControlView2_settings_height);
+        mEmbeddedSettingsItemWidth = mResources.getDimensionPixelSize(
+                R.dimen.mcv2_embedded_settings_width);
+        mFullSettingsItemWidth = mResources.getDimensionPixelSize(R.dimen.mcv2_full_settings_width);
+        mEmbeddedSettingsItemHeight = mResources.getDimensionPixelSize(
+                R.dimen.mcv2_embedded_settings_height);
+        mFullSettingsItemHeight = mResources.getDimensionPixelSize(
+                R.dimen.mcv2_full_settings_height);
         mSettingsWindowMargin = (-1) * mResources.getDimensionPixelSize(
-                R.dimen.MediaControlView2_settings_offset);
-        int width = mResources.getDimensionPixelSize(R.dimen.MediaControlView2_settings_width);
-        mSettingsWindow = new PopupWindow(mSettingsListView, width,
+                R.dimen.mcv2_settings_offset);
+        mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth,
                 ViewGroup.LayoutParams.WRAP_CONTENT, true);
     }
 
@@ -954,36 +996,6 @@
         }
     };
 
-    // The title bar is made up of two separate LinearLayouts. If the sum of the two bars are
-    // greater than the length of the title bar, reduce the size of the left bar (which makes the
-    // TextView that contains the title of the media file shrink).
-    private final View.OnLayoutChangeListener mTitleBarLayoutChangeListener
-            = new View.OnLayoutChangeListener() {
-        @Override
-        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-                int oldTop, int oldRight, int oldBottom) {
-            if (mRoot != null) {
-                int titleBarWidth = mRoot.findViewById(R.id.title_bar).getWidth();
-
-                View leftBar = mRoot.findViewById(R.id.title_bar_left);
-                View rightBar = mRoot.findViewById(R.id.title_bar_right);
-                int leftBarWidth = leftBar.getWidth();
-                int rightBarWidth = rightBar.getWidth();
-
-                RelativeLayout.LayoutParams params =
-                        (RelativeLayout.LayoutParams) leftBar.getLayoutParams();
-                if (leftBarWidth + rightBarWidth > titleBarWidth) {
-                    params.width = titleBarWidth - rightBarWidth;
-                    mPrevLeftBarWidth = leftBarWidth;
-                } else if (leftBarWidth + rightBarWidth < titleBarWidth && mPrevLeftBarWidth != 0) {
-                    params.width = mPrevLeftBarWidth;
-                    mPrevLeftBarWidth = 0;
-                }
-                leftBar.setLayoutParams(params);
-            }
-        }
-    };
-
     private void updateDuration() {
         if (mMetadata != null) {
             if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
@@ -1002,13 +1014,37 @@
         }
     }
 
+    // The title bar is made up of two separate LinearLayouts. If the sum of the two bars are
+    // greater than the length of the title bar, reduce the size of the left bar (which makes the
+    // TextView that contains the title of the media file shrink).
+    private void updateTitleBarLayout() {
+        if (mTitleBar != null) {
+            int titleBarWidth = mTitleBar.getWidth();
+
+            View leftBar = mTitleBar.findViewById(R.id.title_bar_left);
+            View rightBar = mTitleBar.findViewById(R.id.title_bar_right);
+            int leftBarWidth = leftBar.getWidth();
+            int rightBarWidth = rightBar.getWidth();
+
+            RelativeLayout.LayoutParams params =
+                    (RelativeLayout.LayoutParams) leftBar.getLayoutParams();
+            if (leftBarWidth + rightBarWidth > titleBarWidth) {
+                params.width = titleBarWidth - rightBarWidth;
+                mOriginalLeftBarWidth = leftBarWidth;
+            } else if (leftBarWidth + rightBarWidth < titleBarWidth && mOriginalLeftBarWidth != 0) {
+                params.width = mOriginalLeftBarWidth;
+                mOriginalLeftBarWidth = 0;
+            }
+            leftBar.setLayoutParams(params);
+        }
+    }
+
     private void updateLayout() {
         if (mIsAdvertisement) {
             mRewButton.setVisibility(View.GONE);
             mFfwdButton.setVisibility(View.GONE);
             mPrevButton.setVisibility(View.GONE);
-            mCurrentTime.setVisibility(View.GONE);
-            mEndTime.setVisibility(View.GONE);
+            mTimeView.setVisibility(View.GONE);
 
             mAdSkipView.setVisibility(View.VISIBLE);
             mAdRemainingView.setVisibility(View.VISIBLE);
@@ -1021,8 +1057,7 @@
             mRewButton.setVisibility(View.VISIBLE);
             mFfwdButton.setVisibility(View.VISIBLE);
             mPrevButton.setVisibility(View.VISIBLE);
-            mCurrentTime.setVisibility(View.VISIBLE);
-            mEndTime.setVisibility(View.VISIBLE);
+            mTimeView.setVisibility(View.VISIBLE);
 
             mAdSkipView.setVisibility(View.GONE);
             mAdRemainingView.setVisibility(View.GONE);
@@ -1035,6 +1070,79 @@
         }
     }
 
+    private void updateLayoutForSizeChange(int sizeType) {
+        mSizeType = sizeType;
+        RelativeLayout.LayoutParams params =
+                (RelativeLayout.LayoutParams) mTimeView.getLayoutParams();
+        switch (mSizeType) {
+            case SIZE_TYPE_EMBEDDED:
+                mBottomBarLeftView.removeView(mTransportControls);
+                mBottomBarLeftView.setVisibility(View.GONE);
+                mTransportControls = inflateTransportControls(R.layout.embedded_transport_controls);
+                mCenterView.addView(mTransportControls, 0);
+
+                if (params.getRule(RelativeLayout.LEFT_OF) != 0) {
+                    params.removeRule(RelativeLayout.LEFT_OF);
+                    params.addRule(RelativeLayout.RIGHT_OF, R.id.bottom_bar_left);
+                }
+                break;
+            case SIZE_TYPE_FULL:
+                mCenterView.removeView(mTransportControls);
+                mTransportControls = inflateTransportControls(R.layout.full_transport_controls);
+                mBottomBarLeftView.addView(mTransportControls, 0);
+                mBottomBarLeftView.setVisibility(View.VISIBLE);
+
+                if (params.getRule(RelativeLayout.RIGHT_OF) != 0) {
+                    params.removeRule(RelativeLayout.RIGHT_OF);
+                    params.addRule(RelativeLayout.LEFT_OF, R.id.bottom_bar_right);
+                }
+                break;
+            case SIZE_TYPE_MINIMAL:
+                // TODO: implement
+                break;
+        }
+        mTimeView.setLayoutParams(params);
+
+        if (isPlaying()) {
+            mPlayPauseButton.setImageDrawable(
+                    mResources.getDrawable(R.drawable.ic_pause_circle_filled, null));
+            mPlayPauseButton.setContentDescription(mPauseDescription);
+        } else {
+            mPlayPauseButton.setImageDrawable(
+                    mResources.getDrawable(R.drawable.ic_play_circle_filled, null));
+            mPlayPauseButton.setContentDescription(mPlayDescription);
+        }
+    }
+
+    private View inflateTransportControls(int layoutId) {
+        View v = ApiHelper.inflateLibLayout(mInstance.getContext(), layoutId);
+        mPlayPauseButton = v.findViewById(R.id.pause);
+        if (mPlayPauseButton != null) {
+            mPlayPauseButton.requestFocus();
+            mPlayPauseButton.setOnClickListener(mPlayPauseListener);
+        }
+        mFfwdButton = v.findViewById(R.id.ffwd);
+        if (mFfwdButton != null) {
+            mFfwdButton.setOnClickListener(mFfwdListener);
+        }
+        mRewButton = v.findViewById(R.id.rew);
+        if (mRewButton != null) {
+            mRewButton.setOnClickListener(mRewListener);
+        }
+        // TODO: Add support for Next and Previous buttons
+        mNextButton = v.findViewById(R.id.next);
+        if (mNextButton != null) {
+            mNextButton.setOnClickListener(mNextListener);
+            mNextButton.setVisibility(View.GONE);
+        }
+        mPrevButton = v.findViewById(R.id.prev);
+        if (mPrevButton != null) {
+            mPrevButton.setOnClickListener(mPrevListener);
+            mPrevButton.setVisibility(View.GONE);
+        }
+        return v;
+    }
+
     private void initializeSettingsLists() {
         mSettingsMainTextsList = new ArrayList<String>();
         mSettingsMainTextsList.add(
@@ -1079,8 +1187,18 @@
     }
 
     private void displaySettingsWindow(BaseAdapter adapter) {
+        // Set Adapter
         mSettingsListView.setAdapter(adapter);
-        int totalHeight = adapter.getCount() * mSettingsItemHeight;
+
+        // Set width of window
+        int itemWidth = (mSizeType == SIZE_TYPE_EMBEDDED)
+                ? mEmbeddedSettingsItemWidth : mFullSettingsItemWidth;
+        mSettingsWindow.setWidth(itemWidth);
+
+        // Calculate height of window and show
+        int itemHeight = (mSizeType == SIZE_TYPE_EMBEDDED)
+                ? mEmbeddedSettingsItemHeight : mFullSettingsItemHeight;
+        int totalHeight = adapter.getCount() * itemHeight;
         mSettingsWindow.dismiss();
         mSettingsWindow.showAsDropDown(mInstance, mSettingsWindowMargin,
                 mSettingsWindowMargin - totalHeight, Gravity.BOTTOM | Gravity.RIGHT);
@@ -1273,8 +1391,14 @@
 
         @Override
         public View getView(int position, View convertView, ViewGroup container) {
-            View row = ApiHelper.inflateLibLayout(mInstance.getContext(),
-                    R.layout.settings_list_item);
+            View row;
+            if (mSizeType == SIZE_TYPE_FULL) {
+                row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+                        R.layout.full_settings_list_item);
+            } else {
+                row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+                        R.layout.embedded_settings_list_item);
+            }
             TextView mainTextView = (TextView) row.findViewById(R.id.main_text);
             TextView subTextView = (TextView) row.findViewById(R.id.sub_text);
             ImageView iconView = (ImageView) row.findViewById(R.id.icon);
@@ -1347,8 +1471,14 @@
 
         @Override
         public View getView(int position, View convertView, ViewGroup container) {
-            View row = ApiHelper.inflateLibLayout(mInstance.getContext(),
-                    R.layout.sub_settings_list_item);
+            View row;
+            if (mSizeType == SIZE_TYPE_FULL) {
+                row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+                        R.layout.full_sub_settings_list_item);
+            } else {
+                row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+                        R.layout.embedded_sub_settings_list_item);
+            }
             TextView textView = (TextView) row.findViewById(R.id.text);
             ImageView checkView = (ImageView) row.findViewById(R.id.check);
 
diff --git a/packages/MediaComponents/tests/Android.mk b/packages/MediaComponents/tests/Android.mk
new file mode 100644
index 0000000..bf81efb
--- /dev/null
+++ b/packages/MediaComponents/tests/Android.mk
@@ -0,0 +1,35 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    android.test.base.stubs \
+    junit
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := MediaComponentsTest
+
+LOCAL_INSTRUMENTATION_FOR := MediaComponents
+
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/packages/MediaComponents/tests/AndroidManifest.xml b/packages/MediaComponents/tests/AndroidManifest.xml
new file mode 100644
index 0000000..7255265
--- /dev/null
+++ b/packages/MediaComponents/tests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.media.tests">
+
+    <application android:label="Media API Test">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!--
+    To run the tests use the command:
+    "adb shell am instrument -w com.android.media.tests/android.test.InstrumentationTestRunner"
+    -->
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.media.update"
+        android:label="Media API test" />
+
+</manifest>
diff --git a/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java b/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java
new file mode 100644
index 0000000..80370ec
--- /dev/null
+++ b/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java
@@ -0,0 +1,515 @@
+/*
+ * 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.DataSourceDesc;
+import android.media.MediaItem2;
+import android.media.MediaMetadata2;
+import android.media.MediaPlayer2;
+import android.media.MediaPlayerBase;
+import android.media.MediaPlaylistAgent;
+import android.media.MediaSession2;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Tests {@link SessionPlaylistAgent}.
+ */
+public class SessionPlaylistAgentTest extends AndroidTestCase {
+    private static final String TAG = "SessionPlaylistAgentTest";
+    private static final int WAIT_TIME_MS = 1000;
+    private static final int INVALID_REPEAT_MODE = -100;
+    private static final int INVALID_SHUFFLE_MODE = -100;
+
+    private Handler mHandler;
+    private Executor mHandlerExecutor;
+
+    private Object mWaitLock = new Object();
+    private Context mContext;
+    private MediaSession2 mSession;
+    private MediaPlayerBase mPlayer;
+    private SessionPlaylistAgent mAgent;
+    private MyDataSourceHelper mDataSourceHelper;
+    private MyPlaylistEventCallback mEventCallback;
+
+    public class MyPlaylistEventCallback extends MediaPlaylistAgent.PlaylistEventCallback {
+        boolean onPlaylistChangedCalled;
+        boolean onPlaylistMetadataChangedCalled;
+        boolean onRepeatModeChangedCalled;
+        boolean onShuffleModeChangedCalled;
+
+        private Object mWaitLock;
+
+        public MyPlaylistEventCallback(Object waitLock) {
+            mWaitLock = waitLock;
+        }
+
+        public void clear() {
+            onPlaylistChangedCalled = false;
+            onPlaylistMetadataChangedCalled = false;
+            onRepeatModeChangedCalled = false;
+            onShuffleModeChangedCalled = false;
+        }
+
+        public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
+                MediaMetadata2 metadata) {
+            synchronized (mWaitLock) {
+                onPlaylistChangedCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
+                MediaMetadata2 metadata) {
+            synchronized (mWaitLock) {
+                onPlaylistMetadataChangedCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
+            synchronized (mWaitLock) {
+                onRepeatModeChangedCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
+            synchronized (mWaitLock) {
+                onShuffleModeChangedCalled = true;
+                mWaitLock.notify();
+            }
+        }
+    }
+
+    public class MyDataSourceHelper implements MediaSession2.OnDataSourceMissingHelper {
+        @Override
+        public DataSourceDesc onDataSourceMissing(MediaSession2 session, MediaItem2 item) {
+            if (item.getMediaId().contains("WITHOUT_DSD")) {
+                return null;
+            }
+            return new DataSourceDesc.Builder()
+                    .setDataSource(getContext(), Uri.parse("dsd://test"))
+                    .setMediaId(item.getMediaId())
+                    .build();
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        HandlerThread handlerThread = new HandlerThread("SessionPlaylistAgent");
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
+        mHandlerExecutor = (runnable) -> {
+            mHandler.post(runnable);
+        };
+
+        mContext = getContext();
+        mPlayer = MediaPlayer2.create();
+        mSession = new MediaSession2.Builder(mContext)
+                .setPlayer(mPlayer)
+                .setSessionCallback(mHandlerExecutor,
+                        new MediaSession2.SessionCallback(mContext) {})
+                .setId(TAG).build();
+        mDataSourceHelper = new MyDataSourceHelper();
+        mAgent = new SessionPlaylistAgent(mContext, mSession, mPlayer);
+        mAgent.setOnDataSourceMissingHelper(mDataSourceHelper);
+        mEventCallback = new MyPlaylistEventCallback(mWaitLock);
+        mAgent.registerPlaylistEventCallback(mHandlerExecutor, mEventCallback);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSession.close();
+        mPlayer.close();
+        mHandler.getLooper().quitSafely();
+        mHandler = null;
+        mHandlerExecutor = null;
+    }
+
+    @Test
+    public void testSetAndGetShuflleMode() throws Exception {
+        int shuffleMode = mAgent.getShuffleMode();
+        if (shuffleMode != MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
+            mEventCallback.clear();
+            synchronized (mWaitLock) {
+                mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_NONE);
+                mWaitLock.wait(WAIT_TIME_MS);
+                assertTrue(mEventCallback.onShuffleModeChangedCalled);
+            }
+            assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_NONE, mAgent.getShuffleMode());
+        }
+
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_ALL);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onShuffleModeChangedCalled);
+        }
+        assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_ALL, mAgent.getShuffleMode());
+
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_GROUP);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onShuffleModeChangedCalled);
+        }
+        assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_GROUP, mAgent.getShuffleMode());
+
+        // INVALID_SHUFFLE_MODE will not change the shuffle mode.
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            mAgent.setShuffleMode(INVALID_SHUFFLE_MODE);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertFalse(mEventCallback.onShuffleModeChangedCalled);
+        }
+        assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_GROUP, mAgent.getShuffleMode());
+    }
+
+    @Test
+    public void testSetAndGetRepeatMode() throws Exception {
+        int repeatMode = mAgent.getRepeatMode();
+        if (repeatMode != MediaPlaylistAgent.REPEAT_MODE_NONE) {
+            mEventCallback.clear();
+            synchronized (mWaitLock) {
+                mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
+                mWaitLock.wait(WAIT_TIME_MS);
+                assertTrue(mEventCallback.onRepeatModeChangedCalled);
+            }
+            assertEquals(MediaPlaylistAgent.REPEAT_MODE_NONE, mAgent.getRepeatMode());
+        }
+
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ONE);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onRepeatModeChangedCalled);
+        }
+        assertEquals(MediaPlaylistAgent.REPEAT_MODE_ONE, mAgent.getRepeatMode());
+
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onRepeatModeChangedCalled);
+        }
+        assertEquals(MediaPlaylistAgent.REPEAT_MODE_ALL, mAgent.getRepeatMode());
+
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_GROUP);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onRepeatModeChangedCalled);
+        }
+        assertEquals(MediaPlaylistAgent.REPEAT_MODE_GROUP, mAgent.getRepeatMode());
+
+        // INVALID_SHUFFLE_MODE will not change the shuffle mode.
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            mAgent.setRepeatMode(INVALID_REPEAT_MODE);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertFalse(mEventCallback.onRepeatModeChangedCalled);
+        }
+        assertEquals(MediaPlaylistAgent.REPEAT_MODE_GROUP, mAgent.getRepeatMode());
+    }
+
+    @Test
+    public void testSetPlaylist() throws Exception {
+        int listSize = 10;
+        createAndSetPlaylist(10);
+        assertEquals(listSize, mAgent.getPlaylist().size());
+        assertEquals(0, mAgent.getCurShuffledIndex());
+    }
+
+    @Test
+    public void testSkipItems() throws Exception {
+        int listSize = 5;
+        List<MediaItem2> playlist = createAndSetPlaylist(listSize);
+
+        mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
+        // Test skipToPlaylistItem
+        for (int i = listSize - 1; i >= 0; --i) {
+            mAgent.skipToPlaylistItem(playlist.get(i));
+            assertEquals(i, mAgent.getCurShuffledIndex());
+        }
+
+        // Test skipToNextItem
+        // curPlayPos = 0
+        for (int curPlayPos = 0; curPlayPos < listSize - 1; ++curPlayPos) {
+            mAgent.skipToNextItem();
+            assertEquals(curPlayPos + 1, mAgent.getCurShuffledIndex());
+        }
+        mAgent.skipToNextItem();
+        assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
+
+        // Test skipToPrevious
+        // curPlayPos = listSize - 1
+        for (int curPlayPos = listSize - 1; curPlayPos > 0; --curPlayPos) {
+            mAgent.skipToPreviousItem();
+            assertEquals(curPlayPos - 1, mAgent.getCurShuffledIndex());
+        }
+        mAgent.skipToPreviousItem();
+        assertEquals(0, mAgent.getCurShuffledIndex());
+
+        mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
+        // Test skipToPrevious with repeat mode all
+        // curPlayPos = 0
+        mAgent.skipToPreviousItem();
+        assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
+
+        // Test skipToNext with repeat mode all
+        // curPlayPos = listSize - 1
+        mAgent.skipToNextItem();
+        assertEquals(0, mAgent.getCurShuffledIndex());
+
+        mAgent.skipToPreviousItem();
+        // curPlayPos = listSize - 1, nextPlayPos = 0
+        // Test next play pos after setting repeat mode none.
+        mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
+        assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
+    }
+
+    @Test
+    public void testEditPlaylist() throws Exception {
+        int listSize = 5;
+        List<MediaItem2> playlist = createAndSetPlaylist(listSize);
+
+        // Test add item: [0 (cur), 1, 2, 3, 4] -> [0 (cur), 1, 5, 2, 3, 4]
+        mEventCallback.clear();
+        MediaItem2 item_5 = generateMediaItem(5);
+        synchronized (mWaitLock) {
+            playlist.add(2, item_5);
+            mAgent.addPlaylistItem(2, item_5);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+
+        mEventCallback.clear();
+        // Move current: [0 (cur), 1, 5, 2, 3, 4] -> [0, 1, 5 (cur), 2, 3, 4]
+        mAgent.skipToPlaylistItem(item_5);
+        // Remove current item: [0, 1, 5 (cur), 2, 3, 4] -> [0, 1, 2 (cur), 3, 4]
+        synchronized (mWaitLock) {
+            playlist.remove(item_5);
+            mAgent.removePlaylistItem(item_5);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(2, mAgent.getCurShuffledIndex());
+
+        // Remove previous item: [0, 1, 2 (cur), 3, 4] -> [0, 2 (cur), 3, 4]
+        mEventCallback.clear();
+        MediaItem2 previousItem = playlist.get(1);
+        synchronized (mWaitLock) {
+            playlist.remove(previousItem);
+            mAgent.removePlaylistItem(previousItem);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(1, mAgent.getCurShuffledIndex());
+
+        // Remove next item: [0, 2 (cur), 3, 4] -> [0, 2 (cur), 4]
+        mEventCallback.clear();
+        MediaItem2 nextItem = playlist.get(2);
+        synchronized (mWaitLock) {
+            playlist.remove(nextItem);
+            mAgent.removePlaylistItem(nextItem);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(1, mAgent.getCurShuffledIndex());
+
+        // Replace item: [0, 2 (cur), 4] -> [0, 2 (cur), 5]
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            playlist.set(2, item_5);
+            mAgent.replacePlaylistItem(2, item_5);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(1, mAgent.getCurShuffledIndex());
+
+        // Move last and remove the last item: [0, 2 (cur), 5] -> [0, 2, 5 (cur)] -> [0, 2 (cur)]
+        MediaItem2 lastItem = playlist.get(1);
+        mAgent.skipToPlaylistItem(lastItem);
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            playlist.remove(lastItem);
+            mAgent.removePlaylistItem(lastItem);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(1, mAgent.getCurShuffledIndex());
+
+        // Remove all items
+        for (int i = playlist.size() - 1; i >= 0; --i) {
+            MediaItem2 item = playlist.get(i);
+            mAgent.skipToPlaylistItem(item);
+            mEventCallback.clear();
+            synchronized (mWaitLock) {
+                playlist.remove(item);
+                mAgent.removePlaylistItem(item);
+                mWaitLock.wait(WAIT_TIME_MS);
+                assertTrue(mEventCallback.onPlaylistChangedCalled);
+            }
+            assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        }
+        assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
+    }
+
+
+    @Test
+    public void testPlaylistWithInvalidItem() throws Exception {
+        int listSize = 2;
+        List<MediaItem2> playlist = createAndSetPlaylist(listSize);
+
+        // Add item: [0 (cur), 1] -> [0 (cur), 3 (no_dsd), 1]
+        mEventCallback.clear();
+        MediaItem2 invalidItem2 = generateMediaItemWithoutDataSourceDesc(2);
+        synchronized (mWaitLock) {
+            playlist.add(1, invalidItem2);
+            mAgent.addPlaylistItem(1, invalidItem2);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(0, mAgent.getCurShuffledIndex());
+
+        // Test skip to next item:  [0 (cur), 2 (no_dsd), 1] -> [0, 2 (no_dsd), 1 (cur)]
+        mAgent.skipToNextItem();
+        assertEquals(2, mAgent.getCurShuffledIndex());
+
+        // Test skip to previous item: [0, 2 (no_dsd), 1 (cur)] -> [0 (cur), 2 (no_dsd), 1]
+        mAgent.skipToPreviousItem();
+        assertEquals(0, mAgent.getCurShuffledIndex());
+
+        // Remove current item: [0 (cur), 2 (no_dsd), 1] -> [2 (no_dsd), 1 (cur)]
+        mEventCallback.clear();
+        MediaItem2 item = playlist.get(0);
+        synchronized (mWaitLock) {
+            playlist.remove(item);
+            mAgent.removePlaylistItem(item);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(1, mAgent.getCurShuffledIndex());
+
+        // Remove current item: [2 (no_dsd), 1 (cur)] -> [2 (no_dsd)]
+        mEventCallback.clear();
+        item = playlist.get(1);
+        synchronized (mWaitLock) {
+            playlist.remove(item);
+            mAgent.removePlaylistItem(item);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
+
+        // Add invalid item: [2 (no_dsd)] -> [0 (no_dsd), 2 (no_dsd)]
+        MediaItem2 invalidItem0 = generateMediaItemWithoutDataSourceDesc(0);
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            playlist.add(0, invalidItem0);
+            mAgent.addPlaylistItem(0, invalidItem0);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
+
+        // Add valid item: [0 (no_dsd), 2 (no_dsd)] -> [0 (no_dsd), 1, 2 (no_dsd)]
+        MediaItem2 invalidItem1 = generateMediaItem(1);
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            playlist.add(1, invalidItem1);
+            mAgent.addPlaylistItem(1, invalidItem1);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(1, mAgent.getCurShuffledIndex());
+
+        // Replace the valid item with an invalid item:
+        // [0 (no_dsd), 1 (cur), 2 (no_dsd)] -> [0 (no_dsd), 3 (no_dsd), 2 (no_dsd)]
+        MediaItem2 invalidItem3 = generateMediaItemWithoutDataSourceDesc(3);
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            playlist.set(1, invalidItem3);
+            mAgent.replacePlaylistItem(1, invalidItem3);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        assertPlaylistEquals(playlist, mAgent.getPlaylist());
+        assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
+    }
+
+    private List<MediaItem2> createAndSetPlaylist(int listSize) throws Exception {
+        List<MediaItem2> items = new ArrayList<>();
+        for (int i = 0; i < listSize; ++i) {
+            items.add(generateMediaItem(i));
+        }
+        mEventCallback.clear();
+        synchronized (mWaitLock) {
+            mAgent.setPlaylist(items, null);
+            mWaitLock.wait(WAIT_TIME_MS);
+            assertTrue(mEventCallback.onPlaylistChangedCalled);
+        }
+        return items;
+    }
+
+    private void assertPlaylistEquals(List<MediaItem2> expected, List<MediaItem2> actual) {
+        if (expected == actual) {
+            return;
+        }
+        assertTrue(expected != null && actual != null);
+        assertEquals(expected.size(), actual.size());
+        for (int i = 0; i < expected.size(); ++i) {
+            assertTrue(expected.get(i).equals(actual.get(i)));
+        }
+    }
+
+    private MediaItem2 generateMediaItemWithoutDataSourceDesc(int key) {
+        return new MediaItem2.Builder(mContext, 0)
+                .setMediaId("TEST_MEDIA_ID_WITHOUT_DSD_" + key)
+                .build();
+    }
+
+    private MediaItem2 generateMediaItem(int key) {
+        return new MediaItem2.Builder(mContext, 0)
+                .setMediaId("TEST_MEDIA_ID_" + key)
+                .build();
+    }
+}
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 8033382..ea06b6c 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -3139,16 +3139,21 @@
         bool pinned = (sessionId > AUDIO_SESSION_OUTPUT_MIX) && isSessionAcquired_l(sessionId);
         handle = thread->createEffect_l(client, effectClient, priority, sessionId,
                 &desc, enabled, &lStatus, pinned);
-        if (handle != 0 && id != NULL) {
-            *id = handle->id();
-        }
-        if (handle == 0) {
+        if (lStatus != NO_ERROR && lStatus != ALREADY_EXISTS) {
             // remove local strong reference to Client with mClientLock held
             Mutex::Autolock _cl(mClientLock);
             client.clear();
+        } else {
+            // handle must be valid here, but check again to be safe.
+            if (handle.get() != nullptr && id != nullptr) *id = handle->id();
         }
     }
 
+    if (lStatus != NO_ERROR && lStatus != ALREADY_EXISTS) {
+        // handle must be cleared outside lock.
+        handle.clear();
+    }
+
 Exit:
     *status = lStatus;
     return handle;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 20447d8..62e9fe7 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1360,7 +1360,7 @@
         if (chainCreated) {
             removeEffectChain_l(chain);
         }
-        handle.clear();
+        // handle must be cleared by caller to avoid deadlock.
     }
 
     *status = lStatus;
@@ -8524,7 +8524,11 @@
         audio_devices_t outDevice, audio_devices_t inDevice, bool systemReady)
     : MmapThread(audioFlinger, id, hwDev, output->stream, outDevice, inDevice, systemReady),
       mStreamType(AUDIO_STREAM_MUSIC),
-      mStreamVolume(1.0), mStreamMute(false), mOutput(output)
+      mStreamVolume(1.0),
+      mStreamMute(false),
+      mHalVolFloat(-1.0f), // Initialize to illegal value so it always gets set properly later.
+      mNoCallbackWarningCount(0),
+      mOutput(output)
 {
     snprintf(mThreadName, kThreadNameLength, "AudioMmapOut_%X", id);
     mChannelCount = audio_channel_count_from_out_mask(mChannelMask);
@@ -8632,7 +8636,6 @@
     }
 
     if (volume != mHalVolFloat) {
-        mHalVolFloat = volume;
 
         // Convert volumes from float to 8.24
         uint32_t vol = (uint32_t)(volume * (1 << 24));
@@ -8645,7 +8648,10 @@
             volume = (float)vol / (1 << 24);
         }
         // Try to use HW volume control and fall back to SW control if not implemented
-        if (mOutput->stream->setVolume(volume, volume) != NO_ERROR) {
+        if (mOutput->stream->setVolume(volume, volume) == NO_ERROR) {
+            mHalVolFloat = volume; // HW volume control worked, so update value.
+            mNoCallbackWarningCount = 0;
+        } else {
             sp<MmapStreamCallback> callback = mCallback.promote();
             if (callback != 0) {
                 int channelCount;
@@ -8659,8 +8665,13 @@
                     values.add(volume);
                 }
                 callback->onVolumeChanged(mChannelMask, values);
+                mHalVolFloat = volume; // SW volume control worked, so update value.
+                mNoCallbackWarningCount = 0;
             } else {
-                ALOGW("Could not set MMAP stream volume: no volume callback!");
+                if (mNoCallbackWarningCount < kMaxNoCallbackWarnings) {
+                    ALOGW("Could not set MMAP stream volume: no volume callback!");
+                    mNoCallbackWarningCount++;
+                }
             }
         }
     }
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index ae14ac1..7cd46a7 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1666,6 +1666,8 @@
                 bool                        mMasterMute;
                 bool                        mStreamMute;
                 float                       mHalVolFloat;
+                int32_t                     mNoCallbackWarningCount;
+     static     constexpr int32_t           kMaxNoCallbackWarnings = 5;
                 AudioStreamOut*             mOutput;
 };
 
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 74ca902..73d92ac 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -1266,6 +1266,11 @@
         // We do not introduce additional delay here.
     }
 
+    if (stream == AUDIO_STREAM_ENFORCED_AUDIBLE &&
+            mEngine->getForceUse(AUDIO_POLICY_FORCE_FOR_SYSTEM) == AUDIO_POLICY_FORCE_SYSTEM_ENFORCED) {
+        setStrategyMute(STRATEGY_SONIFICATION, true, outputDesc);
+    }
+
     return NO_ERROR;
 }
 
@@ -1366,6 +1371,12 @@
             // update the outputs if stopping one with a stream that can affect notification routing
             handleNotificationRoutingForStream(stream);
         }
+
+        if (stream == AUDIO_STREAM_ENFORCED_AUDIBLE &&
+                mEngine->getForceUse(AUDIO_POLICY_FORCE_FOR_SYSTEM) == AUDIO_POLICY_FORCE_SYSTEM_ENFORCED) {
+            setStrategyMute(STRATEGY_SONIFICATION, false, outputDesc);
+        }
+
         if (stream == AUDIO_STREAM_MUSIC) {
             selectOutputForMusicEffects();
         }
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index ad63899..fb9b0ba 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -1550,6 +1550,9 @@
 
     switch(eventId) {
         case ICameraService::EVENT_USER_SWITCHED: {
+            // Try to register for UID policy updates, in case we're recovering
+            // from a system server crash
+            mUidPolicy->registerSelf();
             doUserSwitch(/*newUserIds*/ args);
             break;
         }
@@ -2365,17 +2368,31 @@
 // ----------------------------------------------------------------------------
 
 void CameraService::UidPolicy::registerSelf() {
+    Mutex::Autolock _l(mUidLock);
+
     ActivityManager am;
+    if (mRegistered) return;
     am.registerUidObserver(this, ActivityManager::UID_OBSERVER_GONE
             | ActivityManager::UID_OBSERVER_IDLE
             | ActivityManager::UID_OBSERVER_ACTIVE,
             ActivityManager::PROCESS_STATE_UNKNOWN,
             String16("cameraserver"));
+    status_t res = am.linkToDeath(this);
+    if (res == OK) {
+        mRegistered = true;
+        ALOGV("UidPolicy: Registered with ActivityManager");
+    }
 }
 
 void CameraService::UidPolicy::unregisterSelf() {
+    Mutex::Autolock _l(mUidLock);
+
     ActivityManager am;
     am.unregisterUidObserver(this);
+    am.unlinkToDeath(this);
+    mRegistered = false;
+    mActiveUids.clear();
+    ALOGV("UidPolicy: Unregistered with ActivityManager");
 }
 
 void CameraService::UidPolicy::onUidGone(uid_t uid, bool disabled) {
@@ -2404,17 +2421,14 @@
 }
 
 bool CameraService::UidPolicy::isUidActive(uid_t uid) {
-    // Non-app UIDs are considered always active
-    if (uid < FIRST_APPLICATION_UID) {
-        return true;
-    }
     Mutex::Autolock _l(mUidLock);
     return isUidActiveLocked(uid);
 }
 
 bool CameraService::UidPolicy::isUidActiveLocked(uid_t uid) {
     // Non-app UIDs are considered always active
-    if (uid < FIRST_APPLICATION_UID) {
+    // If activity manager is unreachable, assume everything is active
+    if (uid < FIRST_APPLICATION_UID || !mRegistered) {
         return true;
     }
     auto it = mOverrideUids.find(uid);
@@ -2432,6 +2446,13 @@
     updateOverrideUid(uid, false, false);
 }
 
+void CameraService::UidPolicy::binderDied(const wp<IBinder>& /*who*/) {
+    Mutex::Autolock _l(mUidLock);
+    ALOGV("UidPolicy: ActivityManager has died");
+    mRegistered = false;
+    mActiveUids.clear();
+}
+
 void CameraService::UidPolicy::updateOverrideUid(uid_t uid, bool active, bool insert) {
     bool wasActive = false;
     bool isActive = false;
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 3812925..86a2b81 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -518,10 +518,10 @@
 
     // Observer for UID lifecycle enforcing that UIDs in idle
     // state cannot use the camera to protect user privacy.
-    class UidPolicy : public BnUidObserver {
+    class UidPolicy : public BnUidObserver, public virtual IBinder::DeathRecipient {
     public:
         explicit UidPolicy(sp<CameraService> service)
-                : mService(service) {}
+                : mRegistered(false), mService(service) {}
 
         void registerSelf();
         void unregisterSelf();
@@ -535,11 +535,14 @@
         void addOverrideUid(uid_t uid, bool active);
         void removeOverrideUid(uid_t uid);
 
+        // IBinder::DeathRecipient implementation
+        virtual void binderDied(const wp<IBinder> &who);
     private:
         bool isUidActiveLocked(uid_t uid);
         void updateOverrideUid(uid_t uid, bool active, bool insert);
 
         Mutex mUidLock;
+        bool mRegistered;
         wp<CameraService> mService;
         std::unordered_set<uid_t> mActiveUids;
         std::unordered_map<uid_t, bool> mOverrideUids;
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index b37f004..077e05e 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -39,9 +39,6 @@
 const std::string kLegacyProviderName("legacy/0");
 const std::string kExternalProviderName("external/0");
 
-// Slash-separated list of provider types to consider for use via the old camera API
-const std::string kStandardProviderTypes("internal/legacy/external");
-
 } // anonymous namespace
 
 CameraProviderManager::HardwareServiceInteractionProxy
@@ -101,10 +98,8 @@
     std::lock_guard<std::mutex> lock(mInterfaceMutex);
     std::vector<std::string> deviceIds;
     for (auto& provider : mProviders) {
-        if (kStandardProviderTypes.find(provider->getType()) != std::string::npos) {
-            for (auto& id : provider->mUniqueAPI1CompatibleCameraIds) {
-                deviceIds.push_back(id);
-            }
+        for (auto& id : provider->mUniqueAPI1CompatibleCameraIds) {
+            deviceIds.push_back(id);
         }
     }
 
diff --git a/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp b/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
index e3bb5dc..f4d5a18 100644
--- a/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
+++ b/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
@@ -423,12 +423,20 @@
                     __FUNCTION__, gbp.get(), strerror(-res), res);
             return res;
         }
+        if ((slot < 0) || (slot > BufferQueue::NUM_BUFFER_SLOTS)) {
+            SP_LOGE("%s: Slot received %d either bigger than expected maximum %d or negative!",
+                    __FUNCTION__, slot, BufferQueue::NUM_BUFFER_SLOTS);
+            return BAD_VALUE;
+        }
         //During buffer attach 'mMutex' is not held which makes the removal of
         //"gbp" possible. Check whether this is the case and continue.
         if (mOutputSlots[gbp] == nullptr) {
             continue;
         }
         auto& outputSlots = *mOutputSlots[gbp];
+        if (static_cast<size_t> (slot + 1) > outputSlots.size()) {
+            outputSlots.resize(slot + 1);
+        }
         if (outputSlots[slot] != nullptr) {
             // If the buffer is attached to a slot which already contains a buffer,
             // the previous buffer will be removed from the output queue. Decrement