Merge "Make prior{Linear/Graphic}Allocation own handle" into pi-dev
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 839b134..346761c 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -1309,8 +1309,8 @@
      * Any state (excluding LOCKED) | ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER is START | PRECAPTURE     | Start AE precapture metering sequence
      * Any state (excluding LOCKED) | ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER is CANCEL| INACTIVE       | Currently active precapture metering sequence is canceled</p>
      * <p>If the camera device supports AE external flash mode (ON_EXTERNAL_FLASH is included in
-     * ACAMERA_CONTROL_AE_AVAILABLE_MODES), aeState must be FLASH_REQUIRED after the camera device
-     * finishes AE scan and it's too dark without flash.</p>
+     * ACAMERA_CONTROL_AE_AVAILABLE_MODES), ACAMERA_CONTROL_AE_STATE must be FLASH_REQUIRED after
+     * the camera device finishes AE scan and it's too dark without flash.</p>
      * <p>For the above table, the camera device may skip reporting any state changes that happen
      * without application intervention (i.e. mode switch, trigger, locking). Any state that
      * can be skipped in that manner is called a transient state.</p>
@@ -1331,6 +1331,7 @@
      * @see ACAMERA_CONTROL_AE_LOCK
      * @see ACAMERA_CONTROL_AE_MODE
      * @see ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER
+     * @see ACAMERA_CONTROL_AE_STATE
      * @see ACAMERA_CONTROL_MODE
      * @see ACAMERA_CONTROL_SCENE_MODE
      */
@@ -2256,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
@@ -2268,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,
@@ -2381,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
@@ -2389,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>
@@ -2457,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,
 
     /**
@@ -4211,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
@@ -4223,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
@@ -5500,9 +5505,11 @@
      * for the external flash. Otherwise, this mode acts like ON.</p>
      * <p>When the external flash is turned off, AE mode should be changed to one of the
      * other available AE modes.</p>
-     * <p>If the camera device supports AE external flash mode, aeState must be
-     * FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without
+     * <p>If the camera device supports AE external flash mode, ACAMERA_CONTROL_AE_STATE must
+     * be FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without
      * flash.</p>
+     *
+     * @see ACAMERA_CONTROL_AE_STATE
      */
     ACAMERA_CONTROL_AE_MODE_ON_EXTERNAL_FLASH                        = 5,
 
@@ -6938,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>
@@ -6956,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,
 
@@ -6991,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
@@ -7017,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/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h
new file mode 120000
index 0000000..e2dd082
--- /dev/null
+++ b/include/media/ExtractorUtils.h
@@ -0,0 +1 @@
+../../media/libmediaextractor/include/media/ExtractorUtils.h
\ No newline at end of file
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index 3f832bc..fc60fd4 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -22,6 +22,7 @@
 #include "MatroskaExtractor.h"
 
 #include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
 #include <media/MediaTrack.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AUtils.h>
@@ -1108,7 +1109,7 @@
     meta.setData(kKeyFlacMetadata, 0, codecPrivate, codecPrivateSize);
 
     int32_t maxInputSize = 64 << 10;
-    sp<FLACDecoder> flacDecoder = FLACDecoder::Create();
+    FLACDecoder *flacDecoder = FLACDecoder::Create();
     if (flacDecoder != NULL
             && flacDecoder->parseMetadata((const uint8_t*)codecPrivate, codecPrivateSize) == OK) {
         FLAC__StreamMetadata_StreamInfo streamInfo = flacDecoder->getStreamInfo();
@@ -1120,6 +1121,7 @@
                     && streamInfo.channels != 0
                     && ((streamInfo.bits_per_sample + 7) / 8) >
                         INT32_MAX / streamInfo.max_blocksize / streamInfo.channels) {
+                delete flacDecoder;
                 return ERROR_MALFORMED;
             }
             maxInputSize = ((streamInfo.bits_per_sample + 7) / 8)
@@ -1128,6 +1130,7 @@
     }
     meta.setInt32(kKeyMaxInputSize, maxInputSize);
 
+    delete flacDecoder;
     return OK;
 }
 
@@ -1143,13 +1146,13 @@
     }
 
     const mkvparser::Block::Frame &frame = block->GetFrame(0);
-    sp<ABuffer> abuf = new ABuffer(frame.len);
-    long n = frame.Read(mReader, abuf->data());
+    auto tmpData = heapbuffer<unsigned char>(frame.len);
+    long n = frame.Read(mReader, tmpData.get());
     if (n != 0) {
         return ERROR_MALFORMED;
     }
 
-    if (!MakeAVCCodecSpecificData(trackInfo->mMeta, abuf)) {
+    if (!MakeAVCCodecSpecificData(trackInfo->mMeta, tmpData.get(), frame.len)) {
         return ERROR_MALFORMED;
     }
 
diff --git a/media/extractors/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp
index 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/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 78d2ac6..a9f66fa 100644
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -31,6 +31,7 @@
 #include "ItemTable.h"
 #include "include/ESDS.h"
 
+#include <media/ExtractorUtils.h>
 #include <media/MediaTrack.h>
 #include <media/stagefright/foundation/ABitReader.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -1324,17 +1325,17 @@
             if (mLastTrack == NULL)
                 return ERROR_MALFORMED;
 
-            sp<ABuffer> buffer = new ABuffer(chunk_data_size);
-            if (buffer->data() == NULL) {
+            auto buffer = heapbuffer<uint8_t>(chunk_data_size);
+            if (buffer.get() == NULL) {
                 return NO_MEMORY;
             }
 
             if (mDataSource->readAt(
-                        data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
+                        data_offset, buffer.get(), chunk_data_size) < chunk_data_size) {
                 return ERROR_IO;
             }
 
-            String8 mimeFormat((const char *)(buffer->data()), chunk_data_size);
+            String8 mimeFormat((const char *)(buffer.get()), chunk_data_size);
             mLastTrack->meta.setCString(kKeyMIMEType, mimeFormat.string());
 
             break;
@@ -1833,15 +1834,15 @@
         {
             *offset += chunk_size;
 
-            sp<ABuffer> buffer = new ABuffer(chunk_data_size);
+            auto buffer = heapbuffer<uint8_t>(chunk_data_size);
 
-            if (buffer->data() == NULL) {
+            if (buffer.get() == NULL) {
                 ALOGE("b/28471206");
                 return NO_MEMORY;
             }
 
             if (mDataSource->readAt(
-                        data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
+                        data_offset, buffer.get(), chunk_data_size) < chunk_data_size) {
                 return ERROR_IO;
             }
 
@@ -1849,21 +1850,21 @@
                 return ERROR_MALFORMED;
 
             mLastTrack->meta.setData(
-                    kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);
+                    kKeyAVCC, kTypeAVCC, buffer.get(), chunk_data_size);
 
             break;
         }
         case FOURCC('h', 'v', 'c', 'C'):
         {
-            sp<ABuffer> buffer = new ABuffer(chunk_data_size);
+            auto buffer = heapbuffer<uint8_t>(chunk_data_size);
 
-            if (buffer->data() == NULL) {
+            if (buffer.get() == NULL) {
                 ALOGE("b/28471206");
                 return NO_MEMORY;
             }
 
             if (mDataSource->readAt(
-                        data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
+                        data_offset, buffer.get(), chunk_data_size) < chunk_data_size) {
                 return ERROR_IO;
             }
 
@@ -1871,7 +1872,7 @@
                 return ERROR_MALFORMED;
 
             mLastTrack->meta.setData(
-                    kKeyHVCC, kTypeHVCC, buffer->data(), chunk_data_size);
+                    kKeyHVCC, kTypeHVCC, buffer.get(), chunk_data_size);
 
             *offset += chunk_size;
             break;
@@ -2212,13 +2213,13 @@
             if (chunk_data_size < 0 || static_cast<uint64_t>(chunk_data_size) >= SIZE_MAX - 1) {
                 return ERROR_MALFORMED;
             }
-            sp<ABuffer> buffer = new ABuffer(chunk_data_size + 1);
-            if (buffer->data() == NULL) {
+            auto buffer = heapbuffer<uint8_t>(chunk_data_size);
+            if (buffer.get() == NULL) {
                 ALOGE("b/28471206");
                 return NO_MEMORY;
             }
             if (mDataSource->readAt(
-                data_offset, buffer->data(), chunk_data_size) != (ssize_t)chunk_data_size) {
+                data_offset, buffer.get(), chunk_data_size) != (ssize_t)chunk_data_size) {
                 return ERROR_IO;
             }
             const int kSkipBytesOfDataBox = 16;
@@ -2228,7 +2229,7 @@
 
             mFileMetaData.setData(
                 kKeyAlbumArt, MetaData::TYPE_NONE,
-                buffer->data() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
+                buffer.get() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
 
             break;
         }
@@ -2626,16 +2627,16 @@
         keySize -= 8;
         keyOffset += 8;
 
-        sp<ABuffer> keyData = new ABuffer(keySize);
-        if (keyData->data() == NULL) {
+        auto keyData = heapbuffer<uint8_t>(keySize);
+        if (keyData.get() == NULL) {
             return ERROR_MALFORMED;
         }
         if (mDataSource->readAt(
-                keyOffset, keyData->data(), keySize) < (ssize_t) keySize) {
+                keyOffset, keyData.get(), keySize) < (ssize_t) keySize) {
             return ERROR_MALFORMED;
         }
 
-        AString key((const char *)keyData->data(), keySize);
+        AString key((const char *)keyData.get(), keySize);
         mMetaKeyMap.add(i, key);
 
         keyOffset += keySize;
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index 4d49013..b2fe69c 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -22,6 +22,7 @@
 
 #include <cutils/properties.h>
 #include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
 #include <media/MediaTrack.h>
 #include <media/VorbisComment.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -990,21 +991,11 @@
     return OK;
 }
 
-struct TmpData {
-    uint8_t *data;
-    TmpData(size_t size) {
-        data = (uint8_t*) malloc(size);
-    }
-    ~TmpData() {
-        free(data);
-    }
-};
-
 status_t MyOpusExtractor::verifyOpusComments(MediaBufferBase *buffer) {
     // add artificial framing bit so we can reuse _vorbis_unpack_comment
     int32_t commentSize = buffer->range_length() + 1;
-    TmpData commentDataHolder(commentSize);
-    uint8_t *commentData = commentDataHolder.data;
+    auto tmp = heapbuffer<uint8_t>(commentSize);
+    uint8_t *commentData = tmp.get();
     if (commentData == nullptr) {
         return ERROR_MALFORMED;
     }
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
index 9eed96d..61d7d27 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
@@ -64,27 +64,54 @@
  * The read and write must be symmetric.
  */
 status_t AudioEndpointParcelable::writeToParcel(Parcel* parcel) const {
-    parcel->writeInt32(mNumSharedMemories);
+    status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+    if (status != NO_ERROR) goto error;
+
+    status = parcel->writeInt32(mNumSharedMemories);
+    if (status != NO_ERROR) goto error;
+
     for (int i = 0; i < mNumSharedMemories; i++) {
-        mSharedMemories[i].writeToParcel(parcel);
+        status = mSharedMemories[i].writeToParcel(parcel);
+        if (status != NO_ERROR) goto error;
     }
-    mUpMessageQueueParcelable.writeToParcel(parcel);
-    mDownMessageQueueParcelable.writeToParcel(parcel);
-    mUpDataQueueParcelable.writeToParcel(parcel);
-    mDownDataQueueParcelable.writeToParcel(parcel);
-    return NO_ERROR; // TODO check for errors above
+    status = mUpMessageQueueParcelable.writeToParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mDownMessageQueueParcelable.writeToParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mUpDataQueueParcelable.writeToParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mDownDataQueueParcelable.writeToParcel(parcel);
+    if (status != NO_ERROR) goto error;
+
+    return NO_ERROR;
+
+error:
+    ALOGE("%s returning %d", __func__, status);
+    return status;
 }
 
 status_t AudioEndpointParcelable::readFromParcel(const Parcel* parcel) {
-    parcel->readInt32(&mNumSharedMemories);
+    status_t status = parcel->readInt32(&mNumSharedMemories);
+    if (status != NO_ERROR) goto error;
+
     for (int i = 0; i < mNumSharedMemories; i++) {
         mSharedMemories[i].readFromParcel(parcel);
+        if (status != NO_ERROR) goto error;
     }
-    mUpMessageQueueParcelable.readFromParcel(parcel);
-    mDownMessageQueueParcelable.readFromParcel(parcel);
-    mUpDataQueueParcelable.readFromParcel(parcel);
-    mDownDataQueueParcelable.readFromParcel(parcel);
-    return NO_ERROR; // TODO check for errors above
+    status = mUpMessageQueueParcelable.readFromParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mDownMessageQueueParcelable.readFromParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mUpDataQueueParcelable.readFromParcel(parcel);
+    if (status != NO_ERROR) goto error;
+    status = mDownDataQueueParcelable.readFromParcel(parcel);
+    if (status != NO_ERROR) goto error;
+
+    return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
+    ALOGE("%s returning %d", __func__, status);
+    return status;
 }
 
 aaudio_result_t AudioEndpointParcelable::resolve(EndpointDescriptor *descriptor) {
@@ -109,35 +136,11 @@
     return AAudioConvert_androidToAAudioResult(err);
 }
 
-aaudio_result_t AudioEndpointParcelable::validate() {
-    aaudio_result_t result;
+aaudio_result_t AudioEndpointParcelable::validate() const {
     if (mNumSharedMemories < 0 || mNumSharedMemories >= MAX_SHARED_MEMORIES) {
         ALOGE("invalid mNumSharedMemories = %d", mNumSharedMemories);
         return AAUDIO_ERROR_INTERNAL;
     }
-    for (int i = 0; i < mNumSharedMemories; i++) {
-        result = mSharedMemories[i].validate();
-        if (result != AAUDIO_OK) {
-            ALOGE("invalid mSharedMemories[%d] = %d", i, result);
-            return result;
-        }
-    }
-    if ((result = mUpMessageQueueParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mUpMessageQueueParcelable = %d", result);
-        return result;
-    }
-    if ((result = mDownMessageQueueParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mDownMessageQueueParcelable = %d", result);
-        return result;
-    }
-    if ((result = mUpDataQueueParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mUpDataQueueParcelable = %d", result);
-        return result;
-    }
-    if ((result = mDownDataQueueParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mDownDataQueueParcelable = %d", result);
-        return result;
-    }
     return AAUDIO_OK;
 }
 
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.h b/media/libaaudio/src/binding/AudioEndpointParcelable.h
index aa8573f..e4f8b9e 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.h
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.h
@@ -56,8 +56,6 @@
 
     aaudio_result_t resolve(EndpointDescriptor *descriptor);
 
-    aaudio_result_t validate();
-
     aaudio_result_t close();
 
     void dump();
@@ -70,6 +68,8 @@
     RingBufferParcelable    mDownDataQueueParcelable;    // eg. playback
 
 private:
+    aaudio_result_t         validate() const;
+
     int32_t                 mNumSharedMemories = 0;
     SharedMemoryParcelable  mSharedMemories[MAX_SHARED_MEMORIES];
 };
diff --git a/media/libaaudio/src/binding/IAAudioService.cpp b/media/libaaudio/src/binding/IAAudioService.cpp
index b3c4934..620edc7 100644
--- a/media/libaaudio/src/binding/IAAudioService.cpp
+++ b/media/libaaudio/src/binding/IAAudioService.cpp
@@ -121,17 +121,11 @@
             ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION passed result %d", result);
             return result;
         }
-        err = parcelable.readFromParcel(&reply);;
+        err = parcelable.readFromParcel(&reply);
         if (err != NO_ERROR) {
             ALOGE("BpAAudioService::client transact(GET_STREAM_DESCRIPTION) read endpoint %d", err);
             return AAudioConvert_androidToAAudioResult(err);
         }
-        //parcelable.dump();
-        result = parcelable.validate();
-        if (result != AAUDIO_OK) {
-            ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION validation fails %d", result);
-            return result;
-        }
         return result;
     }
 
@@ -250,6 +244,7 @@
     pid_t tid;
     int64_t nanoseconds;
     aaudio_result_t result;
+    status_t status = NO_ERROR;
     ALOGV("BnAAudioService::onTransact(%i) %i", code, flags);
 
     switch(code) {
@@ -294,21 +289,20 @@
 
         case GET_STREAM_DESCRIPTION: {
             CHECK_INTERFACE(IAAudioService, data, reply);
-            data.readInt32(&streamHandle);
+            status = data.readInt32(&streamHandle);
+            if (status != NO_ERROR) {
+                return status;
+            }
             aaudio::AudioEndpointParcelable parcelable;
             result = getStreamDescription(streamHandle, parcelable);
             if (result != AAUDIO_OK) {
                 return AAudioConvert_aaudioToAndroidStatus(result);
             }
-            result = parcelable.validate();
-            if (result != AAUDIO_OK) {
-                ALOGE("BnAAudioService::onTransact getStreamDescription() returns %d", result);
-                parcelable.dump();
-                return AAudioConvert_aaudioToAndroidStatus(result);
+            status = reply->writeInt32(result);
+            if (status != NO_ERROR) {
+                return status;
             }
-            reply->writeInt32(result);
-            parcelable.writeToParcel(reply);
-            return NO_ERROR;
+            return parcelable.writeToParcel(reply);
         } break;
 
         case START_STREAM: {
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.cpp b/media/libaaudio/src/binding/RingBufferParcelable.cpp
index 2babbff..4996b3f 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.cpp
+++ b/media/libaaudio/src/binding/RingBufferParcelable.cpp
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include <binder/Parcelable.h>
+#include <utility/AAudioUtilities.h>
 
 #include "binding/AAudioServiceDefinitions.h"
 #include "binding/SharedRegionParcelable.h"
@@ -79,7 +80,10 @@
  * The read and write must be symmetric.
  */
 status_t RingBufferParcelable::writeToParcel(Parcel* parcel) const {
-    status_t status = parcel->writeInt32(mCapacityInFrames);
+    status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+    if (status != NO_ERROR) goto error;
+
+    status = parcel->writeInt32(mCapacityInFrames);
     if (status != NO_ERROR) goto error;
     if (mCapacityInFrames > 0) {
         status = parcel->writeInt32(mBytesPerFrame);
@@ -97,7 +101,7 @@
     }
     return NO_ERROR;
 error:
-    ALOGE("writeToParcel() error = %d", status);
+    ALOGE("%s returning %d", __func__, status);
     return status;
 }
 
@@ -118,9 +122,9 @@
         status = mDataParcelable.readFromParcel(parcel);
         if (status != NO_ERROR) goto error;
     }
-    return NO_ERROR;
+    return AAudioConvert_aaudioToAndroidStatus(validate());
 error:
-    ALOGE("readFromParcel() error = %d", status);
+    ALOGE("%s returning %d", __func__, status);
     return status;
 }
 
@@ -151,8 +155,7 @@
     return AAUDIO_OK;
 }
 
-aaudio_result_t RingBufferParcelable::validate() {
-    aaudio_result_t result;
+aaudio_result_t RingBufferParcelable::validate() const {
     if (mCapacityInFrames < 0 || mCapacityInFrames >= 32 * 1024) {
         ALOGE("invalid mCapacityInFrames = %d", mCapacityInFrames);
         return AAUDIO_ERROR_INTERNAL;
@@ -165,18 +168,6 @@
         ALOGE("invalid mFramesPerBurst = %d", mFramesPerBurst);
         return AAUDIO_ERROR_INTERNAL;
     }
-    if ((result = mReadCounterParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mReadCounterParcelable = %d", result);
-        return result;
-    }
-    if ((result = mWriteCounterParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mWriteCounterParcelable = %d", result);
-        return result;
-    }
-    if ((result = mDataParcelable.validate()) != AAUDIO_OK) {
-        ALOGE("invalid mDataParcelable = %d", result);
-        return result;
-    }
     return AAUDIO_OK;
 }
 
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.h b/media/libaaudio/src/binding/RingBufferParcelable.h
index bd562f2..1dbcf07 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.h
+++ b/media/libaaudio/src/binding/RingBufferParcelable.h
@@ -66,11 +66,12 @@
 
     aaudio_result_t resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor);
 
-    aaudio_result_t validate();
-
     void dump();
 
 private:
+
+    aaudio_result_t validate() const;
+
     SharedRegionParcelable  mReadCounterParcelable;
     SharedRegionParcelable  mWriteCounterParcelable;
     SharedRegionParcelable  mDataParcelable;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
index 4e3e5d1..0b0cf77 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
@@ -48,7 +48,10 @@
 }
 
 status_t SharedMemoryParcelable::writeToParcel(Parcel* parcel) const {
-    status_t status = parcel->writeInt32(mSizeInBytes);
+    status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+    if (status != NO_ERROR) return status;
+
+    status = parcel->writeInt32(mSizeInBytes);
     if (status != NO_ERROR) return status;
     if (mSizeInBytes > 0) {
         ALOGV("writeToParcel() mFd = %d, this = %p\n", mFd.get(), this);
@@ -61,21 +64,27 @@
 
 status_t SharedMemoryParcelable::readFromParcel(const Parcel* parcel) {
     status_t status = parcel->readInt32(&mSizeInBytes);
-    if (status != NO_ERROR) {
-        return status;
-    }
+    if (status != NO_ERROR) goto error;
+
     if (mSizeInBytes > 0) {
         // The Parcel owns the file descriptor and will close it later.
         unique_fd mmapFd;
         status = parcel->readUniqueFileDescriptor(&mmapFd);
         if (status != NO_ERROR) {
             ALOGE("readFromParcel() readUniqueFileDescriptor() failed : %d", status);
-        } else {
-            // Resolve the memory now while we still have the FD from the Parcel.
-            // Closing the FD will not affect the shared memory once mmap() has been called.
-            status = AAudioConvert_androidToAAudioResult(resolveSharedMemory(mmapFd));
+            goto error;
         }
+
+        // Resolve the memory now while we still have the FD from the Parcel.
+        // Closing the FD will not affect the shared memory once mmap() has been called.
+        aaudio_result_t result = resolveSharedMemory(mmapFd);
+        status = AAudioConvert_aaudioToAndroidStatus(result);
+        if (status != NO_ERROR) goto error;
     }
+
+    return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
     return status;
 }
 
@@ -136,7 +145,7 @@
     return mSizeInBytes;
 }
 
-aaudio_result_t SharedMemoryParcelable::validate() {
+aaudio_result_t SharedMemoryParcelable::validate() const {
     if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
         ALOGE("invalid mSizeInBytes = %d", mSizeInBytes);
         return AAUDIO_ERROR_OUT_OF_RANGE;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.h b/media/libaaudio/src/binding/SharedMemoryParcelable.h
index 2a634e0..82c2240 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.h
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.h
@@ -61,8 +61,6 @@
 
     int32_t getSizeInBytes();
 
-    aaudio_result_t validate();
-
     void dump();
 
 protected:
@@ -74,6 +72,11 @@
     android::base::unique_fd   mFd;
     int32_t     mSizeInBytes = 0;
     uint8_t    *mResolvedAddress = MMAP_UNRESOLVED_ADDRESS;
+
+private:
+
+    aaudio_result_t validate() const;
+
 };
 
 } /* namespace aaudio */
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.cpp b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
index 7aa80bf..c776116 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
@@ -24,6 +24,7 @@
 #include <binder/Parcelable.h>
 
 #include <aaudio/AAudio.h>
+#include <utility/AAudioUtilities.h>
 
 #include "binding/SharedMemoryParcelable.h"
 #include "binding/SharedRegionParcelable.h"
@@ -47,21 +48,38 @@
 }
 
 status_t SharedRegionParcelable::writeToParcel(Parcel* parcel) const {
-    parcel->writeInt32(mSizeInBytes);
+    status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+    if (status != NO_ERROR) goto error;
+
+    status = parcel->writeInt32(mSizeInBytes);
+    if (status != NO_ERROR) goto error;
     if (mSizeInBytes > 0) {
-        parcel->writeInt32(mSharedMemoryIndex);
-        parcel->writeInt32(mOffsetInBytes);
+        status = parcel->writeInt32(mSharedMemoryIndex);
+        if (status != NO_ERROR) goto error;
+        status = parcel->writeInt32(mOffsetInBytes);
+        if (status != NO_ERROR) goto error;
     }
-    return NO_ERROR; // TODO check for errors above
+    return NO_ERROR;
+
+error:
+    ALOGE("%s returning %d", __func__, status);
+    return status;
 }
 
 status_t SharedRegionParcelable::readFromParcel(const Parcel* parcel) {
-    parcel->readInt32(&mSizeInBytes);
+    status_t status = parcel->readInt32(&mSizeInBytes);
+    if (status != NO_ERROR) goto error;
     if (mSizeInBytes > 0) {
-        parcel->readInt32(&mSharedMemoryIndex);
-        parcel->readInt32(&mOffsetInBytes);
+        status = parcel->readInt32(&mSharedMemoryIndex);
+        if (status != NO_ERROR) goto error;
+        status = parcel->readInt32(&mOffsetInBytes);
+        if (status != NO_ERROR) goto error;
     }
-    return NO_ERROR; // TODO check for errors above
+    return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
+    ALOGE("%s returning %d", __func__, status);
+    return status;
 }
 
 aaudio_result_t SharedRegionParcelable::resolve(SharedMemoryParcelable *memoryParcels,
@@ -78,7 +96,7 @@
     return memoryParcel->resolve(mOffsetInBytes, mSizeInBytes, regionAddressPtr);
 }
 
-aaudio_result_t SharedRegionParcelable::validate() {
+aaudio_result_t SharedRegionParcelable::validate() const {
     if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
         ALOGE("invalid mSizeInBytes = %d", mSizeInBytes);
         return AAUDIO_ERROR_OUT_OF_RANGE;
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.h b/media/libaaudio/src/binding/SharedRegionParcelable.h
index f6babfd..0cd8c04 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.h
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.h
@@ -47,14 +47,15 @@
 
     bool isFileDescriptorSafe(SharedMemoryParcelable *memoryParcels);
 
-    aaudio_result_t validate();
-
     void dump();
 
 protected:
     int32_t mSharedMemoryIndex = -1;
     int32_t mOffsetInBytes     = 0;
     int32_t mSizeInBytes       = 0;
+
+private:
+    aaudio_result_t validate() const;
 };
 
 } /* namespace aaudio */
diff --git a/media/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/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
index c43509c..7ff1ec7d 100644
--- a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
@@ -33,7 +33,8 @@
 }
 
 status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
-    if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
+    if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 &&
+        strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) {
         return mHidlFactory->openDevice(name, device);
     }
     return mLocalFactory->openDevice(name, device);
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index 63130c4..8dae251 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -337,8 +337,8 @@
 
     if (frameInfo != nullptr) {
         frameInfo->set(
-                videoFrame->mDisplayWidth,
-                videoFrame->mDisplayHeight,
+                videoFrame->mWidth,
+                videoFrame->mHeight,
                 videoFrame->mRotationAngle,
                 videoFrame->mBytesPerPixel,
                 videoFrame->mIccSize,
@@ -415,8 +415,8 @@
 
     if (frameInfo != nullptr) {
         frameInfo->set(
-                videoFrame->mDisplayWidth,
-                videoFrame->mDisplayHeight,
+                videoFrame->mWidth,
+                videoFrame->mHeight,
                 videoFrame->mRotationAngle,
                 videoFrame->mBytesPerPixel,
                 videoFrame->mIccSize,
@@ -435,12 +435,12 @@
         return false;
     }
     VideoFrame* videoFrame = static_cast<VideoFrame*>(mFrameMemory->pointer());
-    if (mCurScanline >= videoFrame->mDisplayHeight) {
+    if (mCurScanline >= videoFrame->mHeight) {
         ALOGE("no more scanline available");
         return false;
     }
     uint8_t* src = videoFrame->getFlattenedData() + videoFrame->mRowBytes * mCurScanline++;
-    memcpy(dst, src, videoFrame->mBytesPerPixel * videoFrame->mDisplayWidth);
+    memcpy(dst, src, videoFrame->mBytesPerPixel * videoFrame->mWidth);
     return true;
 }
 
@@ -452,8 +452,8 @@
 
     uint32_t oldScanline = mCurScanline;
     mCurScanline += count;
-    if (mCurScanline > videoFrame->mDisplayHeight) {
-        mCurScanline = videoFrame->mDisplayHeight;
+    if (mCurScanline > videoFrame->mHeight) {
+        mCurScanline = videoFrame->mHeight;
     }
     return (mCurScanline > oldScanline) ? (mCurScanline - oldScanline) : 0;
 }
diff --git a/media/libmedia/NdkWrapper.cpp b/media/libmedia/NdkWrapper.cpp
index 5418af9..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,
 };
 
@@ -1068,6 +1068,14 @@
     return OK;
 }
 
+status_t AMediaExtractorWrapper::disconnect() {
+    if (mAMediaExtractor != NULL) {
+        media_status_t err = AMediaExtractor_disconnect(mAMediaExtractor);
+        return translateErrorCode(err);
+    }
+    return DEAD_OBJECT;
+}
+
 AMediaExtractor *AMediaExtractorWrapper::getAMediaExtractor() const {
     return mAMediaExtractor;
 }
diff --git a/media/libmedia/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/libmedia/include/media/NdkWrapper.h b/media/libmedia/include/media/NdkWrapper.h
index 191665a..c97d171 100644
--- a/media/libmedia/include/media/NdkWrapper.h
+++ b/media/libmedia/include/media/NdkWrapper.h
@@ -287,6 +287,8 @@
 
     status_t release();
 
+    status_t disconnect();
+
     status_t setDataSource(int fd, off64_t offset, off64_t length);
 
     status_t setDataSource(const char *location);
diff --git a/media/libmediaextractor/include/media/ExtractorUtils.h b/media/libmediaextractor/include/media/ExtractorUtils.h
new file mode 100644
index 0000000..22f9349
--- /dev/null
+++ b/media/libmediaextractor/include/media/ExtractorUtils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EXTRACTOR_UTILS_H_
+
+#define EXTRACTOR_UTILS_H_
+
+#include <memory>
+
+namespace android {
+
+template <class T>
+std::unique_ptr<T[]> heapbuffer(size_t size) {
+    return std::unique_ptr<T[]>(new (std::nothrow) T[size]);
+}
+
+}  // namespace android
+
+#endif  // UTILS_H_
diff --git a/media/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 b296622..a8c6d15 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -37,7 +37,6 @@
 
 #include <media/stagefright/BufferProducerWrapper.h>
 #include <media/stagefright/MediaCodec.h>
-#include <media/stagefright/MediaCodecList.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/OMXClient.h>
 #include <media/stagefright/PersistentSurface.h>
@@ -553,6 +552,7 @@
       mNativeWindowUsageBits(0),
       mLastNativeWindowDataSpace(HAL_DATASPACE_UNKNOWN),
       mIsVideo(false),
+      mIsImage(false),
       mIsEncoder(false),
       mFatalError(false),
       mShutdownInProgress(false),
@@ -1713,6 +1713,7 @@
 
     mIsEncoder = encoder;
     mIsVideo = !strncasecmp(mime, "video/", 6);
+    mIsImage = !strncasecmp(mime, "image/", 6);
 
     mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
     mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;
@@ -1728,10 +1729,11 @@
     // FLAC encoder or video encoder in constant quality mode doesn't need a
     // bitrate, other encoders do.
     if (encoder) {
-        if (mIsVideo && !findVideoBitrateControlInfo(
-                msg, &bitrateMode, &bitrate, &quality)) {
-            return INVALID_OPERATION;
-        } else if (!mIsVideo && strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
+        if (mIsVideo || mIsImage) {
+            if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) {
+                return INVALID_OPERATION;
+            }
+        } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
             && !msg->findInt32("bitrate", &bitrate)) {
             return INVALID_OPERATION;
         }
@@ -2010,7 +2012,7 @@
     (void)msg->findInt32("pcm-encoding", (int32_t*)&pcmEncoding);
     // invalid encodings will default to PCM-16bit in setupRawAudioFormat.
 
-    if (mIsVideo) {
+    if (mIsVideo || mIsImage) {
         // determine need for software renderer
         bool usingSwRenderer = false;
         if (haveNativeWindow && mComponentName.startsWith("OMX.google.")) {
@@ -2290,7 +2292,7 @@
     }
 
     // create data converters if needed
-    if (!mIsVideo && err == OK) {
+    if (!mIsVideo && !mIsImage && err == OK) {
         AudioEncoding codecPcmEncoding = kAudioEncodingPcm16bit;
         if (encoder) {
             (void)mInputFormat->findInt32("pcm-encoding", (int32_t*)&codecPcmEncoding);
@@ -3215,6 +3217,7 @@
     { MEDIA_MIMETYPE_VIDEO_VP8, OMX_VIDEO_CodingVP8 },
     { MEDIA_MIMETYPE_VIDEO_VP9, OMX_VIDEO_CodingVP9 },
     { MEDIA_MIMETYPE_VIDEO_DOLBY_VISION, OMX_VIDEO_CodingDolbyVision },
+    { MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC, OMX_VIDEO_CodingImageHEIC },
 };
 
 static status_t GetVideoCodingTypeFromMime(
@@ -3872,7 +3875,8 @@
             break;
 
         case OMX_VIDEO_CodingHEVC:
-            err = setupHEVCEncoderParameters(msg);
+        case OMX_VIDEO_CodingImageHEIC:
+            err = setupHEVCEncoderParameters(msg, outputFormat);
             break;
 
         case OMX_VIDEO_CodingVP8:
@@ -4378,27 +4382,63 @@
     return configureBitrate(bitrateMode, bitrate);
 }
 
-status_t ACodec::setupHEVCEncoderParameters(const sp<AMessage> &msg) {
-    float iFrameInterval;
-    if (!msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
-        return INVALID_OPERATION;
+status_t ACodec::configureImageGrid(
+        const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
+    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;
     }
 
+    OMX_VIDEO_PARAM_ANDROID_IMAGEGRIDTYPE gridType;
+    InitOMXParams(&gridType);
+    gridType.nPortIndex = kPortIndexOutput;
+    gridType.bEnabled = OMX_TRUE;
+    gridType.nTileWidth = tileWidth;
+    gridType.nTileHeight = tileHeight;
+    gridType.nGridRows = gridRows;
+    gridType.nGridCols = gridCols;
+
+    status_t err = mOMXNode->setParameter(
+            (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidImageGrid,
+            &gridType, sizeof(gridType));
+
+    // for video encoders, grid config is only a hint.
+    if (!mIsImage) {
+        return OK;
+    }
+
+    // image encoders must support grid config.
+    if (err != OK) {
+        return err;
+    }
+
+    // query to get the image encoder's real grid config as it might be
+    // different from the requested, and transfer that to the output.
+    err = mOMXNode->getParameter(
+            (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidImageGrid,
+            &gridType, sizeof(gridType));
+
+    if (err == OK && gridType.bEnabled) {
+        outputFormat->setInt32("tile-width", gridType.nTileWidth);
+        outputFormat->setInt32("tile-height", gridType.nTileHeight);
+        outputFormat->setInt32("grid-rows", gridType.nGridRows);
+        outputFormat->setInt32("grid-cols", gridType.nGridCols);
+    }
+
+    return err;
+}
+
+status_t ACodec::setupHEVCEncoderParameters(
+        const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
     OMX_VIDEO_CONTROLRATETYPE bitrateMode;
     int32_t bitrate, quality;
     if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) {
         return INVALID_OPERATION;
     }
 
-    float frameRate;
-    if (!msg->findFloat("frame-rate", &frameRate)) {
-        int32_t tmp;
-        if (!msg->findInt32("frame-rate", &tmp)) {
-            return INVALID_OPERATION;
-        }
-        frameRate = (float)tmp;
-    }
-
     OMX_VIDEO_PARAM_HEVCTYPE hevcType;
     InitOMXParams(&hevcType);
     hevcType.nPortIndex = kPortIndexOutput;
@@ -4426,7 +4466,27 @@
         hevcType.eLevel = static_cast<OMX_VIDEO_HEVCLEVELTYPE>(level);
     }
     // TODO: finer control?
-    hevcType.nKeyFrameInterval = setPFramesSpacing(iFrameInterval, frameRate) + 1;
+    if (mIsImage) {
+        hevcType.nKeyFrameInterval = 1;
+    } else {
+        float iFrameInterval;
+        if (!msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
+            return INVALID_OPERATION;
+        }
+
+        float frameRate;
+        if (!msg->findFloat("frame-rate", &frameRate)) {
+            int32_t tmp;
+            if (!msg->findInt32("frame-rate", &tmp)) {
+                return INVALID_OPERATION;
+            }
+            frameRate = (float)tmp;
+        }
+
+        hevcType.nKeyFrameInterval =
+                setPFramesSpacing(iFrameInterval, frameRate) + 1;
+    }
+
 
     err = mOMXNode->setParameter(
             (OMX_INDEXTYPE)OMX_IndexParamVideoHevc, &hevcType, sizeof(hevcType));
@@ -4434,6 +4494,12 @@
         return err;
     }
 
+    err = configureImageGrid(msg, outputFormat);
+
+    if (err != OK) {
+        return err;
+    }
+
     return configureBitrate(bitrateMode, bitrate, quality);
 }
 
@@ -4875,7 +4941,8 @@
                             (void)getHDRStaticInfoForVideoCodec(kPortIndexInput, notify);
                         }
                         uint32_t latency = 0;
-                        if (mIsEncoder && getLatency(&latency) == OK && latency > 0) {
+                        if (mIsEncoder && !mIsImage &&
+                                getLatency(&latency) == OK && latency > 0) {
                             notify->setInt32("latency", latency);
                         }
                     }
@@ -4931,7 +4998,8 @@
                         notify->setString("mime", mime.c_str());
                     }
                     uint32_t intraRefreshPeriod = 0;
-                    if (mIsEncoder && getIntraRefreshPeriod(&intraRefreshPeriod) == OK
+                    if (mIsEncoder && !mIsImage &&
+                            getIntraRefreshPeriod(&intraRefreshPeriod) == OK
                             && intraRefreshPeriod > 0) {
                         notify->setInt32("intra-refresh-period", intraRefreshPeriod);
                     }
@@ -6346,37 +6414,19 @@
 
     sp<AMessage> notify = new AMessage(kWhatOMXDied, mCodec);
 
-    Vector<AString> matchingCodecs;
-    Vector<AString> owners;
-
-    AString componentName;
-    CHECK(msg->findString("componentName", &componentName));
-
-    sp<IMediaCodecList> list = MediaCodecList::getInstance();
-    if (list == nullptr) {
-        ALOGE("Unable to obtain MediaCodecList while "
-                "attempting to create codec \"%s\"",
-                componentName.c_str());
-        mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
-        return false;
-    }
-    ssize_t index = list->findCodecByName(componentName.c_str());
-    if (index < 0) {
-        ALOGE("Unable to find codec \"%s\"",
-                componentName.c_str());
-        mCodec->signalError(OMX_ErrorInvalidComponent, NAME_NOT_FOUND);
-        return false;
-    }
-    sp<MediaCodecInfo> info = list->getCodecInfo(index);
+    sp<RefBase> obj;
+    CHECK(msg->findObject("codecInfo", &obj));
+    sp<MediaCodecInfo> info = (MediaCodecInfo *)obj.get();
     if (info == nullptr) {
-        ALOGE("Unexpected error (index out-of-bound) while "
-                "retrieving information for codec \"%s\"",
-                componentName.c_str());
+        ALOGE("Unexpected nullptr for codec information");
         mCodec->signalError(OMX_ErrorUndefined, UNKNOWN_ERROR);
         return false;
     }
     AString owner = (info->getOwnerName() == nullptr) ? "default" : info->getOwnerName();
 
+    AString componentName;
+    CHECK(msg->findString("componentName", &componentName));
+
     sp<CodecObserver> observer = new CodecObserver;
     sp<IOMX> omx;
     sp<IOMXNode> omxNode;
@@ -8226,8 +8276,9 @@
     }
 
     bool isVideo = strncasecmp(mime, "video/", 6) == 0;
+    bool isImage = strncasecmp(mime, "image/", 6) == 0;
 
-    if (isVideo) {
+    if (isVideo || isImage) {
         OMX_VIDEO_PARAM_PROFILELEVELTYPE param;
         InitOMXParams(&param);
         param.nPortIndex = isEncoder ? kPortIndexOutput : kPortIndexInput;
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 13d80f5..71bff84 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -49,6 +49,100 @@
 }
 
 cc_library_shared {
+    name: "libstagefright_codecbase",
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "CodecBase.cpp",
+        "FrameRenderTracker.cpp",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+    shared_libs: [
+        "libgui",
+        "liblog",
+        "libmedia",
+        "libstagefright_foundation",
+        "libui",
+        "libutils",
+        "android.hardware.cas.native@1.0",
+    ],
+
+    sanitize: {
+        cfi: true,
+        misc_undefined: [
+            "unsigned-integer-overflow",
+            "signed-integer-overflow",
+        ],
+        diag: {
+            cfi: true,
+        },
+    },
+}
+
+cc_library_shared {
+    name: "libstagefright_ccodec",
+
+    local_include_dirs: ["include"],
+
+    srcs: [
+        "C2OMXNode.cpp",
+        "CCodec.cpp",
+        "CCodecBufferChannel.cpp",
+        "Codec2Buffer.cpp",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+    header_libs: [
+        "libstagefright_codec2_internal",
+    ],
+
+    shared_libs: [
+        "libbinder",
+        "libcutils",
+        "libgui",
+        "libhidlallocatorutils",
+        "libhidlbase",
+        "liblog",
+        "libmedia",
+        "libmedia_omx",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_codecbase",
+        "libstagefright_foundation",
+        "libstagefright_omx_utils",
+        "libui",
+        "libutils",
+        "libv4l2_c2componentstore",
+        "android.hardware.cas.native@1.0",
+
+        // TODO: do not link directly with impl
+        "libstagefright_bufferqueue_helper",
+        "android.hardware.media.c2@1.0-service-impl",
+    ],
+
+    sanitize: {
+        cfi: true,
+        misc_undefined: [
+            "unsigned-integer-overflow",
+            "signed-integer-overflow",
+        ],
+        diag: {
+            cfi: true,
+        },
+    },
+}
+
+cc_library_shared {
     name: "libstagefright",
 
     srcs: [
@@ -60,11 +154,7 @@
         "AudioPresentationInfo.cpp",
         "AudioSource.cpp",
         "BufferImpl.cpp",
-        "C2OMXNode.cpp",
-        "CCodec.cpp",
-        "CCodecBufferChannel.cpp",
         "Codec2InfoBuilder.cpp",
-        "CodecBase.cpp",
         "CallbackDataSource.cpp",
         "CallbackMediaSource.cpp",
         "CameraSource.cpp",
@@ -74,7 +164,6 @@
         "DataURISource.cpp",
         "FileSource.cpp",
         "FrameDecoder.cpp",
-        "FrameRenderTracker.cpp",
         "HTTPBase.cpp",
         "HevcUtils.cpp",
         "InterfaceUtils.cpp",
@@ -107,10 +196,6 @@
         "VideoFrameScheduler.cpp",
     ],
 
-    header_libs: [
-        "libstagefright_codec2_internal",
-    ],
-
     shared_libs: [
         "libaudioutils",
         "libbinder",
@@ -131,8 +216,10 @@
         "libui",
         "libutils",
         "libmedia_helper",
+        "libstagefright_ccodec",
         "libstagefright_codec2",
         "libstagefright_codec2_vndk",
+        "libstagefright_codecbase",
         "libstagefright_foundation",
         "libstagefright_omx",
         "libstagefright_omx_utils",
@@ -149,10 +236,6 @@
         "android.hardware.media.omx@1.0",
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.mapper@2.0",
-
-        // TODO: do not link directly with impl
-        "android.hardware.media.c2@1.0-service-impl",
-        "libstagefright_bufferqueue_helper",
     ],
 
     static_libs: [
diff --git a/media/libstagefright/BufferImpl.cpp b/media/libstagefright/BufferImpl.cpp
index 7b3fa02..b760273 100644
--- a/media/libstagefright/BufferImpl.cpp
+++ b/media/libstagefright/BufferImpl.cpp
@@ -24,7 +24,6 @@
 #include <media/ICrypto.h>
 #include <utils/NativeHandle.h>
 
-#include "include/Codec2Buffer.h"
 #include "include/SecureBuffer.h"
 #include "include/SharedMemoryBuffer.h"
 
@@ -64,592 +63,4 @@
     return ICrypto::kDestinationTypeNativeHandle;
 }
 
-// Codec2Buffer
-
-bool Codec2Buffer::canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const {
-    if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
-        return false;
-    }
-    if (!buffer) {
-        // Nothing to copy, so we can copy by doing nothing.
-        return true;
-    }
-    if (buffer->data().type() != C2BufferData::LINEAR) {
-        return false;
-    }
-    if (buffer->data().linearBlocks().size() == 0u) {
-        // Nothing to copy, so we can copy by doing nothing.
-        return true;
-    } else if (buffer->data().linearBlocks().size() > 1u) {
-        // We don't know how to copy more than one blocks.
-        return false;
-    }
-    if (buffer->data().linearBlocks()[0].size() > capacity()) {
-        // It won't fit.
-        return false;
-    }
-    return true;
-}
-
-bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
-    // We assume that all canCopyLinear() checks passed.
-    if (!buffer || buffer->data().linearBlocks().size() == 0u) {
-        setRange(0, 0);
-        return true;
-    }
-    C2ReadView view = buffer->data().linearBlocks()[0].map().get();
-    if (view.error() != C2_OK) {
-        ALOGD("Error while mapping: %d", view.error());
-        return false;
-    }
-    if (view.capacity() > capacity()) {
-        ALOGD("C2ConstLinearBlock lied --- it actually doesn't fit: view(%u) > this(%zu)",
-                view.capacity(), capacity());
-        return false;
-    }
-    memcpy(base(), view.data(), view.capacity());
-    setRange(0, view.capacity());
-    return true;
-}
-
-// LocalLinearBuffer
-
-bool LocalLinearBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
-    return canCopyLinear(buffer);
-}
-
-bool LocalLinearBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
-    return copyLinear(buffer);
-}
-
-// DummyContainerBuffer
-
-DummyContainerBuffer::DummyContainerBuffer(
-        const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer)
-    : Codec2Buffer(format, new ABuffer(nullptr, 1)),
-      mBufferRef(buffer) {
-    setRange(0, buffer ? 1 : 0);
-}
-
-std::shared_ptr<C2Buffer> DummyContainerBuffer::asC2Buffer() {
-    return std::move(mBufferRef);
-}
-
-bool DummyContainerBuffer::canCopy(const std::shared_ptr<C2Buffer> &) const {
-    return !mBufferRef;
-}
-
-bool DummyContainerBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
-    mBufferRef = buffer;
-    setRange(0, mBufferRef ? 1 : 0);
-    return true;
-}
-
-// LinearBlockBuffer
-
-// static
-sp<LinearBlockBuffer> LinearBlockBuffer::Allocate(
-        const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
-    C2WriteView writeView(block->map().get());
-    if (writeView.error() != C2_OK) {
-        return nullptr;
-    }
-    return new LinearBlockBuffer(format, std::move(writeView), block);
-}
-
-std::shared_ptr<C2Buffer> LinearBlockBuffer::asC2Buffer() {
-    return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
-}
-
-bool LinearBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
-    return canCopyLinear(buffer);
-}
-
-bool LinearBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
-    return copyLinear(buffer);
-}
-
-LinearBlockBuffer::LinearBlockBuffer(
-        const sp<AMessage> &format,
-        C2WriteView&& writeView,
-        const std::shared_ptr<C2LinearBlock> &block)
-    : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
-      mWriteView(writeView),
-      mBlock(block) {
-}
-
-// ConstLinearBlockBuffer
-
-// static
-sp<ConstLinearBlockBuffer> ConstLinearBlockBuffer::Allocate(
-        const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer) {
-    if (!buffer
-            || buffer->data().type() != C2BufferData::LINEAR
-            || buffer->data().linearBlocks().size() != 1u) {
-        return nullptr;
-    }
-    C2ReadView readView(buffer->data().linearBlocks()[0].map().get());
-    if (readView.error() != C2_OK) {
-        return nullptr;
-    }
-    return new ConstLinearBlockBuffer(format, std::move(readView), buffer);
-}
-
-ConstLinearBlockBuffer::ConstLinearBlockBuffer(
-        const sp<AMessage> &format,
-        C2ReadView&& readView,
-        const std::shared_ptr<C2Buffer> &buffer)
-    : Codec2Buffer(format, new ABuffer(
-            // NOTE: ABuffer only takes non-const pointer but this data is
-            //       supposed to be read-only.
-            const_cast<uint8_t *>(readView.data()), readView.capacity())),
-      mReadView(readView),
-      mBufferRef(buffer) {
-}
-
-std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
-    return std::move(mBufferRef);
-}
-
-// GraphicView2MediaImageConverter
-
-namespace {
-
-class GraphicView2MediaImageConverter {
-public:
-    explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
-        : mInitCheck(NO_INIT),
-          mView(view),
-          mWidth(view.width()),
-          mHeight(view.height()),
-          mAllocatedDepth(0),
-          mBackBufferSize(0),
-          mMediaImage(new ABuffer(sizeof(MediaImage2))) {
-        if (view.error() != C2_OK) {
-            ALOGD("Converter: view.error() = %d", view.error());
-            mInitCheck = BAD_VALUE;
-            return;
-        }
-        MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
-        const C2PlanarLayout &layout = view.layout();
-        if (layout.numPlanes == 0) {
-            ALOGD("Converter: 0 planes");
-            mInitCheck = BAD_VALUE;
-            return;
-        }
-        mAllocatedDepth = layout.planes[0].allocatedDepth;
-        uint32_t bitDepth = layout.planes[0].bitDepth;
-
-        switch (layout.type) {
-            case C2PlanarLayout::TYPE_YUV:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
-            case C2PlanarLayout::TYPE_YUVA:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
-            case C2PlanarLayout::TYPE_RGB:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
-            case C2PlanarLayout::TYPE_RGBA:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
-            default:
-                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
-        }
-        mediaImage->mNumPlanes = layout.numPlanes;
-        mediaImage->mWidth = mWidth;
-        mediaImage->mHeight = mHeight;
-        mediaImage->mBitDepth = bitDepth;
-        mediaImage->mBitDepthAllocated = mAllocatedDepth;
-
-        uint32_t bufferSize = 0;
-        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            if (plane.rightShift != 0) {
-                ALOGV("rightShift value of %u unsupported", plane.rightShift);
-                mInitCheck = BAD_VALUE;
-                return;
-            }
-            if (plane.endianness != C2PlaneInfo::NATIVE) {
-                ALOGV("endianness value of %u unsupported", plane.endianness);
-                mInitCheck = BAD_VALUE;
-                return;
-            }
-            if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
-                ALOGV("different allocatedDepth/bitDepth per plane unsupported");
-                mInitCheck = BAD_VALUE;
-                return;
-            }
-            bufferSize += mWidth * mHeight
-                    / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
-        }
-
-        mBackBufferSize = bufferSize;
-        mInitCheck = OK;
-    }
-
-    status_t initCheck() const { return mInitCheck; }
-
-    uint32_t backBufferSize() const { return mBackBufferSize; }
-
-    /**
-     * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
-     * is not copied over in this function --- the caller should use
-     * CopyGraphicView2MediaImage() function to do that explicitly.
-     *
-     * \param   view[in]          source C2GraphicView object.
-     * \param   alloc[in]         allocator function for ABuffer.
-     * \param   mediaImage[out]   destination MediaImage2 object.
-     * \param   buffer[out]       new buffer object.
-     * \param   wrapped[out]      whether we wrapped around existing map or
-     *                            allocated a new buffer
-     *
-     * \return  true              if conversion succeeds,
-     *          false             otherwise; all output params should be ignored.
-     */
-    sp<ABuffer> wrap() {
-        MediaImage2 *mediaImage = getMediaImage();
-        const C2PlanarLayout &layout = mView.layout();
-        if (layout.numPlanes == 1) {
-            const C2PlaneInfo &plane = layout.planes[0];
-            ssize_t offset = plane.minOffset(mWidth, mHeight);
-            mediaImage->mPlane[0].mOffset = -offset;
-            mediaImage->mPlane[0].mColInc = plane.colInc;
-            mediaImage->mPlane[0].mRowInc = plane.rowInc;
-            mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
-            mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
-            return new ABuffer(
-                    const_cast<uint8_t *>(mView.data()[0] + offset),
-                    plane.maxOffset(mWidth, mHeight) - offset + 1);
-        }
-        const uint8_t *minPtr = mView.data()[0];
-        const uint8_t *maxPtr = mView.data()[0];
-        int32_t planeSize = 0;
-        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            ssize_t minOffset = plane.minOffset(mWidth, mHeight);
-            ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
-            if (minPtr > mView.data()[i] + minOffset) {
-                minPtr = mView.data()[i] + minOffset;
-            }
-            if (maxPtr < mView.data()[i] + maxOffset) {
-                maxPtr = mView.data()[i] + maxOffset;
-            }
-            planeSize += std::abs(plane.rowInc) * mHeight
-                    / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
-        }
-
-        if ((maxPtr - minPtr + 1) <= planeSize) {
-            // FIXME: this is risky as reading/writing data out of bound results in
-            //        an undefined behavior.
-            for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-                const C2PlaneInfo &plane = layout.planes[i];
-                mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
-                mediaImage->mPlane[i].mColInc = plane.colInc;
-                mediaImage->mPlane[i].mRowInc = plane.rowInc;
-                mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
-                mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
-            }
-            return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
-        }
-
-        return nullptr;
-    }
-
-    bool setBackBuffer(const sp<ABuffer> &backBuffer) {
-        if (backBuffer->capacity() < mBackBufferSize) {
-            return false;
-        }
-        backBuffer->setRange(0, mBackBufferSize);
-
-        const C2PlanarLayout &layout = mView.layout();
-        MediaImage2 *mediaImage = getMediaImage();
-        uint32_t offset = 0;
-        // TODO: keep interleaved planes together
-        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            mediaImage->mPlane[i].mOffset = offset;
-            mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
-            mediaImage->mPlane[i].mRowInc =
-                mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
-            mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
-            mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
-            offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
-        }
-        mBackBuffer = backBuffer;
-        return true;
-    }
-
-    /**
-     * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
-     * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
-     *
-     * \param   view[in]          source C2GraphicView object.
-     * \param   mediaImage[in]    destination MediaImage2 object.
-     * \param   buffer[out]       new buffer object.
-     */
-    void copy() {
-        // TODO: more efficient copying --- e.g. one row at a time, copying
-        //       interleaved planes together, etc.
-        const C2PlanarLayout &layout = mView.layout();
-        MediaImage2 *mediaImage = getMediaImage();
-        uint8_t *dst = mBackBuffer->base();
-        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            const uint8_t *src = mView.data()[i];
-            int32_t planeW = mWidth / plane.colSampling;
-            int32_t planeH = mHeight / plane.rowSampling;
-            for (int32_t row = 0; row < planeH; ++row) {
-                for(int32_t col = 0; col < planeW; ++col) {
-                    memcpy(dst, src, mAllocatedDepth / 8);
-                    dst += mediaImage->mPlane[i].mColInc;
-                    src += plane.colInc;
-                }
-                dst -= mediaImage->mPlane[i].mColInc * planeW;
-                dst += mediaImage->mPlane[i].mRowInc;
-                src -= plane.colInc * planeW;
-                src += plane.rowInc;
-            }
-        }
-    }
-
-    const sp<ABuffer> &imageData() const { return mMediaImage; }
-
-private:
-    status_t mInitCheck;
-
-    const C2GraphicView mView;
-    uint32_t mWidth;
-    uint32_t mHeight;
-    uint32_t mAllocatedDepth;
-    uint32_t mBackBufferSize;
-    sp<ABuffer> mMediaImage;
-    std::function<sp<ABuffer>(size_t)> mAlloc;
-
-    sp<ABuffer> mBackBuffer;
-
-    MediaImage2 *getMediaImage() {
-        return (MediaImage2 *)mMediaImage->base();
-    }
-};
-
-}  // namespace
-
-// GraphicBlockBuffer
-
-// static
-sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
-        const sp<AMessage> &format,
-        const std::shared_ptr<C2GraphicBlock> &block,
-        std::function<sp<ABuffer>(size_t)> alloc) {
-    C2GraphicView view(block->map().get());
-    if (view.error() != C2_OK) {
-        ALOGD("C2GraphicBlock::map failed: %d", view.error());
-        return nullptr;
-    }
-    GraphicView2MediaImageConverter converter(view);
-    if (converter.initCheck() != OK) {
-        ALOGD("Converter init failed: %d", converter.initCheck());
-        return nullptr;
-    }
-    bool wrapped = true;
-    sp<ABuffer> buffer = converter.wrap();
-    if (buffer == nullptr) {
-        buffer = alloc(converter.backBufferSize());
-        if (!converter.setBackBuffer(buffer)) {
-            ALOGD("Converter failed to set back buffer");
-            return nullptr;
-        }
-        wrapped = false;
-    }
-    return new GraphicBlockBuffer(
-            format,
-            buffer,
-            std::move(view),
-            block,
-            converter.imageData(),
-            wrapped);
-}
-
-GraphicBlockBuffer::GraphicBlockBuffer(
-        const sp<AMessage> &format,
-        const sp<ABuffer> &buffer,
-        C2GraphicView &&view,
-        const std::shared_ptr<C2GraphicBlock> &block,
-        const sp<ABuffer> &imageData,
-        bool wrapped)
-    : Codec2Buffer(format, buffer),
-      mView(view),
-      mBlock(block),
-      mImageData(imageData),
-      mWrapped(wrapped) {
-    meta()->setBuffer("image-data", imageData);
-}
-
-std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
-    uint32_t width = mView.width();
-    uint32_t height = mView.height();
-    if (!mWrapped) {
-        MediaImage2 *mediaImage = imageData();
-        const C2PlanarLayout &layout = mView.layout();
-        for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
-            const C2PlaneInfo &plane = layout.planes[i];
-            int32_t planeW = width / plane.colSampling;
-            int32_t planeH = height / plane.rowSampling;
-            const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
-            uint8_t *dst = mView.data()[i];
-            for (int32_t row = 0; row < planeH; ++row) {
-                for (int32_t col = 0; col < planeW; ++col) {
-                    memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
-                    src += mediaImage->mPlane[i].mColInc;
-                    dst += plane.colInc;
-                }
-                src -= mediaImage->mPlane[i].mColInc * planeW;
-                dst -= plane.colInc * planeW;
-                src += mediaImage->mPlane[i].mRowInc;
-                dst += plane.rowInc;
-            }
-        }
-    }
-    return C2Buffer::CreateGraphicBuffer(
-            mBlock->share(C2Rect(width, height), C2Fence()));
-}
-
-// ConstGraphicBlockBuffer
-
-// static
-sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
-        const sp<AMessage> &format,
-        const std::shared_ptr<C2Buffer> &buffer,
-        std::function<sp<ABuffer>(size_t)> alloc) {
-    if (!buffer
-            || buffer->data().type() != C2BufferData::GRAPHIC
-            || buffer->data().graphicBlocks().size() != 1u) {
-        ALOGD("C2Buffer precond fail");
-        return nullptr;
-    }
-    std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
-            buffer->data().graphicBlocks()[0].map().get()));
-    std::unique_ptr<const C2GraphicView> holder;
-
-    GraphicView2MediaImageConverter converter(*view);
-    if (converter.initCheck() != OK) {
-        ALOGD("Converter init failed: %d", converter.initCheck());
-        return nullptr;
-    }
-    bool wrapped = true;
-    sp<ABuffer> aBuffer = converter.wrap();
-    if (aBuffer == nullptr) {
-        aBuffer = alloc(converter.backBufferSize());
-        if (!converter.setBackBuffer(aBuffer)) {
-            ALOGD("Converter failed to set back buffer");
-            return nullptr;
-        }
-        wrapped = false;
-        converter.copy();
-        // We don't need the view.
-        holder = std::move(view);
-    }
-    return new ConstGraphicBlockBuffer(
-            format,
-            aBuffer,
-            std::move(view),
-            buffer,
-            converter.imageData(),
-            wrapped);
-}
-
-// static
-sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
-        const sp<AMessage> &format,
-        std::function<sp<ABuffer>(size_t)> alloc) {
-    int32_t width, height;
-    if (!format->findInt32("width", &width)
-            || !format->findInt32("height", &height)) {
-        ALOGD("format had no width / height");
-        return nullptr;
-    }
-    sp<ABuffer> aBuffer(alloc(width * height * 4));
-    return new ConstGraphicBlockBuffer(
-            format,
-            aBuffer,
-            nullptr,
-            nullptr,
-            nullptr,
-            false);
-}
-
-ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
-        const sp<AMessage> &format,
-        const sp<ABuffer> &aBuffer,
-        std::unique_ptr<const C2GraphicView> &&view,
-        const std::shared_ptr<C2Buffer> &buffer,
-        const sp<ABuffer> &imageData,
-        bool wrapped)
-    : Codec2Buffer(format, aBuffer),
-      mView(std::move(view)),
-      mBufferRef(buffer),
-      mWrapped(wrapped) {
-    if (imageData != nullptr) {
-        meta()->setBuffer("image-data", imageData);
-    }
-}
-
-std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
-    mView.reset();
-    return std::move(mBufferRef);
-}
-
-bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
-    if (mWrapped || mBufferRef) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
-                mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
-        return false;
-    }
-    if (!buffer) {
-        // Nothing to copy, so we can copy by doing nothing.
-        return true;
-    }
-    if (buffer->data().type() != C2BufferData::GRAPHIC) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
-        return false;
-    }
-    if (buffer->data().graphicBlocks().size() == 0) {
-        return true;
-    } else if (buffer->data().graphicBlocks().size() != 1u) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
-        return false;
-    }
-    GraphicView2MediaImageConverter converter(
-            buffer->data().graphicBlocks()[0].map().get());
-    if (converter.initCheck() != OK) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
-        return false;
-    }
-    if (converter.backBufferSize() > capacity()) {
-        ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
-                converter.backBufferSize(), capacity());
-        return false;
-    }
-    return true;
-}
-
-bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
-    if (!buffer || buffer->data().graphicBlocks().size() == 0) {
-        setRange(0, 0);
-        return true;
-    }
-    GraphicView2MediaImageConverter converter(
-            buffer->data().graphicBlocks()[0].map().get());
-    if (converter.initCheck() != OK) {
-        ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
-        return false;
-    }
-    sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
-    if (!converter.setBackBuffer(aBuffer)) {
-        ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
-        return false;
-    }
-    converter.copy();
-    meta()->setBuffer("image-data", converter.imageData());
-    mBufferRef = buffer;
-    return true;
-}
-
 }  // namespace android
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index 0bdd808..0a20d34 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -259,20 +259,26 @@
         return;
     }
 
-    AString componentName;
-    if (!msg->findString("componentName", &componentName)) {
-        // TODO: find componentName appropriate with the media type
-    }
+    sp<RefBase> codecInfo;
+    CHECK(msg->findObject("codecInfo", &codecInfo));
+    // For Codec 2.0 components, componentName == codecInfo->getCodecName().
 
     sp<AMessage> allocMsg(new AMessage(kWhatAllocate, this));
-    allocMsg->setString("componentName", componentName);
+    allocMsg->setObject("codecInfo", codecInfo);
     allocMsg->post();
 }
 
-void CCodec::allocate(const AString &componentName) {
-    ALOGV("allocate(%s)", componentName.c_str());
+void CCodec::allocate(const sp<MediaCodecInfo> &codecInfo) {
+    if (codecInfo == nullptr) {
+        mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+        return;
+    }
+    ALOGV("allocate(%s)", codecInfo->getCodecName());
     mListener.reset(new CCodecListener(this));
 
+    AString componentName = codecInfo->getCodecName();
+    // TODO: use codecInfo->getOwnerName() for connecting to remote process.
+
     std::shared_ptr<C2Component> comp;
     c2_status_t err = GetCodec2PlatformComponentStore()->createComponent(
             componentName.c_str(), &comp);
@@ -812,9 +818,9 @@
         case kWhatAllocate: {
             // C2ComponentStore::createComponent() should return within 100ms.
             setDeadline(now + 150ms, "allocate");
-            AString componentName;
-            CHECK(msg->findString("componentName", &componentName));
-            allocate(componentName);
+            sp<RefBase> obj;
+            CHECK(msg->findObject("codecInfo", &obj));
+            allocate((MediaCodecInfo *)obj.get());
             break;
         }
         case kWhatConfigure: {
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index 65d637b..cbe4f16 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -49,6 +49,8 @@
 using namespace hardware::cas::V1_0;
 using namespace hardware::cas::native::V1_0;
 
+using CasStatus = hardware::cas::V1_0::Status;
+
 /**
  * Base class for representation of buffers at one port.
  */
@@ -181,6 +183,7 @@
 // TODO: get this info from component
 const static size_t kMinBufferArraySize = 16;
 const static size_t kLinearBufferSize = 524288;
+const static size_t kMaxGraphicBufferRefCount = 4;
 
 /**
  * Simple local buffer pool backed by std::vector.
@@ -291,21 +294,6 @@
     DISALLOW_EVIL_CONSTRUCTORS(LocalBufferPool);
 };
 
-sp<LinearBlockBuffer> AllocateLinearBuffer(
-        const std::shared_ptr<C2BlockPool> &pool,
-        const sp<AMessage> &format,
-        size_t size,
-        const C2MemoryUsage &usage) {
-    std::shared_ptr<C2LinearBlock> block;
-
-    c2_status_t err = pool->fetchLinearBlock(size, usage, &block);
-    if (err != C2_OK) {
-        return nullptr;
-    }
-
-    return LinearBlockBuffer::Allocate(format, block);
-}
-
 sp<GraphicBlockBuffer> AllocateGraphicBuffer(
         const std::shared_ptr<C2BlockPool> &pool,
         const sp<AMessage> &format,
@@ -572,9 +560,7 @@
     bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
         // TODO: proper max input size
         // TODO: read usage from intf
-        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-        sp<LinearBlockBuffer> newBuffer = AllocateLinearBuffer(
-                mPool, mFormat, kLinearBufferSize, usage);
+        sp<Codec2Buffer> newBuffer = alloc(kLinearBufferSize);
         if (newBuffer == nullptr) {
             return false;
         }
@@ -598,17 +584,88 @@
         array->initialize(
                 mImpl,
                 kMinBufferArraySize,
-                [pool = mPool, format = mFormat] () -> sp<Codec2Buffer> {
-                    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-                    return AllocateLinearBuffer(pool, format, kLinearBufferSize, usage);
-                });
+                [this] () -> sp<Codec2Buffer> { return alloc(kLinearBufferSize); });
         return std::move(array);
     }
 
+    virtual sp<Codec2Buffer> alloc(size_t size) const {
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        std::shared_ptr<C2LinearBlock> block;
+
+        c2_status_t err = mPool->fetchLinearBlock(size, usage, &block);
+        if (err != C2_OK) {
+            return nullptr;
+        }
+
+        return LinearBlockBuffer::Allocate(mFormat, block);
+    }
+
 private:
     FlexBuffersImpl mImpl;
 };
 
+class EncryptedLinearInputBuffers : public LinearInputBuffers {
+public:
+    EncryptedLinearInputBuffers(
+            bool secure,
+            const sp<MemoryDealer> &dealer,
+            const sp<ICrypto> &crypto,
+            int32_t heapSeqNum)
+        : mUsage({0, 0}),
+          mDealer(dealer),
+          mCrypto(crypto),
+          mHeapSeqNum(heapSeqNum) {
+        if (secure) {
+            mUsage = { C2MemoryUsage::READ_PROTECTED, 0 };
+        } else {
+            mUsage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        }
+        for (size_t i = 0; i < kMinBufferArraySize; ++i) {
+            sp<IMemory> memory = mDealer->allocate(kLinearBufferSize);
+            if (memory == nullptr) {
+                ALOGD("Failed to allocate memory from dealer: only %zu slots allocated", i);
+                break;
+            }
+            mMemoryVector.push_back({std::weak_ptr<C2LinearBlock>(), memory});
+        }
+    }
+
+    ~EncryptedLinearInputBuffers() override {
+    }
+
+    sp<Codec2Buffer> alloc(size_t size) const override {
+        sp<IMemory> memory;
+        for (const Entry &entry : mMemoryVector) {
+            if (entry.block.expired()) {
+                memory = entry.memory;
+                break;
+            }
+        }
+        if (memory == nullptr) {
+            return nullptr;
+        }
+
+        std::shared_ptr<C2LinearBlock> block;
+        c2_status_t err = mPool->fetchLinearBlock(size, mUsage, &block);
+        if (err != C2_OK) {
+            return nullptr;
+        }
+
+        return new EncryptedLinearBlockBuffer(mFormat, block, memory, mHeapSeqNum);
+    }
+
+private:
+    C2MemoryUsage mUsage;
+    sp<MemoryDealer> mDealer;
+    sp<ICrypto> mCrypto;
+    int32_t mHeapSeqNum;
+    struct Entry {
+        std::weak_ptr<C2LinearBlock> block;
+        sp<IMemory> memory;
+    };
+    std::vector<Entry> mMemoryVector;
+};
+
 class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
 public:
     GraphicInputBuffers() : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {}
@@ -956,7 +1013,8 @@
 
 CCodecBufferChannel::CCodecBufferChannel(
         const std::function<void(status_t, enum ActionCode)> &onError)
-    : mOnError(onError),
+    : mHeapSeqNum(-1),
+      mOnError(onError),
       mFrameIndex(0u),
       mFirstValidFrameIndex(0u) {
 }
@@ -978,13 +1036,7 @@
     return OK;
 }
 
-status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
-    QueueGuard guard(mSync);
-    if (!guard.isRunning()) {
-        ALOGW("No more buffers should be queued at current state.");
-        return -ENOSYS;
-    }
-
+status_t CCodecBufferChannel::queueInputBufferInternal(const sp<MediaCodecBuffer> &buffer) {
     int64_t timeUs;
     CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
 
@@ -1005,7 +1057,11 @@
     work->input.buffers.clear();
     {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
-        work->input.buffers.push_back((*buffers)->releaseBuffer(buffer));
+        std::shared_ptr<C2Buffer> c2buffer = (*buffers)->releaseBuffer(buffer);
+        if (!c2buffer) {
+            return -ENOENT;
+        }
+        work->input.buffers.push_back(c2buffer);
     }
     // TODO: fill info's
 
@@ -1017,22 +1073,103 @@
     return mComponent->queue_nb(&items);
 }
 
+status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
+    QueueGuard guard(mSync);
+    if (!guard.isRunning()) {
+        ALOGW("No more buffers should be queued at current state.");
+        return -ENOSYS;
+    }
+    return queueInputBufferInternal(buffer);
+}
+
 status_t CCodecBufferChannel::queueSecureInputBuffer(
         const sp<MediaCodecBuffer> &buffer, bool secure, const uint8_t *key,
         const uint8_t *iv, CryptoPlugin::Mode mode, CryptoPlugin::Pattern pattern,
         const CryptoPlugin::SubSample *subSamples, size_t numSubSamples,
         AString *errorDetailMsg) {
-    // TODO
-    (void) buffer;
-    (void) secure;
-    (void) key;
-    (void) iv;
-    (void) mode;
-    (void) pattern;
-    (void) subSamples;
-    (void) numSubSamples;
-    (void) errorDetailMsg;
-    return -ENOSYS;
+    QueueGuard guard(mSync);
+    if (!guard.isRunning()) {
+        ALOGW("No more buffers should be queued at current state.");
+        return -ENOSYS;
+    }
+
+    if (!hasCryptoOrDescrambler()) {
+        return -ENOSYS;
+    }
+    sp<EncryptedLinearBlockBuffer> encryptedBuffer((EncryptedLinearBlockBuffer *)buffer.get());
+
+    ssize_t result = -1;
+    if (mCrypto != nullptr) {
+        ICrypto::DestinationBuffer destination;
+        if (secure) {
+            destination.mType = ICrypto::kDestinationTypeNativeHandle;
+            destination.mHandle = encryptedBuffer->handle();
+        } else {
+            destination.mType = ICrypto::kDestinationTypeSharedMemory;
+            destination.mSharedMemory = mDecryptDestination;
+        }
+        ICrypto::SourceBuffer source;
+        encryptedBuffer->fillSourceBuffer(&source);
+        result = mCrypto->decrypt(
+                key, iv, mode, pattern, source, buffer->offset(),
+                subSamples, numSubSamples, destination, errorDetailMsg);
+        if (result < 0) {
+            return result;
+        }
+        if (destination.mType == ICrypto::kDestinationTypeSharedMemory) {
+            encryptedBuffer->copyDecryptedContent(mDecryptDestination, result);
+        }
+    } else {
+        // Here we cast CryptoPlugin::SubSample to hardware::cas::native::V1_0::SubSample
+        // directly, the structure definitions should match as checked in DescramblerImpl.cpp.
+        hidl_vec<SubSample> hidlSubSamples;
+        hidlSubSamples.setToExternal((SubSample *)subSamples, numSubSamples, false /*own*/);
+
+        hardware::cas::native::V1_0::SharedBuffer srcBuffer;
+        encryptedBuffer->fillSourceBuffer(&srcBuffer);
+
+        DestinationBuffer dstBuffer;
+        if (secure) {
+            dstBuffer.type = BufferType::NATIVE_HANDLE;
+            dstBuffer.secureMemory = hidl_handle(encryptedBuffer->handle());
+        } else {
+            dstBuffer.type = BufferType::SHARED_MEMORY;
+            dstBuffer.nonsecureMemory = srcBuffer;
+        }
+
+        CasStatus status = CasStatus::OK;
+        hidl_string detailedError;
+
+        auto returnVoid = mDescrambler->descramble(
+                key != NULL ? (ScramblingControl)key[0] : ScramblingControl::UNSCRAMBLED,
+                hidlSubSamples,
+                srcBuffer,
+                0,
+                dstBuffer,
+                0,
+                [&status, &result, &detailedError] (
+                        CasStatus _status, uint32_t _bytesWritten,
+                        const hidl_string& _detailedError) {
+                    status = _status;
+                    result = (ssize_t)_bytesWritten;
+                    detailedError = _detailedError;
+                });
+
+        if (!returnVoid.isOk() || status != CasStatus::OK || result < 0) {
+            ALOGE("descramble failed, trans=%s, status=%d, result=%zd",
+                    returnVoid.description().c_str(), status, result);
+            return UNKNOWN_ERROR;
+        }
+
+        ALOGV("descramble succeeded, %zd bytes", result);
+
+        if (dstBuffer.type == BufferType::SHARED_MEMORY) {
+            encryptedBuffer->copyDecryptedContentFromMemory(result);
+        }
+    }
+
+    buffer->setRange(0, result);
+    return queueInputBufferInternal(buffer);
 }
 
 void CCodecBufferChannel::feedInputBufferIfAvailable() {
@@ -1061,8 +1198,8 @@
         c2Buffer = (*buffers)->releaseBuffer(buffer);
     }
 
-    Mutexed<sp<Surface>>::Locked surface(mSurface);
-    if (*surface == nullptr) {
+    Mutexed<OutputSurface>::Locked output(mOutputSurface);
+    if (output->surface == nullptr) {
         ALOGE("no surface");
         return OK;
     }
@@ -1079,7 +1216,7 @@
             GraphicBuffer::CLONE_HANDLE,
             blocks.front().width(),
             blocks.front().height(),
-            HAL_PIXEL_FORMAT_YV12,
+            HAL_PIXEL_FORMAT_YCbCr_420_888,
             // TODO
             1,
             (uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1087,7 +1224,7 @@
             blocks.front().width()));
     native_handle_delete(grallocHandle);
 
-    status_t result = (*surface)->attachBuffer(graphicBuffer.get());
+    status_t result = output->surface->attachBuffer(graphicBuffer.get());
     if (result != OK) {
         ALOGE("attachBuffer failed: %d", result);
         return result;
@@ -1095,23 +1232,32 @@
 
     // TODO: read and set crop
 
-    result = native_window_set_buffers_timestamp((*surface).get(), timestampNs);
+    result = native_window_set_buffers_timestamp(output->surface.get(), timestampNs);
     ALOGW_IF(result != OK, "failed to set buffer timestamp: %d", result);
 
     // TODO: fix after C2Fence implementation
 #if 0
     const C2Fence &fence = blocks.front().fence();
-    result = ((ANativeWindow *)(*surface).get())->queueBuffer(
-            (*surface).get(), graphicBuffer.get(), fence.valid() ? fence.fd() : -1);
+    result = ((ANativeWindow *)output->surface.get())->queueBuffer(
+            output->surface.get(), graphicBuffer.get(), fence.valid() ? fence.fd() : -1);
 #else
-    result = ((ANativeWindow *)(*surface).get())->queueBuffer(
-            (*surface).get(), graphicBuffer.get(), -1);
+    result = ((ANativeWindow *)output->surface.get())->queueBuffer(
+            output->surface.get(), graphicBuffer.get(), -1);
 #endif
     if (result != OK) {
         ALOGE("queueBuffer failed: %d", result);
         return result;
     }
 
+    // XXX: Hack to keep C2Buffers unreleased until the consumer is done
+    //      reading the content. Eventually IGBP-based C2BlockPool should handle
+    //      the lifecycle.
+    output->bufferRefs.push_back(c2Buffer);
+    if (output->bufferRefs.size() > output->maxBufferCount + 1) {
+        output->bufferRefs.pop_front();
+        ALOGV("%zu buffer refs remaining", output->bufferRefs.size());
+    }
+
     return OK;
 }
 
@@ -1166,6 +1312,7 @@
     if (err != C2_OK) {
         return UNKNOWN_ERROR;
     }
+    bool secure = mComponent->intf()->getName().find(".secure") != std::string::npos;
 
     if (inputFormat != nullptr) {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
@@ -1178,7 +1325,24 @@
                 buffers->reset(new GraphicInputBuffers);
             }
         } else {
-            buffers->reset(new LinearInputBuffers);
+            if (hasCryptoOrDescrambler()) {
+                if (mDealer == nullptr) {
+                    mDealer = new MemoryDealer(
+                            align(kLinearBufferSize, MemoryDealer::getAllocationAlignment())
+                                * (kMinBufferArraySize + 1),
+                            "EncryptedLinearInputBuffers");
+                    mDecryptDestination = mDealer->allocate(kLinearBufferSize);
+                }
+                if (mCrypto != nullptr && mHeapSeqNum < 0) {
+                    mHeapSeqNum = mCrypto->setHeap(mDealer->getMemoryHeap());
+                } else {
+                    mHeapSeqNum = -1;
+                }
+                buffers->reset(new EncryptedLinearInputBuffers(
+                        secure, mDealer, mCrypto, mHeapSeqNum));
+            } else {
+                buffers->reset(new LinearInputBuffers);
+            }
         }
         (*buffers)->setFormat(inputFormat);
 
@@ -1200,8 +1364,8 @@
     if (outputFormat != nullptr) {
         bool hasOutputSurface = false;
         {
-            Mutexed<sp<Surface>>::Locked surface(mSurface);
-            hasOutputSurface = (*surface != nullptr);
+            Mutexed<OutputSurface>::Locked output(mOutputSurface);
+            hasOutputSurface = (output->surface != nullptr);
         }
 
         Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
@@ -1381,7 +1545,7 @@
         newSurface->setScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
     }
 
-    Mutexed<sp<Surface>>::Locked surface(mSurface);
+    Mutexed<OutputSurface>::Locked output(mOutputSurface);
 //    if (newSurface == nullptr) {
 //        if (*surface != nullptr) {
 //            ALOGW("cannot unset a surface");
@@ -1395,7 +1559,11 @@
 //        return INVALID_OPERATION;
 //    }
 
-    *surface = newSurface;
+    output->surface = newSurface;
+    output->bufferRefs.clear();
+    // XXX: hack
+    output->maxBufferCount = kMaxGraphicBufferRefCount;
+
     return OK;
 }
 
diff --git a/media/libstagefright/Codec2Buffer.cpp b/media/libstagefright/Codec2Buffer.cpp
new file mode 100644
index 0000000..d2ef229
--- /dev/null
+++ b/media/libstagefright/Codec2Buffer.cpp
@@ -0,0 +1,672 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Codec2Buffer"
+#include <utils/Log.h>
+
+#include <hidlmemory/FrameworkUtils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "include/Codec2Buffer.h"
+
+namespace android {
+
+// Codec2Buffer
+
+bool Codec2Buffer::canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const {
+    if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
+        return false;
+    }
+    if (!buffer) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    }
+    if (buffer->data().type() != C2BufferData::LINEAR) {
+        return false;
+    }
+    if (buffer->data().linearBlocks().size() == 0u) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    } else if (buffer->data().linearBlocks().size() > 1u) {
+        // We don't know how to copy more than one blocks.
+        return false;
+    }
+    if (buffer->data().linearBlocks()[0].size() > capacity()) {
+        // It won't fit.
+        return false;
+    }
+    return true;
+}
+
+bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
+    // We assume that all canCopyLinear() checks passed.
+    if (!buffer || buffer->data().linearBlocks().size() == 0u) {
+        setRange(0, 0);
+        return true;
+    }
+    C2ReadView view = buffer->data().linearBlocks()[0].map().get();
+    if (view.error() != C2_OK) {
+        ALOGD("Error while mapping: %d", view.error());
+        return false;
+    }
+    if (view.capacity() > capacity()) {
+        ALOGD("C2ConstLinearBlock lied --- it actually doesn't fit: view(%u) > this(%zu)",
+                view.capacity(), capacity());
+        return false;
+    }
+    memcpy(base(), view.data(), view.capacity());
+    setRange(0, view.capacity());
+    return true;
+}
+
+// LocalLinearBuffer
+
+bool LocalLinearBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+    return canCopyLinear(buffer);
+}
+
+bool LocalLinearBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    return copyLinear(buffer);
+}
+
+// DummyContainerBuffer
+
+DummyContainerBuffer::DummyContainerBuffer(
+        const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer)
+    : Codec2Buffer(format, new ABuffer(nullptr, 1)),
+      mBufferRef(buffer) {
+    setRange(0, buffer ? 1 : 0);
+}
+
+std::shared_ptr<C2Buffer> DummyContainerBuffer::asC2Buffer() {
+    return std::move(mBufferRef);
+}
+
+bool DummyContainerBuffer::canCopy(const std::shared_ptr<C2Buffer> &) const {
+    return !mBufferRef;
+}
+
+bool DummyContainerBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    mBufferRef = buffer;
+    setRange(0, mBufferRef ? 1 : 0);
+    return true;
+}
+
+// LinearBlockBuffer
+
+// static
+sp<LinearBlockBuffer> LinearBlockBuffer::Allocate(
+        const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
+    C2WriteView writeView(block->map().get());
+    if (writeView.error() != C2_OK) {
+        return nullptr;
+    }
+    return new LinearBlockBuffer(format, std::move(writeView), block);
+}
+
+std::shared_ptr<C2Buffer> LinearBlockBuffer::asC2Buffer() {
+    return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
+}
+
+bool LinearBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+    return canCopyLinear(buffer);
+}
+
+bool LinearBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    return copyLinear(buffer);
+}
+
+LinearBlockBuffer::LinearBlockBuffer(
+        const sp<AMessage> &format,
+        C2WriteView&& writeView,
+        const std::shared_ptr<C2LinearBlock> &block)
+    : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
+      mWriteView(writeView),
+      mBlock(block) {
+}
+
+// ConstLinearBlockBuffer
+
+// static
+sp<ConstLinearBlockBuffer> ConstLinearBlockBuffer::Allocate(
+        const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer) {
+    if (!buffer
+            || buffer->data().type() != C2BufferData::LINEAR
+            || buffer->data().linearBlocks().size() != 1u) {
+        return nullptr;
+    }
+    C2ReadView readView(buffer->data().linearBlocks()[0].map().get());
+    if (readView.error() != C2_OK) {
+        return nullptr;
+    }
+    return new ConstLinearBlockBuffer(format, std::move(readView), buffer);
+}
+
+ConstLinearBlockBuffer::ConstLinearBlockBuffer(
+        const sp<AMessage> &format,
+        C2ReadView&& readView,
+        const std::shared_ptr<C2Buffer> &buffer)
+    : Codec2Buffer(format, new ABuffer(
+            // NOTE: ABuffer only takes non-const pointer but this data is
+            //       supposed to be read-only.
+            const_cast<uint8_t *>(readView.data()), readView.capacity())),
+      mReadView(readView),
+      mBufferRef(buffer) {
+}
+
+std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
+    return std::move(mBufferRef);
+}
+
+// GraphicView2MediaImageConverter
+
+namespace {
+
+class GraphicView2MediaImageConverter {
+public:
+    explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
+        : mInitCheck(NO_INIT),
+          mView(view),
+          mWidth(view.width()),
+          mHeight(view.height()),
+          mAllocatedDepth(0),
+          mBackBufferSize(0),
+          mMediaImage(new ABuffer(sizeof(MediaImage2))) {
+        if (view.error() != C2_OK) {
+            ALOGD("Converter: view.error() = %d", view.error());
+            mInitCheck = BAD_VALUE;
+            return;
+        }
+        MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
+        const C2PlanarLayout &layout = view.layout();
+        if (layout.numPlanes == 0) {
+            ALOGD("Converter: 0 planes");
+            mInitCheck = BAD_VALUE;
+            return;
+        }
+        mAllocatedDepth = layout.planes[0].allocatedDepth;
+        uint32_t bitDepth = layout.planes[0].bitDepth;
+
+        switch (layout.type) {
+            case C2PlanarLayout::TYPE_YUV:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
+            case C2PlanarLayout::TYPE_YUVA:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
+            case C2PlanarLayout::TYPE_RGB:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
+            case C2PlanarLayout::TYPE_RGBA:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
+            default:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
+        }
+        mediaImage->mNumPlanes = layout.numPlanes;
+        mediaImage->mWidth = mWidth;
+        mediaImage->mHeight = mHeight;
+        mediaImage->mBitDepth = bitDepth;
+        mediaImage->mBitDepthAllocated = mAllocatedDepth;
+
+        uint32_t bufferSize = 0;
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            if (plane.rightShift != 0) {
+                ALOGV("rightShift value of %u unsupported", plane.rightShift);
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            if (plane.endianness != C2PlaneInfo::NATIVE) {
+                ALOGV("endianness value of %u unsupported", plane.endianness);
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
+                ALOGV("different allocatedDepth/bitDepth per plane unsupported");
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            bufferSize += mWidth * mHeight
+                    / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
+        }
+
+        mBackBufferSize = bufferSize;
+        mInitCheck = OK;
+    }
+
+    status_t initCheck() const { return mInitCheck; }
+
+    uint32_t backBufferSize() const { return mBackBufferSize; }
+
+    /**
+     * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
+     * is not copied over in this function --- the caller should use
+     * CopyGraphicView2MediaImage() function to do that explicitly.
+     *
+     * \param   view[in]          source C2GraphicView object.
+     * \param   alloc[in]         allocator function for ABuffer.
+     * \param   mediaImage[out]   destination MediaImage2 object.
+     * \param   buffer[out]       new buffer object.
+     * \param   wrapped[out]      whether we wrapped around existing map or
+     *                            allocated a new buffer
+     *
+     * \return  true              if conversion succeeds,
+     *          false             otherwise; all output params should be ignored.
+     */
+    sp<ABuffer> wrap() {
+        MediaImage2 *mediaImage = getMediaImage();
+        const C2PlanarLayout &layout = mView.layout();
+        if (layout.numPlanes == 1) {
+            const C2PlaneInfo &plane = layout.planes[0];
+            ssize_t offset = plane.minOffset(mWidth, mHeight);
+            mediaImage->mPlane[0].mOffset = -offset;
+            mediaImage->mPlane[0].mColInc = plane.colInc;
+            mediaImage->mPlane[0].mRowInc = plane.rowInc;
+            mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
+            mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
+            return new ABuffer(
+                    const_cast<uint8_t *>(mView.data()[0] + offset),
+                    plane.maxOffset(mWidth, mHeight) - offset + 1);
+        }
+        const uint8_t *minPtr = mView.data()[0];
+        const uint8_t *maxPtr = mView.data()[0];
+        int32_t planeSize = 0;
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            ssize_t minOffset = plane.minOffset(mWidth, mHeight);
+            ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
+            if (minPtr > mView.data()[i] + minOffset) {
+                minPtr = mView.data()[i] + minOffset;
+            }
+            if (maxPtr < mView.data()[i] + maxOffset) {
+                maxPtr = mView.data()[i] + maxOffset;
+            }
+            planeSize += std::abs(plane.rowInc) * mHeight
+                    / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
+        }
+
+        if ((maxPtr - minPtr + 1) <= planeSize) {
+            // FIXME: this is risky as reading/writing data out of bound results in
+            //        an undefined behavior.
+            for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+                const C2PlaneInfo &plane = layout.planes[i];
+                mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
+                mediaImage->mPlane[i].mColInc = plane.colInc;
+                mediaImage->mPlane[i].mRowInc = plane.rowInc;
+                mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+                mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+            }
+            return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
+        }
+
+        return nullptr;
+    }
+
+    bool setBackBuffer(const sp<ABuffer> &backBuffer) {
+        if (backBuffer->capacity() < mBackBufferSize) {
+            return false;
+        }
+        backBuffer->setRange(0, mBackBufferSize);
+
+        const C2PlanarLayout &layout = mView.layout();
+        MediaImage2 *mediaImage = getMediaImage();
+        uint32_t offset = 0;
+        // TODO: keep interleaved planes together
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            mediaImage->mPlane[i].mOffset = offset;
+            mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
+            mediaImage->mPlane[i].mRowInc =
+                mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
+            mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+            mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+            offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
+        }
+        mBackBuffer = backBuffer;
+        return true;
+    }
+
+    /**
+     * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
+     * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
+     *
+     * \param   view[in]          source C2GraphicView object.
+     * \param   mediaImage[in]    destination MediaImage2 object.
+     * \param   buffer[out]       new buffer object.
+     */
+    void copy() {
+        // TODO: more efficient copying --- e.g. one row at a time, copying
+        //       interleaved planes together, etc.
+        const C2PlanarLayout &layout = mView.layout();
+        MediaImage2 *mediaImage = getMediaImage();
+        uint8_t *dst = mBackBuffer->base();
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            const uint8_t *src = mView.data()[i];
+            int32_t planeW = mWidth / plane.colSampling;
+            int32_t planeH = mHeight / plane.rowSampling;
+            for (int32_t row = 0; row < planeH; ++row) {
+                for(int32_t col = 0; col < planeW; ++col) {
+                    memcpy(dst, src, mAllocatedDepth / 8);
+                    dst += mediaImage->mPlane[i].mColInc;
+                    src += plane.colInc;
+                }
+                dst -= mediaImage->mPlane[i].mColInc * planeW;
+                dst += mediaImage->mPlane[i].mRowInc;
+                src -= plane.colInc * planeW;
+                src += plane.rowInc;
+            }
+        }
+    }
+
+    const sp<ABuffer> &imageData() const { return mMediaImage; }
+
+private:
+    status_t mInitCheck;
+
+    const C2GraphicView mView;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mAllocatedDepth;
+    uint32_t mBackBufferSize;
+    sp<ABuffer> mMediaImage;
+    std::function<sp<ABuffer>(size_t)> mAlloc;
+
+    sp<ABuffer> mBackBuffer;
+
+    MediaImage2 *getMediaImage() {
+        return (MediaImage2 *)mMediaImage->base();
+    }
+};
+
+}  // namespace
+
+// GraphicBlockBuffer
+
+// static
+sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2GraphicBlock> &block,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    C2GraphicView view(block->map().get());
+    if (view.error() != C2_OK) {
+        ALOGD("C2GraphicBlock::map failed: %d", view.error());
+        return nullptr;
+    }
+    GraphicView2MediaImageConverter converter(view);
+    if (converter.initCheck() != OK) {
+        ALOGD("Converter init failed: %d", converter.initCheck());
+        return nullptr;
+    }
+    bool wrapped = true;
+    sp<ABuffer> buffer = converter.wrap();
+    if (buffer == nullptr) {
+        buffer = alloc(converter.backBufferSize());
+        if (!converter.setBackBuffer(buffer)) {
+            ALOGD("Converter failed to set back buffer");
+            return nullptr;
+        }
+        wrapped = false;
+    }
+    return new GraphicBlockBuffer(
+            format,
+            buffer,
+            std::move(view),
+            block,
+            converter.imageData(),
+            wrapped);
+}
+
+GraphicBlockBuffer::GraphicBlockBuffer(
+        const sp<AMessage> &format,
+        const sp<ABuffer> &buffer,
+        C2GraphicView &&view,
+        const std::shared_ptr<C2GraphicBlock> &block,
+        const sp<ABuffer> &imageData,
+        bool wrapped)
+    : Codec2Buffer(format, buffer),
+      mView(view),
+      mBlock(block),
+      mImageData(imageData),
+      mWrapped(wrapped) {
+    meta()->setBuffer("image-data", imageData);
+}
+
+std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
+    uint32_t width = mView.width();
+    uint32_t height = mView.height();
+    if (!mWrapped) {
+        MediaImage2 *mediaImage = imageData();
+        const C2PlanarLayout &layout = mView.layout();
+        for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            int32_t planeW = width / plane.colSampling;
+            int32_t planeH = height / plane.rowSampling;
+            const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
+            uint8_t *dst = mView.data()[i];
+            for (int32_t row = 0; row < planeH; ++row) {
+                for (int32_t col = 0; col < planeW; ++col) {
+                    memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
+                    src += mediaImage->mPlane[i].mColInc;
+                    dst += plane.colInc;
+                }
+                src -= mediaImage->mPlane[i].mColInc * planeW;
+                dst -= plane.colInc * planeW;
+                src += mediaImage->mPlane[i].mRowInc;
+                dst += plane.rowInc;
+            }
+        }
+    }
+    return C2Buffer::CreateGraphicBuffer(
+            mBlock->share(C2Rect(width, height), C2Fence()));
+}
+
+// ConstGraphicBlockBuffer
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2Buffer> &buffer,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    if (!buffer
+            || buffer->data().type() != C2BufferData::GRAPHIC
+            || buffer->data().graphicBlocks().size() != 1u) {
+        ALOGD("C2Buffer precond fail");
+        return nullptr;
+    }
+    std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
+            buffer->data().graphicBlocks()[0].map().get()));
+    std::unique_ptr<const C2GraphicView> holder;
+
+    GraphicView2MediaImageConverter converter(*view);
+    if (converter.initCheck() != OK) {
+        ALOGD("Converter init failed: %d", converter.initCheck());
+        return nullptr;
+    }
+    bool wrapped = true;
+    sp<ABuffer> aBuffer = converter.wrap();
+    if (aBuffer == nullptr) {
+        aBuffer = alloc(converter.backBufferSize());
+        if (!converter.setBackBuffer(aBuffer)) {
+            ALOGD("Converter failed to set back buffer");
+            return nullptr;
+        }
+        wrapped = false;
+        converter.copy();
+        // We don't need the view.
+        holder = std::move(view);
+    }
+    return new ConstGraphicBlockBuffer(
+            format,
+            aBuffer,
+            std::move(view),
+            buffer,
+            converter.imageData(),
+            wrapped);
+}
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
+        const sp<AMessage> &format,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    int32_t width, height;
+    if (!format->findInt32("width", &width)
+            || !format->findInt32("height", &height)) {
+        ALOGD("format had no width / height");
+        return nullptr;
+    }
+    sp<ABuffer> aBuffer(alloc(width * height * 4));
+    return new ConstGraphicBlockBuffer(
+            format,
+            aBuffer,
+            nullptr,
+            nullptr,
+            nullptr,
+            false);
+}
+
+ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
+        const sp<AMessage> &format,
+        const sp<ABuffer> &aBuffer,
+        std::unique_ptr<const C2GraphicView> &&view,
+        const std::shared_ptr<C2Buffer> &buffer,
+        const sp<ABuffer> &imageData,
+        bool wrapped)
+    : Codec2Buffer(format, aBuffer),
+      mView(std::move(view)),
+      mBufferRef(buffer),
+      mWrapped(wrapped) {
+    if (imageData != nullptr) {
+        meta()->setBuffer("image-data", imageData);
+    }
+}
+
+std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
+    mView.reset();
+    return std::move(mBufferRef);
+}
+
+bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+    if (mWrapped || mBufferRef) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
+                mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
+        return false;
+    }
+    if (!buffer) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    }
+    if (buffer->data().type() != C2BufferData::GRAPHIC) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
+        return false;
+    }
+    if (buffer->data().graphicBlocks().size() == 0) {
+        return true;
+    } else if (buffer->data().graphicBlocks().size() != 1u) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
+        return false;
+    }
+    GraphicView2MediaImageConverter converter(
+            buffer->data().graphicBlocks()[0].map().get());
+    if (converter.initCheck() != OK) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
+        return false;
+    }
+    if (converter.backBufferSize() > capacity()) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
+                converter.backBufferSize(), capacity());
+        return false;
+    }
+    return true;
+}
+
+bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    if (!buffer || buffer->data().graphicBlocks().size() == 0) {
+        setRange(0, 0);
+        return true;
+    }
+    GraphicView2MediaImageConverter converter(
+            buffer->data().graphicBlocks()[0].map().get());
+    if (converter.initCheck() != OK) {
+        ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
+        return false;
+    }
+    sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
+    if (!converter.setBackBuffer(aBuffer)) {
+        ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
+        return false;
+    }
+    converter.copy();
+    meta()->setBuffer("image-data", converter.imageData());
+    mBufferRef = buffer;
+    return true;
+}
+
+// EncryptedLinearBlockBuffer
+
+EncryptedLinearBlockBuffer::EncryptedLinearBlockBuffer(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2LinearBlock> &block,
+        const sp<IMemory> &memory,
+        int32_t heapSeqNum)
+    : Codec2Buffer(format, new ABuffer(memory->pointer(), memory->size())),
+      mBlock(block),
+      mMemory(memory),
+      mHeapSeqNum(heapSeqNum) {
+}
+
+std::shared_ptr<C2Buffer> EncryptedLinearBlockBuffer::asC2Buffer() {
+    return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
+}
+
+void EncryptedLinearBlockBuffer::fillSourceBuffer(
+        ICrypto::SourceBuffer *source) {
+    source->mSharedMemory = mMemory;
+    source->mHeapSeqNum = mHeapSeqNum;
+}
+
+void EncryptedLinearBlockBuffer::fillSourceBuffer(
+        hardware::cas::native::V1_0::SharedBuffer *source) {
+    ssize_t offset;
+    size_t size;
+
+    mHidlMemory = hardware::fromHeap(mMemory->getMemory(&offset, &size));
+    source->heapBase = *mHidlMemory;
+    source->offset = offset;
+    source->size = size;
+}
+
+bool EncryptedLinearBlockBuffer::copyDecryptedContent(
+        const sp<IMemory> &decrypted, size_t length) {
+    C2WriteView view = mBlock->map().get();
+    if (view.error() != C2_OK) {
+        return false;
+    }
+    if (view.size() < length) {
+        return false;
+    }
+    memcpy(view.data(), decrypted->pointer(), length);
+    return true;
+}
+
+bool EncryptedLinearBlockBuffer::copyDecryptedContentFromMemory(size_t length) {
+    return copyDecryptedContent(mMemory, length);
+}
+
+native_handle_t *EncryptedLinearBlockBuffer::handle() const {
+    return const_cast<native_handle_t *>(mBlock->handle());
+}
+
+}  // namespace android
diff --git a/media/libstagefright/Codec2InfoBuilder.cpp b/media/libstagefright/Codec2InfoBuilder.cpp
index 7ce2ff1..78c4e38 100644
--- a/media/libstagefright/Codec2InfoBuilder.cpp
+++ b/media/libstagefright/Codec2InfoBuilder.cpp
@@ -31,6 +31,63 @@
 
 using ConstTraitsPtr = std::shared_ptr<const C2Component::Traits>;
 
+struct ProfileLevel {
+    uint32_t profile;
+    uint32_t level;
+};
+static const ProfileLevel kAvcProfileLevels[] = {
+    { 0x01, 0x0001 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1  },
+    { 0x01, 0x0002 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1b },
+    { 0x01, 0x0004 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel11 },
+    { 0x01, 0x0008 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel12 },
+    { 0x01, 0x0010 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel13 },
+    { 0x01, 0x0020 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel2  },
+    { 0x01, 0x0040 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel21 },
+    { 0x01, 0x0080 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel22 },
+    { 0x01, 0x0100 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel3  },
+    { 0x01, 0x0200 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel31 },
+    { 0x01, 0x0400 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel32 },
+    { 0x01, 0x0800 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel4  },
+    { 0x01, 0x1000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel41 },
+    { 0x01, 0x2000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel42 },
+    { 0x01, 0x4000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel5  },
+    { 0x01, 0x8000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel51 },
+
+    { 0x02, 0x0001 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel1  },
+    { 0x02, 0x0002 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel1b },
+    { 0x02, 0x0004 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel11 },
+    { 0x02, 0x0008 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel12 },
+    { 0x02, 0x0010 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel13 },
+    { 0x02, 0x0020 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel2  },
+    { 0x02, 0x0040 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel21 },
+    { 0x02, 0x0080 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel22 },
+    { 0x02, 0x0100 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel3  },
+    { 0x02, 0x0200 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel31 },
+    { 0x02, 0x0400 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel32 },
+    { 0x02, 0x0800 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel4  },
+    { 0x02, 0x1000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel41 },
+    { 0x02, 0x2000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel42 },
+    { 0x02, 0x4000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel5  },
+    { 0x02, 0x8000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel51 },
+
+    { 0x04, 0x0001 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel1  },
+    { 0x04, 0x0002 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel1b },
+    { 0x04, 0x0004 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel11 },
+    { 0x04, 0x0008 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel12 },
+    { 0x04, 0x0010 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel13 },
+    { 0x04, 0x0020 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel2  },
+    { 0x04, 0x0040 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel21 },
+    { 0x04, 0x0080 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel22 },
+    { 0x04, 0x0100 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel3  },
+    { 0x04, 0x0200 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel31 },
+    { 0x04, 0x0400 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel32 },
+    { 0x04, 0x0800 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel4  },
+    { 0x04, 0x1000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel41 },
+    { 0x04, 0x2000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel42 },
+    { 0x04, 0x4000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel5  },
+    { 0x04, 0x8000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel51 },
+};
+
 status_t Codec2InfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) {
     // Obtain C2ComponentStore
     std::shared_ptr<C2ComponentStore> store = GetCodec2PlatformComponentStore();
@@ -86,6 +143,12 @@
                     caps->addDetail(key.c_str(), value.c_str());
                 }
             }
+            // TODO: get this from intf(), and apply to other codecs as well.
+            if (mediaType.find("video/avc") != std::string::npos && !encoder) {
+                for (const auto& pl : kAvcProfileLevels) {
+                    caps->addProfileLevel(pl.profile, pl.level);
+                }
+            }
             // TODO: get this from intf().
             if (mediaType.find("video") != std::string::npos && !encoder) {
                 caps->addColorFormat(0x7F420888);  // COLOR_FormatYUV420Flexible
diff --git a/media/libstagefright/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 cfbbcb2..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;
 
@@ -1376,15 +1376,12 @@
 }
 
 void MPEG4Writer::addMultipleLengthPrefixedSamples_l(MediaBuffer *buffer) {
-    const size_t kExtensionNALSearchRange = 64; // bytes to look for non-VCL NALUs
-
     const uint8_t *dataStart = (const uint8_t *)buffer->data() + buffer->range_offset();
     const uint8_t *currentNalStart = dataStart;
     const uint8_t *nextNalStart;
     const uint8_t *data = dataStart;
     size_t nextNalSize;
-    size_t searchSize = buffer->range_length() > kExtensionNALSearchRange ?
-                   kExtensionNALSearchRange : buffer->range_length();
+    size_t searchSize = buffer->range_length();
 
     while (getNextNALUnit(&data, &searchSize, &nextNalStart,
             &nextNalSize, true) == OK) {
@@ -1773,8 +1770,8 @@
       mIsPrimary(0),
       mWidth(0),
       mHeight(0),
-      mGridWidth(0),
-      mGridHeight(0),
+      mTileWidth(0),
+      mTileHeight(0),
       mGridRows(0),
       mGridCols(0),
       mNumTiles(1),
@@ -1805,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;
@@ -1962,6 +1959,17 @@
         return;
     }
 
+    // Rotation angle in HEIF is CCW, framework angle is CW.
+    int32_t heifRotation = 0;
+    switch(mRotation) {
+        case 90: heifRotation = 3; break;
+        case 180: heifRotation = 2; break;
+        case 270: heifRotation = 1; break;
+        default: break; // don't set if invalid
+    }
+
+    bool hasGrid = (mNumTiles > 1);
+
     if (mProperties.empty()) {
         mProperties.push_back(mOwner->addProperty_l({
             .type = FOURCC('h', 'v', 'c', 'C'),
@@ -1970,22 +1978,29 @@
 
         mProperties.push_back(mOwner->addProperty_l({
             .type = FOURCC('i', 's', 'p', 'e'),
-            .width = (mNumTiles > 1) ? mGridWidth : mWidth,
-            .height = (mNumTiles > 1) ? mGridHeight : mHeight,
+            .width = hasGrid ? mTileWidth : mWidth,
+            .height = hasGrid ? mTileHeight : mHeight,
         }));
+
+        if (!hasGrid && heifRotation > 0) {
+            mProperties.push_back(mOwner->addProperty_l({
+                .type = FOURCC('i', 'r', 'o', 't'),
+                .rotation = heifRotation,
+            }));
+        }
     }
 
     uint16_t itemId = mOwner->addItem_l({
         .itemType = "hvc1",
-        .isPrimary = (mNumTiles > 1) ? false : (mIsPrimary != 0),
-        .isHidden = (mNumTiles > 1),
+        .isPrimary = hasGrid ? false : (mIsPrimary != 0),
+        .isHidden = hasGrid,
         .offset = (uint32_t)offset,
         .size = (uint32_t)size,
         .properties = mProperties,
     });
 
     mTileIndex++;
-    if (mNumTiles > 1) {
+    if (hasGrid) {
         mDimgRefs.push_back(itemId);
 
         if (mTileIndex == mNumTiles) {
@@ -1995,6 +2010,12 @@
                 .width = mWidth,
                 .height = mHeight,
             }));
+            if (heifRotation > 0) {
+                mProperties.push_back(mOwner->addProperty_l({
+                    .type = FOURCC('i', 'r', 'o', 't'),
+                    .rotation = heifRotation,
+                }));
+            }
             mOwner->addItem_l({
                 .itemType = "grid",
                 .isPrimary = (mIsPrimary != 0),
@@ -2305,7 +2326,8 @@
     mStartTimeRealUs = startTimeUs;
 
     int32_t rotationDegrees;
-    if (mIsVideo && params && params->findInt32(kKeyRotation, &rotationDegrees)) {
+    if ((mIsVideo || mIsHeic) && params &&
+            params->findInt32(kKeyRotation, &rotationDegrees)) {
         mRotation = rotationDegrees;
     }
 
@@ -3430,16 +3452,36 @@
 
 int32_t MPEG4Writer::Track::getMetaSizeIncrease() const {
     CHECK(mIsHeic);
-    return    20                           // 1. 'ispe' property
-            + (8 + mCodecSpecificDataSize) // 2. 'hvcC' property
-            + (20                          // 3. extra 'ispe'
-            + (8 + 2 + 2 + mNumTiles * 2)  // 4. 'dimg' ref
-            + 12)                          // 5. ImageGrid in 'idat' (worst case)
-            * (mNumTiles > 1)              // -  (3~5: applicable only if grid)
-            + (16                          // 6. increase to 'iloc'
-            + 21                           // 7. increase to 'iinf'
-            + (3 + 2 * 2))                 // 8. increase to 'ipma' (worst case)
-            * (mNumTiles + 1);             // -  (6~8: are per-item)
+
+    int32_t grid = (mNumTiles > 1);
+
+    // Note that the rotation angle is in the file meta, and we don't have
+    // it until start, so here the calculation has to assume rotation.
+
+    // increase to ipco
+    int32_t increase = 20 * (grid + 1)              // 'ispe' property
+                     + (8 + mCodecSpecificDataSize) // 'hvcC' property
+                     + 9;                           // 'irot' property (worst case)
+
+    // increase to iref and idat
+    if (grid) {
+        increase += (8 + 2 + 2 + mNumTiles * 2)  // 'dimg' in iref
+                  + 12;                          // ImageGrid in 'idat' (worst case)
+    }
+
+    // increase to iloc, iinf and ipma
+    increase += (16             // increase to 'iloc'
+              + 21              // increase to 'iinf'
+              + (3 + 2 * 2))    // increase to 'ipma' (worst case, 2 props x 2 bytes)
+              * (mNumTiles + grid);
+
+    // adjust to ipma:
+    // if rotation is present and only one tile, it could ref 3 properties
+    if (!grid) {
+        increase += 2;
+    }
+
+    return increase;
 }
 
 status_t MPEG4Writer::Track::checkCodecSpecificData() const {
@@ -4267,24 +4309,38 @@
         numProperties = 32767;
     }
     for (size_t propIndex = 0; propIndex < numProperties; propIndex++) {
-        if (mProperties[propIndex].type == FOURCC('h', 'v', 'c', 'C')) {
-            beginBox("hvcC");
-            sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
-            // Patch avcc's lengthSize field to match the number
-            // of bytes we use to indicate the size of a nal unit.
-            uint8_t *ptr = (uint8_t *)hvcc->data();
-            ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
-            write(hvcc->data(), hvcc->size());
-            endBox();
-        } else if (mProperties[propIndex].type == FOURCC('i', 's', 'p', 'e')) {
-            beginBox("ispe");
-            writeInt32(0); // Version = 0, Flags = 0
-            writeInt32(mProperties[propIndex].width);
-            writeInt32(mProperties[propIndex].height);
-            endBox();
-        } else {
-            ALOGW("Skipping unrecognized property: type 0x%08x",
-                    mProperties[propIndex].type);
+        switch (mProperties[propIndex].type) {
+            case FOURCC('h', 'v', 'c', 'C'):
+            {
+                beginBox("hvcC");
+                sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
+                // Patch avcc's lengthSize field to match the number
+                // of bytes we use to indicate the size of a nal unit.
+                uint8_t *ptr = (uint8_t *)hvcc->data();
+                ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
+                write(hvcc->data(), hvcc->size());
+                endBox();
+                break;
+            }
+            case FOURCC('i', 's', 'p', 'e'):
+            {
+                beginBox("ispe");
+                writeInt32(0); // Version = 0, Flags = 0
+                writeInt32(mProperties[propIndex].width);
+                writeInt32(mProperties[propIndex].height);
+                endBox();
+                break;
+            }
+            case FOURCC('i', 'r', 'o', 't'):
+            {
+                beginBox("irot");
+                writeInt8(mProperties[propIndex].rotation);
+                endBox();
+                break;
+            }
+            default:
+                ALOGW("Skipping unrecognized property: type 0x%08x",
+                        mProperties[propIndex].type);
         }
     }
     endBox();
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 7d5c63a..edcb8c7 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -422,14 +422,12 @@
         const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
         uid_t uid) {
     Vector<AString> matchingCodecs;
-    Vector<AString> owners;
 
     MediaCodecList::findMatchingCodecs(
             mime.c_str(),
             encoder,
             0,
-            &matchingCodecs,
-            &owners);
+            &matchingCodecs);
 
     if (err != NULL) {
         *err = NAME_NOT_FOUND;
@@ -603,6 +601,8 @@
         return NAME_NOT_FOUND;
     }
 
+    mCodecInfo.clear();
+
     bool secureCodec = false;
     AString tmp = name;
     if (tmp.endsWith(".secure")) {
@@ -614,17 +614,24 @@
         mCodec = NULL;  // remove the codec.
         return NO_INIT; // if called from Java should raise IOException
     }
-    ssize_t codecIdx = mcl->findCodecByName(tmp.c_str());
-    if (codecIdx >= 0) {
-        const sp<MediaCodecInfo> info = mcl->getCodecInfo(codecIdx);
+    for (const AString &codecName : { name, tmp }) {
+        ssize_t codecIdx = mcl->findCodecByName(codecName.c_str());
+        if (codecIdx < 0) {
+            continue;
+        }
+        mCodecInfo = mcl->getCodecInfo(codecIdx);
         Vector<AString> mimes;
-        info->getSupportedMimes(&mimes);
+        mCodecInfo->getSupportedMimes(&mimes);
         for (size_t i = 0; i < mimes.size(); i++) {
             if (mimes[i].startsWith("video/")) {
                 mIsVideo = true;
                 break;
             }
         }
+        break;
+    }
+    if (mCodecInfo == nullptr) {
+        return NAME_NOT_FOUND;
     }
 
     if (mIsVideo) {
@@ -651,6 +658,9 @@
                     new BufferCallback(new AMessage(kWhatCodecNotify, this))));
 
     sp<AMessage> msg = new AMessage(kWhatInit, this);
+    msg->setObject("codecInfo", mCodecInfo);
+    // name may be different from mCodecInfo->getCodecName() if we stripped
+    // ".secure"
     msg->setString("name", name);
 
     if (mAnalyticsItem != NULL) {
@@ -1205,6 +1215,22 @@
     return OK;
 }
 
+status_t MediaCodec::getCodecInfo(sp<MediaCodecInfo> *codecInfo) const {
+    sp<AMessage> msg = new AMessage(kWhatGetCodecInfo, this);
+
+    sp<AMessage> response;
+    status_t err;
+    if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
+        return err;
+    }
+
+    sp<RefBase> obj;
+    CHECK(response->findObject("codecInfo", &obj));
+    *codecInfo = static_cast<MediaCodecInfo *>(obj.get());
+
+    return OK;
+}
+
 status_t MediaCodec::getMetrics(MediaAnalyticsItem * &reply) {
 
     reply = NULL;
@@ -1972,11 +1998,14 @@
             mReplyID = replyID;
             setState(INITIALIZING);
 
+            sp<RefBase> codecInfo;
+            CHECK(msg->findObject("codecInfo", &codecInfo));
             AString name;
             CHECK(msg->findString("name", &name));
 
             sp<AMessage> format = new AMessage;
-            format->setString("componentName", name.c_str());
+            format->setObject("codecInfo", codecInfo);
+            format->setString("componentName", name);
 
             mCodec->initiateAllocateComponent(format);
             break;
@@ -2595,6 +2624,17 @@
             break;
         }
 
+        case kWhatGetCodecInfo:
+        {
+            sp<AReplyToken> replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> response = new AMessage;
+            response->setObject("codecInfo", mCodecInfo);
+            response->postReply(replyID);
+            break;
+        }
+
         case kWhatSetParameters:
         {
             sp<AReplyToken> replyID;
diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp
index f595646..9244886 100644
--- a/media/libstagefright/MediaCodecList.cpp
+++ b/media/libstagefright/MediaCodecList.cpp
@@ -307,11 +307,8 @@
 //static
 void MediaCodecList::findMatchingCodecs(
         const char *mime, bool encoder, uint32_t flags,
-        Vector<AString> *matches, Vector<AString> *owners) {
+        Vector<AString> *matches) {
     matches->clear();
-    if (owners != nullptr) {
-        owners->clear();
-    }
 
     const sp<IMediaCodecList> list = getInstance();
     if (list == nullptr) {
@@ -337,9 +334,6 @@
             ALOGV("skipping SW codec '%s'", componentName.c_str());
         } else {
             matches->push(componentName);
-            if (owners != nullptr) {
-                owners->push(AString(info->getOwnerName()));
-            }
             ALOGV("matching '%s'", componentName.c_str());
         }
     }
diff --git a/media/libstagefright/MetaDataUtils.cpp b/media/libstagefright/MetaDataUtils.cpp
index af8f539..04f6ade 100644
--- a/media/libstagefright/MetaDataUtils.cpp
+++ b/media/libstagefright/MetaDataUtils.cpp
@@ -24,11 +24,12 @@
 
 namespace android {
 
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit) {
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size) {
     int32_t width;
     int32_t height;
     int32_t sarWidth;
     int32_t sarHeight;
+    sp<ABuffer> accessUnit = new ABuffer((void*)data,  size);
     sp<ABuffer> csd = MakeAVCCodecSpecificData(accessUnit, &width, &height, &sarWidth, &sarHeight);
     if (csd == nullptr) {
         return false;
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index f6fc813..27b9a5d 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
 #define LOG_TAG "NuMediaExtractor"
 #include <utils/Log.h>
 
@@ -205,6 +205,15 @@
     return OK;
 }
 
+void NuMediaExtractor::disconnect() {
+    if (mDataSource != NULL) {
+        // disconnect data source
+        if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
+            static_cast<NuCachedSource2 *>(mDataSource.get())->disconnect();
+        }
+    }
+}
+
 status_t NuMediaExtractor::updateDurationAndBitrate() {
     if (mImpl->countTracks() > kMaxTrackCount) {
         return ERROR_UNSUPPORTED;
diff --git a/media/libstagefright/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/codecs/amrnb/dec/Android.bp b/media/libstagefright/codecs/amrnb/dec/Android.bp
index e4a2607..f97af89 100644
--- a/media/libstagefright/codecs/amrnb/dec/Android.bp
+++ b/media/libstagefright/codecs/amrnb/dec/Android.bp
@@ -110,7 +110,6 @@
     ],
     compile_multilib: "32",
 }
-
 //###############################################################################
 cc_test {
     name: "libstagefright_amrnbdec_test",
@@ -139,3 +138,100 @@
     //    ],
     //},
 }
+
+//###############################################################################
+cc_library_shared {
+    name: "libstagefright_soft_c2amrnbdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftAMR.cpp",],
+
+    include_dirs: [
+        "frameworks/av/media/libstagefright/codecs/amrwb/src",
+    ],
+
+    local_include_dirs: ["src"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DAMRNB",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    static_libs: [
+        "libstagefright_amrnbdec",
+        "libstagefright_amrwbdec",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+        "libstagefright_amrnb_common",
+    ],
+}
+
+//###############################################################################
+cc_library_shared {
+    name: "libstagefright_soft_c2amrwbdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftAMR.cpp",],
+
+    include_dirs: [
+        "frameworks/av/media/libstagefright/codecs/amrwb/src",
+    ],
+
+    local_include_dirs: ["src"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    static_libs: [
+        "libstagefright_amrnbdec",
+        "libstagefright_amrwbdec",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+        "libstagefright_amrnb_common",
+    ],
+}
\ No newline at end of file
diff --git a/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.cpp b/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.cpp
new file mode 100644
index 0000000..1d4928a
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.cpp
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "C2SoftAMR"
+#include <utils/Log.h>
+
+#include "C2SoftAMR.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+#include "gsmamr_dec.h"
+#include "pvamrwbdecoder.h"
+
+namespace android {
+
+#ifdef AMRNB
+  constexpr char kComponentName[] = "c2.google.amrnb.decoder";
+#else
+  constexpr char kComponentName[] = "c2.google.amrwb.decoder";
+#endif
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatAudio)
+            .inputMediaType(
+#ifdef AMRNB
+                    MEDIA_MIMETYPE_AUDIO_AMR_NB
+#else
+                    MEDIA_MIMETYPE_AUDIO_AMR_WB
+#endif
+            )
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .build();
+}
+
+C2SoftAMR::C2SoftAMR(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mAmrHandle(nullptr),
+      mDecoderBuf(nullptr),
+      mDecoderCookie(nullptr) {
+}
+
+C2SoftAMR::~C2SoftAMR() {
+    (void)onRelease();
+}
+
+c2_status_t C2SoftAMR::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+c2_status_t C2SoftAMR::onStop() {
+    if (!mIsWide) {
+        Speech_Decode_Frame_reset(mAmrHandle);
+    } else {
+        pvDecoder_AmrWb_Reset(mAmrHandle, 0 /* reset_all */);
+    }
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+
+    return C2_OK;
+}
+
+void C2SoftAMR::onReset() {
+    (void)onStop();
+}
+
+void C2SoftAMR::onRelease() {
+    if (!mIsWide) {
+        GSMDecodeFrameExit(&mAmrHandle);
+        mAmrHandle = nullptr;
+    } else {
+        free(mDecoderBuf);
+        mDecoderBuf = nullptr;
+
+        mAmrHandle = nullptr;
+        mDecoderCookie = nullptr;
+    }
+}
+
+c2_status_t C2SoftAMR::onFlush_sm() {
+    return onStop();
+}
+
+status_t C2SoftAMR::initDecoder() {
+#ifdef AMRNB
+    mIsWide = false;
+#else
+    mIsWide = true;
+#endif
+    if (!mIsWide) {
+        if (GSMInitDecode(&mAmrHandle, (int8_t *)"AMRNBDecoder"))
+            return UNKNOWN_ERROR;
+    } else {
+        uint32_t memReq = pvDecoder_AmrWbMemRequirements();
+        mDecoderBuf = malloc(memReq);
+        if (mDecoderBuf) {
+            pvDecoder_AmrWb_Init(&mAmrHandle, mDecoderBuf, &mDecoderCookie);
+        }
+        else {
+            return NO_MEMORY;
+        }
+    }
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+
+    return OK;
+}
+
+static size_t getFrameSize(bool isWide, unsigned FM) {
+    static const size_t kFrameSizeNB[16] = {
+        12, 13, 15, 17, 19, 20, 26, 31,
+        5, 6, 5, 5, // SID
+        0, 0, 0, // future use
+        0 // no data
+    };
+    static const size_t kFrameSizeWB[16] = {
+        17, 23, 32, 36, 40, 46, 50, 58, 60,
+        5, // SID
+        0, 0, 0, 0, // future use
+        0, // speech lost
+        0 // no data
+    };
+
+    if (FM > 15 || (isWide && FM > 9 && FM < 14) || (!isWide && FM > 11 && FM < 15)) {
+        ALOGE("illegal AMR frame mode %d", FM);
+        return 0;
+    }
+    // add 1 for header byte
+    return (isWide ? kFrameSizeWB[FM] : kFrameSizeNB[FM]) + 1;
+}
+
+static status_t calculateNumFrames(const uint8 *input, size_t inSize,
+                                   std::vector<size_t> *frameSizeList, bool isWide) {
+    for (size_t k = 0; k < inSize;) {
+        int16_t FM = ((input[0] >> 3) & 0x0f);
+        size_t frameSize = getFrameSize(isWide, FM);
+        if (frameSize == 0) return UNKNOWN_ERROR;
+        if ((inSize - k) >= frameSize) {
+            input += frameSize;
+            k += frameSize;
+        }
+        else break;
+        frameSizeList->push_back(frameSize);
+    }
+    return OK;
+}
+
+void C2SoftAMR::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer =
+            work->input.buffers[0]->data().linearBlocks().front();
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = rView.error();
+        return;
+    }
+    if (inSize == 0) {
+        work->worklets.front()->output.flags = work->input.flags;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->workletsProcessed = 1u;
+        if (eos) {
+            mSignalledOutputEos = true;
+            ALOGV("signalled EOS");
+        }
+        return;
+    }
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+          (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+
+    std::vector<size_t> frameSizeList;
+    if (OK != calculateNumFrames(rView.data() + inOffset, inSize, &frameSizeList,
+                                 mIsWide)) {
+        work->result = C2_CORRUPTED;
+        mSignalledError = true;
+        return;
+    }
+    if (frameSizeList.empty()) {
+        ALOGE("input size smaller than expected");
+        work->result = C2_CORRUPTED;
+        mSignalledError = true;
+        return;
+    }
+
+    int16_t outSamples = mIsWide ? kNumSamplesPerFrameWB : kNumSamplesPerFrameNB;
+    size_t calOutSize = outSamples * frameSizeList.size() * sizeof(int16_t);
+    std::shared_ptr<C2LinearBlock> block;
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchLinearBlock(calOutSize, usage, &block);
+    if (err != C2_OK) {
+        ALOGE("fetchLinearBlock for Output failed with status %d", err);
+        work->result = C2_NO_MEMORY;
+        return;
+    }
+    C2WriteView wView = block->map().get();
+    if (wView.error()) {
+        ALOGE("write view map failed %d", wView.error());
+        work->result = wView.error();
+        return;
+    }
+
+    int16_t *output = reinterpret_cast<int16_t *>(wView.data());
+    auto it = frameSizeList.begin();
+    const uint8_t *inPtr = rView.data() + inOffset;
+    size_t inPos = 0;
+    while (inPos < inSize) {
+        if (it == frameSizeList.end()) {
+            ALOGD("unexpected trailing bytes, ignoring them");
+            break;
+        }
+        uint8_t *input = const_cast<uint8_t *>(inPtr + inPos);
+        int16_t FM = ((*input >> 3) & 0x0f);
+        if (!mIsWide) {
+            int32_t numBytesRead = AMRDecode(mAmrHandle,
+                                             (Frame_Type_3GPP) FM,
+                                             input + 1, output, MIME_IETF);
+            if (static_cast<size_t>(numBytesRead + 1) != *it) {
+                ALOGE("panic, parsed size does not match decoded size");
+                work->result = C2_CORRUPTED;
+                mSignalledError = true;
+                return;
+            }
+        } else {
+            if (FM >= 9) {
+                // Produce silence instead of comfort noise and for
+                // speech lost/no data.
+                memset(output, 0, outSamples * sizeof(int16_t));
+            } else {
+                int16_t FT;
+                RX_State_wb rx_state;
+                int16_t numRecSamples;
+
+                mime_unsorting(const_cast<uint8_t *>(&input[1]),
+                               mInputSampleBuffer, &FT, &FM, 1, &rx_state);
+                pvDecoder_AmrWb(FM, mInputSampleBuffer, output, &numRecSamples,
+                                mDecoderBuf, FT, mDecoderCookie);
+                if (numRecSamples != outSamples) {
+                    ALOGE("Sample output per frame incorrect");
+                    work->result = C2_CORRUPTED;
+                    mSignalledError = true;
+                    return;
+                }
+                /* Delete the 2 LSBs (14-bit output) */
+                for (int i = 0; i < numRecSamples; ++i) {
+                    output[i] &= 0xfffC;
+                }
+            }
+        }
+        inPos += *it;
+        output += outSamples;
+        ++it;
+    }
+
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.buffers.push_back(createLinearBuffer(block));
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+    if (eos) {
+        mSignalledOutputEos = true;
+        ALOGV("signalled EOS");
+    }
+}
+
+c2_status_t C2SoftAMR::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+    return C2_OK;
+}
+
+class C2SoftAMRDecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftAMR(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftAMRDecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftAMRDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.h b/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.h
new file mode 100644
index 0000000..69fe213
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C2_SOFT_AMR_H_
+#define C2_SOFT_AMR_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct C2SoftAMR : public SimpleC2Component {
+    C2SoftAMR(const char *name, c2_node_id_t id);
+    virtual ~C2SoftAMR();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+private:
+    enum {
+        kSampleRateNB           = 8000,
+        kSampleRateWB           = 16000,
+        kNumSamplesPerFrameNB   = 160,
+        kNumSamplesPerFrameWB   = 320,
+    };
+
+    void *mAmrHandle;
+    void *mDecoderBuf;
+    int16_t *mDecoderCookie;
+
+    int16_t mInputSampleBuffer[477];
+
+    bool mIsWide;
+    bool mSignalledError;
+    bool mSignalledOutputEos;
+
+    status_t initDecoder();
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftAMR);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_AMR_H_
diff --git a/media/libstagefright/codecs/amrwbenc/Android.bp b/media/libstagefright/codecs/amrwbenc/Android.bp
index ebe08c6..88e9206 100644
--- a/media/libstagefright/codecs/amrwbenc/Android.bp
+++ b/media/libstagefright/codecs/amrwbenc/Android.bp
@@ -185,4 +185,46 @@
 
 //###############################################################################
 
+cc_library_shared {
+    name: "libstagefright_soft_c2amrwbenc",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftAmrWbEnc.cpp"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    static_libs: [
+        "libstagefright_amrwbenc",
+    ],
+
+    shared_libs: [
+        "libutils",
+        "liblog",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+        "libstagefright_enc_common",
+    ],
+}
+
+//###############################################################################
+
 subdirs = ["SampleCode"]
diff --git a/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.cpp b/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.cpp
new file mode 100644
index 0000000..2a831e5
--- /dev/null
+++ b/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.cpp
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "C2SoftAmrWbEnc"
+#include <utils/Log.h>
+
+#include "C2SoftAmrWbEnc.h"
+
+#include "cmnMemory.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.amrwb.encoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatAudio)
+            .outputFormat(C2FormatCompressed)
+            .inputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_AMR_WB)
+            .build();
+}
+
+C2SoftAmrWbEnc::C2SoftAmrWbEnc(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mEncoderHandle(nullptr),
+      mApiHandle(nullptr),
+      mMemOperator(nullptr) {
+}
+
+C2SoftAmrWbEnc::~C2SoftAmrWbEnc() {
+    onRelease();
+}
+
+c2_status_t C2SoftAmrWbEnc::onInit() {
+    status_t err = initEncoder();
+    mMode = VOAMRWB_MD2305;
+    mIsFirst = true;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    mAnchorTimeStamp = 0;
+    mProcessedSamples = 0;
+    mFilledLen = 0;
+
+    return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+void C2SoftAmrWbEnc::onRelease() {
+    if (mEncoderHandle) {
+        CHECK_EQ((VO_U32)VO_ERR_NONE, mApiHandle->Uninit(mEncoderHandle));
+        mEncoderHandle = nullptr;
+    }
+    if (mApiHandle) {
+        delete mApiHandle;
+        mApiHandle = nullptr;
+    }
+    if (mMemOperator) {
+        delete mMemOperator;
+        mMemOperator = nullptr;
+    }
+}
+
+c2_status_t C2SoftAmrWbEnc::onStop() {
+    for (int i = 0; i < kNumSamplesPerFrame; i++) {
+        mInputFrame[i] = 0x0008; /* EHF_MASK */
+    }
+    uint8_t outBuffer[kNumBytesPerInputFrame];
+    (void) encodeInput(outBuffer, kNumBytesPerInputFrame);
+    mIsFirst = true;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    mAnchorTimeStamp = 0;
+    mProcessedSamples = 0;
+    mFilledLen = 0;
+
+    return C2_OK;
+}
+
+void C2SoftAmrWbEnc::onReset() {
+    (void) onStop();
+}
+
+c2_status_t C2SoftAmrWbEnc::onFlush_sm() {
+    return onStop();
+}
+
+status_t C2SoftAmrWbEnc::initEncoder() {
+    mApiHandle = new VO_AUDIO_CODECAPI;
+    if (!mApiHandle) return NO_MEMORY;
+
+    if (VO_ERR_NONE != voGetAMRWBEncAPI(mApiHandle)) {
+        ALOGE("Failed to get api handle");
+        return UNKNOWN_ERROR;
+    }
+
+    mMemOperator = new VO_MEM_OPERATOR;
+    if (!mMemOperator) return NO_MEMORY;
+
+    mMemOperator->Alloc = cmnMemAlloc;
+    mMemOperator->Copy = cmnMemCopy;
+    mMemOperator->Free = cmnMemFree;
+    mMemOperator->Set = cmnMemSet;
+    mMemOperator->Check = cmnMemCheck;
+
+    VO_CODEC_INIT_USERDATA userData;
+    memset(&userData, 0, sizeof(userData));
+    userData.memflag = VO_IMF_USERMEMOPERATOR;
+    userData.memData = (VO_PTR) mMemOperator;
+
+    if (VO_ERR_NONE != mApiHandle->Init(
+                &mEncoderHandle, VO_AUDIO_CodingAMRWB, &userData)) {
+        ALOGE("Failed to init AMRWB encoder");
+        return UNKNOWN_ERROR;
+    }
+
+    VOAMRWBFRAMETYPE type = VOAMRWB_RFC3267;
+    if (VO_ERR_NONE != mApiHandle->SetParam(
+                mEncoderHandle, VO_PID_AMRWB_FRAMETYPE, &type)) {
+        ALOGE("Failed to set AMRWB encoder frame type to %d", type);
+        return UNKNOWN_ERROR;
+    }
+
+    if (VO_ERR_NONE !=
+            mApiHandle->SetParam(
+                    mEncoderHandle, VO_PID_AMRWB_MODE,  &mMode)) {
+        ALOGE("Failed to set AMRWB encoder mode to %d", mMode);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+int C2SoftAmrWbEnc::encodeInput(uint8_t *buffer, uint32_t length) {
+    VO_CODECBUFFER inputData;
+    memset(&inputData, 0, sizeof(inputData));
+    inputData.Buffer = (unsigned char *) mInputFrame;
+    inputData.Length = kNumBytesPerInputFrame;
+
+    CHECK_EQ((VO_U32)VO_ERR_NONE,
+             mApiHandle->SetInputData(mEncoderHandle, &inputData));
+
+    VO_AUDIO_OUTPUTINFO outputInfo;
+    memset(&outputInfo, 0, sizeof(outputInfo));
+    VO_CODECBUFFER outputData;
+    memset(&outputData, 0, sizeof(outputData));
+    outputData.Buffer = buffer;
+    outputData.Length = length;
+    VO_U32 ret = mApiHandle->GetOutputData(
+            mEncoderHandle, &outputData, &outputInfo);
+    if (ret != VO_ERR_NONE && ret != VO_ERR_INPUT_BUFFER_SMALL) {
+        ALOGD("encountered error during encode call");
+        return -1;
+    }
+    return outputData.Length;
+}
+
+static void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+void C2SoftAmrWbEnc::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = rView.error();
+        return;
+    }
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+          inSize, (int)work->input.ordinal.timestamp.peeku(),
+          (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+
+    size_t outCapacity = kNumBytesPerInputFrame;
+    outCapacity += mFilledLen + inSize;
+    std::shared_ptr<C2LinearBlock> outputBlock;
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &outputBlock);
+    if (err != C2_OK) {
+        ALOGE("fetchLinearBlock for Output failed with status %d", err);
+        work->result = C2_NO_MEMORY;
+        return;
+    }
+    C2WriteView wView = outputBlock->map().get();
+    if (wView.error()) {
+        ALOGE("write view map failed %d", wView.error());
+        work->result = wView.error();
+        return;
+    }
+
+    uint64_t outTimeStamp = mProcessedSamples * 1000000ll / kSampleRate;
+    const uint8_t *inPtr = rView.data() + inOffset;
+    size_t inPos = 0;
+    size_t outPos = 0;
+    while (inPos < inSize) {
+        int validSamples = mFilledLen / sizeof(int16_t);
+        if ((inPos + (kNumBytesPerInputFrame - mFilledLen)) <= inSize) {
+            memcpy(mInputFrame + validSamples, inPtr + inPos,
+                   (kNumBytesPerInputFrame - mFilledLen));
+            inPos += (kNumBytesPerInputFrame - mFilledLen);
+        } else {
+            memcpy(mInputFrame + validSamples, inPtr + inPos, (inSize - inPos));
+            mFilledLen += (inSize - inPos);
+            inPos += (inSize - inPos);
+            if (eos) {
+                validSamples = mFilledLen / sizeof(int16_t);
+                memset(mInputFrame + validSamples, 0, (kNumBytesPerInputFrame - mFilledLen));
+            } else break;
+        }
+        int numEncBytes = encodeInput((wView.data() + outPos), outCapacity - outPos);
+        if (numEncBytes < 0) {
+            ALOGE("encodeFrame call failed, state [%d %zu %zu]", numEncBytes, outPos, outCapacity);
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return;
+        }
+        outPos += numEncBytes;
+        mProcessedSamples += kNumSamplesPerFrame;
+        mFilledLen = 0;
+    }
+    ALOGV("causal sample size %d", mFilledLen);
+    if (mIsFirst) {
+        mIsFirst = false;
+        mAnchorTimeStamp = work->input.ordinal.timestamp.peekull();
+    }
+    fillEmptyWork(work);
+    if (outPos != 0) {
+        work->worklets.front()->output.buffers.push_back(
+                createLinearBuffer(std::move(outputBlock), 0, outPos));
+        work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp;
+    }
+    if (eos) {
+        mSignalledOutputEos = true;
+        ALOGV("signalled EOS");
+        if (mFilledLen) ALOGV("Discarding trailing %d bytes", mFilledLen);
+    }
+}
+
+c2_status_t C2SoftAmrWbEnc::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    onFlush_sm();
+    return C2_OK;
+}
+
+class C2SoftAmrWbEncFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftAmrWbEnc(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftAmrWbEncFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftAmrWbEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.h b/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.h
new file mode 100644
index 0000000..29e68ac
--- /dev/null
+++ b/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C2_SOFT_AMRWB_ENC_H_
+#define C2_SOFT_AMRWB_ENC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+
+#include "voAMRWB.h"
+
+namespace android {
+
+class C2SoftAmrWbEnc : public SimpleC2Component {
+public:
+    C2SoftAmrWbEnc(const char *name, c2_node_id_t id);
+    virtual ~C2SoftAmrWbEnc();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+
+private:
+    static const int32_t kNumSamplesPerFrame = 320;
+    static const int32_t kNumBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t);
+    static const int32_t kSampleRate = 16000;
+
+    void *mEncoderHandle;
+    VO_AUDIO_CODECAPI *mApiHandle;
+    VO_MEM_OPERATOR *mMemOperator;
+    VOAMRWBMODE mMode;
+    bool mIsFirst;
+    bool mSignalledError;
+    bool mSignalledOutputEos;
+    uint64_t mAnchorTimeStamp;
+    uint64_t mProcessedSamples;
+    int32_t mFilledLen;
+    int16_t mInputFrame[kNumSamplesPerFrame];
+
+    status_t initEncoder();
+    int encodeInput(uint8_t *buffer, uint32_t length);
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftAmrWbEnc);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_AMRWB_ENC_H_
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
index 0f1fecc..87138af 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
@@ -48,6 +48,7 @@
 }
 
 C2SoftFlacDecoder::~C2SoftFlacDecoder() {
+    delete mFLACDecoder;
 }
 
 c2_status_t C2SoftFlacDecoder::onInit() {
@@ -77,6 +78,9 @@
 }
 
 status_t C2SoftFlacDecoder::initDecoder() {
+    if (mFLACDecoder) {
+        delete mFLACDecoder;
+    }
     mFLACDecoder = FLACDecoder::Create();
     if (!mFLACDecoder) {
         ALOGE("initDecoder: failed to create FLACDecoder");
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
index a5c01a9..43d913b 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
@@ -46,7 +46,7 @@
         kMaxBlockSize   = 4096
     };
 
-    sp<FLACDecoder> mFLACDecoder;
+    FLACDecoder *mFLACDecoder;
     FLAC__StreamMetadata_StreamInfo mStreamInfo;
     bool mSignalledError;
     bool mSignalledOutputEos;
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
index 4ab1ab2..d0b72b7 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
@@ -57,6 +57,7 @@
 
 SoftFlacDecoder::~SoftFlacDecoder() {
     ALOGV("dtor:");
+    delete mFLACDecoder;
 }
 
 void SoftFlacDecoder::initPorts() {
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
index 4a21c34..0f17ed8 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
@@ -50,7 +50,7 @@
         kNumOutputBuffers  = 4,
     };
 
-    sp<FLACDecoder> mFLACDecoder;
+    FLACDecoder *mFLACDecoder;
     FLAC__StreamMetadata_StreamInfo mStreamInfo;
     bool mHasStreamInfo;
     size_t mInputBufferCount;
diff --git a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
index fa93fe5..f94ed49 100644
--- a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
+++ b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
@@ -181,11 +181,13 @@
 
     mEncoderWriteData = true;
     mEncoderReturnedNbBytes = 0;
-    while (inOffset < inSize) {
-        size_t processSize = MIN(kInBlockSize * mNumChannels * sizeof(int16_t), (inSize - inOffset));
+    size_t inPos = 0;
+    const uint8_t *inPtr = rView.data() + inOffset;
+    while (inPos < inSize) {
+        size_t processSize = MIN(kInBlockSize * mNumChannels * sizeof(int16_t), (inSize - inPos));
         const unsigned nbInputFrames = processSize / (mNumChannels * sizeof(int16_t));
         const unsigned nbInputSamples = processSize / sizeof(int16_t);
-        const int16_t *pcm16 = reinterpret_cast<const int16_t *>(rView.data() + inOffset);
+        const int16_t *pcm16 = reinterpret_cast<const int16_t *>(inPtr + inPos);
         ALOGV("about to encode %zu bytes", processSize);
 
         for (unsigned i = 0; i < nbInputSamples; i++) {
@@ -201,7 +203,7 @@
             mOutputBlock.reset();
             return;
         }
-        inOffset += processSize;
+        inPos += processSize;
     }
     if (eos && !drain(DRAIN_COMPONENT_WITH_EOS, pool)) {
         ALOGE("error encountered during encoding");
diff --git a/media/libstagefright/codecs/gsm/dec/Android.bp b/media/libstagefright/codecs/gsm/dec/Android.bp
index 1c3208b..c057d8a 100644
--- a/media/libstagefright/codecs/gsm/dec/Android.bp
+++ b/media/libstagefright/codecs/gsm/dec/Android.bp
@@ -1,4 +1,45 @@
 cc_library_shared {
+    name: "libstagefright_soft_c2gsmdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: [
+        "C2SoftGSM.cpp"
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+
+    static_libs: [
+        "libgsm"
+    ],
+}
+
+cc_library_shared {
     name: "libstagefright_soft_gsmdec",
     vendor_available: true,
     vndk: {
diff --git a/media/libstagefright/codecs/gsm/dec/C2SoftGSM.cpp b/media/libstagefright/codecs/gsm/dec/C2SoftGSM.cpp
new file mode 100644
index 0000000..d021768
--- /dev/null
+++ b/media/libstagefright/codecs/gsm/dec/C2SoftGSM.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "C2SoftGSM"
+#include <utils/Log.h>
+
+#include "C2SoftGSM.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.gsm.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatAudio)
+            .inputMediaType(MEDIA_MIMETYPE_AUDIO_MSGSM)
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .build();
+}
+
+C2SoftGSM::C2SoftGSM(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mGsm(nullptr) {
+}
+
+C2SoftGSM::~C2SoftGSM() {
+    onRelease();
+}
+
+c2_status_t C2SoftGSM::onInit() {
+    if (!mGsm) mGsm = gsm_create();
+    if (!mGsm) return C2_NO_MEMORY;
+    int msopt = 1;
+    (void)gsm_option(mGsm, GSM_OPT_WAV49, &msopt);
+    mSignalledError = false;
+    mSignalledEos = false;
+    return C2_OK;
+}
+
+c2_status_t C2SoftGSM::onStop() {
+    if (mGsm) {
+        gsm_destroy(mGsm);
+        mGsm = nullptr;
+    }
+    if (!mGsm) mGsm = gsm_create();
+    if (!mGsm) return C2_NO_MEMORY;
+    int msopt = 1;
+    (void)gsm_option(mGsm, GSM_OPT_WAV49, &msopt);
+    mSignalledError = false;
+    mSignalledEos = false;
+    return C2_OK;
+}
+
+void C2SoftGSM::onReset() {
+    (void)onStop();
+}
+
+void C2SoftGSM::onRelease() {
+    if (mGsm) {
+        gsm_destroy(mGsm);
+        mGsm = nullptr;
+    }
+}
+
+c2_status_t C2SoftGSM::onFlush_sm() {
+    return onStop();
+}
+
+static size_t decodeGSM(gsm handle, int16_t *out, size_t outCapacity,
+                        uint8_t *in, size_t inSize) {
+    size_t outSize = 0;
+
+    if (inSize % MSGSM_IN_FRM_SZ == 0
+            && (inSize / MSGSM_IN_FRM_SZ * MSGSM_OUT_FRM_SZ * sizeof(*out)
+                    <= outCapacity)) {
+        while (inSize > 0) {
+            gsm_decode(handle, in, out);
+            in += FRGSM_IN_FRM_SZ;
+            inSize -= FRGSM_IN_FRM_SZ;
+            out += FRGSM_OUT_FRM_SZ;
+            outSize += FRGSM_OUT_FRM_SZ;
+
+            gsm_decode(handle, in, out);
+            in += FRGSM_IN_FRM_SZ_MINUS_1;
+            inSize -= FRGSM_IN_FRM_SZ_MINUS_1;
+            out += FRGSM_OUT_FRM_SZ;
+            outSize += FRGSM_OUT_FRM_SZ;
+        }
+    }
+
+    return outSize * sizeof(int16_t);
+}
+
+void C2SoftGSM::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = rView.error();
+        return;
+    }
+
+    if (inSize == 0) {
+        work->worklets.front()->output.flags = work->input.flags;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->workletsProcessed = 1u;
+        if (eos) {
+            mSignalledEos = true;
+            ALOGV("signalled EOS");
+        }
+        return;
+    }
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+          (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+
+    size_t outCapacity = (inSize / MSGSM_IN_FRM_SZ ) * MSGSM_OUT_FRM_SZ * sizeof(int16_t);
+    std::shared_ptr<C2LinearBlock> block;
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &block);
+    if (err != C2_OK) {
+        ALOGE("fetchLinearBlock for Output failed with status %d", err);
+        work->result = C2_NO_MEMORY;
+        return;
+    }
+    C2WriteView wView = block->map().get();
+    if (wView.error()) {
+        ALOGE("write view map failed %d", wView.error());
+        work->result = wView.error();
+        return;
+    }
+
+    int16_t *output = reinterpret_cast<int16_t *>(wView.data());
+    uint8_t *input = const_cast<uint8_t *>(rView.data() + inOffset);
+    size_t outSize = decodeGSM(mGsm, output, outCapacity, input, inSize);
+    if (!outSize) {
+        ALOGE("encountered improper insize or outsize");
+        mSignalledError = true;
+        work->result = C2_CORRUPTED;
+        return;
+    }
+    ALOGV("out buffer attr. size %zu", outSize);
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, 0, outSize));
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+    if (eos) {
+        mSignalledEos = true;
+        ALOGV("signalled EOS");
+    }
+}
+
+c2_status_t C2SoftGSM::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    return C2_OK;
+}
+
+class C2SoftGSMDecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftGSM(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftGSMDecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftGSMDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/gsm/dec/C2SoftGSM.h b/media/libstagefright/codecs/gsm/dec/C2SoftGSM.h
new file mode 100644
index 0000000..597711c
--- /dev/null
+++ b/media/libstagefright/codecs/gsm/dec/C2SoftGSM.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C2_SOFT_GSM_H_
+#define C2_SOFT_GSM_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+extern "C" {
+    #include "gsm.h"
+}
+
+namespace android {
+
+#define FRGSM_IN_FRM_SZ             33
+#define FRGSM_IN_FRM_SZ_MINUS_1     32
+#define FRGSM_OUT_FRM_SZ            160
+#define MSGSM_IN_FRM_SZ             (FRGSM_IN_FRM_SZ + FRGSM_IN_FRM_SZ_MINUS_1)
+#define MSGSM_OUT_FRM_SZ            (FRGSM_OUT_FRM_SZ * 2)
+
+struct C2SoftGSM : public SimpleC2Component {
+    C2SoftGSM(const char *name, c2_node_id_t id);
+    virtual ~C2SoftGSM();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+ private:
+    gsm mGsm;
+    bool mSignalledError;
+    bool mSignalledEos;
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftGSM);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_GSM_H_
diff --git a/media/libstagefright/codecs/hevcdec/Android.bp b/media/libstagefright/codecs/hevcdec/Android.bp
index 45920e6..385807a 100644
--- a/media/libstagefright/codecs/hevcdec/Android.bp
+++ b/media/libstagefright/codecs/hevcdec/Android.bp
@@ -46,3 +46,48 @@
     ldflags: ["-Wl,-Bsymbolic"],
     compile_multilib: "32",
 }
+
+cc_library_shared {
+    name: "libstagefright_soft_c2hevcdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftHevcDec.cpp"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    include_dirs: [
+        "external/libhevc/decoder",
+        "external/libhevc/common",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+        ],
+        cfi: false, // true,
+        diag: {
+            cfi: false, // true,
+        },
+    },
+
+    static_libs: ["libhevcdec"],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libmedia",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+
+    ldflags: ["-Wl,-Bsymbolic"],
+
+}
diff --git a/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.cpp b/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.cpp
new file mode 100644
index 0000000..6d208bd
--- /dev/null
+++ b/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.cpp
@@ -0,0 +1,705 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "C2SoftHevcDec"
+#include <utils/Log.h>
+
+#include "ihevc_typedefs.h"
+#include "iv.h"
+#include "ivd.h"
+#include "ihevcd_cxa.h"
+#include "C2SoftHevcDec.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.hevc.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatVideo)
+            .inputMediaType(MEDIA_MIMETYPE_VIDEO_HEVC)
+            .outputMediaType(MEDIA_MIMETYPE_VIDEO_RAW)
+            .build();
+}
+
+static size_t getCpuCoreCount() {
+    long cpuCoreCount = 1;
+#if defined(_SC_NPROCESSORS_ONLN)
+    cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+    // _SC_NPROC_ONLN must be defined...
+    cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+    CHECK(cpuCoreCount >= 1);
+    ALOGV("Number of CPU cores: %ld", cpuCoreCount);
+    return (size_t)cpuCoreCount;
+}
+
+static void *ivd_aligned_malloc(void *ctxt, WORD32 alignment, WORD32 size) {
+    (void) ctxt;
+    return memalign(alignment, size);
+}
+
+static void ivd_aligned_free(void *ctxt, void *mem) {
+    (void) ctxt;
+    free(mem);
+}
+
+C2SoftHevcDec::C2SoftHevcDec(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+            mDecHandle(nullptr),
+            mOutBufferFlush(nullptr),
+            mIvColorformat(IV_YUV_420P),
+            mWidth(320),
+            mHeight(240) {
+}
+
+C2SoftHevcDec::~C2SoftHevcDec() {
+    onRelease();
+}
+
+c2_status_t C2SoftHevcDec::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftHevcDec::onStop() {
+    if (OK != resetDecoder()) return C2_CORRUPTED;
+    resetPlugin();
+    return C2_OK;
+}
+
+void C2SoftHevcDec::onReset() {
+    (void) onStop();
+}
+
+void C2SoftHevcDec::onRelease() {
+    (void) deleteDecoder();
+    if (mOutBufferFlush) {
+        ivd_aligned_free(nullptr, mOutBufferFlush);
+        mOutBufferFlush = nullptr;
+    }
+    if (mOutBlock) {
+        mOutBlock.reset();
+    }
+}
+
+c2_status_t C2SoftHevcDec::onFlush_sm() {
+    if (OK != setFlushMode()) return C2_CORRUPTED;
+
+    uint32_t displayStride = mStride;
+    uint32_t displayHeight = mHeight;
+    uint32_t bufferSize = displayStride * displayHeight * 3 / 2;
+    mOutBufferFlush = (uint8_t *)ivd_aligned_malloc(nullptr, 128, bufferSize);
+    if (!mOutBufferFlush) {
+        ALOGE("could not allocate tmp output buffer (for flush) of size %u ", bufferSize);
+        return C2_NO_MEMORY;
+    }
+
+    while (true) {
+        ivd_video_decode_ip_t s_decode_ip;
+        ivd_video_decode_op_t s_decode_op;
+
+        setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, nullptr, 0, 0, 0);
+        (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+        if (0 == s_decode_op.u4_output_present) {
+            resetPlugin();
+            break;
+        }
+    }
+
+    ivd_aligned_free(nullptr, mOutBufferFlush);
+    mOutBufferFlush = nullptr;
+
+    return C2_OK;
+}
+
+status_t C2SoftHevcDec::createDecoder() {
+    ivdext_create_ip_t s_create_ip;
+    ivdext_create_op_t s_create_op;
+
+    s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ivdext_create_ip_t);
+    s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE;
+    s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 0;
+    s_create_ip.s_ivd_create_ip_t.e_output_format = mIvColorformat;
+    s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc;
+    s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free;
+    s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = nullptr;
+    s_create_op.s_ivd_create_op_t.u4_size = sizeof(ivdext_create_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_create_ip,
+                                                     &s_create_op);
+    if (status != IV_SUCCESS) {
+        ALOGE("error in %s: 0x%x", __func__,
+              s_create_op.s_ivd_create_op_t.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+    mDecHandle = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle;
+    mDecHandle->pv_fxns = (void *)ivdec_api_function;
+    mDecHandle->u4_size = sizeof(iv_obj_t);
+
+    return OK;
+}
+
+status_t C2SoftHevcDec::setNumCores() {
+    ivdext_ctl_set_num_cores_ip_t s_set_num_cores_ip;
+    ivdext_ctl_set_num_cores_op_t s_set_num_cores_op;
+
+    s_set_num_cores_ip.u4_size = sizeof(ivdext_ctl_set_num_cores_ip_t);
+    s_set_num_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_set_num_cores_ip.e_sub_cmd = IVDEXT_CMD_CTL_SET_NUM_CORES;
+    s_set_num_cores_ip.u4_num_cores = mNumCores;
+    s_set_num_cores_op.u4_size = sizeof(ivdext_ctl_set_num_cores_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_set_num_cores_ip,
+                                                     &s_set_num_cores_op);
+    if (IV_SUCCESS != status) {
+        ALOGD("error in %s: 0x%x", __func__, s_set_num_cores_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2SoftHevcDec::setParams(size_t stride) {
+    ivd_ctl_set_config_ip_t s_set_dyn_params_ip;
+    ivd_ctl_set_config_op_t s_set_dyn_params_op;
+
+    s_set_dyn_params_ip.u4_size = sizeof(ivd_ctl_set_config_ip_t);
+    s_set_dyn_params_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_set_dyn_params_ip.e_sub_cmd = IVD_CMD_CTL_SETPARAMS;
+    s_set_dyn_params_ip.u4_disp_wd = (UWORD32) stride;
+    s_set_dyn_params_ip.e_frm_skip_mode = IVD_SKIP_NONE;
+    s_set_dyn_params_ip.e_frm_out_mode = IVD_DISPLAY_FRAME_OUT;
+    s_set_dyn_params_ip.e_vid_dec_mode = IVD_DECODE_FRAME;
+    s_set_dyn_params_op.u4_size = sizeof(ivd_ctl_set_config_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_set_dyn_params_ip,
+                                                     &s_set_dyn_params_op);
+    if (status != IV_SUCCESS) {
+        ALOGE("error in %s: 0x%x", __func__, s_set_dyn_params_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2SoftHevcDec::getVersion() {
+    ivd_ctl_getversioninfo_ip_t s_get_versioninfo_ip;
+    ivd_ctl_getversioninfo_op_t s_get_versioninfo_op;
+    UWORD8 au1_buf[512];
+
+    s_get_versioninfo_ip.u4_size = sizeof(ivd_ctl_getversioninfo_ip_t);
+    s_get_versioninfo_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_get_versioninfo_ip.e_sub_cmd = IVD_CMD_CTL_GETVERSION;
+    s_get_versioninfo_ip.pv_version_buffer = au1_buf;
+    s_get_versioninfo_ip.u4_version_buffer_size = sizeof(au1_buf);
+    s_get_versioninfo_op.u4_size = sizeof(ivd_ctl_getversioninfo_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_get_versioninfo_ip,
+                                                     &s_get_versioninfo_op);
+    if (status != IV_SUCCESS) {
+        ALOGD("error in %s: 0x%x", __func__,
+              s_get_versioninfo_op.u4_error_code);
+    } else {
+        ALOGV("ittiam decoder version number: %s",
+              (char *) s_get_versioninfo_ip.pv_version_buffer);
+    }
+
+    return OK;
+}
+
+status_t C2SoftHevcDec::initDecoder() {
+    if (OK != createDecoder()) return UNKNOWN_ERROR;
+    mNumCores = MIN(getCpuCoreCount(), MAX_NUM_CORES);
+    mStride = ALIGN64(mWidth);
+    mSignalledError = false;
+    mPreference = kPreferBitstream;
+    memset(&mDefaultColorAspects, 0, sizeof(ColorAspects));
+    memset(&mBitstreamColorAspects, 0, sizeof(ColorAspects));
+    memset(&mFinalColorAspects, 0, sizeof(ColorAspects));
+    mUpdateColorAspects = false;
+    resetPlugin();
+    (void) setNumCores();
+    if (OK != setParams(mStride)) return UNKNOWN_ERROR;
+    (void) getVersion();
+
+    return OK;
+}
+
+bool C2SoftHevcDec::setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip,
+                                  ivd_video_decode_op_t *ps_decode_op,
+                                  C2ReadView *inBuffer,
+                                  C2GraphicView *outBuffer,
+                                  size_t inOffset,
+                                  size_t inSize,
+                                  uint32_t tsMarker) {
+    uint32_t displayStride = mStride;
+    uint32_t displayHeight = mHeight;
+    size_t lumaSize = displayStride * displayHeight;
+    size_t chromaSize = lumaSize >> 2;
+
+    ps_decode_ip->u4_size = sizeof(ivd_video_decode_ip_t);
+    ps_decode_ip->e_cmd = IVD_CMD_VIDEO_DECODE;
+    if (inBuffer) {
+        ps_decode_ip->u4_ts = tsMarker;
+        ps_decode_ip->pv_stream_buffer = const_cast<uint8_t *>(inBuffer->data() + inOffset);
+        ps_decode_ip->u4_num_Bytes = inSize;
+    } else {
+        ps_decode_ip->u4_ts = 0;
+        ps_decode_ip->pv_stream_buffer = nullptr;
+        ps_decode_ip->u4_num_Bytes = 0;
+    }
+    ps_decode_ip->s_out_buffer.u4_min_out_buf_size[0] = lumaSize;
+    ps_decode_ip->s_out_buffer.u4_min_out_buf_size[1] = chromaSize;
+    ps_decode_ip->s_out_buffer.u4_min_out_buf_size[2] = chromaSize;
+    if (outBuffer) {
+        if (outBuffer->width() < displayStride || outBuffer->height() < displayHeight) {
+            ALOGE("Output buffer too small: provided (%dx%d) required (%ux%u)",
+                  outBuffer->width(), outBuffer->height(), displayStride, displayHeight);
+            return false;
+        }
+        ps_decode_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[C2PlanarLayout::PLANE_Y];
+        ps_decode_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[C2PlanarLayout::PLANE_U];
+        ps_decode_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[C2PlanarLayout::PLANE_V];
+    } else {
+        ps_decode_ip->s_out_buffer.pu1_bufs[0] = mOutBufferFlush;
+        ps_decode_ip->s_out_buffer.pu1_bufs[1] = mOutBufferFlush + lumaSize;
+        ps_decode_ip->s_out_buffer.pu1_bufs[2] = mOutBufferFlush + lumaSize + chromaSize;
+    }
+    ps_decode_ip->s_out_buffer.u4_num_bufs = 3;
+    ps_decode_op->u4_size = sizeof(ivd_video_decode_op_t);
+    ps_decode_op->u4_output_present = 0;
+
+    return true;
+}
+
+bool C2SoftHevcDec::colorAspectsDiffer(
+        const ColorAspects &a, const ColorAspects &b) {
+    if (a.mRange != b.mRange
+        || a.mPrimaries != b.mPrimaries
+        || a.mTransfer != b.mTransfer
+        || a.mMatrixCoeffs != b.mMatrixCoeffs) {
+        return true;
+    }
+    return false;
+}
+
+void C2SoftHevcDec::updateFinalColorAspects(
+        const ColorAspects &otherAspects, const ColorAspects &preferredAspects) {
+    Mutex::Autolock autoLock(mColorAspectsLock);
+    ColorAspects newAspects;
+    newAspects.mRange = preferredAspects.mRange != ColorAspects::RangeUnspecified ?
+        preferredAspects.mRange : otherAspects.mRange;
+    newAspects.mPrimaries = preferredAspects.mPrimaries != ColorAspects::PrimariesUnspecified ?
+        preferredAspects.mPrimaries : otherAspects.mPrimaries;
+    newAspects.mTransfer = preferredAspects.mTransfer != ColorAspects::TransferUnspecified ?
+        preferredAspects.mTransfer : otherAspects.mTransfer;
+    newAspects.mMatrixCoeffs = preferredAspects.mMatrixCoeffs != ColorAspects::MatrixUnspecified ?
+        preferredAspects.mMatrixCoeffs : otherAspects.mMatrixCoeffs;
+
+    // Check to see if need update mFinalColorAspects.
+    if (colorAspectsDiffer(mFinalColorAspects, newAspects)) {
+        mFinalColorAspects = newAspects;
+        mUpdateColorAspects = true;
+    }
+}
+
+status_t C2SoftHevcDec::handleColorAspectsChange() {
+    if (mPreference == kPreferBitstream) {
+        updateFinalColorAspects(mDefaultColorAspects, mBitstreamColorAspects);
+    } else if (mPreference == kPreferContainer) {
+        updateFinalColorAspects(mBitstreamColorAspects, mDefaultColorAspects);
+    } else {
+        return C2_CORRUPTED;
+    }
+    return C2_OK;
+}
+
+bool C2SoftHevcDec::getVuiParams() {
+    ivdext_ctl_get_vui_params_ip_t s_get_vui_params_ip;
+    ivdext_ctl_get_vui_params_op_t s_get_vui_params_op;
+
+    s_get_vui_params_ip.u4_size = sizeof(ivdext_ctl_get_vui_params_ip_t);
+    s_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_get_vui_params_ip.e_sub_cmd =
+            (IVD_CONTROL_API_COMMAND_TYPE_T) IHEVCD_CXA_CMD_CTL_GET_VUI_PARAMS;
+    s_get_vui_params_op.u4_size = sizeof(ivdext_ctl_get_vui_params_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_get_vui_params_ip,
+                                                     &s_get_vui_params_op);
+    if (status != IV_SUCCESS) {
+        ALOGD("error in %s: 0x%x", __func__, s_get_vui_params_op.u4_error_code);
+        return false;
+    }
+
+    int32_t primaries = s_get_vui_params_op.u1_colour_primaries;
+    int32_t transfer = s_get_vui_params_op.u1_transfer_characteristics;
+    int32_t coeffs = s_get_vui_params_op.u1_matrix_coefficients;
+    bool full_range = s_get_vui_params_op.u1_video_full_range_flag;
+
+    ColorAspects colorAspects;
+    ColorUtils::convertIsoColorAspectsToCodecAspects(
+            primaries, transfer, coeffs, full_range, colorAspects);
+    // Update color aspects if necessary.
+    if (colorAspectsDiffer(colorAspects, mBitstreamColorAspects)) {
+        mBitstreamColorAspects = colorAspects;
+        status_t err = handleColorAspectsChange();
+        CHECK(err == OK);
+    }
+
+    return true;
+}
+
+status_t C2SoftHevcDec::setFlushMode() {
+    ivd_ctl_flush_ip_t s_set_flush_ip;
+    ivd_ctl_flush_op_t s_set_flush_op;
+
+    s_set_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t);
+    s_set_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_set_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH;
+    s_set_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_set_flush_ip,
+                                                     &s_set_flush_op);
+    if (status != IV_SUCCESS) {
+        ALOGE("error in %s: 0x%x", __func__, s_set_flush_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2SoftHevcDec::resetDecoder() {
+    ivd_ctl_reset_ip_t s_reset_ip;
+    ivd_ctl_reset_op_t s_reset_op;
+
+    s_reset_ip.u4_size = sizeof(ivd_ctl_reset_ip_t);
+    s_reset_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_reset_ip.e_sub_cmd = IVD_CMD_CTL_RESET;
+    s_reset_op.u4_size = sizeof(ivd_ctl_reset_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_reset_ip,
+                                                     &s_reset_op);
+    if (IV_SUCCESS != status) {
+        ALOGE("error in %s: 0x%x", __func__, s_reset_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+    mStride = 0;
+    (void) setNumCores();
+    mSignalledError = false;
+
+    return OK;
+}
+
+void C2SoftHevcDec::resetPlugin() {
+    mSignalledOutputEos = false;
+    gettimeofday(&mTimeStart, nullptr);
+    gettimeofday(&mTimeEnd, nullptr);
+}
+
+status_t C2SoftHevcDec::deleteDecoder() {
+    if (mDecHandle) {
+        ivdext_delete_ip_t s_delete_ip;
+        ivdext_delete_op_t s_delete_op;
+
+        s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ivdext_delete_ip_t);
+        s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE;
+        s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ivdext_delete_op_t);
+        IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                         &s_delete_ip,
+                                                         &s_delete_op);
+        if (status != IV_SUCCESS) {
+            ALOGE("error in %s: 0x%x", __func__,
+                  s_delete_op.s_ivd_delete_op_t.u4_error_code);
+            return UNKNOWN_ERROR;
+        }
+        mDecHandle = nullptr;
+    }
+
+    return OK;
+}
+
+void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    uint32_t flags = 0;
+    if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+        flags |= C2FrameData::FLAG_END_OF_STREAM;
+        ALOGV("signalling eos");
+    }
+    work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+void C2SoftHevcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) {
+    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mOutBlock),
+                                                           C2Rect(mWidth, mHeight));
+    mOutBlock = nullptr;
+    auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) {
+        uint32_t flags = 0;
+        if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
+                (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) {
+            flags |= C2FrameData::FLAG_END_OF_STREAM;
+            ALOGV("signalling eos");
+        }
+        work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.buffers.push_back(buffer);
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->workletsProcessed = 1u;
+    };
+    if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
+        fillWork(work);
+    } else {
+        finish(index, fillWork);
+    }
+}
+
+c2_status_t C2SoftHevcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) {
+    if (!mDecHandle) {
+        ALOGE("not supposed to be here, invalid decoder context");
+        return C2_CORRUPTED;
+    }
+    if (mStride != ALIGN64(mWidth)) {
+        mStride = ALIGN64(mWidth);
+        if (OK != setParams(mStride)) return C2_CORRUPTED;
+    }
+    if (mOutBlock &&
+            (mOutBlock->width() != mStride || mOutBlock->height() != mHeight)) {
+        mOutBlock.reset();
+    }
+    if (!mOutBlock) {
+        uint32_t format = HAL_PIXEL_FORMAT_YV12;
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        c2_status_t err = pool->fetchGraphicBlock(mStride, mHeight, format, usage, &mOutBlock);
+        if (err != C2_OK) {
+            ALOGE("fetchGraphicBlock for Output failed with status %d", err);
+            return err;
+        }
+        ALOGV("provided (%dx%d) required (%dx%d)",
+              mOutBlock->width(), mOutBlock->height(), mStride, mHeight);
+    }
+
+    return C2_OK;
+}
+
+// TODO: can overall error checking be improved?
+// TODO: allow configuration of color format and usage for graphic buffers instead
+//       of hard coding them to HAL_PIXEL_FORMAT_YV12
+// TODO: pass coloraspects information to surface
+// TODO: test support for dynamic change in resolution
+// TODO: verify if the decoder sent back all frames
+void C2SoftHevcDec::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = rView.error();
+        return;
+    }
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    bool hasPicture = false;
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+          inSize, (int)work->input.ordinal.timestamp.peeku(),
+          (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+    size_t inPos = 0;
+    while (inPos < inSize) {
+        if (C2_OK != ensureDecoderState(pool)) {
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return;
+        }
+        C2GraphicView wView = mOutBlock->map().get();
+        if (wView.error()) {
+            ALOGE("graphic view map failed %d", wView.error());
+            work->result = wView.error();
+            return;
+        }
+        ivd_video_decode_ip_t s_decode_ip;
+        ivd_video_decode_op_t s_decode_op;
+        if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView,
+                           inOffset + inPos, inSize - inPos, workIndex)) {
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return;
+        }
+        WORD32 delay;
+        GETTIME(&mTimeStart, NULL);
+        TIME_DIFF(mTimeEnd, mTimeStart, delay);
+        (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+        WORD32 decodeTime;
+        GETTIME(&mTimeEnd, nullptr);
+        TIME_DIFF(mTimeStart, mTimeEnd, decodeTime);
+        ALOGV("decodeTime=%6d delay=%6d numBytes=%6d", decodeTime, delay,
+              s_decode_op.u4_num_bytes_consumed);
+        if (IVD_MEM_ALLOC_FAILED == (s_decode_op.u4_error_code & 0xFF)) {
+            ALOGE("allocation failure in decoder");
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        } else if (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_decode_op.u4_error_code & 0xFF)) {
+            ALOGE("unsupported resolution : %dx%d", mWidth, mHeight);
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & 0xFF)) {
+            ALOGV("resolution changed");
+            drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work);
+            resetDecoder();
+            resetPlugin();
+            continue;
+        }
+        if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) {
+            if (s_decode_op.u4_pic_wd != mWidth ||  s_decode_op.u4_pic_ht != mHeight) {
+                mWidth = s_decode_op.u4_pic_wd;
+                mHeight = s_decode_op.u4_pic_ht;
+                CHECK_EQ(0u, s_decode_op.u4_output_present);
+            }
+        }
+        (void) getVuiParams();
+        if (mUpdateColorAspects) {
+            mUpdateColorAspects = false;
+        }
+        hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag);
+        if (s_decode_op.u4_output_present) {
+            finishWork(s_decode_op.u4_ts, work);
+        }
+        inPos += s_decode_op.u4_num_bytes_consumed;
+        if (hasPicture && (inSize - inPos)) {
+            ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d",
+                  (int)inSize - (int)inPos);
+            break;
+        }
+    }
+
+    if (eos) {
+        drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
+        mSignalledOutputEos = true;
+    } else if (!hasPicture) {
+        fillEmptyWork(work);
+    }
+}
+
+c2_status_t C2SoftHevcDec::drainInternal(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool,
+        const std::unique_ptr<C2Work> &work) {
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    if (OK != setFlushMode()) return C2_CORRUPTED;
+    while (true) {
+        if (C2_OK != ensureDecoderState(pool)) {
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return C2_CORRUPTED;
+        }
+        C2GraphicView wView = mOutBlock->map().get();
+        if (wView.error()) {
+            ALOGE("graphic view map failed %d", wView.error());
+            return C2_CORRUPTED;
+        }
+        ivd_video_decode_ip_t s_decode_ip;
+        ivd_video_decode_op_t s_decode_op;
+        if (!setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, &wView, 0, 0, 0)) {
+            mSignalledError = true;
+            return C2_CORRUPTED;
+        }
+        (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+        if (s_decode_op.u4_output_present) {
+            finishWork(s_decode_op.u4_ts, work);
+        } else {
+            break;
+        }
+    }
+
+    if (drainMode == DRAIN_COMPONENT_WITH_EOS &&
+            work && work->workletsProcessed == 0u) {
+        fillEmptyWork(work);
+    }
+
+    return C2_OK;
+}
+
+c2_status_t C2SoftHevcDec::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    return drainInternal(drainMode, pool, nullptr);
+}
+
+class C2SoftHevcDecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftHevcDec(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftHevcDecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftHevcDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.h b/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.h
new file mode 100644
index 0000000..28a5a78
--- /dev/null
+++ b/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C2_SOFT_HEVC_DEC_H_
+#define C2_SOFT_HEVC_DEC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/ColorUtils.h>
+
+namespace android {
+
+#define ivdec_api_function              ihevcd_cxa_api_function
+#define ivdext_create_ip_t              ihevcd_cxa_create_ip_t
+#define ivdext_create_op_t              ihevcd_cxa_create_op_t
+#define ivdext_delete_ip_t              ihevcd_cxa_delete_ip_t
+#define ivdext_delete_op_t              ihevcd_cxa_delete_op_t
+#define ivdext_ctl_set_num_cores_ip_t   ihevcd_cxa_ctl_set_num_cores_ip_t
+#define ivdext_ctl_set_num_cores_op_t   ihevcd_cxa_ctl_set_num_cores_op_t
+#define ivdext_ctl_get_vui_params_ip_t  ihevcd_cxa_ctl_get_vui_params_ip_t
+#define ivdext_ctl_get_vui_params_op_t  ihevcd_cxa_ctl_get_vui_params_op_t
+#define ALIGN64(x)                      ((((x) + 63) >> 6) << 6)
+#define MAX_NUM_CORES                   4
+#define IVDEXT_CMD_CTL_SET_NUM_CORES    \
+        (IVD_CONTROL_API_COMMAND_TYPE_T)IHEVCD_CXA_CMD_CTL_SET_NUM_CORES
+#define MIN(a, b)                       (((a) < (b)) ? (a) : (b))
+#define GETTIME(a, b)                   gettimeofday(a, b);
+#define TIME_DIFF(start, end, diff)     \
+    diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \
+            ((end).tv_usec - (start).tv_usec);
+
+
+struct C2SoftHevcDec : public SimpleC2Component {
+    C2SoftHevcDec(const char *name, c2_node_id_t id);
+    virtual ~C2SoftHevcDec();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+ private:
+    status_t createDecoder();
+    status_t setNumCores();
+    status_t setParams(size_t stride);
+    status_t getVersion();
+    status_t initDecoder();
+    bool setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip,
+                       ivd_video_decode_op_t *ps_decode_op,
+                       C2ReadView *inBuffer,
+                       C2GraphicView *outBuffer,
+                       size_t inOffset,
+                       size_t inSize,
+                       uint32_t tsMarker);
+    bool getVuiParams();
+    // TODO:This is not the right place for colorAspects functions. These should
+    // be part of c2-vndk so that they can be accessed by all video plugins
+    // until then, make them feel at home
+    bool colorAspectsDiffer(const ColorAspects &a, const ColorAspects &b);
+    void updateFinalColorAspects(
+            const ColorAspects &otherAspects, const ColorAspects &preferredAspects);
+    status_t handleColorAspectsChange();
+    c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool);
+    void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work);
+    status_t setFlushMode();
+    c2_status_t drainInternal(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool,
+            const std::unique_ptr<C2Work> &work);
+    status_t resetDecoder();
+    void resetPlugin();
+    status_t deleteDecoder();
+
+    // TODO:This is not the right place for this enum. These should
+    // be part of c2-vndk so that they can be accessed by all video plugins
+    // until then, make them feel at home
+    enum {
+        kNotSupported,
+        kPreferBitstream,
+        kPreferContainer,
+    };
+
+    iv_obj_t *mDecHandle;
+    std::shared_ptr<C2GraphicBlock> mOutBlock;
+    uint8_t *mOutBufferFlush;
+
+    size_t mNumCores;
+    IV_COLOR_FORMAT_T mIvColorformat;
+
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mStride;
+    bool mSignalledOutputEos;
+    bool mSignalledError;
+
+    // ColorAspects
+    Mutex mColorAspectsLock;
+    int mPreference;
+    ColorAspects mDefaultColorAspects;
+    ColorAspects mBitstreamColorAspects;
+    ColorAspects mFinalColorAspects;
+    bool mUpdateColorAspects;
+
+    // profile
+    struct timeval mTimeStart;
+    struct timeval mTimeEnd;
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftHevcDec);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_HEVC_DEC_H_
diff --git a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
index 641c342..eaef532 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
@@ -57,7 +57,8 @@
 
 C2SoftMpeg4Dec::C2SoftMpeg4Dec(const char *name, c2_node_id_t id)
     : SimpleC2Component(BuildIntf(name, id)),
-      mDecHandle(nullptr) {
+      mDecHandle(nullptr),
+      mOutputBuffer{} {
 }
 
 C2SoftMpeg4Dec::~C2SoftMpeg4Dec() {
@@ -95,6 +96,7 @@
 void C2SoftMpeg4Dec::onRelease() {
     if (mInitialized) {
         PVCleanUpVideoDecoder(mDecHandle);
+        mInitialized = false;
     }
     if (mOutBlock) {
         mOutBlock.reset();
@@ -130,10 +132,6 @@
     }
     memset(mDecHandle, 0, sizeof(tagvideoDecControls));
 
-    for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
-        mOutputBuffer[i] = nullptr;
-    }
-
     /* TODO: bring these values to 352 and 288. It cannot be done as of now
      * because, h263 doesn't seem to allow port reconfiguration. In OMX, the
      * problem of larger width and height than default width and height is
@@ -371,7 +369,8 @@
         }
     }
 
-    while (inOffset < inSize) {
+    size_t inPos = 0;
+    while (inPos < inSize) {
         c2_status_t err = ensureDecoderState(pool);
         if (C2_OK != err) {
             mSignalledError = true;
@@ -401,7 +400,7 @@
 
         // Need to check if header contains new info, e.g., width/height, etc.
         VopHeaderInfo header_info;
-        uint32_t useExtTimestamp = (inOffset == 0);
+        uint32_t useExtTimestamp = (inPos == 0);
         int32_t tmpInSize = (int32_t)inSize;
         uint8_t *bitstreamTmp = bitstream;
         uint32_t timestamp = workIndex;
@@ -442,12 +441,12 @@
         (void)copyOutputBufferToYV12Frame(outputBufferY, mOutputBuffer[mNumSamplesOutput & 1],
                                           wView.width(), align(mWidth, 16), mWidth, mHeight);
 
-        inOffset += inSize - (size_t)tmpInSize;
+        inPos += inSize - (size_t)tmpInSize;
         finishWork(workIndex, work);
         ++mNumSamplesOutput;
-        if (inSize - inOffset) {
-            ALOGD("decoded frame, ignoring further trailing bytes %zu",
-                   inSize - (size_t)tmpInSize);
+        if (inSize - inPos != 0) {
+            ALOGD("decoded frame, ignoring further trailing bytes %d",
+                  (int)inSize - (int)inPos);
             break;
         }
     }
diff --git a/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
index 0a8891e..51b9656 100644
--- a/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
+++ b/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
@@ -32,7 +32,7 @@
 
 namespace android {
 
-constexpr char kComponentName[] = "c2.google.aac.encoder";
+constexpr char kComponentName[] = "c2.google.mp3.decoder";
 
 static std::shared_ptr<C2ComponentInterface> BuildIntf(
         const char *name, c2_node_id_t id,
@@ -68,6 +68,8 @@
     mSignalledError = false;
     mIsFirst = true;
     mSignalledOutputEos = false;
+    mAnchorTimeStamp = 0;
+    mProcessedSamples = 0;
 
     return C2_OK;
 }
@@ -105,6 +107,8 @@
     mIsFirst = true;
     mSignalledError = false;
     mSignalledOutputEos = false;
+    mAnchorTimeStamp = 0;
+    mProcessedSamples = 0;
 
     return OK;
 }
@@ -269,11 +273,6 @@
 
 // TODO: Can overall error checking be improved? As in the check for validity of
 //       work, pool ptr, work->input.buffers.size() == 1, ...
-// TODO: Gapless playback: decoder has a delay of 529 samples. For the first
-//       frame we intend to remove 529 samples worth of data. When this is
-//       done it is going to effect the timestamps of future frames. This
-//       timestamp correction is handled by the client or plugin? Soft omx mp3
-//       plugin also has this problem
 // TODO: Blind removal of 529 samples from the output may not work. Because
 //       mpeg layer 1 frame size is 384 samples per frame. This should introduce
 //       negative values and can cause SEG faults. Soft omx mp3 plugin can have
@@ -292,10 +291,10 @@
     bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
     size_t inOffset = inBuffer.offset();
     size_t inSize = inBuffer.size();
-    C2ReadView rView = inBuffer.map().get();
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
     if (inSize && rView.error()) {
         ALOGE("read view map failed %d", rView.error());
-        work->result = C2_CORRUPTED;
+        work->result = rView.error();
         return;
     }
 
@@ -315,8 +314,8 @@
 
     size_t calOutSize;
     std::vector<size_t> decodedSizes;
-    if (OK != calculateOutSize(const_cast<uint8 *>(rView.data() + inOffset),
-                               inSize, &decodedSizes)) {
+    const uint8_t *inPtr = rView.data() + inOffset;
+    if (OK != calculateOutSize(const_cast<uint8 *>(inPtr), inSize, &decodedSizes)) {
         work->result = C2_CORRUPTED;
         return;
     }
@@ -332,21 +331,22 @@
     C2WriteView wView = block->map().get();
     if (wView.error()) {
         ALOGE("write view map failed %d", wView.error());
-        work->result = C2_CORRUPTED;
+        work->result = wView.error();
         return;
     }
 
     int outSize = 0;
     int outOffset = 0;
     auto it = decodedSizes.begin();
-    while (inOffset < inSize) {
+    size_t inPos = 0;
+    while (inPos < inSize) {
         if (it == decodedSizes.end()) {
             ALOGE("unexpected trailing bytes, ignoring them");
             break;
         }
 
-        mConfig->pInputBuffer = const_cast<uint8 *>(rView.data() + inOffset);
-        mConfig->inputBufferCurrentLength = (inSize - inOffset);
+        mConfig->pInputBuffer = const_cast<uint8 *>(inPtr + inPos);
+        mConfig->inputBufferCurrentLength = (inSize - inPos);
         mConfig->inputBufferMaxLength = 0;
         mConfig->inputBufferUsedLength = 0;
         mConfig->outputFrameSize = (calOutSize - outSize);
@@ -382,7 +382,7 @@
             return;
         }
         outSize += mConfig->outputFrameSize * sizeof(int16_t);
-        inOffset += mConfig->inputBufferUsedLength;
+        inPos += mConfig->inputBufferUsedLength;
         it++;
     }
     if (mIsFirst) {
@@ -391,13 +391,19 @@
         // the start of the first output buffer. This essentially makes this
         // decoder have zero delay, which the rest of the pipeline assumes.
         outOffset = kPVMP3DecoderDelay * mNumChannels * sizeof(int16_t);
+        mAnchorTimeStamp = work->input.ordinal.timestamp.peekull();
     }
-    ALOGV("out buffer attr. offset %d size %d", outOffset, outSize);
+    uint64_t outTimeStamp = mProcessedSamples * 1000000ll / mSamplingRate;
+    mProcessedSamples += ((outSize - outOffset) / (mNumChannels * sizeof(int16_t)));
+    ALOGV("out buffer attr. offset %d size %d timestamp %u", outOffset, outSize - outOffset,
+          (uint32_t)(mAnchorTimeStamp + outTimeStamp));
     decodedSizes.clear();
     work->worklets.front()->output.flags = work->input.flags;
     work->worklets.front()->output.buffers.clear();
-    work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, outOffset, outSize));
+    work->worklets.front()->output.buffers.push_back(
+            createLinearBuffer(block, outOffset, outSize - outOffset));
     work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp;
     work->workletsProcessed = 1u;
     if (eos) {
         mSignalledOutputEos = true;
diff --git a/media/libstagefright/codecs/mp3dec/C2SoftMP3.h b/media/libstagefright/codecs/mp3dec/C2SoftMP3.h
index ad974bd..6e9a571 100644
--- a/media/libstagefright/codecs/mp3dec/C2SoftMP3.h
+++ b/media/libstagefright/codecs/mp3dec/C2SoftMP3.h
@@ -61,6 +61,8 @@
     bool mIsFirst;
     bool mSignalledError;
     bool mSignalledOutputEos;
+    uint64_t mAnchorTimeStamp;
+    uint64_t mProcessedSamples;
 
     status_t initDecoder();
 
diff --git a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
index 74ea340..f8008aa 100644
--- a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
+++ b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
@@ -352,7 +352,7 @@
     if (inBuffer) {
         ps_decode_ip->u4_ts = tsMarker;
         ps_decode_ip->pv_stream_buffer = const_cast<uint8_t *>(inBuffer->data() + inOffset);
-        ps_decode_ip->u4_num_Bytes = inSize - inOffset;
+        ps_decode_ip->u4_num_Bytes = inSize;
     } else {
         ps_decode_ip->u4_ts = 0;
         ps_decode_ip->pv_stream_buffer = nullptr;
@@ -630,7 +630,8 @@
     ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
           inSize, (int)work->input.ordinal.timestamp.peeku(),
           (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
-    while (inOffset < inSize) {
+    size_t inPos = 0;
+    while (inPos < inSize) {
         if (C2_OK != ensureDecoderState(pool)) {
             mSignalledError = true;
             work->result = C2_CORRUPTED;
@@ -646,7 +647,7 @@
         ivd_video_decode_ip_t s_decode_ip;
         ivd_video_decode_op_t s_decode_op;
         if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView,
-                           inOffset, inSize, workIndex)) {
+                           inOffset + inPos, inSize - inPos, workIndex)) {
             mSignalledError = true;
             work->result = C2_CORRUPTED;
             return;
@@ -693,10 +694,10 @@
         if (s_decode_op.u4_output_present) {
             finishWork(s_decode_op.u4_ts, work);
         }
-        inOffset += s_decode_op.u4_num_bytes_consumed;
-        if (hasPicture && (inSize - inOffset)) {
+        inPos += s_decode_op.u4_num_bytes_consumed;
+        if (hasPicture && (inSize - inPos) != 0) {
             ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d",
-                  (int)inSize - (int)inOffset);
+                  (int)inSize - (int)inPos);
             break;
         }
     }
diff --git a/media/libstagefright/codecs/raw/Android.bp b/media/libstagefright/codecs/raw/Android.bp
index c8d7d00..c431c18 100644
--- a/media/libstagefright/codecs/raw/Android.bp
+++ b/media/libstagefright/codecs/raw/Android.bp
@@ -35,3 +35,38 @@
     ],
     compile_multilib: "32",
 }
+
+cc_library_shared {
+    name: "libstagefright_soft_c2rawdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftRaw.cpp",],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+}
diff --git a/media/libstagefright/codecs/raw/C2SoftRaw.cpp b/media/libstagefright/codecs/raw/C2SoftRaw.cpp
new file mode 100644
index 0000000..91b920d
--- /dev/null
+++ b/media/libstagefright/codecs/raw/C2SoftRaw.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "C2SoftRAW"
+#include <utils/Log.h>
+
+#include "C2SoftRaw.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.raw.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatAudio)
+            .inputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .build();
+}
+
+C2SoftRaw::C2SoftRaw(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)) {
+}
+
+C2SoftRaw::~C2SoftRaw() {
+    onRelease();
+}
+
+c2_status_t C2SoftRaw::onInit() {
+    mSignalledEos = false;
+    return C2_OK;
+}
+
+c2_status_t C2SoftRaw::onStop() {
+    mSignalledEos = false;
+    return C2_OK;
+}
+
+void C2SoftRaw::onReset() {
+    (void)onStop();
+}
+
+void C2SoftRaw::onRelease() {
+}
+
+c2_status_t C2SoftRaw::onFlush_sm() {
+    return onStop();
+}
+
+void C2SoftRaw::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void)pool;
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    size_t inSize = inBuffer.size();
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+          (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    if (inSize != 0) {
+        work->worklets.front()->output.buffers.push_back(work->input.buffers[0]);
+    }
+    work->workletsProcessed = 1u;
+    if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+        mSignalledEos = true;
+        ALOGV("signalled EOS");
+    }
+}
+
+c2_status_t C2SoftRaw::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    return C2_OK;
+}
+
+class C2SoftRawDecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftRaw(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftRawDecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftRawDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/raw/C2SoftRaw.h b/media/libstagefright/codecs/raw/C2SoftRaw.h
new file mode 100644
index 0000000..f7d2cfd
--- /dev/null
+++ b/media/libstagefright/codecs/raw/C2SoftRaw.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C2_SOFT_RAW_H_
+#define C2_SOFT_RAW_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct C2SoftRaw : public SimpleC2Component {
+    C2SoftRaw(const char *name, c2_node_id_t id);
+    virtual ~C2SoftRaw();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+private:
+    bool mSignalledEos;
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftRaw);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_RAW_H_
diff --git a/media/libstagefright/codecs/vorbis/dec/Android.bp b/media/libstagefright/codecs/vorbis/dec/Android.bp
index a9265cb..78dd61b 100644
--- a/media/libstagefright/codecs/vorbis/dec/Android.bp
+++ b/media/libstagefright/codecs/vorbis/dec/Android.bp
@@ -1,4 +1,40 @@
 cc_library_shared {
+    name: "libstagefright_soft_c2vorbisdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftVorbis.cpp"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+        "libvorbisidec",
+    ],
+}
+
+cc_library_shared {
     name: "libstagefright_soft_vorbisdec",
     vendor_available: true,
     vndk: {
diff --git a/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.cpp b/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.cpp
new file mode 100644
index 0000000..8342725
--- /dev/null
+++ b/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "C2SoftVorbis"
+#include <utils/Log.h>
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+#include "C2SoftVorbis.h"
+
+extern "C" {
+    #include <Tremolo/codec_internal.h>
+
+    int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb);
+    int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb);
+    int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb);
+}
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.vorbis.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatAudio)
+            .inputMediaType(MEDIA_MIMETYPE_AUDIO_VORBIS)
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .build();
+}
+
+C2SoftVorbis::C2SoftVorbis(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mState(nullptr),
+      mVi(nullptr) {
+}
+
+C2SoftVorbis::~C2SoftVorbis() {
+    onRelease();
+}
+
+c2_status_t C2SoftVorbis::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+c2_status_t C2SoftVorbis::onStop() {
+    if (mState) vorbis_dsp_clear(mState);
+    if (mVi) vorbis_info_clear(mVi);
+    mNumFramesLeftOnPage = -1;
+    mNumChannels = 1;
+    mSamplingRate = 48000;
+    mInputBufferCount = 0;
+    mSignalledOutputEos = false;
+    mSignalledError = false;
+
+    return C2_OK;
+}
+
+void C2SoftVorbis::onReset() {
+    (void)onStop();
+}
+
+void C2SoftVorbis::onRelease() {
+    if (mState) {
+        vorbis_dsp_clear(mState);
+        delete mState;
+        mState = nullptr;
+    }
+
+    if (mVi) {
+        vorbis_info_clear(mVi);
+        delete mVi;
+        mVi = nullptr;
+    }
+}
+
+status_t C2SoftVorbis::initDecoder() {
+    mVi = new vorbis_info{};
+    if (!mVi) return NO_MEMORY;
+    vorbis_info_clear(mVi);
+
+    mState = new vorbis_dsp_state{};
+    if (!mState) return NO_MEMORY;
+    vorbis_dsp_clear(mState);
+
+    mNumFramesLeftOnPage = -1;
+    mNumChannels = 1;
+    mSamplingRate = 48000;
+    mInputBufferCount = 0;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+
+    return OK;
+}
+
+c2_status_t C2SoftVorbis::onFlush_sm() {
+    mNumFramesLeftOnPage = -1;
+    mSignalledOutputEos = false;
+    if (mState) vorbis_dsp_restart(mState);
+
+    return C2_OK;
+}
+
+c2_status_t C2SoftVorbis::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    return C2_OK;
+}
+
+static void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+static void makeBitReader(
+        const void *data, size_t size,
+        ogg_buffer *buf, ogg_reference *ref, oggpack_buffer *bits) {
+    buf->data = (uint8_t *)data;
+    buf->size = size;
+    buf->refcount = 1;
+    buf->ptr.owner = nullptr;
+
+    ref->buffer = buf;
+    ref->begin = 0;
+    ref->length = size;
+    ref->next = nullptr;
+
+    oggpack_readinit(bits, ref);
+}
+
+// (CHECK!) multiframe is tricky. decode call doesnt return the number of bytes
+// consumed by the component. Also it is unclear why numPageFrames is being
+// tagged at the end of input buffers for new pages. Refer lines 297-300 in
+// SimpleDecodingSource.cpp
+void C2SoftVorbis::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    C2ReadView rView = inBuffer.map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = rView.error();
+        return;
+    }
+
+    if (inSize == 0) {
+        fillEmptyWork(work);
+        if (eos) {
+            mSignalledOutputEos = true;
+            ALOGV("signalled EOS");
+        }
+        return;
+    }
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+          (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+    const uint8_t *data = rView.data() + inOffset;
+    if (mInputBufferCount < 2) {
+        if (inSize < 7 || memcmp(&data[1], "vorbis", 6)) {
+            ALOGE("unexpected first 7 bytes in CSD");
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return;
+        }
+
+        ogg_buffer buf;
+        ogg_reference ref;
+        oggpack_buffer bits;
+
+        // skip 7 <type + "vorbis"> bytes
+        makeBitReader((const uint8_t *)data + 7, inSize - 7, &buf, &ref, &bits);
+        if (mInputBufferCount == 0) {
+            if (data[0] != 1) {
+                ALOGE("unexpected type received %d", data[0]);
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+            vorbis_info_init(mVi);
+            if (0 != _vorbis_unpack_info(mVi, &bits)) {
+                ALOGE("Encountered error while unpacking info");
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+            if (mVi->rate != mSamplingRate ||
+                    mVi->channels != mNumChannels) {
+                ALOGV("vorbis: rate/channels changed: %ld/%d", mVi->rate, mVi->channels);
+                mSamplingRate = mVi->rate;
+                mNumChannels = mVi->channels;
+            }
+        } else {
+            if (data[0] != 5) {
+                ALOGE("unexpected type received %d", data[0]);
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+            if (0 != _vorbis_unpack_books(mVi, &bits)) {
+                ALOGE("Encountered error while unpacking books");
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+            if (0 != vorbis_dsp_init(mState, mVi)) {
+                ALOGE("Encountered error while dsp init");
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+        }
+        ++mInputBufferCount;
+        fillEmptyWork(work);
+        if (eos) {
+            mSignalledOutputEos = true;
+            ALOGV("signalled EOS");
+        }
+        return;
+    }
+    int32_t numPageFrames = 0;
+    if (inSize < sizeof(numPageFrames)) {
+        ALOGE("input header has size %zu, expected %zu", inSize, sizeof(numPageFrames));
+        mSignalledError = true;
+        work->result = C2_CORRUPTED;
+        return;
+    }
+    memcpy(&numPageFrames, data + inSize - sizeof(numPageFrames), sizeof(numPageFrames));
+    inSize -= sizeof(numPageFrames);
+    if (numPageFrames >= 0) {
+        mNumFramesLeftOnPage = numPageFrames;
+    }
+
+    ogg_buffer buf;
+    buf.data = const_cast<unsigned char*>(data);
+    buf.size = inSize;
+    buf.refcount = 1;
+    buf.ptr.owner = nullptr;
+
+    ogg_reference ref;
+    ref.buffer = &buf;
+    ref.begin = 0;
+    ref.length = buf.size;
+    ref.next = nullptr;
+
+    ogg_packet pack;
+    pack.packet = &ref;
+    pack.bytes = ref.length;
+    pack.b_o_s = 0;
+    pack.e_o_s = 0;
+    pack.granulepos = 0;
+    pack.packetno = 0;
+
+    size_t maxSamplesInBuffer = kMaxNumSamplesPerChannel * mVi->channels;
+    size_t outCapacity =  maxSamplesInBuffer * sizeof(int16_t);
+    std::shared_ptr<C2LinearBlock> block;
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &block);
+    if (err != C2_OK) {
+        ALOGE("fetchLinearBlock for Output failed with status %d", err);
+        work->result = C2_NO_MEMORY;
+        return;
+    }
+    C2WriteView wView = block->map().get();
+    if (wView.error()) {
+        ALOGE("write view map failed %d", wView.error());
+        work->result = wView.error();
+        return;
+    }
+
+    int numFrames = 0;
+    int ret = vorbis_dsp_synthesis(mState, &pack, 1);
+    if (0 != ret) {
+        ALOGE("vorbis_dsp_synthesis returned %d", ret);
+        mSignalledError = true;
+        work->result = C2_CORRUPTED;
+        return;
+    } else {
+        numFrames = vorbis_dsp_pcmout(
+                mState,  reinterpret_cast<int16_t *> (wView.data()),
+                kMaxNumSamplesPerChannel);
+        if (numFrames < 0) {
+            ALOGD("vorbis_dsp_pcmout returned %d", numFrames);
+            numFrames = 0;
+        }
+    }
+
+    if (mNumFramesLeftOnPage >= 0) {
+        if (numFrames > mNumFramesLeftOnPage) {
+            ALOGV("discarding %d frames at end of page", numFrames - mNumFramesLeftOnPage);
+            numFrames = mNumFramesLeftOnPage;
+        }
+        mNumFramesLeftOnPage -= numFrames;
+    }
+
+    if (numFrames) {
+        int outSize = numFrames * sizeof(int16_t) * mVi->channels;
+
+        work->worklets.front()->output.flags = work->input.flags;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, 0, outSize));
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->workletsProcessed = 1u;
+    } else {
+        fillEmptyWork(work);
+        block.reset();
+    }
+    if (eos) {
+        mSignalledOutputEos = true;
+        ALOGV("signalled EOS");
+    }
+}
+
+class C2SoftVorbisDecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftVorbis(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftVorbisDecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftVorbisDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.h b/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.h
new file mode 100644
index 0000000..340c235
--- /dev/null
+++ b/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C2_SOFT_VORBIS_H_
+#define C2_SOFT_VORBIS_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+struct vorbis_dsp_state;
+struct vorbis_info;
+
+namespace android {
+
+struct C2SoftVorbis : public SimpleC2Component {
+    C2SoftVorbis(const char *name, c2_node_id_t id);
+    virtual ~C2SoftVorbis();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+
+ private:
+    enum {
+        kMaxNumSamplesPerChannel = 8192,
+    };
+
+    vorbis_dsp_state *mState;
+    vorbis_info *mVi;
+
+    int32_t mNumFramesLeftOnPage;
+    int32_t mNumChannels;
+    int32_t mSamplingRate;
+    size_t mInputBufferCount;
+    bool mSignalledError;
+    bool mSignalledOutputEos;
+
+    status_t initDecoder();
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftVorbis);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_VORBIS_H_
+
diff --git a/media/libstagefright/flac/dec/FLACDecoder.cpp b/media/libstagefright/flac/dec/FLACDecoder.cpp
index 8c7137c..e0e9211 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.cpp
+++ b/media/libstagefright/flac/dec/FLACDecoder.cpp
@@ -220,9 +220,10 @@
 }
 
 // static
-sp<FLACDecoder> FLACDecoder::Create() {
-    sp<FLACDecoder> decoder = new FLACDecoder();
-    if (decoder->init() != OK) {
+FLACDecoder *FLACDecoder::Create() {
+    FLACDecoder *decoder = new (std::nothrow) FLACDecoder();
+    if (decoder == NULL || decoder->init() != OK) {
+        delete decoder;
         return NULL;
     }
     return decoder;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.h b/media/libstagefright/flac/dec/FLACDecoder.h
index 36282a8..1a33cae 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.h
+++ b/media/libstagefright/flac/dec/FLACDecoder.h
@@ -26,14 +26,14 @@
 namespace android {
 
 // packet based FLAC decoder, wrapps libFLAC stream decoder.
-class FLACDecoder : public RefBase {
+class FLACDecoder {
 
 public:
     enum {
         kMaxChannels = 8,
     };
 
-    static sp<FLACDecoder> Create();
+    static FLACDecoder *Create();
 
     FLAC__StreamMetadata_StreamInfo getStreamInfo() const {
         return mStreamInfo;
@@ -43,10 +43,10 @@
     status_t decodeOneFrame(const uint8_t *inBuffer, size_t inBufferLen,
             short *outBuffer, size_t *outBufferLen);
     void flush();
+    virtual ~FLACDecoder();
 
 protected:
     FLACDecoder();
-    virtual ~FLACDecoder() override;
 
 private:
     // stream properties
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index 51eee10..c8618db 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -162,6 +162,7 @@
     };
 
     void feedInputBufferIfAvailable();
+    status_t queueInputBufferInternal(const sp<MediaCodecBuffer> &buffer);
 
     QueueSync mSync;
     sp<MemoryDealer> mDealer;
@@ -180,7 +181,13 @@
     std::atomic_uint64_t mFirstValidFrameIndex;
 
     sp<MemoryDealer> makeMemoryDealer(size_t heapSize);
-    Mutexed<sp<Surface>> mSurface;
+
+    struct OutputSurface {
+        sp<Surface> surface;
+        std::list<std::shared_ptr<C2Buffer>> bufferRefs;
+        size_t maxBufferCount;
+    };
+    Mutexed<OutputSurface> mOutputSurface;
 
     std::shared_ptr<InputSurfaceWrapper> mInputSurface;
 
diff --git a/media/libstagefright/include/Codec2Buffer.h b/media/libstagefright/include/Codec2Buffer.h
index eeb889d..b2e3b1b 100644
--- a/media/libstagefright/include/Codec2Buffer.h
+++ b/media/libstagefright/include/Codec2Buffer.h
@@ -20,8 +20,11 @@
 
 #include <C2Buffer.h>
 
+#include <android/hardware/cas/native/1.0/types.h>
+#include <binder/IMemory.h>
 #include <media/hardware/VideoAPI.h>
 #include <media/MediaCodecBuffer.h>
+#include <media/ICrypto.h>
 
 namespace android {
 
@@ -271,6 +274,79 @@
     const bool mWrapped;
 };
 
+/**
+ * MediaCodecBuffer implementation wraps around C2LinearBlock for component
+ * and IMemory for client. Underlying C2LinearBlock won't be mapped for secure
+ * usecases..
+ */
+class EncryptedLinearBlockBuffer : public Codec2Buffer {
+public:
+    /**
+     * Construct a new EncryptedLinearBufferBlock wrapping around C2LinearBlock
+     * object and writable IMemory region.
+     *
+     * \param   format      mandatory buffer format for MediaCodecBuffer
+     * \param   block       C2LinearBlock object to wrap around.
+     * \param   memory      IMemory object to store encrypted content.
+     * \param   heapSeqNum  Heap sequence number from ICrypto; -1 if N/A
+     */
+    EncryptedLinearBlockBuffer(
+            const sp<AMessage> &format,
+            const std::shared_ptr<C2LinearBlock> &block,
+            const sp<IMemory> &memory,
+            int32_t heapSeqNum = -1);
+    EncryptedLinearBlockBuffer() = delete;
+
+    virtual ~EncryptedLinearBlockBuffer() = default;
+
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+
+    /**
+     * Fill the source buffer structure with appropriate value based on
+     * internal IMemory object.
+     *
+     * \param source  source buffer structure to fill.
+     */
+    void fillSourceBuffer(ICrypto::SourceBuffer *source);
+    void fillSourceBuffer(
+            hardware::cas::native::V1_0::SharedBuffer *source);
+
+    /**
+     * Copy the content of |decrypted| into C2LinearBlock inside. This shall
+     * only be called in non-secure usecases.
+     *
+     * \param   decrypted   decrypted content to copy from.
+     * \param   length      length of the content
+     * \return  true        if successful
+     *          false       otherwise.
+     */
+    bool copyDecryptedContent(const sp<IMemory> &decrypted, size_t length);
+
+    /**
+     * Copy the content of internal IMemory object into C2LinearBlock inside.
+     * This shall only be called in non-secure usecases.
+     *
+     * \param   length      length of the content
+     * \return  true        if successful
+     *          false       otherwise.
+     */
+    bool copyDecryptedContentFromMemory(size_t length);
+
+    /**
+     * Return native handle of secure buffer understood by ICrypto.
+     *
+     * \return secure buffer handle
+     */
+    native_handle_t *handle() const;
+
+private:
+
+    std::shared_ptr<C2LinearBlock> mBlock;
+    sp<IMemory> mMemory;
+    sp<hardware::HidlMemory> mHidlMemory;
+    int32_t mHeapSeqNum;
+};
+
 }  // namespace android
 
 #endif  // CODEC2_BUFFER_H_
diff --git a/media/libstagefright/include/media/stagefright/ACodec.h b/media/libstagefright/include/media/stagefright/ACodec.h
index fa22003..1a5304b 100644
--- a/media/libstagefright/include/media/stagefright/ACodec.h
+++ b/media/libstagefright/include/media/stagefright/ACodec.h
@@ -253,6 +253,7 @@
 
     sp<AMessage> mLastOutputFormat;
     bool mIsVideo;
+    bool mIsImage;
     bool mIsEncoder;
     bool mFatalError;
     bool mShutdownInProgress;
@@ -489,11 +490,12 @@
     status_t setupMPEG4EncoderParameters(const sp<AMessage> &msg);
     status_t setupH263EncoderParameters(const sp<AMessage> &msg);
     status_t setupAVCEncoderParameters(const sp<AMessage> &msg);
-    status_t setupHEVCEncoderParameters(const sp<AMessage> &msg);
+    status_t setupHEVCEncoderParameters(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
     status_t setupVPXEncoderParameters(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
 
     status_t verifySupportForProfileAndLevel(int32_t profile, int32_t level);
 
+    status_t configureImageGrid(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
     status_t configureBitrate(
             OMX_VIDEO_CONTROLRATETYPE bitrateMode, int32_t bitrate, int32_t quality = 0);
     void configureEncoderLatency(const sp<AMessage> &msg);
diff --git a/media/libstagefright/include/media/stagefright/CCodec.h b/media/libstagefright/include/media/stagefright/CCodec.h
index 078b03e..86fbd3a 100644
--- a/media/libstagefright/include/media/stagefright/CCodec.h
+++ b/media/libstagefright/include/media/stagefright/CCodec.h
@@ -36,6 +36,7 @@
 
 class CCodecBufferChannel;
 class InputSurfaceWrapper;
+struct MediaCodecInfo;
 
 class CCodec : public CodecBase {
 public:
@@ -74,7 +75,7 @@
     void initiateStop();
     void initiateRelease(bool sendCallback = true);
 
-    void allocate(const AString &componentName);
+    void allocate(const sp<MediaCodecInfo> &codecInfo);
     void configure(const sp<AMessage> &msg);
     void start();
     void stop();
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 2a062cc..7b41362 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -195,6 +195,7 @@
         uint32_t type;
         int32_t width;
         int32_t height;
+        int32_t rotation;
         sp<ABuffer> hvcc;
     } ItemProperty;
 
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index e7faea5..ef8de1f 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -184,6 +184,8 @@
 
     status_t getName(AString *componentName) const;
 
+    status_t getCodecInfo(sp<MediaCodecInfo> *codecInfo) const;
+
     status_t getMetrics(MediaAnalyticsItem * &reply);
 
     status_t setParameters(const sp<AMessage> &params);
@@ -248,6 +250,7 @@
         kWhatRequestIDRFrame                = 'ridr',
         kWhatRequestActivityNotification    = 'racN',
         kWhatGetName                        = 'getN',
+        kWhatGetCodecInfo                   = 'gCoI',
         kWhatSetParameters                  = 'setP',
         kWhatSetCallback                    = 'setC',
         kWhatSetNotification                = 'setN',
@@ -308,6 +311,7 @@
     sp<ALooper> mCodecLooper;
     sp<CodecBase> mCodec;
     AString mComponentName;
+    sp<MediaCodecInfo> mCodecInfo;
     sp<AReplyToken> mReplyID;
     uint32_t mFlags;
     status_t mStickyError;
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecList.h b/media/libstagefright/include/media/stagefright/MediaCodecList.h
index d46fe85..bb4da09 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecList.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecList.h
@@ -74,8 +74,7 @@
             const char *mime,
             bool createEncoder,
             uint32_t flags,
-            Vector<AString> *matchingCodecs,
-            Vector<AString> *owners = nullptr);
+            Vector<AString> *matchingCodecs);
 
     static bool isSoftwareCodec(const AString &componentName);
 
diff --git a/media/libstagefright/include/media/stagefright/MetaDataUtils.h b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
index 3af2218..d5a8080 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataUtils.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
@@ -23,7 +23,7 @@
 namespace android {
 
 struct ABuffer;
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit);
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size);
 bool MakeAACCodecSpecificData(MetaDataBase &meta, unsigned profile, unsigned sampling_freq_index,
         unsigned channel_configuration);
 
diff --git a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
index 5e5ef6e..675c932 100644
--- a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
@@ -64,6 +64,8 @@
 
     status_t setMediaCas(const HInterfaceToken &casToken);
 
+    void disconnect();
+
     size_t countTracks() const;
     status_t getTrackFormat(size_t index, sp<AMessage> *format, uint32_t flags = 0) const;
 
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 090d4e1..0fa9fcb 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -634,7 +634,7 @@
 
         if (mFormat == NULL) {
             mFormat = new MetaData;
-            if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+            if (!MakeAVCCodecSpecificData(*mFormat, accessUnit->data(), accessUnit->size())) {
                 mFormat.clear();
             }
         }
@@ -1010,7 +1010,7 @@
         }
         if (mFormat == NULL) {
             mFormat = new MetaData;
-            if (!MakeAVCCodecSpecificData(*mFormat, mBuffer)) {
+            if (!MakeAVCCodecSpecificData(*mFormat, mBuffer->data(), mBuffer->size())) {
                 ALOGW("Creating dummy AVC format for scrambled content");
                 mFormat = new MetaData;
                 mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
@@ -1172,7 +1172,9 @@
 
             if (mFormat == NULL) {
                 mFormat = new MetaData;
-                if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+                if (!MakeAVCCodecSpecificData(*mFormat,
+                        accessUnit->data(),
+                        accessUnit->size())) {
                     mFormat.clear();
                 }
             }
diff --git a/media/libstagefright/omx/OMXUtils.cpp b/media/libstagefright/omx/OMXUtils.cpp
index f597e02..f7b569d 100644
--- a/media/libstagefright/omx/OMXUtils.cpp
+++ b/media/libstagefright/omx/OMXUtils.cpp
@@ -163,6 +163,8 @@
             "audio_decoder.ac3", "audio_encoder.ac3" },
         { MEDIA_MIMETYPE_AUDIO_EAC3,
             "audio_decoder.eac3", "audio_encoder.eac3" },
+        { MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC,
+            "image_decoder.heic", "image_encoder.heic" },
     };
 
     static const size_t kNumMimeToRole =
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index ac837a3..b5e60a4 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -475,5 +475,11 @@
     return AMEDIA_OK;
 }
 
+EXPORT
+media_status_t AMediaExtractor_disconnect(AMediaExtractor * ex) {
+    ex->mImpl->disconnect();
+    return AMEDIA_OK;
+}
+
 } // extern "C"
 
diff --git a/media/ndk/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/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index f7b9cfd..1d295e4 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -216,6 +216,12 @@
 
 #endif /* __ANDROID_API__ >= 28 */
 
+#if __ANDROID_API__ >= 29
+
+media_status_t AMediaExtractor_disconnect(AMediaExtractor *ex);
+
+#endif /* __ANDROID_API__ >= 29 */
+
 #endif /* __ANDROID_API__ >= 21 */
 
 __END_DECLS
diff --git a/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 19e4b24..b6480a2 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -19,7 +19,7 @@
 ifneq ($(TARGET_BUILD_PDK),true)
 # Build MediaComponents only if this is not a PDK build.  MediaComponents won't
 # build in PDK builds because frameworks/base/core/java is not available but
-# IMediaSession2.aidl and IMediaSession2Callback.aidl are using classes from
+# IMediaSession2.aidl and IMediaController2.aidl are using classes from
 # frameworks/base/core/java.
 
 include $(CLEAR_VARS)
@@ -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/drawable/custom_progress.xml b/packages/MediaComponents/res/drawable/custom_progress.xml
new file mode 100644
index 0000000..9731a6e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle" >
+            <solid android:color="#26000000" />
+        </shape>
+    </item>
+    <item android:id="@android:id/secondaryProgress">
+        <clip>
+            <shape android:shape="rectangle" >
+                <solid android:color="#5Cffffff" />
+            </shape>
+        </clip>
+    </item>
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape android:shape="rectangle" >
+                <solid android:color="#ffffff" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/custom_progress_thumb.xml b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
new file mode 100644
index 0000000..2e247f2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval" >
+    <solid android:color="#ffffff" />
+    <size
+        android:height="12dp"
+        android:width="12dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_high_quality.xml b/packages/MediaComponents/res/drawable/ic_high_quality.xml
index e27d3e2..f76e22f 100644
--- a/packages/MediaComponents/res/drawable/ic_high_quality.xml
+++ b/packages/MediaComponents/res/drawable/ic_high_quality.xml
@@ -1,9 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
+    android:width="34dp"
+    android:height="34dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M0 0h24v24H0z" />
     <path
         android:fillColor="#FFFFFF"
-        android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
+        android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 11H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 0.55-0.45 1-1 1h-0.75v1.5h-1.5V15H14c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v4zm-3.5-0.5h2v-3h-2v3z" />
 </vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
index 389396b..a56d5d9 100644
--- a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
+++ b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
+    android:width="40dp"
+    android:height="40dp"
     android:viewportWidth="24"
     android:viewportHeight="24">
 
diff --git a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
similarity index 75%
rename from packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
rename to packages/MediaComponents/res/drawable/ic_subtitle_off.xml
index a79cd11..c0a727a 100644
--- a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
@@ -9,8 +9,7 @@
         android:pathData="M0,0h24v24H0V0z" />
     <path
         android:fillColor="#FFFFFF"
-        android:pathData="M19,5.5c0.27,0,0.5,0.23,0.5,0.5v12c0,0.27-0.23,0.5-0.5,0.5H5c-0.28,0-0.5-0.22-0.5-0.5V6c0-0.28,0.22-0.5,0.5-0.5H19
-M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
+        android:pathData="M19.5,5.5v13h-15v-13H19.5z M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
     <path
         android:fillColor="#FFFFFF"
         android:pathData="M11,11H9.5v-0.5h-2v3h2V13H11v1c0,0.55-0.45,1-1,1H7c-0.55,0-1-0.45-1-1v-4c0-0.55,0.45-1,1-1h3c0.55,0,1,0.45,1,1V11z" />
diff --git a/packages/MediaComponents/res/drawable/ic_subtitle_on.xml b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
new file mode 100644
index 0000000..7c91c06
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M0 0h24v24H0z" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 7H9.5v-0.5h-2v3h2V13H11v1c0 0.55-0.45 1-1 1H7c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1zm7 0h-1.5v-0.5h-2v3h2V13H18v1c0 0.55-0.45 1-1 1h-3c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1z" />
+</vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/embedded_settings_list_item.xml b/packages/MediaComponents/res/layout/embedded_settings_list_item.xml
new file mode 100644
index 0000000..1156dca
--- /dev/null
+++ b/packages/MediaComponents/res/layout/embedded_settings_list_item.xml
@@ -0,0 +1,63 @@
+<?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="wrap_content"
+    android:layout_height="@dimen/mcv2_embedded_settings_height"
+    android:orientation="horizontal"
+    android:background="@color/black_opacity_70">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/mcv2_embedded_settings_height"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/icon"
+            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/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/mcv2_embedded_settings_text_height"
+            android:gravity="center"
+            android:paddingLeft="2dp"
+            android:textColor="@color/white"
+            android:textSize="@dimen/mcv2_embedded_settings_main_text_size"/>
+
+        <TextView
+            android:id="@+id/sub_text"
+            android:layout_width="wrap_content"
+            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_opacity_70"
+            android:textSize="@dimen/mcv2_embedded_settings_sub_text_size"/>
+    </RelativeLayout>
+</LinearLayout>
+
diff --git a/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml b/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
new file mode 100644
index 0000000..5947a72
--- /dev/null
+++ b/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
@@ -0,0 +1,53 @@
+<?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="wrap_content"
+    android:layout_height="@dimen/mcv2_embedded_settings_height"
+    android:orientation="horizontal"
+    android:background="@color/black_opacity_70">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/mcv2_embedded_settings_height"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/check"
+            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:src="@drawable/ic_check"/>
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        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/mcv2_embedded_settings_text_height"
+            android:gravity="center"
+            android:paddingLeft="2dp"
+            android:textColor="@color/white"
+            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/full_settings_list_item.xml b/packages/MediaComponents/res/layout/full_settings_list_item.xml
new file mode 100644
index 0000000..f92ea5e
--- /dev/null
+++ b/packages/MediaComponents/res/layout/full_settings_list_item.xml
@@ -0,0 +1,62 @@
+<?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="wrap_content"
+    android:layout_height="@dimen/mcv2_full_settings_height"
+    android:orientation="horizontal"
+    android:background="@color/black_opacity_70">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/mcv2_full_settings_height"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/icon"
+            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/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/mcv2_full_settings_text_height"
+            android:paddingLeft="2dp"
+            android:gravity="center"
+            android:textColor="@color/white"
+            android:textSize="@dimen/mcv2_full_settings_main_text_size"/>
+
+        <TextView
+            android:id="@+id/sub_text"
+            android:layout_width="wrap_content"
+            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_opacity_70"
+            android:textSize="@dimen/mcv2_full_settings_sub_text_size"/>
+    </RelativeLayout>
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml b/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
new file mode 100644
index 0000000..49128d0
--- /dev/null
+++ b/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
@@ -0,0 +1,53 @@
+<?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="wrap_content"
+    android:layout_height="@dimen/mcv2_full_settings_height"
+    android:orientation="horizontal"
+    android:background="@color/black_opacity_70">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/mcv2_full_settings_height"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/check"
+            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:src="@drawable/ic_check"/>
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        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/mcv2_full_settings_text_height"
+            android:gravity="center"
+            android:paddingLeft="2dp"
+            android:textColor="@color/white"
+            android:textSize="@dimen/mcv2_full_settings_main_text_size"/>
+    </RelativeLayout>
+</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 8488c84..c5fbee8 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -111,131 +111,126 @@
     </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
         android:id="@+id/mediacontroller_progress"
         android:layout_width="match_parent"
-        android:layout_height="32dp"
-        android:padding="0dp"
-        android:progressTint="#FFFFFFFF"
-        android:thumbTint="#FFFFFFFF"/>
+        android:layout_height="12dp"
+        android:maxHeight="2dp"
+        android:minHeight="2dp"
+        android:padding="0dp"/>
 
     <RelativeLayout
         android:layout_width="match_parent"
         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"
-                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/aspect_ratio"
-                style="@style/BottomBarButton.AspectRatio" />
-            <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/layout/settings_list_item.xml b/packages/MediaComponents/res/layout/settings_list_item.xml
deleted file mode 100644
index e7522b7..0000000
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/MediaControlView2_settings_width"
-    android:layout_height="@dimen/MediaControlView2_settings_height"
-    android:orientation="horizontal"
-    android:background="@color/black_transparent_70">
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_settings_height"
-        android:paddingRight="2dp"
-        android:gravity="center"
-        android:orientation="horizontal">
-
-        <ImageView
-            android:id="@+id/check"
-            android:layout_width="@dimen/MediaControlView2_settings_icon_size"
-            android:layout_height="@dimen/MediaControlView2_settings_icon_size"
-            android:gravity="center"
-            android:paddingLeft="2dp"
-            android:src="@drawable/ic_check"/>
-
-        <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"/>
-    </LinearLayout>
-
-    <RelativeLayout
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/MediaControlView2_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:paddingLeft="2dp"
-            android:textColor="@color/white"
-            android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
-
-        <TextView
-            android:id="@+id/sub_text"
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/MediaControlView2_text_width"
-            android:layout_below="@id/main_text"
-            android:paddingLeft="2dp"
-            android:textColor="@color/white_transparent_70"
-            android:textSize="@dimen/MediaControlView2_settings_sub_text_size"/>
-    </RelativeLayout>
-
-</LinearLayout>
-
diff --git a/packages/MediaComponents/res/values/arrays.xml b/packages/MediaComponents/res/values/arrays.xml
new file mode 100644
index 0000000..1187320
--- /dev/null
+++ b/packages/MediaComponents/res/values/arrays.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <integer-array name="speed_multiplied_by_100">
+        <item>25</item>
+        <item>50</item>
+        <item>75</item>
+        <item>100</item>
+        <item>125</item>
+        <item>150</item>
+        <item>200</item>
+    </integer-array>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/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 305cef4..69e9dff 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -111,15 +111,20 @@
     <string name="MediaControlView2_audio_track_none_text">None</string>
     <string name="MediaControlView2_video_quality_text">Video quality</string>
     <string name="MediaControlView2_video_quality_auto_text">Auto</string>
-    <string name="MediaControlView2_playback_speed_text">Playback speed</string>
-    <string name="MediaControlView2_playback_speed_0_25x_text">0.25x</string>
-    <string name="MediaControlView2_playback_speed_0_5x_text">0.5x</string>
-    <string name="MediaControlView2_playback_speed_0_75x_text">0.75x</string>
-    <string name="MediaControlView2_playback_speed_1x_text">Normal</string>
-    <string name="MediaControlView2_playback_speed_1_25x_text">1.25x</string>
-    <string name="MediaControlView2_playback_speed_1_5x_text">1.5x</string>
-    <string name="MediaControlView2_playback_speed_2x_text">2x</string>
     <string name="MediaControlView2_help_text">Help &amp; feedback</string>
+    <string name="MediaControlView2_playback_speed_text">Playback speed</string>
+    <string-array name="MediaControlView2_playback_speeds">
+        <item>0.25x</item>
+        <item>0.5x</item>
+        <item>0.75x</item>
+        <item>Normal</item>
+        <item>1.25x</item>
+        <item>1.5x</item>
+        <item>2x</item>
+    </string-array>
+    <!-- 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">
         Track <xliff:g id="track_number" example="1">%1$s</xliff:g>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index 23c7bc9..b1da137 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -1,29 +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: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">
@@ -44,18 +101,42 @@
         <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">
-        <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
+        <item name="android:src">@drawable/ic_subtitle_off</item>
     </style>
 
     <style name="BottomBarButton.FullScreen">
@@ -74,12 +155,8 @@
         <item name="android:src">@drawable/ic_settings</item>
     </style>
 
-    <style name="BottomBarButton.AspectRatio">
-        <item name="android:src">@drawable/ic_aspect_ratio</item>
-    </style>
-
     <style name="BottomBarButton.Mute">
-        <item name="android:src">@drawable/ic_mute</item>
+        <item name="android:src">@drawable/ic_unmute</item>
     </style>
 
     <style name="BottomBarButton.VideoQuality">
diff --git a/packages/MediaComponents/runcts.sh b/packages/MediaComponents/runcts.sh
index 0cf0e44..61b1a1e 100644
--- a/packages/MediaComponents/runcts.sh
+++ b/packages/MediaComponents/runcts.sh
@@ -23,6 +23,7 @@
   echo '     -h|--help: This help'
   echo '     --skip: Skip build and flash. Just rerun-tests'
   echo '     --min: Only rebuild tests and updatable library.'
+  echo '     --test: Only rebuild tests'
   echo '     -s [device_id]: Specify a device name to run test against.'
   echo '                     You can define ${ADBHOST} instead.'
   echo '     -r [count]: Repeat tests for given count. It will stop when fails.'
@@ -58,6 +59,7 @@
   while true; do
     local OPTION_SKIP="false"
     local OPTION_MIN="false"
+    local OPTION_TEST="false"
     local OPTION_REPEAT_COUNT="1"
     local OPTION_IGNORE="false"
     local OPTION_TEST_TARGET="${DEFAULT_TEST_TARGET}"
@@ -74,6 +76,9 @@
         --min)
           OPTION_MIN="true"
           ;;
+        --test)
+          OPTION_TEST="true"
+          ;;
         -s)
           shift
           adbhost_local=${1}
@@ -133,36 +138,43 @@
       fi
 
       # Build test apk and required apk.
-      local build_targets="${BUILD_TARGETS[@]}"
-      if [[ "${OPTION_MIN}" != "true" ]]; then
-        build_targets="${build_targets} droid"
+      local build_targets
+      if [[ "${OPTION_TEST}" == "true" ]]; then
+        build_targets="${INSTALL_TARGETS[@]}"
+      elif [[ "${OPTION_MIN}" == "true" ]]; then
+        build_targets="${BUILD_TARGETS[@]}"
+      else
+        build_targets="${BUILD_TARGETS[@]} droid"
       fi
       m ${build_targets} -j || break
 
-      local device_build_type="$(${adb} shell getprop ro.build.type)"
-      if [[ "${device_build_type}" == "user" ]]; then
-        # User build. Cannot adb sync
-        ${adb} reboot bootloader
-        fastboot flashall
-      else
-        ${adb} root
-        local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
-        if [[ "${device_verity_mode}" != "disabled" ]]; then
-          ${adb} disable-verity
-          ${adb} reboot
-          ${adb} wait-for-device || break
+      if [[ "${OPTION_TEST}" != "true" ]]; then
+        # Flash only when needed
+        local device_build_type="$(${adb} shell getprop ro.build.type)"
+        if [[ "${device_build_type}" == "user" ]]; then
+          # User build. Cannot adb sync
+          ${adb} reboot bootloader
+          fastboot flashall
+        else
           ${adb} root
+          local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
+          if [[ "${device_verity_mode}" != "disabled" ]]; then
+            ${adb} disable-verity
+            ${adb} reboot
+            ${adb} wait-for-device || break
+            ${adb} root
+          fi
+          ${adb} remount
+          ${adb} shell stop
+          ${adb} shell setprop log.tag.MediaSessionService DEBUG
+          ${adb} sync
+          ${adb} shell start
         fi
-        ${adb} remount
-        ${adb} shell stop
-        ${adb} shell setprop log.tag.MediaSessionService DEBUG
-        ${adb} sync
-        ${adb} shell start
+        ${adb} wait-for-device || break
+        # Ensure package manager is loaded.
+        # TODO(jaewan): Find better way to wait
+        sleep 15
       fi
-      ${adb} wait-for-device || break
-      # Ensure package manager is loaded.
-      # TODO(jaewan): Find better way to wait
-      sleep 15
 
       # Install apks
       local install_failed="false"
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
similarity index 67%
rename from packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
rename to packages/MediaComponents/src/com/android/media/IMediaController2.aidl
index 3d812f8..0488b70 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
@@ -23,21 +23,29 @@
 import com.android.media.IMediaSession2;
 
 /**
- * Interface from MediaSession2 to MediaSession2Record.
+ * Interface from MediaSession2 to MediaController2.
  * <p>
  * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
  * and holds calls from session to make session owner(s) frozen.
  */
-oneway interface IMediaSession2Callback {
-    void onPlaybackStateChanged(in Bundle state);
-    void onPlaylistChanged(in List<Bundle> playlist);
-    void onPlaylistParamsChanged(in Bundle params);
+// TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+//               Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
+oneway interface IMediaController2 {
+    void onPlayerStateChanged(int state);
+    void onPositionChanged(long eventTimeMs, long positionMs);
+    void onPlaybackSpeedChanged(float speed);
+    void onBufferedPositionChanged(long bufferedPositionMs);
+    void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
+    void onPlaylistMetadataChanged(in Bundle metadata);
     void onPlaybackInfoChanged(in Bundle playbackInfo);
+    void onRepeatModeChanged(int repeatMode);
+    void onShuffleModeChanged(int shuffleMode);
+    void onError(int errorCode, in Bundle extras);
 
-    // TODO(jaewan): Handle when the playlist becomes too huge.
-    void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState,
-            in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist,
-            in PendingIntent sessionActivity);
+    void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
+            int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+            long bufferedPositionMs, in Bundle playbackInfo, int repeatMode, int shuffleMode,
+            in List<Bundle> playlist, in PendingIntent sessionActivity);
     void onDisconnected();
 
     void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index ef7120d..664467d 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -20,55 +20,66 @@
 import android.os.ResultReceiver;
 import android.net.Uri;
 
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
 
 /**
- * Interface to MediaSession2.
+ * Interface from MediaController2 to MediaSession2.
  * <p>
  * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
  * and holds calls from session to make session owner(s) frozen.
  */
+ // TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+ //               Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
 oneway interface IMediaSession2 {
     // TODO(jaewan): add onCommand() to send private command
-    // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
-    //               Add id for individual calls to address this.
 
-    // TODO(jaewan): We may consider to add another binder just for the connection
+    // TODO(jaewan): (Post P) We may consider to add another binder just for the connection
     //               not to expose other methods to the controller whose connection wasn't accepted.
     //               But this would be enough for now because it's the same as existing
     //               MediaBrowser and MediaBrowserService.
-    void connect(IMediaSession2Callback caller, String callingPackage);
-    void release(IMediaSession2Callback caller);
+    void connect(IMediaController2 caller, String callingPackage);
+    void release(IMediaController2 caller);
 
-    void setVolumeTo(IMediaSession2Callback caller, int value, int flags);
-    void adjustVolume(IMediaSession2Callback caller, int direction, int flags);
+    void setVolumeTo(IMediaController2 caller, int value, int flags);
+    void adjustVolume(IMediaController2 caller, int direction, int flags);
 
     //////////////////////////////////////////////////////////////////////////////////////////////
     // send command
     //////////////////////////////////////////////////////////////////////////////////////////////
-    void sendTransportControlCommand(IMediaSession2Callback caller,
+    void sendTransportControlCommand(IMediaController2 caller,
             int commandCode, in Bundle args);
-    void sendCustomCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args,
+    void sendCustomCommand(IMediaController2 caller, in Bundle command, in Bundle args,
             in ResultReceiver receiver);
 
-    void prepareFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
-    void prepareFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
-    void prepareFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
-    void playFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
-    void playFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
-    void playFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
-    void setRating(IMediaSession2Callback caller, String mediaId, in Bundle rating);
+    void prepareFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+    void prepareFromSearch(IMediaController2 caller, String query, in Bundle extras);
+    void prepareFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+    void playFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+    void playFromSearch(IMediaController2 caller, String query, in Bundle extras);
+    void playFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+    void setRating(IMediaController2 caller, String mediaId, in Bundle rating);
+
+    void setPlaylist(IMediaController2 caller, in List<Bundle> playlist, in Bundle metadata);
+    void updatePlaylistMetadata(IMediaController2 caller, in Bundle metadata);
+    void addPlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
+    void removePlaylistItem(IMediaController2 caller, in Bundle mediaItem);
+    void replacePlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
+    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
     //////////////////////////////////////////////////////////////////////////////////////////////
-    void getLibraryRoot(IMediaSession2Callback caller, in Bundle rootHints);
-    void getItem(IMediaSession2Callback caller, String mediaId);
-    void getChildren(IMediaSession2Callback caller, String parentId, int page, int pageSize,
+    void getLibraryRoot(IMediaController2 caller, in Bundle rootHints);
+    void getItem(IMediaController2 caller, String mediaId);
+    void getChildren(IMediaController2 caller, String parentId, int page, int pageSize,
             in Bundle extras);
-    void search(IMediaSession2Callback caller, String query, in Bundle extras);
-    void getSearchResult(IMediaSession2Callback caller, String query, int page, int pageSize,
+    void search(IMediaController2 caller, String query, in Bundle extras);
+    void getSearchResult(IMediaController2 caller, String query, int page, int pageSize,
             in Bundle extras);
-    void subscribe(IMediaSession2Callback caller, String parentId, in Bundle extras);
-    void unsubscribe(IMediaSession2Callback caller, String parentId);
+    void subscribe(IMediaController2 caller, String parentId, in Bundle extras);
+    void unsubscribe(IMediaController2 caller, String parentId);
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 96fdda0..0091816 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -16,7 +16,20 @@
 
 package com.android.media;
 
-import static android.media.MediaSession2.*;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
+import static android.media.MediaSession2.COMMAND_CODE_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;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_URI;
 
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -28,13 +41,14 @@
 import android.media.MediaController2.ControllerCallback;
 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.PlaybackState2;
 import android.media.Rating2;
 import android.media.SessionToken2;
 import android.media.update.MediaController2Provider;
@@ -61,7 +75,7 @@
     private final Context mContext;
     private final Object mLock = new Object();
 
-    private final MediaSession2CallbackStub mSessionCallbackStub;
+    private final MediaController2Stub mControllerStub;
     private final SessionToken2 mToken;
     private final ControllerCallback mCallback;
     private final Executor mCallbackExecutor;
@@ -72,11 +86,23 @@
     @GuardedBy("mLock")
     private boolean mIsReleased;
     @GuardedBy("mLock")
-    private PlaybackState2 mPlaybackState;
-    @GuardedBy("mLock")
     private List<MediaItem2> mPlaylist;
     @GuardedBy("mLock")
-    private PlaylistParams mPlaylistParams;
+    private MediaMetadata2 mPlaylistMetadata;
+    @GuardedBy("mLock")
+    private @RepeatMode int mRepeatMode;
+    @GuardedBy("mLock")
+    private @ShuffleMode int mShuffleMode;
+    @GuardedBy("mLock")
+    private int mPlayerState;
+    @GuardedBy("mLock")
+    private long mPositionEventTimeMs;
+    @GuardedBy("mLock")
+    private long mPositionMs;
+    @GuardedBy("mLock")
+    private float mPlaybackSpeed;
+    @GuardedBy("mLock")
+    private long mBufferedPositionMs;
     @GuardedBy("mLock")
     private PlaybackInfo mPlaybackInfo;
     @GuardedBy("mLock")
@@ -110,7 +136,7 @@
             throw new IllegalArgumentException("executor shouldn't be null");
         }
         mContext = context;
-        mSessionCallbackStub = new MediaSession2CallbackStub(this);
+        mControllerStub = new MediaController2Stub(this);
         mToken = token;
         mCallback = callback;
         mCallbackExecutor = executor;
@@ -191,7 +217,7 @@
 
     private void connectToSession(IMediaSession2 sessionBinder) {
         try {
-            sessionBinder.connect(mSessionCallbackStub, mContext.getPackageName());
+            sessionBinder.connect(mControllerStub, mContext.getPackageName());
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to call connection request. Framework will retry"
                     + " automatically");
@@ -216,12 +242,12 @@
             }
             binder = mSessionBinder;
             mSessionBinder = null;
-            mSessionCallbackStub.destroy();
+            mControllerStub.destroy();
         }
         if (binder != null) {
             try {
                 binder.asBinder().unlinkToDeath(mDeathRecipient, 0);
-                binder.release(mSessionCallbackStub);
+                binder.release(mControllerStub);
             } catch (RemoteException e) {
                 // No-op.
             }
@@ -235,8 +261,8 @@
         return mSessionBinder;
     }
 
-    MediaSession2CallbackStub getControllerStub() {
-        return mSessionCallbackStub;
+    MediaController2Stub getControllerStub() {
+        return mControllerStub;
     }
 
     Executor getCallbackExecutor() {
@@ -314,13 +340,48 @@
     }
 
     @Override
+    public void skipToPlaylistItem_impl(MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                binder.skipToPlaylistItem(mControllerStub, item.toBundle());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+    }
+
+    @Override
     public void skipToPreviousItem_impl() {
-        sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                binder.skipToPreviousItem(mControllerStub);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
     public void skipToNextItem_impl() {
-        sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                binder.skipToNextItem(mControllerStub);
+            } 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());
+        }
     }
 
     private void sendTransportControlCommand(int commandCode) {
@@ -331,7 +392,7 @@
         final IMediaSession2 binder = mSessionBinder;
         if (binder != null) {
             try {
-                binder.sendTransportControlCommand(mSessionCallbackStub, commandCode, args);
+                binder.sendTransportControlCommand(mControllerStub, commandCode, args);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -340,9 +401,6 @@
         }
     }
 
-    //////////////////////////////////////////////////////////////////////////////////////
-    // TODO(jaewan): Implement follows
-    //////////////////////////////////////////////////////////////////////////////////////
     @Override
     public PendingIntent getSessionActivity_impl() {
         return mSessionActivity;
@@ -354,7 +412,7 @@
         final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
         if (binder != null) {
             try {
-                binder.setVolumeTo(mSessionCallbackStub, value, flags);
+                binder.setVolumeTo(mControllerStub, value, flags);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -369,7 +427,7 @@
         final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
         if (binder != null) {
             try {
-                binder.adjustVolume(mSessionCallbackStub, direction, flags);
+                binder.adjustVolume(mControllerStub, direction, flags);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -386,7 +444,7 @@
         }
         if (binder != null) {
             try {
-                binder.prepareFromUri(mSessionCallbackStub, uri, extras);
+                binder.prepareFromUri(mControllerStub, uri, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -403,7 +461,7 @@
         }
         if (binder != null) {
             try {
-                binder.prepareFromSearch(mSessionCallbackStub, query, extras);
+                binder.prepareFromSearch(mControllerStub, query, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -420,7 +478,7 @@
         }
         if (binder != null) {
             try {
-                binder.prepareFromMediaId(mSessionCallbackStub, mediaId, extras);
+                binder.prepareFromMediaId(mControllerStub, mediaId, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -437,7 +495,7 @@
         }
         if (binder != null) {
             try {
-                binder.playFromUri(mSessionCallbackStub, uri, extras);
+                binder.playFromUri(mControllerStub, uri, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -454,7 +512,7 @@
         }
         if (binder != null) {
             try {
-                binder.playFromSearch(mSessionCallbackStub, query, extras);
+                binder.playFromSearch(mControllerStub, query, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -471,7 +529,7 @@
         }
         if (binder != null) {
             try {
-                binder.playFromMediaId(mSessionCallbackStub, mediaId, extras);
+                binder.playFromMediaId(mControllerStub, mediaId, extras);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -492,7 +550,7 @@
         final IMediaSession2 binder = mSessionBinder;
         if (binder != null) {
             try {
-                binder.setRating(mSessionCallbackStub, mediaId, rating.toBundle());
+                binder.setRating(mControllerStub, mediaId, rating.toBundle());
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -509,7 +567,7 @@
         final IMediaSession2 binder = getSessionBinderIfAble(command);
         if (binder != null) {
             try {
-                binder.sendCustomCommand(mSessionCallbackStub, command.toBundle(), args, cb);
+                binder.sendCustomCommand(mControllerStub, command.toBundle(), args, cb);
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to the service or the session is gone", e);
             }
@@ -526,6 +584,51 @@
     }
 
     @Override
+    public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+        if (list == null) {
+            throw new IllegalArgumentException("list shouldn't be null");
+        }
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_LIST);
+        if (binder != null) {
+            List<Bundle> bundleList = new ArrayList<>();
+            for (int i = 0; i < list.size(); i++) {
+                bundleList.add(list.get(i).toBundle());
+            }
+            Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+            try {
+                binder.setPlaylist(mControllerStub, bundleList, metadataBundle);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+    }
+
+    @Override
+    public MediaMetadata2 getPlaylistMetadata_impl() {
+        synchronized (mLock) {
+            return mPlaylistMetadata;
+        }
+    }
+
+    @Override
+    public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+        final IMediaSession2 binder = getSessionBinderIfAble(
+                COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+        if (binder != null) {
+            Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+            try {
+                binder.updatePlaylistMetadata(mControllerStub, metadataBundle);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+    }
+
+    @Override
     public void prepare_impl() {
         sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
     }
@@ -551,61 +654,98 @@
     }
 
     @Override
-    public void skipToPlaylistItem_impl(MediaItem2 item) {
-        if (item == null) {
-            throw new IllegalArgumentException("item shouldn't be null");
-        }
-
-        // TODO(jaewan): Implement this
-        /*
-        Bundle args = new Bundle();
-        args.putInt(MediaSession2Stub.ARGUMENT_KEY_ITEM_INDEX, item);
-        sendTransportControlCommand(
-                MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM, args);
-        */
-    }
-
-    @Override
-    public PlaybackState2 getPlaybackState_impl() {
-        synchronized (mLock) {
-            return mPlaybackState;
-        }
-    }
-
-    @Override
     public void addPlaylistItem_impl(int index, MediaItem2 item) {
-        // TODO(jaewan): Implement (b/73149584)
         if (index < 0) {
             throw new IllegalArgumentException("index shouldn't be negative");
         }
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_ADD_ITEM);
+        if (binder != null) {
+            try {
+                binder.addPlaylistItem(mControllerStub, index, item.toBundle());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
     public void removePlaylistItem_impl(MediaItem2 item) {
-        // TODO(jaewan): Implement (b/73149584)
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+        if (binder != null) {
+            try {
+                binder.removePlaylistItem(mControllerStub, item.toBundle());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
     public void replacePlaylistItem_impl(int index, MediaItem2 item) {
-        // TODO: Implement this (b/73149407)
         if (index < 0) {
             throw new IllegalArgumentException("index shouldn't be negative");
         }
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+        if (binder != null) {
+            try {
+                binder.replacePlaylistItem(mControllerStub, index, item.toBundle());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
-    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());
         }
     }
 
@@ -617,37 +757,33 @@
     }
 
     @Override
-    public void setPlaylistParams_impl(PlaylistParams params) {
-        if (params == null) {
-            throw new IllegalArgumentException("params shouldn't be null");
-        }
-        Bundle args = new Bundle();
-        args.putBundle(MediaSession2Stub.ARGUMENT_KEY_PLAYLIST_PARAMS, params.toBundle());
-        sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS, args);
-    }
-
-    @Override
     public int getPlayerState_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        synchronized (mLock) {
+            return mPlayerState;
+        }
     }
 
     @Override
     public long getPosition_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        synchronized (mLock) {
+            long timeDiff = System.currentTimeMillis() - mPositionEventTimeMs;
+            long expectedPosition = mPositionMs + (long) (mPlaybackSpeed * timeDiff);
+            return Math.max(0, expectedPosition);
+        }
     }
 
     @Override
     public float getPlaybackSpeed_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        synchronized (mLock) {
+            return mPlaybackSpeed;
+        }
     }
 
     @Override
     public long getBufferedPosition_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        synchronized (mLock) {
+            return mBufferedPositionMs;
+        }
     }
 
     @Override
@@ -656,27 +792,52 @@
         return null;
     }
 
-    void pushPlaybackStateChanges(final PlaybackState2 state) {
+    void pushPlayerStateChanges(final int state) {
         synchronized (mLock) {
-            mPlaybackState = state;
+            mPlayerState = state;
         }
         mCallbackExecutor.execute(() -> {
             if (!mInstance.isConnected()) {
                 return;
             }
-            mCallback.onPlaybackStateChanged(mInstance, state);
+            mCallback.onPlayerStateChanged(mInstance, state);
         });
     }
 
-    void pushPlaylistParamsChanges(final PlaylistParams params) {
+    void pushPositionChanges(final long eventTimeMs, final long positionMs) {
         synchronized (mLock) {
-            mPlaylistParams = params;
+            mPositionEventTimeMs = eventTimeMs;
+            mPositionMs = positionMs;
         }
         mCallbackExecutor.execute(() -> {
             if (!mInstance.isConnected()) {
                 return;
             }
-            mCallback.onPlaylistParamsChanged(mInstance, params);
+            mCallback.onPositionChanged(mInstance, eventTimeMs, positionMs);
+        });
+    }
+
+    void pushPlaybackSpeedChanges(final float speed) {
+        synchronized (mLock) {
+            mPlaybackSpeed = speed;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            mCallback.onPlaybackSpeedChanged(mInstance, speed);
+        });
+    }
+
+    void pushBufferedPositionChanges(final long bufferedPositionMs) {
+        synchronized (mLock) {
+            mBufferedPositionMs = bufferedPositionMs;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            mCallback.onBufferedPositionChanged(mInstance, bufferedPositionMs);
         });
     }
 
@@ -692,22 +853,80 @@
         });
     }
 
-    void pushPlaylistChanges(final List<MediaItem2> playlist) {
+    void pushPlaylistChanges(final List<MediaItem2> playlist, final MediaMetadata2 metadata) {
         synchronized (mLock) {
             mPlaylist = playlist;
-            mCallbackExecutor.execute(() -> {
-                if (!mInstance.isConnected()) {
-                    return;
-                }
-                mCallback.onPlaylistChanged(mInstance, playlist);
-            });
+            mPlaylistMetadata = metadata;
         }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            // TODO(jaewan): Fix public API not to take playlistAgent.
+            mCallback.onPlaylistChanged(mInstance, null, playlist, metadata);
+        });
+    }
+
+    void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
+        synchronized (mLock) {
+            mPlaylistMetadata = metadata;
+        }
+        mCallbackExecutor.execute(() -> {
+            if (!mInstance.isConnected()) {
+                return;
+            }
+            // TODO(jaewan): Fix public API not to take playlistAgent.
+            mCallback.onPlaylistMetadataChanged(mInstance, null, metadata);
+        });
+    }
+
+    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, final PlaybackState2 state, final PlaybackInfo info,
-            final PlaylistParams params, final List<MediaItem2> playlist,
+            final CommandGroup allowedCommands,
+            final int playerState,
+            final long positionEventTimeMs,
+            final long positionMs,
+            final float playbackSpeed,
+            final long bufferedPositionMs,
+            final PlaybackInfo info,
+            final int repeatMode,
+            final int shuffleMode,
+            final List<MediaItem2> playlist,
             final PendingIntent sessionActivity) {
         if (DEBUG) {
             Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
@@ -731,9 +950,14 @@
                     return;
                 }
                 mAllowedCommands = allowedCommands;
-                mPlaybackState = state;
+                mPlayerState = playerState;
+                mPositionEventTimeMs = positionEventTimeMs;
+                mPositionMs = positionMs;
+                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/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
similarity index 76%
rename from packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
rename to packages/MediaComponents/src/com/android/media/MediaController2Stub.java
index 451368f..721c963 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
@@ -20,11 +20,10 @@
 import android.content.Context;
 import android.media.MediaController2;
 import android.media.MediaItem2;
+import android.media.MediaMetadata2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.text.TextUtils;
@@ -37,13 +36,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
-    private static final String TAG = "MS2CallbackStub";
+public class MediaController2Stub extends IMediaController2.Stub {
+    private static final String TAG = "MediaController2Stub";
     private static final boolean DEBUG = true; // TODO(jaewan): Change
 
     private final WeakReference<MediaController2Impl> mController;
 
-    MediaSession2CallbackStub(MediaController2Impl controller) {
+    MediaController2Stub(MediaController2Impl controller) {
         mController = new WeakReference<>(controller);
     }
 
@@ -68,7 +67,7 @@
     }
 
     @Override
-    public void onPlaybackStateChanged(Bundle state) throws RuntimeException {
+    public void onPlayerStateChanged(int state) {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -76,12 +75,59 @@
             Log.w(TAG, "Don't fail silently here. Highly likely a bug");
             return;
         }
-        controller.pushPlaybackStateChanges(
-                PlaybackState2.fromBundle(controller.getContext(), state));
+        controller.pushPlayerStateChanges(state);
     }
 
     @Override
-    public void onPlaylistChanged(List<Bundle> playlistBundle) throws RuntimeException {
+    public void onPositionChanged(long eventTimeMs, long positionMs) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (eventTimeMs < 0) {
+            Log.w(TAG, "onPositionChanged(): Ignoring negative eventTimeMs");
+            return;
+        }
+        if (positionMs < 0) {
+            Log.w(TAG, "onPositionChanged(): Ignoring negative positionMs");
+            return;
+        }
+        controller.pushPositionChanges(eventTimeMs, positionMs);
+    }
+
+    @Override
+    public void onPlaybackSpeedChanged(float speed) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        controller.pushPlaybackSpeedChanges(speed);
+    }
+
+    @Override
+    public void onBufferedPositionChanged(long bufferedPositionMs) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (bufferedPositionMs < 0) {
+            Log.w(TAG, "onBufferedPositionChanged(): Ignoring negative bufferedPositionMs");
+            return;
+        }
+        controller.pushBufferedPositionChanges(bufferedPositionMs);
+    }
+
+    @Override
+    public void onPlaylistChanged(List<Bundle> playlistBundle, Bundle metadataBundle) {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -90,7 +136,7 @@
             return;
         }
         if (playlistBundle == null) {
-            Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist");
+            Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist from " + controller);
             return;
         }
         List<MediaItem2> playlist = new ArrayList<>();
@@ -102,11 +148,13 @@
                 playlist.add(item);
             }
         }
-        controller.pushPlaylistChanges(playlist);
+        MediaMetadata2 metadata =
+                MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+        controller.pushPlaylistChanges(playlist, metadata);
     }
 
     @Override
-    public void onPlaylistParamsChanged(Bundle paramsBundle) throws RuntimeException {
+    public void onPlaylistMetadataChanged(Bundle metadataBundle) throws RuntimeException {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -114,12 +162,21 @@
             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");
+        MediaMetadata2 metadata =
+                MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+        controller.pushPlaylistMetadataChanges(metadata);
+    }
+
+    @Override
+    public void onRepeatModeChanged(int repeatMode) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
             return;
         }
-        controller.pushPlaylistParamsChanges(params);
+        controller.pushRepeatModeChanges(repeatMode);
     }
 
     @Override
@@ -145,9 +202,34 @@
     }
 
     @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,
-            Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
-            itemBundleList, PendingIntent sessionActivity) {
+            int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+            long bufferedPositionMs, Bundle playbackInfo, int shuffleMode, int repeatMode,
+            List<Bundle> itemBundleList, PendingIntent sessionActivity) {
         final MediaController2Impl controller = mController.get();
         if (controller == null) {
             if (DEBUG) {
@@ -168,9 +250,8 @@
         }
         controller.onConnectedNotLocked(sessionBinder,
                 CommandGroup.fromBundle(context, commandGroup),
-                PlaybackState2.fromBundle(context, playbackState),
-                PlaybackInfoImpl.fromBundle(context, playbackInfo),
-                PlaylistParams.fromBundle(context, playlistParams),
+                playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
+                PlaybackInfoImpl.fromBundle(context, playbackInfo), repeatMode, shuffleMode,
                 itemList, sessionActivity);
     }
 
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index 039ff8f..c95b43f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -31,21 +31,31 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 
+import java.util.UUID;
+
 public class MediaItem2Impl implements MediaItem2Provider {
     private static final String KEY_ID = "android.media.mediaitem2.id";
     private static final String KEY_FLAGS = "android.media.mediaitem2.flags";
     private static final String KEY_METADATA = "android.media.mediaitem2.metadata";
+    private static final String KEY_UUID = "android.media.mediaitem2.uuid";
 
     private final Context mContext;
     private final MediaItem2 mInstance;
     private final String mId;
     private final int mFlags;
+    private final UUID mUUID;
     private MediaMetadata2 mMetadata;
     private DataSourceDesc mDataSourceDesc;
 
     // From the public API
     public MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
             @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags) {
+        this(context, mediaId, dsd, metadata, flags, null);
+    }
+
+    private MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
+            @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags,
+            @Nullable UUID uuid) {
         if (mediaId == null) {
             throw new IllegalArgumentException("mediaId shouldn't be null");
         }
@@ -58,24 +68,18 @@
         mDataSourceDesc = dsd;
         mMetadata = metadata;
         mFlags = flags;
+        mUUID = (uuid == null) ? UUID.randomUUID() : uuid;
 
         mInstance = new MediaItem2(this);
     }
 
-    // Create anonymized version
-    public MediaItem2Impl(Context context, String mediaId, MediaMetadata2 metadata,
-            @Flags int flags) {
-        if (mediaId == null) {
-            throw new IllegalArgumentException("mediaId shouldn't be null");
+    @Override
+    public boolean equals_impl(Object obj) {
+        if (!(obj instanceof MediaItem2)) {
+            return false;
         }
-        if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
-            throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
-        }
-        mContext = context;
-        mId = mediaId;
-        mMetadata = metadata;
-        mFlags = flags;
-        mInstance = new MediaItem2(this);
+        MediaItem2 other = (MediaItem2) obj;
+        return mUUID.equals(((MediaItem2Impl) other.getProvider()).mUUID);
     }
 
     /**
@@ -90,10 +94,37 @@
         if (mMetadata != null) {
             bundle.putBundle(KEY_METADATA, mMetadata.toBundle());
         }
+        bundle.putString(KEY_UUID, mUUID.toString());
         return bundle;
     }
 
-    public static MediaItem2 fromBundle(Context context, Bundle bundle) {
+    /**
+     * Create a MediaItem2 from the {@link Bundle}.
+     *
+     * @param context A context.
+     * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+     * @return The newly created MediaItem2
+     */
+    public static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
+        if (bundle == null) {
+            return null;
+        }
+        final String uuidString = bundle.getString(KEY_UUID);
+        return fromBundle(context, bundle, UUID.fromString(uuidString));
+    }
+
+    /**
+     * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID}.
+     * If {@link UUID}
+     * can be null for creating new.
+     *
+     * @param context A context.
+     * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+     * @param uuid A {@link UUID} to override. Can be {@link null} for override.
+     * @return The newly created MediaItem2
+     */
+    static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle,
+            @Nullable UUID uuid) {
         if (bundle == null) {
             return null;
         }
@@ -102,7 +133,7 @@
         final MediaMetadata2 metadata = metadataBundle != null
                 ? MediaMetadata2.fromBundle(context, metadataBundle) : null;
         final int flags = bundle.getInt(KEY_FLAGS);
-        return new MediaItem2Impl(context, id, metadata, flags).getInstance();
+        return new MediaItem2Impl(context, id, null, metadata, flags, uuid).getInstance();
     }
 
     private MediaItem2 getInstance() {
diff --git a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
index 333455d..286f5ee 100644
--- a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
@@ -228,7 +228,7 @@
     }
 
     public static MediaMetadata2 fromBundle(Context context, Bundle bundle) {
-        return new MediaMetadata2Impl(context, bundle).getInstance();
+        return (bundle == null) ? null : new MediaMetadata2Impl(context, bundle).getInstance();
     }
 
     public static final class BuilderImpl implements MediaMetadata2Provider.BuilderProvider {
diff --git a/packages/MediaComponents/src/com/android/media/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 5c18515..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,12 +48,8 @@
 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.PlaybackState2;
 import android.media.SessionToken2;
 import android.media.VolumeProvider2;
 import android.media.session.MediaSessionManager;
@@ -68,8 +65,11 @@
 import android.util.Log;
 
 import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.concurrent.Executor;
 
 public class MediaSession2Impl implements MediaSession2Provider {
@@ -86,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;
@@ -248,11 +247,12 @@
                 oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
             }
         }
-        // TODO(jaewan): Notify controllers about the change in the media player base (b/74370608)
-        //               Note that notification will be done indirectly by telling player state,
-        //               position, buffered position, etc.
-        mSessionStub.notifyPlaybackInfoChanged(info);
-        notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
+
+        if (oldPlayer != null) {
+            mSessionStub.notifyPlaybackInfoChanged(info);
+            notifyPlayerUpdatedNotLocked(oldPlayer);
+        }
+        // TODO(jaewan): Repeat the same thing for the playlist agent.
     }
 
     private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -385,32 +385,36 @@
     }
 
     @Override
-    public void skipToPreviousItem_impl() {
-        ensureCallingThread();
-        // TODO(jaewan): Implement this (b/74175632)
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-            // TODO implement
-            //player.skipToPrevious();
+    public void skipToPlaylistItem_impl(MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.skipToPlaylistItem(item);
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
+    }
+
+    @Override
+    public void skipToPreviousItem_impl() {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.skipToPreviousItem();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void skipToNextItem_impl() {
-        ensureCallingThread();
-        // TODO(jaewan): Implement this (b/74175632)
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-            player.skipToNext();
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.skipToNextItem();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
     }
 
     @Override
@@ -425,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
     //////////////////////////////////////////////////////////////////////////////////////
@@ -496,22 +465,27 @@
     }
 
     @Override
-    public void setPlaylist_impl(List<MediaItem2> playlist) {
-        if (playlist == null) {
-            throw new IllegalArgumentException("playlist shouldn't be null");
+    public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+        if (list == null) {
+            throw new IllegalArgumentException("list shouldn't be null");
         }
         ensureCallingThread();
-        // TODO: Uncomment or remove
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-            // TODO implement and use SessionPlaylistAgent
-            //player.setPlaylist(playlist);
-            mSessionStub.notifyPlaylistChanged(playlist);
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.setPlaylist(list, metadata);
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
+    }
+
+    @Override
+    public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.updatePlaylistMetadata(metadata);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
@@ -522,7 +496,12 @@
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
-        // TODO(jaewan): Implement
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.addPlaylistItem(index, item);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
@@ -530,15 +509,12 @@
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
-        // TODO(jaewan): Implement
-    }
-
-    @Override
-    public void editPlaylistItem_impl(MediaItem2 item) {
-        if (item == null) {
-            throw new IllegalArgumentException("item shouldn't be null");
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.removePlaylistItem(item);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        // TODO(jaewan): Implement
     }
 
     @Override
@@ -549,24 +525,33 @@
         if (item == null) {
             throw new IllegalArgumentException("item shouldn't be null");
         }
-        // TODO(jaewan): Implement
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            agent.replacePlaylistItem(index, item);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public List<MediaItem2> getPlaylist_impl() {
-        // TODO: Uncomment or remove
-        /*
-        final MediaPlayerBase player = mPlayer;
-        if (player != null) {
-            // TODO(jaewan): Is it safe to be called on any thread?
-            //               Otherwise MediaSession2 should cache parameter of setPlaylist.
-            // TODO implement
-            //return player.getPlaylist();
-            return null;
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            return agent.getPlaylist();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
+        return null;
+    }
+
+    @Override
+    public MediaMetadata2 getPlaylistMetadata_impl() {
+        final MediaPlaylistAgent agent = mPlaylistAgent;
+        if (agent != null) {
+            return agent.getPlaylistMetadata();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
         return null;
     }
 
@@ -577,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;
@@ -590,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
@@ -629,76 +648,41 @@
     }
 
     @Override
-    public void skipToPlaylistItem_impl(MediaItem2 item) {
-        ensureCallingThread();
-        if (item == null) {
-            throw new IllegalArgumentException("item shouldn't be null");
-        }
-        // TODO: Uncomment or remove
-        /*
+    public @PlayerState int getPlayerState_impl() {
         final MediaPlayerBase player = mPlayer;
         if (player != null) {
-            // TODO implement
-            //player.setCurrentPlaylistItem(item);
+            return mPlayer.getPlayerState();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
+        return MediaPlayerBase.PLAYER_STATE_ERROR;
     }
 
     @Override
-    public void registerPlayerEventCallback_impl(Executor executor, PlayerEventCallback callback) {
-        if (executor == null) {
-            throw new IllegalArgumentException("executor shouldn't be null");
-        }
-        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);
-        // TODO: Uncomment or remove
-        /*
-        // TODO(jaewan): Double check if we need this.
-        final PlaybackState2 state = getInstance().getPlaybackState();
-        executor.execute(() -> callback.onPlaybackStateChanged(state));
-        */
-    }
-
-    @Override
-    public void unregisterPlayerEventCallback_impl(PlayerEventCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback shouldn't be null");
-        }
-        ensureCallingThread();
-        mCallbacks.remove(callback);
-    }
-
-    @Override
-    public PlaybackState2 getPlaybackState_impl() {
-        ensureCallingThread();
-        // TODO: Uncomment or remove
-        /*
+    public long getPosition_impl() {
         final MediaPlayerBase player = mPlayer;
         if (player != null) {
-           // TODO(jaewan): Is it safe to be called on any thread?
-            //               Otherwise MediaSession2 should cache the result from listener.
-            // TODO implement
-            //return player.getPlaybackState();
-            return null;
+            return mPlayer.getCurrentPosition();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        */
-        return null;
+        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);
     }
 
     ///////////////////////////////////////////////////
@@ -725,35 +709,73 @@
         }*/
     }
 
-    private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
-        ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
-        synchronized (mLock) {
-            callbacks.putAll(mCallbacks);
+    private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+            List<MediaItem2> list, MediaMetadata2 metadata) {
+        if (playlistAgent != mPlaylistAgent) {
+            // Ignore calls from the old agent.
+            return;
         }
-        // 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));
-        }
-        // Notify to controllers as well.
-        mSessionStub.notifyPlaybackStateChangedNotLocked(state);
+        mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
+        mSessionStub.notifyPlaylistChangedNotLocked(list, metadata);
     }
 
-    private void notifyErrorNotLocked(String mediaId, int what, int extra) {
-        ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
-        synchronized (mLock) {
-            callbacks.putAll(mCallbacks);
+    private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+            MediaMetadata2 metadata) {
+        if (playlistAgent != mPlaylistAgent) {
+            // Ignore calls from the old agent.
+            return;
         }
-        // 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));
+        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;
         }
-        // TODO(jaewan): Notify to controllers as well.
+        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) {
+        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()) {
+            mSessionStub.notifyPlayerStateChangedNotLocked(state);
+        }
+
+        final long currentTimeMs = System.currentTimeMillis();
+        final long position = player.getCurrentPosition();
+        if (position != oldPlayer.getCurrentPosition()) {
+            mSessionStub.notifyPositionChangedNotLocked(currentTimeMs, position);
+        }
+
+        final float speed = player.getPlaybackSpeed();
+        if (speed != oldPlayer.getPlaybackSpeed()) {
+            mSessionStub.notifyPlaybackSpeedChangedNotLocked(speed);
+        }
+
+        final long bufferedPosition = player.getBufferedPosition();
+        if (bufferedPosition != oldPlayer.getBufferedPosition()) {
+            mSessionStub.notifyBufferedPositionChangedNotLocked(bufferedPosition);
+        }
     }
 
     Context getContext() {
@@ -768,6 +790,10 @@
         return mPlayer;
     }
 
+    MediaPlaylistAgent getPlaylistAgent() {
+        return mPlaylistAgent;
+    }
+
     Executor getCallbackExecutor() {
         return mCallbackExecutor;
     }
@@ -803,26 +829,89 @@
 
         @Override
         public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
-            super.onCurrentDataSourceChanged(mpb, dsd);
-            // TODO(jaewan): Handle this b/74370608
+            MediaSession2Impl session = getSession();
+            if (session == null || dsd == null) {
+                return;
+            }
+            session.getCallbackExecutor().execute(() -> {
+                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)
+            });
         }
 
         @Override
         public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
-            super.onMediaPrepared(mpb, dsd);
-            // TODO(jaewan): Handle this b/74370608
+            MediaSession2Impl session = getSession();
+            if (session == null || dsd == null) {
+                return;
+            }
+            session.getCallbackExecutor().execute(() -> {
+                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)
+            });
         }
 
         @Override
         public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
-            super.onPlayerStateChanged(mpb, state);
-            // TODO(jaewan): Handle this b/74370608
+            MediaSession2Impl session = getSession();
+            if (session == null) {
+                return;
+            }
+            session.getCallbackExecutor().execute(() -> {
+                session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state);
+                session.getSessionStub().notifyPlayerStateChangedNotLocked(state);
+            });
         }
 
         @Override
         public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
-            super.onBufferingStateChanged(mpb, dsd, state);
-            // TODO(jaewan): Handle this b/74370608
+            MediaSession2Impl session = getSession();
+            if (session == null || dsd == null) {
+                return;
+            }
+            session.getCallbackExecutor().execute(() -> {
+                MediaItem2 item = getMediaItem(session, dsd);
+                if (item == null) {
+                    return;
+                }
+                session.getCallback().onBufferingStateChanged(
+                        session.getInstance(), mpb, item, state);
+                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+            });
+        }
+
+        private MediaSession2Impl getSession() {
+            final MediaSession2Impl session = mSession.get();
+            if (session == null && DEBUG) {
+                Log.d(TAG, "Session is closed", new IllegalStateException());
+            }
+            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;
         }
     }
 
@@ -836,27 +925,39 @@
         @Override
         public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
                 MediaMetadata2 metadata) {
-            super.onPlaylistChanged(playlistAgent, list, metadata);
-            // TODO(jaewan): Handle this (b/74326040)
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata);
         }
 
         @Override
         public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
                 MediaMetadata2 metadata) {
-            super.onPlaylistMetadataChanged(playlistAgent, metadata);
-            // TODO(jaewan): Handle this (b/74174649)
-        }
-
-        @Override
-        public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
-            super.onShuffleModeChanged(playlistAgent, shuffleMode);
-            // TODO(jaewan): Handle this (b/74118768)
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata);
         }
 
         @Override
         public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
-            super.onRepeatModeChanged(playlistAgent, repeatMode);
-            // TODO(jaewan): Handle this (b/74118768)
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            session.notifyRepeatModeChangedOnExecutor(playlistAgent, repeatMode);
+        }
+
+        @Override
+        public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            session.notifyShuffleModeChangedOnExecutor(playlistAgent, shuffleMode);
         }
     }
 
@@ -891,14 +992,17 @@
             mExtras = extras;
         }
 
+        @Override
         public int getCommandCode_impl() {
             return mCommandCode;
         }
 
+        @Override
         public @Nullable String getCustomCommand_impl() {
             return mCustomCommand;
         }
 
+        @Override
         public @Nullable Bundle getExtras_impl() {
             return mExtras;
         }
@@ -906,6 +1010,7 @@
         /**
          * @return a new Bundle instance from the Command
          */
+        @Override
         public Bundle toBundle_impl() {
             Bundle bundle = new Bundle();
             bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
@@ -959,6 +1064,16 @@
     public static class CommandGroupImpl implements CommandGroupProvider {
         private static final String KEY_COMMANDS =
                 "android.media.mediasession2.commandgroup.commands";
+
+        // Prefix for all command codes
+        private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
+
+        // Prefix for command codes that will be sent directly to the MediaPlayerBase
+        private static final String PREFIX_COMMAND_CODE_PLAYBACK = "COMMAND_CODE_PLAYBACK_";
+
+        // Prefix for command codes that will be sent directly to the MediaPlaylistAgent
+        private static final String PREFIX_COMMAND_CODE_PLAYLIST = "COMMAND_CODE_PLAYLIST_";
+
         private List<Command> mCommands = new ArrayList<>();
         private final Context mContext;
         private final CommandGroup mInstance;
@@ -971,6 +1086,11 @@
             }
         }
 
+        public CommandGroupImpl(Context context) {
+            mContext = context;
+            mInstance = new CommandGroup(this);
+        }
+
         @Override
         public void addCommand_impl(Command command) {
             if (command == null) {
@@ -981,8 +1101,30 @@
 
         @Override
         public void addAllPredefinedCommands_impl() {
-            for (int i = 1; i <= MediaSession2.COMMAND_CODE_MAX; i++) {
-                mCommands.add(new Command(mContext, i));
+            addCommandsWithPrefix(PREFIX_COMMAND_CODE);
+        }
+
+        public void addAllPlaybackCommands() {
+            addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYBACK);
+        }
+
+        public void addAllPlaylistCommands() {
+            addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYLIST);
+        }
+
+        private void addCommandsWithPrefix(String prefix) {
+            // TODO(jaewan): (Can be post-P): Don't use reflection for this purpose.
+            final Field[] fields = MediaSession2.class.getFields();
+            if (fields != null) {
+                for (int i = 0; i < fields.length; i++) {
+                    if (fields[i].getName().startsWith(prefix)) {
+                        try {
+                            mCommands.add(new Command(mContext, fields[i].getInt(null)));
+                        } catch (IllegalAccessException e) {
+                            Log.w(TAG, "Unexpected " + fields[i] + " in MediaSession2");
+                        }
+                    }
+                }
             }
         }
 
@@ -1017,7 +1159,11 @@
 
         @Override
         public List<Command> getCommands_impl() {
-            return mCommands;
+            return getCommands();
+        }
+
+        public List<Command> getCommands() {
+            return Collections.unmodifiableList(mCommands);
         }
 
         /**
@@ -1068,10 +1214,10 @@
         private final int mUid;
         private final String mPackageName;
         private final boolean mIsTrusted;
-        private final IMediaSession2Callback mControllerBinder;
+        private final IMediaController2 mControllerBinder;
 
         public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
-                int pid, String packageName, IMediaSession2Callback callback) {
+                int pid, String packageName, IMediaController2 callback) {
             if (TextUtils.isEmpty(packageName)) {
                 throw new IllegalArgumentException("packageName shouldn't be empty");
             }
@@ -1147,7 +1293,7 @@
             return mControllerBinder.asBinder();
         }
 
-        public IMediaSession2Callback getControllerBinder() {
+        public IMediaController2 getControllerBinder() {
             return mControllerBinder;
         }
 
@@ -1156,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 a9c5224..caf834a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -21,28 +21,31 @@
 import android.media.MediaController2;
 import android.media.MediaItem2;
 import android.media.MediaLibraryService2.LibraryRoot;
+import android.media.MediaMetadata2;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
 import android.media.Rating2;
 import android.media.VolumeProvider2;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.media.MediaLibraryService2Impl.MediaLibrarySessionImpl;
 import com.android.media.MediaSession2Impl.CommandButtonImpl;
+import com.android.media.MediaSession2Impl.CommandGroupImpl;
 import com.android.media.MediaSession2Impl.ControllerInfoImpl;
 
 import java.lang.ref.WeakReference;
@@ -61,6 +64,8 @@
     private static final String TAG = "MediaSession2Stub";
     private static final boolean DEBUG = true; // TODO(jaewan): Rename.
 
+    private static final SparseArray<Command> sCommandsForOnCommandRequest = new SparseArray<>();
+
     private final Object mLock = new Object();
     private final WeakReference<MediaSession2Impl> mSession;
 
@@ -75,6 +80,19 @@
 
     public MediaSession2Stub(MediaSession2Impl session) {
         mSession = new WeakReference<>(session);
+
+        synchronized (sCommandsForOnCommandRequest) {
+            if (sCommandsForOnCommandRequest.size() == 0) {
+                CommandGroupImpl group = new CommandGroupImpl(session.getContext());
+                group.addAllPlaybackCommands();
+                group.addAllPlaylistCommands();
+                List<Command> commands = group.getCommands();
+                for (int i = 0; i < commands.size(); i++) {
+                    Command command = commands.get(i);
+                    sCommandsForOnCommandRequest.append(command.getCommandCode(), command);
+                }
+            }
+        }
     }
 
     public void destroyNotLocked() {
@@ -85,7 +103,7 @@
             mControllers.clear();
         }
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback controllerBinder =
+            IMediaController2 controllerBinder =
                     ((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
             try {
                 // Should be used without a lock hold to prevent potential deadlock.
@@ -113,7 +131,7 @@
     }
 
     // Get controller if the command from caller to session is able to be handled.
-    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller) {
+    private ControllerInfo getControllerIfAble(IMediaController2 caller) {
         synchronized (mLock) {
             final ControllerInfo controllerInfo = mControllers.get(caller.asBinder());
             if (controllerInfo == null && DEBUG) {
@@ -124,7 +142,7 @@
     }
 
     // Get controller if the command from caller to session is able to be handled.
-    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, int commandCode) {
+    private ControllerInfo getControllerIfAble(IMediaController2 caller, int commandCode) {
         synchronized (mLock) {
             final ControllerInfo controllerInfo = getControllerIfAble(caller);
             if (controllerInfo == null) {
@@ -147,7 +165,7 @@
     }
 
     // Get controller if the command from caller to session is able to be handled.
-    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, Command command) {
+    private ControllerInfo getControllerIfAble(IMediaController2 caller, Command command) {
         synchronized (mLock) {
             final ControllerInfo controllerInfo = getControllerIfAble(caller);
             if (controllerInfo == null) {
@@ -170,7 +188,7 @@
     }
 
     // Return binder if the session is able to send a command to the controller.
-    private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller) {
+    private IMediaController2 getControllerBinderIfAble(ControllerInfo controller) {
         if (getSession() == null) {
             // getSession() already logged if session is closed.
             return null;
@@ -190,7 +208,7 @@
     }
 
     // Return binder if the session is able to send a command to the controller.
-    private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller,
+    private IMediaController2 getControllerBinderIfAble(ControllerInfo controller,
             int commandCode) {
         synchronized (mLock) {
             CommandGroup allowedCommands = mAllowedCommandGroupMap.get(controller);
@@ -208,11 +226,122 @@
         }
     }
 
+    private void onCommand(@NonNull IMediaController2 caller, int commandCode,
+            @NonNull SessionRunnable runnable) {
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(caller, commandCode);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, commandCode) == null) {
+                return;
+            }
+            Command command = sCommandsForOnCommandRequest.get(commandCode);
+            if (command != null) {
+                boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                        controller, command);
+                if (!accepted) {
+                    // Don't run rejected command.
+                    if (DEBUG) {
+                        Log.d(TAG, "Command (code=" + commandCode + ") from "
+                                + controller + " was rejected by " + session);
+                    }
+                    return;
+                }
+            }
+            runnable.run(session, controller);
+        });
+    }
+
+    private void onBrowserCommand(@NonNull IMediaController2 caller,
+            @NonNull LibrarySessionRunnable runnable) {
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller =
+                getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+                return;
+            }
+            runnable.run(session, controller);
+        });
+    }
+
+
+    private void notifyAll(int commandCode, @NonNull NotifyRunnable runnable) {
+        List<ControllerInfo> controllers = getControllers();
+        for (int i = 0; i < controllers.size(); i++) {
+            notifyInternal(controllers.get(i),
+                    getControllerBinderIfAble(controllers.get(i), commandCode), runnable);
+        }
+    }
+
+    private void notifyAll(@NonNull NotifyRunnable runnable) {
+        List<ControllerInfo> controllers = getControllers();
+        for (int i = 0; i < controllers.size(); i++) {
+            notifyInternal(controllers.get(i),
+                    getControllerBinderIfAble(controllers.get(i)), runnable);
+        }
+    }
+
+    private void notify(@NonNull ControllerInfo controller, @NonNull NotifyRunnable runnable) {
+        notifyInternal(controller, getControllerBinderIfAble(controller), runnable);
+    }
+
+    private void notify(@NonNull ControllerInfo controller, int commandCode,
+            @NonNull NotifyRunnable runnable) {
+        notifyInternal(controller, getControllerBinderIfAble(controller, commandCode), runnable);
+    }
+
+    // Do not call this API directly. Use notify() instead.
+    private void notifyInternal(@NonNull ControllerInfo controller,
+            @NonNull IMediaController2 iController, @NonNull NotifyRunnable runnable) {
+        if (controller == null || iController == null) {
+            return;
+        }
+        try {
+            runnable.run(controller, iController);
+        } catch (DeadObjectException e) {
+            if (DEBUG) {
+                Log.d(TAG, controller.toString() + " is gone", e);
+            }
+            onControllerClosed(iController);
+        } catch (RemoteException e) {
+            // Currently it's TransactionTooLargeException or DeadSystemException.
+            // We'd better to leave log for those cases because
+            //   - TransactionTooLargeException means that we may need to fix our code.
+            //     (e.g. add pagination or special way to deliver Bitmap)
+            //   - DeadSystemException means that errors around it can be ignored.
+            Log.w(TAG, "Exception in " + controller.toString(), e);
+        }
+    }
+
+    private void onControllerClosed(IMediaController2 iController) {
+        ControllerInfo controller;
+        synchronized (mLock) {
+            controller = mControllers.remove(iController.asBinder());
+            if (DEBUG) {
+                Log.d(TAG, "releasing " + controller);
+            }
+            mSubscriptions.remove(controller);
+        }
+        final MediaSession2Impl session = getSession();
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            session.getCallback().onDisconnected(session.getInstance(), controller);
+        });
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////////////
     // AIDL methods for session overrides
     //////////////////////////////////////////////////////////////////////////////////////////////
     @Override
-    public void connect(final IMediaSession2Callback caller, final String callingPackage)
+    public void connect(final IMediaController2 caller, final String callingPackage)
             throws RuntimeException {
         final MediaSession2Impl session = getSession();
         if (session == null) {
@@ -256,15 +385,17 @@
                 // It's needed because we cannot call synchronous calls between session/controller.
                 // Note: We're doing this after the onConnectionChanged(), but there's no guarantee
                 //       that events here are notified after the onConnected() because
-                //       IMediaSession2Callback is oneway (i.e. async call) and CallbackStub will
+                //       IMediaController2 is oneway (i.e. async call) and Stub will
                 //       use thread poll for incoming calls.
-                // TODO(jaewan): Should we protect getting playback state?
-                final PlaybackState2 state = session.getInstance().getPlaybackState();
-                final Bundle playbackStateBundle = (state != null) ? state.toBundle() : null;
+                final int playerState = session.getInstance().getPlayerState();
+                final long positionEventTimeMs = System.currentTimeMillis();
+                final long positionMs = session.getInstance().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)
@@ -292,9 +423,10 @@
                     return;
                 }
                 try {
-                    caller.onConnected(MediaSession2Stub.this,
-                            allowedCommands.toBundle(), playbackStateBundle, playbackInfoBundle,
-                            paramsBundle, playlistBundle, sessionActivity);
+                    caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
+                            playerState, positionEventTimeMs, positionMs, playbackSpeed,
+                            bufferedPositionMs, playbackInfoBundle, repeatMode, shuffleMode,
+                            playlistBundle, sessionActivity);
                 } catch (RemoteException e) {
                     // Controller may be died prematurely.
                     // TODO(jaewan): Handle here.
@@ -317,43 +449,14 @@
     }
 
     @Override
-    public void release(final IMediaSession2Callback caller) throws RemoteException {
-        synchronized (mLock) {
-            ControllerInfo controllerInfo = mControllers.remove(caller.asBinder());
-            if (DEBUG) {
-                Log.d(TAG, "releasing " + controllerInfo);
-            }
-            mSubscriptions.remove(controllerInfo);
-        }
+    public void release(final IMediaController2 caller) throws RemoteException {
+        onControllerClosed(caller);
     }
 
     @Override
-    public void setVolumeTo(final IMediaSession2Callback caller, final int value, final int flags)
+    public void setVolumeTo(final IMediaController2 caller, final int value, final int flags)
             throws RuntimeException {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
-                return;
-            }
-            // TODO(jaewan): Sanity check.
-            Command command = new Command(
-                    session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
-            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
-                    controller, command);
-            if (!accepted) {
-                // Don't run rejected command.
-                if (DEBUG) {
-                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
-                            + controller + " was rejected by " + session);
-                }
-                return;
-            }
-
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME, (session, controller) -> {
             VolumeProvider2 volumeProvider = session.getVolumeProvider();
             if (volumeProvider == null) {
                 // TODO(jaewan): Set local stream volume
@@ -364,32 +467,9 @@
     }
 
     @Override
-    public void adjustVolume(IMediaSession2Callback caller, int direction, int flags)
+    public void adjustVolume(IMediaController2 caller, int direction, int flags)
             throws RuntimeException {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
-                return;
-            }
-            // TODO(jaewan): Sanity check.
-            Command command = new Command(
-                    session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
-            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
-                    controller, command);
-            if (!accepted) {
-                // Don't run rejected command.
-                if (DEBUG) {
-                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
-                            + controller + " was rejected by " + session);
-                }
-                return;
-            }
-
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME, (session, controller) -> {
             VolumeProvider2 volumeProvider = session.getVolumeProvider();
             if (volumeProvider == null) {
                 // TODO(jaewan): Adjust local stream volume
@@ -400,30 +480,9 @@
     }
 
     @Override
-    public void sendTransportControlCommand(IMediaSession2Callback caller,
+    public void sendTransportControlCommand(IMediaController2 caller,
             int commandCode, Bundle args) throws RuntimeException {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(caller, commandCode);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, commandCode) == null) {
-                return;
-            }
-            // TODO(jaewan): Sanity check.
-            Command command = new Command(session.getContext(), commandCode);
-            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
-                    controller, command);
-            if (!accepted) {
-                // Don't run rejected command.
-                if (DEBUG) {
-                    Log.d(TAG, "Command " + commandCode + " from "
-                            + controller + " was rejected by " + session);
-                }
-                return;
-            }
-
+        onCommand(caller, commandCode, (session, controller) -> {
             switch (commandCode) {
                 case MediaSession2.COMMAND_CODE_PLAYBACK_PLAY:
                     session.getInstance().play();
@@ -434,12 +493,6 @@
                 case MediaSession2.COMMAND_CODE_PLAYBACK_STOP:
                     session.getInstance().stop();
                     break;
-                case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
-                    session.getInstance().skipToPreviousItem();
-                    break;
-                case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
-                    session.getInstance().skipToNextItem();
-                    break;
                 case MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE:
                     session.getInstance().prepare();
                     break;
@@ -452,21 +505,6 @@
                 case MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO:
                     session.getInstance().seekTo(args.getLong(ARGUMENT_KEY_POSITION));
                     break;
-                case MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM:
-                    // TODO(jaewan): Implement
-                    /*
-                    session.getInstance().skipToPlaylistItem(
-                            args.getInt(ARGUMENT_KEY_ITEM_INDEX));
-                    */
-                    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.
             }
@@ -474,7 +512,7 @@
     }
 
     @Override
-    public void sendCustomCommand(final IMediaSession2Callback caller, final Bundle commandBundle,
+    public void sendCustomCommand(final IMediaController2 caller, final Bundle commandBundle,
             final Bundle args, final ResultReceiver receiver) {
         final MediaSession2Impl session = getSession();
         if (session == null) {
@@ -500,44 +538,23 @@
     }
 
     @Override
-    public void prepareFromUri(final IMediaSession2Callback caller, final Uri uri,
+    public void prepareFromUri(final IMediaController2 caller, final Uri uri,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (uri == null) {
-            Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI, (session, controller) -> {
+            if (uri == null) {
+                Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
                 return;
             }
-            session.getCallback().onPrepareFromUri(session.getInstance(),
-                    controller, uri, extras);
+            session.getCallback().onPrepareFromUri(session.getInstance(), controller, uri, extras);
         });
     }
 
     @Override
-    public void prepareFromSearch(final IMediaSession2Callback caller, final String query,
+    public void prepareFromSearch(final IMediaController2 caller, final String query,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH, (session, controller) -> {
+            if (TextUtils.isEmpty(query)) {
+                Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
                 return;
             }
             session.getCallback().onPrepareFromSearch(session.getInstance(),
@@ -546,21 +563,12 @@
     }
 
     @Override
-    public void prepareFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+    public void prepareFromMediaId(final IMediaController2 caller, final String mediaId,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (mediaId == null) {
-            Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID,
+                (session, controller) -> {
+            if (mediaId == null) {
+                Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
                 return;
             }
             session.getCallback().onPrepareFromMediaId(session.getInstance(),
@@ -569,21 +577,11 @@
     }
 
     @Override
-    public void playFromUri(final IMediaSession2Callback caller, final Uri uri,
+    public void playFromUri(final IMediaController2 caller, final Uri uri,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (uri == null) {
-            Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI, (session, controller) -> {
+            if (uri == null) {
+                Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
                 return;
             }
             session.getCallback().onPlayFromUri(session.getInstance(), controller, uri, extras);
@@ -591,21 +589,11 @@
     }
 
     @Override
-    public void playFromSearch(final IMediaSession2Callback caller, final String query,
+    public void playFromSearch(final IMediaController2 caller, final String query,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(
-                    caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH) == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH, (session, controller) -> {
+            if (TextUtils.isEmpty(query)) {
+                Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
                 return;
             }
             session.getCallback().onPlayFromSearch(session.getInstance(),
@@ -614,20 +602,11 @@
     }
 
     @Override
-    public void playFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+    public void playFromMediaId(final IMediaController2 caller, final String mediaId,
             final Bundle extras) {
-        final MediaSession2Impl session = getSession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (mediaId == null) {
-            Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (session == null) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID, (session, controller) -> {
+            if (mediaId == null) {
+                Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
                 return;
             }
             session.getCallback().onPlayFromMediaId(session.getInstance(),
@@ -636,113 +615,188 @@
     }
 
     @Override
-    public void setRating(final IMediaSession2Callback caller, final String mediaId,
+    public void setRating(final IMediaController2 caller, final String mediaId,
             final Bundle ratingBundle) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getControllerIfAble(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+        // TODO(jaewan): Define COMMAND_CODE_SET_RATING
+        onCommand(caller, MediaSession2.COMMAND_CODE_SET_RATING, (session, controller) -> {
+            if (mediaId == null) {
+                Log.w(TAG, "setRating(): Ignoring null mediaId from " + controller);
+                return;
             }
-            return;
-        }
-        Rating2 rating = Rating2Impl.fromBundle(sessionImpl.getContext(), ratingBundle);
-        if (rating == null) {
-            Log.w(TAG, "setRating(): Ignore null rating");
-            return;
-        }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+            if (ratingBundle == null) {
+                Log.w(TAG, "setRating(): Ignoring null ratingBundle from " + controller);
+                return;
+            }
+            Rating2 rating = Rating2Impl.fromBundle(session.getContext(), ratingBundle);
+            if (rating == null) {
+                if (ratingBundle == null) {
+                    Log.w(TAG, "setRating(): Ignoring null rating from " + controller);
+                    return;
+                }
                 return;
             }
             session.getCallback().onSetRating(session.getInstance(), controller, mediaId, rating);
         });
     }
 
+    @Override
+    public void setPlaylist(final IMediaController2 caller, final List<Bundle> playlist,
+            final Bundle metadata) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST, (session, controller) -> {
+            if (playlist == null) {
+                Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
+                return;
+            }
+            List<MediaItem2> list = new ArrayList<>();
+            for (int i = 0; i < playlist.size(); i++) {
+                // Recreates UUID in the playlist
+                MediaItem2 item = MediaItem2Impl.fromBundle(
+                        session.getContext(), playlist.get(i), null);
+                if (item != null) {
+                    list.add(item);
+                }
+            }
+            session.getInstance().setPlaylist(list,
+                    MediaMetadata2.fromBundle(session.getContext(), metadata));
+        });
+    }
+
+    @Override
+    public void updatePlaylistMetadata(final IMediaController2 caller, final Bundle metadata) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA,
+                (session, controller) -> {
+            session.getInstance().updatePlaylistMetadata(
+                    MediaMetadata2.fromBundle(session.getContext(), metadata));
+        });
+    }
+
+    @Override
+    public void addPlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM, (session, controller) -> {
+            // Resets the UUID from the incoming media id, so controller may reuse a media item
+            // multiple times for addPlaylistItem.
+            session.getInstance().addPlaylistItem(index,
+                    MediaItem2Impl.fromBundle(session.getContext(), mediaItem, null));
+        });
+    }
+
+    @Override
+    public void removePlaylistItem(IMediaController2 caller, Bundle mediaItem) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM,
+                (session, controller) -> {
+            MediaItem2 item = MediaItem2.fromBundle(session.getContext(), mediaItem);
+            // Note: MediaItem2 has hidden UUID to identify it across the processes.
+            session.getInstance().removePlaylistItem(item);
+        });
+    }
+
+    @Override
+    public void replacePlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM,
+                (session, controller) -> {
+                    // Resets the UUID from the incoming media id, so controller may reuse a media
+                    // item multiple times for replacePlaylistItem.
+                    session.getInstance().replacePlaylistItem(index,
+                            MediaItem2Impl.fromBundle(session.getContext(), mediaItem, null));
+                });
+    }
+
+    @Override
+    public void skipToPlaylistItem(IMediaController2 caller, Bundle mediaItem) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM,
+                (session, controller) -> {
+                    if (mediaItem == null) {
+                        Log.w(TAG, "skipToPlaylistItem(): Ignoring null mediaItem from "
+                                + controller);
+                    }
+                    // Note: MediaItem2 has hidden UUID to identify it across the processes.
+                    session.getInstance().skipToPlaylistItem(
+                            MediaItem2.fromBundle(session.getContext(), mediaItem));
+                });
+    }
+
+    @Override
+    public void skipToPreviousItem(IMediaController2 caller) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM,
+                (session, controller) -> {
+                    session.getInstance().skipToPreviousItem();
+                });
+    }
+
+    @Override
+    public void skipToNextItem(IMediaController2 caller) {
+        onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM,
+                (session, controller) -> {
+                    session.getInstance().skipToNextItem();
+                });
+    }
+
+    @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
     //////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
-    public void getLibraryRoot(final IMediaSession2Callback caller, final Bundle rootHints)
+    public void getLibraryRoot(final IMediaController2 caller, final Bundle rootHints)
             throws RuntimeException {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
-                return;
-            }
-            LibraryRoot root = session.getCallback().onGetLibraryRoot(session.getInstance(),
+        onBrowserCommand(caller, (session, controller) -> {
+            final LibraryRoot root = session.getCallback().onGetLibraryRoot(session.getInstance(),
                     controller, rootHints);
-            try {
-                caller.onGetLibraryRootDone(rootHints,
+            notify(controller, (unused, iController) -> {
+                iController.onGetLibraryRootDone(rootHints,
                         root == null ? null : root.getRootId(),
                         root == null ? null : root.getExtras());
-            } catch (RemoteException e) {
-                // Controller may be died prematurely.
-                // TODO(jaewan): Handle this.
-            }
+            });
         });
     }
 
     @Override
-    public void getItem(final IMediaSession2Callback caller, final String mediaId)
+    public void getItem(final IMediaController2 caller, final String mediaId)
             throws RuntimeException {
-        if (mediaId == null) {
-            if (DEBUG) {
-                Log.d(TAG, "mediaId shouldn't be null");
-            }
-            return;
-        }
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (mediaId == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "mediaId shouldn't be null");
+                }
                 return;
             }
-            MediaItem2 result = session.getCallback().onGetItem(session.getInstance(),
+            final MediaItem2 result = session.getCallback().onGetItem(session.getInstance(),
                     controller, mediaId);
-            try {
-                caller.onGetItemDone(mediaId, result == null ? null : result.toBundle());
-            } catch (RemoteException e) {
-                // Controller may be died prematurely.
-                // TODO(jaewan): Handle this.
-            }
+            notify(controller, (unused, iController) -> {
+                iController.onGetItemDone(mediaId, result == null ? null : result.toBundle());
+            });
         });
     }
 
     @Override
-    public void getChildren(final IMediaSession2Callback caller, final String parentId,
+    public void getChildren(final IMediaController2 caller, final String parentId,
             final int page, final int pageSize, final Bundle extras) throws RuntimeException {
-        if (parentId == null) {
-            if (DEBUG) {
-                Log.d(TAG, "parentId shouldn't be null");
+        onBrowserCommand(caller, (session, controller) -> {
+            if (parentId == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "parentId shouldn't be null");
+                }
+                return;
             }
-            return;
-        }
-        if (page < 1 || pageSize < 1) {
-            if (DEBUG) {
-                Log.d(TAG, "Neither page nor pageSize should be less than 1");
-            }
-            return;
-        }
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+            if (page < 1 || pageSize < 1) {
+                if (DEBUG) {
+                    Log.d(TAG, "Neither page nor pageSize should be less than 1");
+                }
                 return;
             }
             List<MediaItem2> result = session.getCallback().onGetChildren(session.getInstance(),
@@ -752,36 +806,26 @@
                         + "more than pageSize. result.size()=" + result.size() + " pageSize="
                         + pageSize);
             }
-            List<Bundle> bundleList = null;
+            final List<Bundle> bundleList;
             if (result != null) {
                 bundleList = new ArrayList<>();
                 for (MediaItem2 item : result) {
                     bundleList.add(item == null ? null : item.toBundle());
                 }
+            } else {
+                bundleList = null;
             }
-            try {
-                caller.onGetChildrenDone(parentId, page, pageSize, bundleList, extras);
-            } catch (RemoteException e) {
-                // Controller may be died prematurely.
-                // TODO(jaewan): Handle this.
-            }
+            notify(controller, (unused, iController) -> {
+                iController.onGetChildrenDone(parentId, page, pageSize, bundleList, extras);
+            });
         });
     }
 
     @Override
-    public void search(IMediaSession2Callback caller, String query, Bundle extras) {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "search(): Ignoring empty query from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+    public void search(IMediaController2 caller, String query, Bundle extras) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (TextUtils.isEmpty(query)) {
+                Log.w(TAG, "search(): Ignoring empty query from " + controller);
                 return;
             }
             session.getCallback().onSearch(session.getInstance(), controller, query, extras);
@@ -789,25 +833,16 @@
     }
 
     @Override
-    public void getSearchResult(final IMediaSession2Callback caller, final String query,
+    public void getSearchResult(final IMediaController2 caller, final String query,
             final int page, final int pageSize, final Bundle extras) {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (TextUtils.isEmpty(query)) {
-            Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
-            return;
-        }
-        if (page < 1 || pageSize < 1) {
-            Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
-                    + " page=" + page + " pageSize=" + pageSize + " from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (TextUtils.isEmpty(query)) {
+                Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
+                return;
+            }
+            if (page < 1 || pageSize < 1) {
+                Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
+                        + " page=" + page + " pageSize=" + pageSize + " from " + controller);
                 return;
             }
             List<MediaItem2> result = session.getCallback().onGetSearchResult(session.getInstance(),
@@ -817,38 +852,27 @@
                         + "items more than pageSize. result.size()=" + result.size() + " pageSize="
                         + pageSize);
             }
-            List<Bundle> bundleList = null;
+            final List<Bundle> bundleList;
             if (result != null) {
                 bundleList = new ArrayList<>();
                 for (MediaItem2 item : result) {
                     bundleList.add(item == null ? null : item.toBundle());
                 }
+            } else {
+                bundleList = null;
             }
-
-            try {
-                caller.onGetSearchResultDone(query, page, pageSize, bundleList, extras);
-            } catch (RemoteException e) {
-                // Controller may be died prematurely.
-                // TODO(jaewan): Handle this.
-            }
+            notify(controller, (unused, iController) -> {
+                iController.onGetSearchResultDone(query, page, pageSize, bundleList, extras);
+            });
         });
     }
 
     @Override
-    public void subscribe(final IMediaSession2Callback caller, final String parentId,
+    public void subscribe(final IMediaController2 caller, final String parentId,
             final Bundle option) {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (parentId == null) {
-            Log.w(TAG, "subscribe(): Ignoring null parentId from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (parentId == null) {
+                Log.w(TAG, "subscribe(): Ignoring null parentId from " + controller);
                 return;
             }
             session.getCallback().onSubscribe(session.getInstance(),
@@ -865,19 +889,10 @@
     }
 
     @Override
-    public void unsubscribe(final IMediaSession2Callback caller, final String parentId) {
-        final MediaLibrarySessionImpl session = getLibrarySession();
-        final ControllerInfo controller = getControllerIfAble(
-                caller, MediaSession2.COMMAND_CODE_BROWSER);
-        if (session == null || controller == null) {
-            return;
-        }
-        if (parentId == null) {
-            Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
-            return;
-        }
-        session.getCallbackExecutor().execute(() -> {
-            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+    public void unsubscribe(final IMediaController2 caller, final String parentId) {
+        onBrowserCommand(caller, (session, controller) -> {
+            if (parentId == null) {
+                Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
                 return;
             }
             session.getCallback().onUnsubscribe(session.getInstance(), controller, parentId);
@@ -891,7 +906,7 @@
     // APIs for MediaSession2Impl
     //////////////////////////////////////////////////////////////////////////////////////////////
 
-    // TODO(jaewan): Need a way to get controller with permissions
+    // TODO(jaewan): (Can be Post-P) Need a way to get controller with permissions
     public List<ControllerInfo> getControllers() {
         ArrayList<ControllerInfo> controllers = new ArrayList<>();
         synchronized (mLock) {
@@ -903,29 +918,32 @@
     }
 
     // Should be used without a lock to prevent potential deadlock.
-    public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
-        final List<ControllerInfo> list = getControllers();
-        for (int i = 0; i < list.size(); i++) {
-            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
-            if (controllerBinder == null) {
-                return;
-            }
-            try {
-                final Bundle bundle = state != null ? state.toBundle() : null;
-                controllerBinder.onPlaybackStateChanged(bundle);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Controller is gone", e);
-                // TODO(jaewan): What to do when the controller is gone?
-            }
-        }
+    public void notifyPlayerStateChangedNotLocked(int state) {
+        notifyAll((controller, iController) -> {
+            iController.onPlayerStateChanged(state);
+        });
+    }
+
+    public void notifyPositionChangedNotLocked(long eventTimeMs, long positionMs) {
+        notifyAll((controller, iController) -> {
+            iController.onPositionChanged(eventTimeMs, positionMs);
+        });
+    }
+
+    public void notifyPlaybackSpeedChangedNotLocked(float speed) {
+        notifyAll((controller, iController) -> {
+            iController.onPlaybackSpeedChanged(speed);
+        });
+    }
+
+    public void notifyBufferedPositionChangedNotLocked(long bufferedPositionMs) {
+        notifyAll((controller, iController) -> {
+            iController.onBufferedPositionChanged(bufferedPositionMs);
+        });
     }
 
     public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
-        if (controllerBinder == null) {
-            return;
-        }
-        try {
+        notify(controller, (unused, iController) -> {
             List<Bundle> layoutBundles = new ArrayList<>();
             for (int i = 0; i < layout.size(); i++) {
                 Bundle bundle = ((CommandButtonImpl) layout.get(i).getProvider()).toBundle();
@@ -933,85 +951,72 @@
                     layoutBundles.add(bundle);
                 }
             }
-            controllerBinder.onCustomLayoutChanged(layoutBundles);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Controller is gone", e);
-            // TODO(jaewan): What to do when the controller is gone?
-        }
+            iController.onCustomLayoutChanged(layoutBundles);
+        });
     }
 
-    public void notifyPlaylistChanged(List<MediaItem2> playlist) {
-        final List<Bundle> bundleList = new ArrayList<>();
-        for (int i = 0; i < playlist.size(); i++) {
-            if (playlist.get(i) != null) {
-                Bundle bundle = playlist.get(i).toBundle();
-                if (bundle != null) {
-                    bundleList.add(bundle);
+    public void notifyPlaylistChangedNotLocked(List<MediaItem2> playlist, MediaMetadata2 metadata) {
+        final List<Bundle> bundleList;
+        if (playlist != null) {
+            bundleList = new ArrayList<>();
+            for (int i = 0; i < playlist.size(); i++) {
+                if (playlist.get(i) != null) {
+                    Bundle bundle = playlist.get(i).toBundle();
+                    if (bundle != null) {
+                        bundleList.add(bundle);
+                    }
                 }
             }
+        } else {
+            bundleList = null;
         }
-        final List<ControllerInfo> list = getControllers();
-        for (int i = 0; i < list.size(); i++) {
-            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(
-                    list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST);
-            if (controllerBinder != null) {
-                try {
-                    controllerBinder.onPlaylistChanged(bundleList);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Controller is gone", e);
-                    // TODO(jaewan): What to do when the controller is gone?
-                }
+        final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+        notifyAll((controller, iController) -> {
+            if (getControllerBinderIfAble(controller,
+                    MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST) != null) {
+                iController.onPlaylistChanged(bundleList, metadataBundle);
+            } else if (getControllerBinderIfAble(controller,
+                    MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA) != null) {
+                iController.onPlaylistMetadataChanged(metadataBundle);
             }
-        }
+        });
     }
 
-    public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
-        final List<ControllerInfo> list = getControllers();
-        for (int i = 0; i < list.size(); i++) {
-            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
-            if (controllerBinder == null) {
-                return;
-            }
-            try {
-                controllerBinder.onPlaylistParamsChanged(params.toBundle());
-            } catch (RemoteException e) {
-                Log.w(TAG, "Controller is gone", e);
-                // TODO(jaewan): What to do when the controller is gone?
-            }
-        }
+    public void notifyPlaylistMetadataChangedNotLocked(MediaMetadata2 metadata) {
+        final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+        notifyAll(MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA,
+                (unused, iController) -> {
+                    iController.onPlaylistMetadataChanged(metadataBundle);
+                });
+    }
+
+    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 List<ControllerInfo> list = getControllers();
-        for (int i = 0; i < list.size(); i++) {
-            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
-            if (controllerBinder == null) {
-                return;
-            }
-            try {
-                controllerBinder.onPlaybackInfoChanged(((MediaController2Impl.PlaybackInfoImpl)
-                        playbackInfo.getProvider()).toBundle());
-            } catch (RemoteException e) {
-                Log.w(TAG, "Controller is gone", e);
-                // TODO(jaewan): What to do when the controller is gone?
-            }
-        }
+        final Bundle playbackInfoBundle =
+                ((MediaController2Impl.PlaybackInfoImpl) playbackInfo.getProvider()).toBundle();
+        notifyAll((unused, iController) -> {
+            iController.onPlaybackInfoChanged(playbackInfoBundle);
+        });
     }
 
     public void setAllowedCommands(ControllerInfo controller, CommandGroup commands) {
         synchronized (mLock) {
             mAllowedCommandGroupMap.put(controller, commands);
         }
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
-        if (controllerBinder == null) {
-            return;
-        }
-        try {
-            controllerBinder.onAllowedCommandsChanged(commands.toBundle());
-        } catch (RemoteException e) {
-            Log.w(TAG, "Controller is gone", e);
-            // TODO(jaewan): What to do when the controller is gone?
-        }
+        notify(controller, (unused, iController) -> {
+            iController.onAllowedCommandsChanged(commands.toBundle());
+        });
     }
 
     public void sendCustomCommand(ControllerInfo controller, Command command, Bundle args,
@@ -1023,32 +1028,26 @@
         if (command == null) {
             throw new IllegalArgumentException("command shouldn't be null");
         }
-        sendCustomCommandInternal(controller, command, args, receiver);
+        notify(controller, (unused, iController) -> {
+            Bundle commandBundle = command.toBundle();
+            iController.onCustomCommand(commandBundle, args, null);
+        });
     }
 
     public void sendCustomCommand(Command command, Bundle args) {
         if (command == null) {
             throw new IllegalArgumentException("command shouldn't be null");
         }
-        final List<ControllerInfo> controllers = getControllers();
-        for (int i = 0; i < controllers.size(); i++) {
-            sendCustomCommand(controllers.get(i), command, args, null);
-        }
+        Bundle commandBundle = command.toBundle();
+        notifyAll((unused, iController) -> {
+            iController.onCustomCommand(commandBundle, args, null);
+        });
     }
 
-    private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
-            ResultReceiver receiver) {
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
-        if (controllerBinder == null) {
-            return;
-        }
-        try {
-            Bundle commandBundle = command.toBundle();
-            controllerBinder.onCustomCommand(commandBundle, args, receiver);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Controller is gone", e);
-            // TODO(jaewan): What to do when the controller is gone?
-        }
+    public void notifyError(int errorCode, Bundle extras) {
+        notifyAll((unused, iController) -> {
+            iController.onError(errorCode, extras);
+        });
     }
 
     //////////////////////////////////////////////////////////////////////////////////////////////
@@ -1057,48 +1056,55 @@
 
     public void notifySearchResultChanged(ControllerInfo controller, String query, int itemCount,
             Bundle extras) {
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
-        if (controllerBinder == null) {
-            return;
-        }
-        try {
-            controllerBinder.onSearchResultChanged(query, itemCount, extras);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Controller is gone", e);
-            // TODO(jaewan): What to do when the controller is gone?
-        }
+        notify(controller, (unused, iController) -> {
+            iController.onSearchResultChanged(query, itemCount, extras);
+        });
     }
 
     public void notifyChildrenChangedNotLocked(ControllerInfo controller, String parentId,
             int itemCount, Bundle extras) {
-        notifyChildrenChangedInternalNotLocked(controller, parentId, itemCount, extras);
+        notify(controller, (unused, iController) -> {
+            if (isSubscribed(controller, parentId)) {
+                iController.onChildrenChanged(parentId, itemCount, extras);
+            }
+        });
     }
 
     public void notifyChildrenChangedNotLocked(String parentId, int itemCount, Bundle extras) {
-        final List<ControllerInfo> controllers = getControllers();
-        for (int i = 0; i < controllers.size(); i++) {
-            notifyChildrenChangedInternalNotLocked(controllers.get(i), parentId, itemCount,
-                    extras);
-        }
+        notifyAll((controller, iController) -> {
+            if (isSubscribed(controller, parentId)) {
+                iController.onChildrenChanged(parentId, itemCount, extras);
+            }
+        });
     }
 
-    public void notifyChildrenChangedInternalNotLocked(final ControllerInfo controller,
-            String parentId, int itemCount, Bundle extras) {
-        // Ensure subscription
+    private boolean isSubscribed(ControllerInfo controller, String parentId) {
         synchronized (mLock) {
             Set<String> subscriptions = mSubscriptions.get(controller);
             if (subscriptions == null || !subscriptions.contains(parentId)) {
-                return;
+                return false;
             }
         }
-        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
-        if (controller == null) {
-            return;
-        }
-        try {
-            controllerBinder.onChildrenChanged(parentId, itemCount, extras);
-        } catch (RemoteException e) {
-            // TODO(jaewan): Handle controller removed?
-        }
+        return true;
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // Misc
+    //////////////////////////////////////////////////////////////////////////////////////////////
+
+    @FunctionalInterface
+    private interface SessionRunnable {
+        void run(final MediaSession2Impl session, final ControllerInfo controller);
+    }
+
+    @FunctionalInterface
+    private interface LibrarySessionRunnable {
+        void run(final MediaLibrarySessionImpl session, final ControllerInfo controller);
+    }
+
+    @FunctionalInterface
+    private interface NotifyRunnable {
+        void run(final ControllerInfo controller,
+                final IMediaController2 iController) throws RemoteException;
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 6bd2b2a..a0123b5 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -27,10 +27,8 @@
 import android.media.MediaSession2;
 import android.media.MediaSessionService2;
 import android.media.MediaSessionService2.MediaNotification;
-import android.media.PlaybackState2;
 import android.media.SessionToken2;
 import android.media.SessionToken2.TokenType;
-import android.media.session.PlaybackState;
 import android.media.update.MediaSessionService2Provider;
 import android.os.IBinder;
 import android.support.annotation.GuardedBy;
@@ -109,13 +107,13 @@
         return null;
     }
 
-    private void updateNotification(PlaybackState2 state) {
+    private void updateNotification(int playerState) {
         MediaNotification mediaNotification = mInstance.onUpdateNotification();
         if (mediaNotification == null) {
             return;
         }
-        switch((int) state.getState()) {
-            case PlaybackState.STATE_PLAYING:
+        switch(playerState) {
+            case MediaPlayerBase.PLAYER_STATE_PLAYING:
                 if (!mIsRunningForeground) {
                     mIsRunningForeground = true;
                     mInstance.startForegroundService(mStartSelfIntent);
@@ -124,7 +122,8 @@
                     return;
                 }
                 break;
-            case PlaybackState.STATE_STOPPED:
+            case MediaPlayerBase.PLAYER_STATE_IDLE:
+            case MediaPlayerBase.PLAYER_STATE_ERROR:
                 if (mIsRunningForeground) {
                     mIsRunningForeground = false;
                     mInstance.stopForeground(true);
@@ -142,15 +141,6 @@
             // TODO: Implement this
             return;
         }
-        // TODO: Uncomment or remove
-        //public void onPlaybackStateChanged(PlaybackState2 state) {
-        //    if (state == null) {
-        //        Log.w(TAG, "Ignoring null playback state");
-        //        return;
-        //    }
-        //    MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
-        //    updateNotification(impl.getInstance().getPlaybackState());
-        //}
     }
 
     public static class MediaNotificationImpl implements MediaNotificationProvider {
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java b/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
deleted file mode 100644
index ee8d6d7..0000000
--- a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media;
-
-import android.content.Context;
-import android.media.PlaybackState2;
-import android.media.update.PlaybackState2Provider;
-import android.os.Bundle;
-
-public final class PlaybackState2Impl implements PlaybackState2Provider {
-    /**
-     * Keys used for converting a PlaybackState2 to a bundle object and vice versa.
-     */
-    private static final String KEY_STATE = "android.media.playbackstate2.state";
-    private static final String KEY_POSITION = "android.media.playbackstate2.position";
-    private static final String KEY_BUFFERED_POSITION =
-            "android.media.playbackstate2.buffered_position";
-    private static final String KEY_SPEED = "android.media.playbackstate2.speed";
-    private static final String KEY_UPDATE_TIME = "android.media.playbackstate2.update_time";
-    private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id";
-
-    private final Context mContext;
-    private final PlaybackState2 mInstance;
-    private final int mState;
-    private final long mPosition;
-    private final long mUpdateTime;
-    private final float mSpeed;
-    private final long mBufferedPosition;
-    private final long mActiveItemId;
-
-    public PlaybackState2Impl(Context context, PlaybackState2 instance, int state, long position,
-            long updateTime, float speed, long bufferedPosition, long activeItemId) {
-        mContext = context;
-        mInstance = instance;
-        mState = state;
-        mPosition = position;
-        mSpeed = speed;
-        mUpdateTime = updateTime;
-        mBufferedPosition = bufferedPosition;
-        mActiveItemId = activeItemId;
-    }
-
-    @Override
-    public String toString_impl() {
-        StringBuilder bob = new StringBuilder("PlaybackState {");
-        bob.append("state=").append(mState);
-        bob.append(", position=").append(mPosition);
-        bob.append(", buffered position=").append(mBufferedPosition);
-        bob.append(", speed=").append(mSpeed);
-        bob.append(", updated=").append(mUpdateTime);
-        bob.append(", active item id=").append(mActiveItemId);
-        bob.append("}");
-        return bob.toString();
-    }
-
-    @Override
-    public int getState_impl() {
-        return mState;
-    }
-
-    @Override
-    public long getPosition_impl() {
-        return mPosition;
-    }
-
-    @Override
-    public long getBufferedPosition_impl() {
-        return mBufferedPosition;
-    }
-
-    @Override
-    public float getPlaybackSpeed_impl() {
-        return mSpeed;
-    }
-
-    @Override
-    public long getLastPositionUpdateTime_impl() {
-        return mUpdateTime;
-    }
-
-    @Override
-    public long getCurrentPlaylistItemIndex_impl() {
-        return mActiveItemId;
-    }
-
-    @Override
-    public Bundle toBundle_impl() {
-        Bundle bundle = new Bundle();
-        bundle.putInt(KEY_STATE, mState);
-        bundle.putLong(KEY_POSITION, mPosition);
-        bundle.putLong(KEY_UPDATE_TIME, mUpdateTime);
-        bundle.putFloat(KEY_SPEED, mSpeed);
-        bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition);
-        bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId);
-        return bundle;
-    }
-
-    public static PlaybackState2 fromBundle(Context context, Bundle bundle) {
-        if (bundle == null) {
-            return null;
-        }
-        if (!bundle.containsKey(KEY_STATE)
-                || !bundle.containsKey(KEY_POSITION)
-                || !bundle.containsKey(KEY_UPDATE_TIME)
-                || !bundle.containsKey(KEY_SPEED)
-                || !bundle.containsKey(KEY_BUFFERED_POSITION)
-                || !bundle.containsKey(KEY_ACTIVE_ITEM_ID)) {
-            return null;
-        }
-        return new PlaybackState2(context,
-                bundle.getInt(KEY_STATE),
-                bundle.getLong(KEY_POSITION),
-                bundle.getLong(KEY_UPDATE_TIME),
-                bundle.getFloat(KEY_SPEED),
-                bundle.getLong(KEY_BUFFERED_POSITION),
-                bundle.getLong(KEY_ACTIVE_ITEM_ID));
-    }
-}
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/media/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/subtitle/ClosedCaptionRenderer.java b/packages/MediaComponents/src/com/android/media/subtitle/ClosedCaptionRenderer.java
new file mode 100644
index 0000000..ff7eec9
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/subtitle/ClosedCaptionRenderer.java
@@ -0,0 +1,1501 @@
+/*
+ * 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.subtitle;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.media.MediaFormat;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.style.CharacterStyle;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.text.style.UpdateAppearance;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.CaptioningManager;
+import android.view.accessibility.CaptioningManager.CaptionStyle;
+import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Vector;
+
+// Note: This is forked from android.media.ClosedCaptionRenderer since P
+public class ClosedCaptionRenderer extends SubtitleController.Renderer {
+    private final Context mContext;
+    private Cea608CCWidget mCCWidget;
+
+    public ClosedCaptionRenderer(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public boolean supports(MediaFormat format) {
+        if (format.containsKey(MediaFormat.KEY_MIME)) {
+            String mimeType = format.getString(MediaFormat.KEY_MIME);
+            return MediaFormat.MIMETYPE_TEXT_CEA_608.equals(mimeType);
+        }
+        return false;
+    }
+
+    @Override
+    public SubtitleTrack createTrack(MediaFormat format) {
+        String mimeType = format.getString(MediaFormat.KEY_MIME);
+        if (MediaFormat.MIMETYPE_TEXT_CEA_608.equals(mimeType)) {
+            if (mCCWidget == null) {
+                mCCWidget = new Cea608CCWidget(mContext);
+            }
+            return new Cea608CaptionTrack(mCCWidget, format);
+        }
+        throw new RuntimeException("No matching format: " + format.toString());
+    }
+}
+
+class Cea608CaptionTrack extends SubtitleTrack {
+    private final Cea608CCParser mCCParser;
+    private final Cea608CCWidget mRenderingWidget;
+
+    Cea608CaptionTrack(Cea608CCWidget renderingWidget, MediaFormat format) {
+        super(format);
+
+        mRenderingWidget = renderingWidget;
+        mCCParser = new Cea608CCParser(mRenderingWidget);
+    }
+
+    @Override
+    public void onData(byte[] data, boolean eos, long runID) {
+        mCCParser.parse(data);
+    }
+
+    @Override
+    public RenderingWidget getRenderingWidget() {
+        return mRenderingWidget;
+    }
+
+    @Override
+    public void updateView(Vector<Cue> activeCues) {
+        // Overriding with NO-OP, CC rendering by-passes this
+    }
+}
+
+/**
+ * Abstract widget class to render a closed caption track.
+ */
+abstract class ClosedCaptionWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
+
+    interface ClosedCaptionLayout {
+        void setCaptionStyle(CaptionStyle captionStyle);
+        void setFontScale(float scale);
+    }
+
+    private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT;
+
+    /** Captioning manager, used to obtain and track caption properties. */
+    private final CaptioningManager mManager;
+
+    /** Current caption style. */
+    protected CaptionStyle mCaptionStyle;
+
+    /** Callback for rendering changes. */
+    protected OnChangedListener mListener;
+
+    /** Concrete layout of CC. */
+    protected ClosedCaptionLayout mClosedCaptionLayout;
+
+    /** Whether a caption style change listener is registered. */
+    private boolean mHasChangeListener;
+
+    public ClosedCaptionWidget(Context context) {
+        this(context, null);
+    }
+
+    public ClosedCaptionWidget(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, 0);
+    }
+
+    public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        // Cannot render text over video when layer type is hardware.
+        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+        mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
+        mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(mManager.getUserStyle());
+
+        mClosedCaptionLayout = createCaptionLayout(context);
+        mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
+        mClosedCaptionLayout.setFontScale(mManager.getFontScale());
+        addView((ViewGroup) mClosedCaptionLayout, LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT);
+
+        requestLayout();
+    }
+
+    public abstract ClosedCaptionLayout createCaptionLayout(Context context);
+
+    @Override
+    public void setOnChangedListener(OnChangedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void setSize(int width, int height) {
+        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+        measure(widthSpec, heightSpec);
+        layout(0, 0, width, height);
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            setVisibility(View.VISIBLE);
+        } else {
+            setVisibility(View.GONE);
+        }
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        manageChangeListener();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        ((ViewGroup) mClosedCaptionLayout).measure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        ((ViewGroup) mClosedCaptionLayout).layout(l, t, r, b);
+    }
+
+    /**
+     * Manages whether this renderer is listening for caption style changes.
+     */
+    private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
+        @Override
+        public void onUserStyleChanged(CaptionStyle userStyle) {
+            mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(userStyle);
+            mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
+        }
+
+        @Override
+        public void onFontScaleChanged(float fontScale) {
+            mClosedCaptionLayout.setFontScale(fontScale);
+        }
+    };
+
+    private void manageChangeListener() {
+        final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
+        if (mHasChangeListener != needsListener) {
+            mHasChangeListener = needsListener;
+
+            if (needsListener) {
+                mManager.addCaptioningChangeListener(mCaptioningListener);
+            } else {
+                mManager.removeCaptioningChangeListener(mCaptioningListener);
+            }
+        }
+    }
+}
+
+/**
+ * CCParser processes CEA-608 closed caption data.
+ *
+ * It calls back into OnDisplayChangedListener upon
+ * display change with styled text for rendering.
+ *
+ */
+class Cea608CCParser {
+    public static final int MAX_ROWS = 15;
+    public static final int MAX_COLS = 32;
+
+    private static final String TAG = "Cea608CCParser";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int INVALID = -1;
+
+    // EIA-CEA-608: Table 70 - Control Codes
+    private static final int RCL = 0x20;
+    private static final int BS  = 0x21;
+    private static final int AOF = 0x22;
+    private static final int AON = 0x23;
+    private static final int DER = 0x24;
+    private static final int RU2 = 0x25;
+    private static final int RU3 = 0x26;
+    private static final int RU4 = 0x27;
+    private static final int FON = 0x28;
+    private static final int RDC = 0x29;
+    private static final int TR  = 0x2a;
+    private static final int RTD = 0x2b;
+    private static final int EDM = 0x2c;
+    private static final int CR  = 0x2d;
+    private static final int ENM = 0x2e;
+    private static final int EOC = 0x2f;
+
+    // Transparent Space
+    private static final char TS = '\u00A0';
+
+    // Captioning Modes
+    private static final int MODE_UNKNOWN = 0;
+    private static final int MODE_PAINT_ON = 1;
+    private static final int MODE_ROLL_UP = 2;
+    private static final int MODE_POP_ON = 3;
+    private static final int MODE_TEXT = 4;
+
+    private final DisplayListener mListener;
+
+    private int mMode = MODE_PAINT_ON;
+    private int mRollUpSize = 4;
+    private int mPrevCtrlCode = INVALID;
+
+    private CCMemory mDisplay = new CCMemory();
+    private CCMemory mNonDisplay = new CCMemory();
+    private CCMemory mTextMem = new CCMemory();
+
+    Cea608CCParser(DisplayListener listener) {
+        mListener = listener;
+    }
+
+    public void parse(byte[] data) {
+        CCData[] ccData = CCData.fromByteArray(data);
+
+        for (int i = 0; i < ccData.length; i++) {
+            if (DEBUG) {
+                Log.d(TAG, ccData[i].toString());
+            }
+
+            if (handleCtrlCode(ccData[i])
+                    || handleTabOffsets(ccData[i])
+                    || handlePACCode(ccData[i])
+                    || handleMidRowCode(ccData[i])) {
+                continue;
+            }
+
+            handleDisplayableChars(ccData[i]);
+        }
+    }
+
+    interface DisplayListener {
+        void onDisplayChanged(SpannableStringBuilder[] styledTexts);
+        CaptionStyle getCaptionStyle();
+    }
+
+    private CCMemory getMemory() {
+        // get the CC memory to operate on for current mode
+        switch (mMode) {
+        case MODE_POP_ON:
+            return mNonDisplay;
+        case MODE_TEXT:
+            // TODO(chz): support only caption mode for now,
+            // in text mode, dump everything to text mem.
+            return mTextMem;
+        case MODE_PAINT_ON:
+        case MODE_ROLL_UP:
+            return mDisplay;
+        default:
+            Log.w(TAG, "unrecoginized mode: " + mMode);
+        }
+        return mDisplay;
+    }
+
+    private boolean handleDisplayableChars(CCData ccData) {
+        if (!ccData.isDisplayableChar()) {
+            return false;
+        }
+
+        // Extended char includes 1 automatic backspace
+        if (ccData.isExtendedChar()) {
+            getMemory().bs();
+        }
+
+        getMemory().writeText(ccData.getDisplayText());
+
+        if (mMode == MODE_PAINT_ON || mMode == MODE_ROLL_UP) {
+            updateDisplay();
+        }
+
+        return true;
+    }
+
+    private boolean handleMidRowCode(CCData ccData) {
+        StyleCode m = ccData.getMidRow();
+        if (m != null) {
+            getMemory().writeMidRowCode(m);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean handlePACCode(CCData ccData) {
+        PAC pac = ccData.getPAC();
+
+        if (pac != null) {
+            if (mMode == MODE_ROLL_UP) {
+                getMemory().moveBaselineTo(pac.getRow(), mRollUpSize);
+            }
+            getMemory().writePAC(pac);
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean handleTabOffsets(CCData ccData) {
+        int tabs = ccData.getTabOffset();
+
+        if (tabs > 0) {
+            getMemory().tab(tabs);
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean handleCtrlCode(CCData ccData) {
+        int ctrlCode = ccData.getCtrlCode();
+
+        if (mPrevCtrlCode != INVALID && mPrevCtrlCode == ctrlCode) {
+            // discard double ctrl codes (but if there's a 3rd one, we still take that)
+            mPrevCtrlCode = INVALID;
+            return true;
+        }
+
+        switch(ctrlCode) {
+        case RCL:
+            // select pop-on style
+            mMode = MODE_POP_ON;
+            break;
+        case BS:
+            getMemory().bs();
+            break;
+        case DER:
+            getMemory().der();
+            break;
+        case RU2:
+        case RU3:
+        case RU4:
+            mRollUpSize = (ctrlCode - 0x23);
+            // erase memory if currently in other style
+            if (mMode != MODE_ROLL_UP) {
+                mDisplay.erase();
+                mNonDisplay.erase();
+            }
+            // select roll-up style
+            mMode = MODE_ROLL_UP;
+            break;
+        case FON:
+            Log.i(TAG, "Flash On");
+            break;
+        case RDC:
+            // select paint-on style
+            mMode = MODE_PAINT_ON;
+            break;
+        case TR:
+            mMode = MODE_TEXT;
+            mTextMem.erase();
+            break;
+        case RTD:
+            mMode = MODE_TEXT;
+            break;
+        case EDM:
+            // erase display memory
+            mDisplay.erase();
+            updateDisplay();
+            break;
+        case CR:
+            if (mMode == MODE_ROLL_UP) {
+                getMemory().rollUp(mRollUpSize);
+            } else {
+                getMemory().cr();
+            }
+            if (mMode == MODE_ROLL_UP) {
+                updateDisplay();
+            }
+            break;
+        case ENM:
+            // erase non-display memory
+            mNonDisplay.erase();
+            break;
+        case EOC:
+            // swap display/non-display memory
+            swapMemory();
+            // switch to pop-on style
+            mMode = MODE_POP_ON;
+            updateDisplay();
+            break;
+        case INVALID:
+        default:
+            mPrevCtrlCode = INVALID;
+            return false;
+        }
+
+        mPrevCtrlCode = ctrlCode;
+
+        // handled
+        return true;
+    }
+
+    private void updateDisplay() {
+        if (mListener != null) {
+            CaptionStyle captionStyle = mListener.getCaptionStyle();
+            mListener.onDisplayChanged(mDisplay.getStyledText(captionStyle));
+        }
+    }
+
+    private void swapMemory() {
+        CCMemory temp = mDisplay;
+        mDisplay = mNonDisplay;
+        mNonDisplay = temp;
+    }
+
+    private static class StyleCode {
+        static final int COLOR_WHITE = 0;
+        static final int COLOR_GREEN = 1;
+        static final int COLOR_BLUE = 2;
+        static final int COLOR_CYAN = 3;
+        static final int COLOR_RED = 4;
+        static final int COLOR_YELLOW = 5;
+        static final int COLOR_MAGENTA = 6;
+        static final int COLOR_INVALID = 7;
+
+        static final int STYLE_ITALICS   = 0x00000001;
+        static final int STYLE_UNDERLINE = 0x00000002;
+
+        static final String[] mColorMap = {
+            "WHITE", "GREEN", "BLUE", "CYAN", "RED", "YELLOW", "MAGENTA", "INVALID"
+        };
+
+        final int mStyle;
+        final int mColor;
+
+        static StyleCode fromByte(byte data2) {
+            int style = 0;
+            int color = (data2 >> 1) & 0x7;
+
+            if ((data2 & 0x1) != 0) {
+                style |= STYLE_UNDERLINE;
+            }
+
+            if (color == COLOR_INVALID) {
+                // WHITE ITALICS
+                color = COLOR_WHITE;
+                style |= STYLE_ITALICS;
+            }
+
+            return new StyleCode(style, color);
+        }
+
+        StyleCode(int style, int color) {
+            mStyle = style;
+            mColor = color;
+        }
+
+        boolean isItalics() {
+            return (mStyle & STYLE_ITALICS) != 0;
+        }
+
+        boolean isUnderline() {
+            return (mStyle & STYLE_UNDERLINE) != 0;
+        }
+
+        int getColor() {
+            return mColor;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder str = new StringBuilder();
+            str.append("{");
+            str.append(mColorMap[mColor]);
+            if ((mStyle & STYLE_ITALICS) != 0) {
+                str.append(", ITALICS");
+            }
+            if ((mStyle & STYLE_UNDERLINE) != 0) {
+                str.append(", UNDERLINE");
+            }
+            str.append("}");
+
+            return str.toString();
+        }
+    }
+
+    private static class PAC extends StyleCode {
+        final int mRow;
+        final int mCol;
+
+        static PAC fromBytes(byte data1, byte data2) {
+            int[] rowTable = {11, 1, 3, 12, 14, 5, 7, 9};
+            int row = rowTable[data1 & 0x07] + ((data2 & 0x20) >> 5);
+            int style = 0;
+            if ((data2 & 1) != 0) {
+                style |= STYLE_UNDERLINE;
+            }
+            if ((data2 & 0x10) != 0) {
+                // indent code
+                int indent = (data2 >> 1) & 0x7;
+                return new PAC(row, indent * 4, style, COLOR_WHITE);
+            } else {
+                // style code
+                int color = (data2 >> 1) & 0x7;
+
+                if (color == COLOR_INVALID) {
+                    // WHITE ITALICS
+                    color = COLOR_WHITE;
+                    style |= STYLE_ITALICS;
+                }
+                return new PAC(row, -1, style, color);
+            }
+        }
+
+        PAC(int row, int col, int style, int color) {
+            super(style, color);
+            mRow = row;
+            mCol = col;
+        }
+
+        boolean isIndentPAC() {
+            return (mCol >= 0);
+        }
+
+        int getRow() {
+            return mRow;
+        }
+
+        int getCol() {
+            return mCol;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{%d, %d}, %s",
+                    mRow, mCol, super.toString());
+        }
+    }
+
+    /**
+     * Mutable version of BackgroundSpan to facilitate text rendering with edge styles.
+     */
+    public static class MutableBackgroundColorSpan extends CharacterStyle
+            implements UpdateAppearance {
+        private int mColor;
+
+        public MutableBackgroundColorSpan(int color) {
+            mColor = color;
+        }
+
+        public void setBackgroundColor(int color) {
+            mColor = color;
+        }
+
+        public int getBackgroundColor() {
+            return mColor;
+        }
+
+        @Override
+        public void updateDrawState(TextPaint ds) {
+            ds.bgColor = mColor;
+        }
+    }
+
+    /* CCLineBuilder keeps track of displayable chars, as well as
+     * MidRow styles and PACs, for a single line of CC memory.
+     *
+     * It generates styled text via getStyledText() method.
+     */
+    private static class CCLineBuilder {
+        private final StringBuilder mDisplayChars;
+        private final StyleCode[] mMidRowStyles;
+        private final StyleCode[] mPACStyles;
+
+        CCLineBuilder(String str) {
+            mDisplayChars = new StringBuilder(str);
+            mMidRowStyles = new StyleCode[mDisplayChars.length()];
+            mPACStyles = new StyleCode[mDisplayChars.length()];
+        }
+
+        void setCharAt(int index, char ch) {
+            mDisplayChars.setCharAt(index, ch);
+            mMidRowStyles[index] = null;
+        }
+
+        void setMidRowAt(int index, StyleCode m) {
+            mDisplayChars.setCharAt(index, ' ');
+            mMidRowStyles[index] = m;
+        }
+
+        void setPACAt(int index, PAC pac) {
+            mPACStyles[index] = pac;
+        }
+
+        char charAt(int index) {
+            return mDisplayChars.charAt(index);
+        }
+
+        int length() {
+            return mDisplayChars.length();
+        }
+
+        void applyStyleSpan(
+                SpannableStringBuilder styledText,
+                StyleCode s, int start, int end) {
+            if (s.isItalics()) {
+                styledText.setSpan(
+                        new StyleSpan(android.graphics.Typeface.ITALIC),
+                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            if (s.isUnderline()) {
+                styledText.setSpan(
+                        new UnderlineSpan(),
+                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+
+        SpannableStringBuilder getStyledText(CaptionStyle captionStyle) {
+            SpannableStringBuilder styledText = new SpannableStringBuilder(mDisplayChars);
+            int start = -1, next = 0;
+            int styleStart = -1;
+            StyleCode curStyle = null;
+            while (next < mDisplayChars.length()) {
+                StyleCode newStyle = null;
+                if (mMidRowStyles[next] != null) {
+                    // apply mid-row style change
+                    newStyle = mMidRowStyles[next];
+                } else if (mPACStyles[next] != null
+                    && (styleStart < 0 || start < 0)) {
+                    // apply PAC style change, only if:
+                    // 1. no style set, or
+                    // 2. style set, but prev char is none-displayable
+                    newStyle = mPACStyles[next];
+                }
+                if (newStyle != null) {
+                    curStyle = newStyle;
+                    if (styleStart >= 0 && start >= 0) {
+                        applyStyleSpan(styledText, newStyle, styleStart, next);
+                    }
+                    styleStart = next;
+                }
+
+                if (mDisplayChars.charAt(next) != TS) {
+                    if (start < 0) {
+                        start = next;
+                    }
+                } else if (start >= 0) {
+                    int expandedStart = mDisplayChars.charAt(start) == ' ' ? start : start - 1;
+                    int expandedEnd = mDisplayChars.charAt(next - 1) == ' ' ? next : next + 1;
+                    styledText.setSpan(
+                            new MutableBackgroundColorSpan(captionStyle.backgroundColor),
+                            expandedStart, expandedEnd,
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    if (styleStart >= 0) {
+                        applyStyleSpan(styledText, curStyle, styleStart, expandedEnd);
+                    }
+                    start = -1;
+                }
+                next++;
+            }
+
+            return styledText;
+        }
+    }
+
+    /*
+     * CCMemory models a console-style display.
+     */
+    private static class CCMemory {
+        private final String mBlankLine;
+        private final CCLineBuilder[] mLines = new CCLineBuilder[MAX_ROWS + 2];
+        private int mRow;
+        private int mCol;
+
+        CCMemory() {
+            char[] blank = new char[MAX_COLS + 2];
+            Arrays.fill(blank, TS);
+            mBlankLine = new String(blank);
+        }
+
+        void erase() {
+            // erase all lines
+            for (int i = 0; i < mLines.length; i++) {
+                mLines[i] = null;
+            }
+            mRow = MAX_ROWS;
+            mCol = 1;
+        }
+
+        void der() {
+            if (mLines[mRow] != null) {
+                for (int i = 0; i < mCol; i++) {
+                    if (mLines[mRow].charAt(i) != TS) {
+                        for (int j = mCol; j < mLines[mRow].length(); j++) {
+                            mLines[j].setCharAt(j, TS);
+                        }
+                        return;
+                    }
+                }
+                mLines[mRow] = null;
+            }
+        }
+
+        void tab(int tabs) {
+            moveCursorByCol(tabs);
+        }
+
+        void bs() {
+            moveCursorByCol(-1);
+            if (mLines[mRow] != null) {
+                mLines[mRow].setCharAt(mCol, TS);
+                if (mCol == MAX_COLS - 1) {
+                    // Spec recommendation:
+                    // if cursor was at col 32, move cursor
+                    // back to col 31 and erase both col 31&32
+                    mLines[mRow].setCharAt(MAX_COLS, TS);
+                }
+            }
+        }
+
+        void cr() {
+            moveCursorTo(mRow + 1, 1);
+        }
+
+        void rollUp(int windowSize) {
+            int i;
+            for (i = 0; i <= mRow - windowSize; i++) {
+                mLines[i] = null;
+            }
+            int startRow = mRow - windowSize + 1;
+            if (startRow < 1) {
+                startRow = 1;
+            }
+            for (i = startRow; i < mRow; i++) {
+                mLines[i] = mLines[i + 1];
+            }
+            for (i = mRow; i < mLines.length; i++) {
+                // clear base row
+                mLines[i] = null;
+            }
+            // default to col 1, in case PAC is not sent
+            mCol = 1;
+        }
+
+        void writeText(String text) {
+            for (int i = 0; i < text.length(); i++) {
+                getLineBuffer(mRow).setCharAt(mCol, text.charAt(i));
+                moveCursorByCol(1);
+            }
+        }
+
+        void writeMidRowCode(StyleCode m) {
+            getLineBuffer(mRow).setMidRowAt(mCol, m);
+            moveCursorByCol(1);
+        }
+
+        void writePAC(PAC pac) {
+            if (pac.isIndentPAC()) {
+                moveCursorTo(pac.getRow(), pac.getCol());
+            } else {
+                moveCursorTo(pac.getRow(), 1);
+            }
+            getLineBuffer(mRow).setPACAt(mCol, pac);
+        }
+
+        SpannableStringBuilder[] getStyledText(CaptionStyle captionStyle) {
+            ArrayList<SpannableStringBuilder> rows = new ArrayList<>(MAX_ROWS);
+            for (int i = 1; i <= MAX_ROWS; i++) {
+                rows.add(mLines[i] != null ?
+                        mLines[i].getStyledText(captionStyle) : null);
+            }
+            return rows.toArray(new SpannableStringBuilder[MAX_ROWS]);
+        }
+
+        private static int clamp(int x, int min, int max) {
+            return x < min ? min : (x > max ? max : x);
+        }
+
+        private void moveCursorTo(int row, int col) {
+            mRow = clamp(row, 1, MAX_ROWS);
+            mCol = clamp(col, 1, MAX_COLS);
+        }
+
+        private void moveCursorToRow(int row) {
+            mRow = clamp(row, 1, MAX_ROWS);
+        }
+
+        private void moveCursorByCol(int col) {
+            mCol = clamp(mCol + col, 1, MAX_COLS);
+        }
+
+        private void moveBaselineTo(int baseRow, int windowSize) {
+            if (mRow == baseRow) {
+                return;
+            }
+            int actualWindowSize = windowSize;
+            if (baseRow < actualWindowSize) {
+                actualWindowSize = baseRow;
+            }
+            if (mRow < actualWindowSize) {
+                actualWindowSize = mRow;
+            }
+
+            int i;
+            if (baseRow < mRow) {
+                // copy from bottom to top row
+                for (i = actualWindowSize - 1; i >= 0; i--) {
+                    mLines[baseRow - i] = mLines[mRow - i];
+                }
+            } else {
+                // copy from top to bottom row
+                for (i = 0; i < actualWindowSize; i++) {
+                    mLines[baseRow - i] = mLines[mRow - i];
+                }
+            }
+            // clear rest of the rows
+            for (i = 0; i <= baseRow - windowSize; i++) {
+                mLines[i] = null;
+            }
+            for (i = baseRow + 1; i < mLines.length; i++) {
+                mLines[i] = null;
+            }
+        }
+
+        private CCLineBuilder getLineBuffer(int row) {
+            if (mLines[row] == null) {
+                mLines[row] = new CCLineBuilder(mBlankLine);
+            }
+            return mLines[row];
+        }
+    }
+
+    /*
+     * CCData parses the raw CC byte pair into displayable chars,
+     * misc control codes, Mid-Row or Preamble Address Codes.
+     */
+    private static class CCData {
+        private final byte mType;
+        private final byte mData1;
+        private final byte mData2;
+
+        private static final String[] mCtrlCodeMap = {
+            "RCL", "BS" , "AOF", "AON",
+            "DER", "RU2", "RU3", "RU4",
+            "FON", "RDC", "TR" , "RTD",
+            "EDM", "CR" , "ENM", "EOC",
+        };
+
+        private static final String[] mSpecialCharMap = {
+            "\u00AE",
+            "\u00B0",
+            "\u00BD",
+            "\u00BF",
+            "\u2122",
+            "\u00A2",
+            "\u00A3",
+            "\u266A", // Eighth note
+            "\u00E0",
+            "\u00A0", // Transparent space
+            "\u00E8",
+            "\u00E2",
+            "\u00EA",
+            "\u00EE",
+            "\u00F4",
+            "\u00FB",
+        };
+
+        private static final String[] mSpanishCharMap = {
+            // Spanish and misc chars
+            "\u00C1", // A
+            "\u00C9", // E
+            "\u00D3", // I
+            "\u00DA", // O
+            "\u00DC", // U
+            "\u00FC", // u
+            "\u2018", // opening single quote
+            "\u00A1", // inverted exclamation mark
+            "*",
+            "'",
+            "\u2014", // em dash
+            "\u00A9", // Copyright
+            "\u2120", // Servicemark
+            "\u2022", // round bullet
+            "\u201C", // opening double quote
+            "\u201D", // closing double quote
+            // French
+            "\u00C0",
+            "\u00C2",
+            "\u00C7",
+            "\u00C8",
+            "\u00CA",
+            "\u00CB",
+            "\u00EB",
+            "\u00CE",
+            "\u00CF",
+            "\u00EF",
+            "\u00D4",
+            "\u00D9",
+            "\u00F9",
+            "\u00DB",
+            "\u00AB",
+            "\u00BB"
+        };
+
+        private static final String[] mProtugueseCharMap = {
+            // Portuguese
+            "\u00C3",
+            "\u00E3",
+            "\u00CD",
+            "\u00CC",
+            "\u00EC",
+            "\u00D2",
+            "\u00F2",
+            "\u00D5",
+            "\u00F5",
+            "{",
+            "}",
+            "\\",
+            "^",
+            "_",
+            "|",
+            "~",
+            // German and misc chars
+            "\u00C4",
+            "\u00E4",
+            "\u00D6",
+            "\u00F6",
+            "\u00DF",
+            "\u00A5",
+            "\u00A4",
+            "\u2502", // vertical bar
+            "\u00C5",
+            "\u00E5",
+            "\u00D8",
+            "\u00F8",
+            "\u250C", // top-left corner
+            "\u2510", // top-right corner
+            "\u2514", // lower-left corner
+            "\u2518", // lower-right corner
+        };
+
+        static CCData[] fromByteArray(byte[] data) {
+            CCData[] ccData = new CCData[data.length / 3];
+
+            for (int i = 0; i < ccData.length; i++) {
+                ccData[i] = new CCData(
+                        data[i * 3],
+                        data[i * 3 + 1],
+                        data[i * 3 + 2]);
+            }
+
+            return ccData;
+        }
+
+        CCData(byte type, byte data1, byte data2) {
+            mType = type;
+            mData1 = data1;
+            mData2 = data2;
+        }
+
+        int getCtrlCode() {
+            if ((mData1 == 0x14 || mData1 == 0x1c)
+                    && mData2 >= 0x20 && mData2 <= 0x2f) {
+                return mData2;
+            }
+            return INVALID;
+        }
+
+        StyleCode getMidRow() {
+            // only support standard Mid-row codes, ignore
+            // optional background/foreground mid-row codes
+            if ((mData1 == 0x11 || mData1 == 0x19)
+                    && mData2 >= 0x20 && mData2 <= 0x2f) {
+                return StyleCode.fromByte(mData2);
+            }
+            return null;
+        }
+
+        PAC getPAC() {
+            if ((mData1 & 0x70) == 0x10
+                    && (mData2 & 0x40) == 0x40
+                    && ((mData1 & 0x07) != 0 || (mData2 & 0x20) == 0)) {
+                return PAC.fromBytes(mData1, mData2);
+            }
+            return null;
+        }
+
+        int getTabOffset() {
+            if ((mData1 == 0x17 || mData1 == 0x1f)
+                    && mData2 >= 0x21 && mData2 <= 0x23) {
+                return mData2 & 0x3;
+            }
+            return 0;
+        }
+
+        boolean isDisplayableChar() {
+            return isBasicChar() || isSpecialChar() || isExtendedChar();
+        }
+
+        String getDisplayText() {
+            String str = getBasicChars();
+
+            if (str == null) {
+                str =  getSpecialChar();
+
+                if (str == null) {
+                    str = getExtendedChar();
+                }
+            }
+
+            return str;
+        }
+
+        private String ctrlCodeToString(int ctrlCode) {
+            return mCtrlCodeMap[ctrlCode - 0x20];
+        }
+
+        private boolean isBasicChar() {
+            return mData1 >= 0x20 && mData1 <= 0x7f;
+        }
+
+        private boolean isSpecialChar() {
+            return ((mData1 == 0x11 || mData1 == 0x19)
+                    && mData2 >= 0x30 && mData2 <= 0x3f);
+        }
+
+        private boolean isExtendedChar() {
+            return ((mData1 == 0x12 || mData1 == 0x1A
+                    || mData1 == 0x13 || mData1 == 0x1B)
+                    && mData2 >= 0x20 && mData2 <= 0x3f);
+        }
+
+        private char getBasicChar(byte data) {
+            char c;
+            // replace the non-ASCII ones
+            switch (data) {
+                case 0x2A: c = '\u00E1'; break;
+                case 0x5C: c = '\u00E9'; break;
+                case 0x5E: c = '\u00ED'; break;
+                case 0x5F: c = '\u00F3'; break;
+                case 0x60: c = '\u00FA'; break;
+                case 0x7B: c = '\u00E7'; break;
+                case 0x7C: c = '\u00F7'; break;
+                case 0x7D: c = '\u00D1'; break;
+                case 0x7E: c = '\u00F1'; break;
+                case 0x7F: c = '\u2588'; break; // Full block
+                default: c = (char) data; break;
+            }
+            return c;
+        }
+
+        private String getBasicChars() {
+            if (mData1 >= 0x20 && mData1 <= 0x7f) {
+                StringBuilder builder = new StringBuilder(2);
+                builder.append(getBasicChar(mData1));
+                if (mData2 >= 0x20 && mData2 <= 0x7f) {
+                    builder.append(getBasicChar(mData2));
+                }
+                return builder.toString();
+            }
+
+            return null;
+        }
+
+        private String getSpecialChar() {
+            if ((mData1 == 0x11 || mData1 == 0x19)
+                    && mData2 >= 0x30 && mData2 <= 0x3f) {
+                return mSpecialCharMap[mData2 - 0x30];
+            }
+
+            return null;
+        }
+
+        private String getExtendedChar() {
+            if ((mData1 == 0x12 || mData1 == 0x1A)
+                    && mData2 >= 0x20 && mData2 <= 0x3f){
+                // 1 Spanish/French char
+                return mSpanishCharMap[mData2 - 0x20];
+            } else if ((mData1 == 0x13 || mData1 == 0x1B)
+                    && mData2 >= 0x20 && mData2 <= 0x3f){
+                // 1 Portuguese/German/Danish char
+                return mProtugueseCharMap[mData2 - 0x20];
+            }
+
+            return null;
+        }
+
+        @Override
+        public String toString() {
+            String str;
+
+            if (mData1 < 0x10 && mData2 < 0x10) {
+                // Null Pad, ignore
+                return String.format("[%d]Null: %02x %02x", mType, mData1, mData2);
+            }
+
+            int ctrlCode = getCtrlCode();
+            if (ctrlCode != INVALID) {
+                return String.format("[%d]%s", mType, ctrlCodeToString(ctrlCode));
+            }
+
+            int tabOffset = getTabOffset();
+            if (tabOffset > 0) {
+                return String.format("[%d]Tab%d", mType, tabOffset);
+            }
+
+            PAC pac = getPAC();
+            if (pac != null) {
+                return String.format("[%d]PAC: %s", mType, pac.toString());
+            }
+
+            StyleCode m = getMidRow();
+            if (m != null) {
+                return String.format("[%d]Mid-row: %s", mType, m.toString());
+            }
+
+            if (isDisplayableChar()) {
+                return String.format("[%d]Displayable: %s (%02x %02x)",
+                        mType, getDisplayText(), mData1, mData2);
+            }
+
+            return String.format("[%d]Invalid: %02x %02x", mType, mData1, mData2);
+        }
+    }
+}
+
+/**
+ * Widget capable of rendering CEA-608 closed captions.
+ */
+class Cea608CCWidget extends ClosedCaptionWidget implements Cea608CCParser.DisplayListener {
+    private static final Rect mTextBounds = new Rect();
+    private static final String mDummyText = "1234567890123456789012345678901234";
+
+    public Cea608CCWidget(Context context) {
+        this(context, null);
+    }
+
+    public Cea608CCWidget(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public Cea608CCWidget(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, 0);
+    }
+
+    public Cea608CCWidget(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public ClosedCaptionLayout createCaptionLayout(Context context) {
+        return new CCLayout(context);
+    }
+
+    @Override
+    public void onDisplayChanged(SpannableStringBuilder[] styledTexts) {
+        ((CCLayout) mClosedCaptionLayout).update(styledTexts);
+
+        if (mListener != null) {
+            mListener.onChanged(this);
+        }
+    }
+
+    @Override
+    public CaptionStyle getCaptionStyle() {
+        return mCaptionStyle;
+    }
+
+    private static class CCLineBox extends TextView {
+        private static final float FONT_PADDING_RATIO = 0.75f;
+        private static final float EDGE_OUTLINE_RATIO = 0.1f;
+        private static final float EDGE_SHADOW_RATIO = 0.05f;
+        private float mOutlineWidth;
+        private float mShadowRadius;
+        private float mShadowOffset;
+
+        private int mTextColor = Color.WHITE;
+        private int mBgColor = Color.BLACK;
+        private int mEdgeType = CaptionStyle.EDGE_TYPE_NONE;
+        private int mEdgeColor = Color.TRANSPARENT;
+
+        CCLineBox(Context context) {
+            super(context);
+            setGravity(Gravity.CENTER);
+            setBackgroundColor(Color.TRANSPARENT);
+            setTextColor(Color.WHITE);
+            setTypeface(Typeface.MONOSPACE);
+            setVisibility(View.INVISIBLE);
+
+            final Resources res = getContext().getResources();
+
+            // get the default (will be updated later during measure)
+            mOutlineWidth = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.subtitle_outline_width);
+            mShadowRadius = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.subtitle_shadow_radius);
+            mShadowOffset = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.subtitle_shadow_offset);
+        }
+
+        void setCaptionStyle(CaptionStyle captionStyle) {
+            mTextColor = captionStyle.foregroundColor;
+            mBgColor = captionStyle.backgroundColor;
+            mEdgeType = captionStyle.edgeType;
+            mEdgeColor = captionStyle.edgeColor;
+
+            setTextColor(mTextColor);
+            if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
+                setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor);
+            } else {
+                setShadowLayer(0, 0, 0, 0);
+            }
+            invalidate();
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            float fontSize = MeasureSpec.getSize(heightMeasureSpec) * FONT_PADDING_RATIO;
+            setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
+
+            mOutlineWidth = EDGE_OUTLINE_RATIO * fontSize + 1.0f;
+            mShadowRadius = EDGE_SHADOW_RATIO * fontSize + 1.0f;;
+            mShadowOffset = mShadowRadius;
+
+            // set font scale in the X direction to match the required width
+            setScaleX(1.0f);
+            getPaint().getTextBounds(mDummyText, 0, mDummyText.length(), mTextBounds);
+            float actualTextWidth = mTextBounds.width();
+            float requiredTextWidth = MeasureSpec.getSize(widthMeasureSpec);
+            setScaleX(requiredTextWidth / actualTextWidth);
+
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        @Override
+        protected void onDraw(Canvas c) {
+            if (mEdgeType == CaptionStyle.EDGE_TYPE_UNSPECIFIED
+                    || mEdgeType == CaptionStyle.EDGE_TYPE_NONE
+                    || mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
+                // these edge styles don't require a second pass
+                super.onDraw(c);
+                return;
+            }
+
+            if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
+                drawEdgeOutline(c);
+            } else {
+                // Raised or depressed
+                drawEdgeRaisedOrDepressed(c);
+            }
+        }
+
+        private void drawEdgeOutline(Canvas c) {
+            TextPaint textPaint = getPaint();
+
+            Paint.Style previousStyle = textPaint.getStyle();
+            Paint.Join previousJoin = textPaint.getStrokeJoin();
+            float previousWidth = textPaint.getStrokeWidth();
+
+            setTextColor(mEdgeColor);
+            textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+            textPaint.setStrokeJoin(Paint.Join.ROUND);
+            textPaint.setStrokeWidth(mOutlineWidth);
+
+            // Draw outline and background only.
+            super.onDraw(c);
+
+            // Restore original settings.
+            setTextColor(mTextColor);
+            textPaint.setStyle(previousStyle);
+            textPaint.setStrokeJoin(previousJoin);
+            textPaint.setStrokeWidth(previousWidth);
+
+            // Remove the background.
+            setBackgroundSpans(Color.TRANSPARENT);
+            // Draw foreground only.
+            super.onDraw(c);
+            // Restore the background.
+            setBackgroundSpans(mBgColor);
+        }
+
+        private void drawEdgeRaisedOrDepressed(Canvas c) {
+            TextPaint textPaint = getPaint();
+
+            Paint.Style previousStyle = textPaint.getStyle();
+            textPaint.setStyle(Paint.Style.FILL);
+
+            final boolean raised = mEdgeType == CaptionStyle.EDGE_TYPE_RAISED;
+            final int colorUp = raised ? Color.WHITE : mEdgeColor;
+            final int colorDown = raised ? mEdgeColor : Color.WHITE;
+            final float offset = mShadowRadius / 2f;
+
+            // Draw background and text with shadow up
+            setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
+            super.onDraw(c);
+
+            // Remove the background.
+            setBackgroundSpans(Color.TRANSPARENT);
+
+            // Draw text with shadow down
+            setShadowLayer(mShadowRadius, +offset, +offset, colorDown);
+            super.onDraw(c);
+
+            // Restore settings
+            textPaint.setStyle(previousStyle);
+
+            // Restore the background.
+            setBackgroundSpans(mBgColor);
+        }
+
+        private void setBackgroundSpans(int color) {
+            CharSequence text = getText();
+            if (text instanceof Spannable) {
+                Spannable spannable = (Spannable) text;
+                Cea608CCParser.MutableBackgroundColorSpan[] bgSpans = spannable.getSpans(
+                        0, spannable.length(), Cea608CCParser.MutableBackgroundColorSpan.class);
+                for (int i = 0; i < bgSpans.length; i++) {
+                    bgSpans[i].setBackgroundColor(color);
+                }
+            }
+        }
+    }
+
+    private static class CCLayout extends LinearLayout implements ClosedCaptionLayout {
+        private static final int MAX_ROWS = Cea608CCParser.MAX_ROWS;
+        private static final float SAFE_AREA_RATIO = 0.9f;
+
+        private final CCLineBox[] mLineBoxes = new CCLineBox[MAX_ROWS];
+
+        CCLayout(Context context) {
+            super(context);
+            setGravity(Gravity.START);
+            setOrientation(LinearLayout.VERTICAL);
+            for (int i = 0; i < MAX_ROWS; i++) {
+                mLineBoxes[i] = new CCLineBox(getContext());
+                addView(mLineBoxes[i], LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+            }
+        }
+
+        @Override
+        public void setCaptionStyle(CaptionStyle captionStyle) {
+            for (int i = 0; i < MAX_ROWS; i++) {
+                mLineBoxes[i].setCaptionStyle(captionStyle);
+            }
+        }
+
+        @Override
+        public void setFontScale(float fontScale) {
+            // Ignores the font scale changes of the system wide CC preference.
+        }
+
+        void update(SpannableStringBuilder[] textBuffer) {
+            for (int i = 0; i < MAX_ROWS; i++) {
+                if (textBuffer[i] != null) {
+                    mLineBoxes[i].setText(textBuffer[i], TextView.BufferType.SPANNABLE);
+                    mLineBoxes[i].setVisibility(View.VISIBLE);
+                } else {
+                    mLineBoxes[i].setVisibility(View.INVISIBLE);
+                }
+            }
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+            int safeWidth = getMeasuredWidth();
+            int safeHeight = getMeasuredHeight();
+
+            // CEA-608 assumes 4:3 video
+            if (safeWidth * 3 >= safeHeight * 4) {
+                safeWidth = safeHeight * 4 / 3;
+            } else {
+                safeHeight = safeWidth * 3 / 4;
+            }
+            safeWidth *= SAFE_AREA_RATIO;
+            safeHeight *= SAFE_AREA_RATIO;
+
+            int lineHeight = safeHeight / MAX_ROWS;
+            int lineHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                    lineHeight, MeasureSpec.EXACTLY);
+            int lineWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                    safeWidth, MeasureSpec.EXACTLY);
+
+            for (int i = 0; i < MAX_ROWS; i++) {
+                mLineBoxes[i].measure(lineWidthMeasureSpec, lineHeightMeasureSpec);
+            }
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+            // safe caption area
+            int viewPortWidth = r - l;
+            int viewPortHeight = b - t;
+            int safeWidth, safeHeight;
+            // CEA-608 assumes 4:3 video
+            if (viewPortWidth * 3 >= viewPortHeight * 4) {
+                safeWidth = viewPortHeight * 4 / 3;
+                safeHeight = viewPortHeight;
+            } else {
+                safeWidth = viewPortWidth;
+                safeHeight = viewPortWidth * 3 / 4;
+            }
+            safeWidth *= SAFE_AREA_RATIO;
+            safeHeight *= SAFE_AREA_RATIO;
+            int left = (viewPortWidth - safeWidth) / 2;
+            int top = (viewPortHeight - safeHeight) / 2;
+
+            for (int i = 0; i < MAX_ROWS; i++) {
+                mLineBoxes[i].layout(
+                        left,
+                        top + safeHeight * i / MAX_ROWS,
+                        left + safeWidth,
+                        top + safeHeight * (i + 1) / MAX_ROWS);
+            }
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java b/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java
new file mode 100644
index 0000000..af36d7f
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.subtitle;
+
+// Note: This is just copied from android.media.MediaTimeProvider.
+public interface MediaTimeProvider {
+    // we do not allow negative media time
+    /**
+     * Presentation time value if no timed event notification is requested.
+     */
+    public final static long NO_TIME = -1;
+
+    /**
+     * Cancels all previous notification request from this listener if any.  It
+     * registers the listener to get seek and stop notifications.  If timeUs is
+     * not negative, it also registers the listener for a timed event
+     * notification when the presentation time reaches (becomes greater) than
+     * the value specified.  This happens immediately if the current media time
+     * is larger than or equal to timeUs.
+     *
+     * @param timeUs presentation time to get timed event callback at (or
+     *               {@link #NO_TIME})
+     */
+    public void notifyAt(long timeUs, OnMediaTimeListener listener);
+
+    /**
+     * Cancels all previous notification request from this listener if any.  It
+     * registers the listener to get seek and stop notifications.  If the media
+     * is stopped, the listener will immediately receive a stop notification.
+     * Otherwise, it will receive a timed event notificaton.
+     */
+    public void scheduleUpdate(OnMediaTimeListener listener);
+
+    /**
+     * Cancels all previous notification request from this listener if any.
+     */
+    public void cancelNotifications(OnMediaTimeListener listener);
+
+    /**
+     * Get the current presentation time.
+     *
+     * @param precise   Whether getting a precise time is important. This is
+     *                  more costly.
+     * @param monotonic Whether returned time should be monotonic: that is,
+     *                  greater than or equal to the last returned time.  Don't
+     *                  always set this to true.  E.g. this has undesired
+     *                  consequences if the media is seeked between calls.
+     * @throws IllegalStateException if the media is not initialized
+     */
+    public long getCurrentTimeUs(boolean precise, boolean monotonic)
+            throws IllegalStateException;
+
+    public static interface OnMediaTimeListener {
+        /**
+         * Called when the registered time was reached naturally.
+         *
+         * @param timeUs current media time
+         */
+        void onTimedEvent(long timeUs);
+
+        /**
+         * Called when the media time changed due to seeking.
+         *
+         * @param timeUs current media time
+         */
+        void onSeek(long timeUs);
+
+        /**
+         * Called when the playback stopped.  This is not called on pause, only
+         * on full stop, at which point there is no further current media time.
+         */
+        void onStop();
+    }
+}
+
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java
new file mode 100644
index 0000000..a4d55d7
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java
@@ -0,0 +1,508 @@
+/*
+ * 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.subtitle;
+
+import java.util.Locale;
+import java.util.Vector;
+
+import android.content.Context;
+import android.media.MediaFormat;
+import android.media.MediaPlayer2;
+import android.media.MediaPlayer2.TrackInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.accessibility.CaptioningManager;
+
+import com.android.media.subtitle.SubtitleTrack.RenderingWidget;
+
+// Note: This is forked from android.media.SubtitleController since P
+/**
+ * The subtitle controller provides the architecture to display subtitles for a
+ * media source.  It allows specifying which tracks to display, on which anchor
+ * to display them, and also allows adding external, out-of-band subtitle tracks.
+ */
+public class SubtitleController {
+    private MediaTimeProvider mTimeProvider;
+    private Vector<Renderer> mRenderers;
+    private Vector<SubtitleTrack> mTracks;
+    private SubtitleTrack mSelectedTrack;
+    private boolean mShowing;
+    private CaptioningManager mCaptioningManager;
+    private Handler mHandler;
+
+    private static final int WHAT_SHOW = 1;
+    private static final int WHAT_HIDE = 2;
+    private static final int WHAT_SELECT_TRACK = 3;
+    private static final int WHAT_SELECT_DEFAULT_TRACK = 4;
+
+    private final Handler.Callback mCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+            case WHAT_SHOW:
+                doShow();
+                return true;
+            case WHAT_HIDE:
+                doHide();
+                return true;
+            case WHAT_SELECT_TRACK:
+                doSelectTrack((SubtitleTrack)msg.obj);
+                return true;
+            case WHAT_SELECT_DEFAULT_TRACK:
+                doSelectDefaultTrack();
+                return true;
+            default:
+                return false;
+            }
+        }
+    };
+
+    private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener =
+        new CaptioningManager.CaptioningChangeListener() {
+            @Override
+            public void onEnabledChanged(boolean enabled) {
+                selectDefaultTrack();
+            }
+
+            @Override
+            public void onLocaleChanged(Locale locale) {
+                selectDefaultTrack();
+            }
+        };
+
+    public SubtitleController(Context context) {
+        this(context, null, null);
+    }
+
+    /**
+     * Creates a subtitle controller for a media playback object that implements
+     * the MediaTimeProvider interface.
+     *
+     * @param timeProvider
+     */
+    public SubtitleController(
+            Context context,
+            MediaTimeProvider timeProvider,
+            Listener listener) {
+        mTimeProvider = timeProvider;
+        mListener = listener;
+
+        mRenderers = new Vector<Renderer>();
+        mShowing = false;
+        mTracks = new Vector<SubtitleTrack>();
+        mCaptioningManager =
+            (CaptioningManager)context.getSystemService(Context.CAPTIONING_SERVICE);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        mCaptioningManager.removeCaptioningChangeListener(
+                mCaptioningChangeListener);
+        super.finalize();
+    }
+
+    /**
+     * @return the available subtitle tracks for this media. These include
+     * the tracks found by {@link MediaPlayer} as well as any tracks added
+     * manually via {@link #addTrack}.
+     */
+    public SubtitleTrack[] getTracks() {
+        synchronized(mTracks) {
+            SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()];
+            mTracks.toArray(tracks);
+            return tracks;
+        }
+    }
+
+    /**
+     * @return the currently selected subtitle track
+     */
+    public SubtitleTrack getSelectedTrack() {
+        return mSelectedTrack;
+    }
+
+    private RenderingWidget getRenderingWidget() {
+        if (mSelectedTrack == null) {
+            return null;
+        }
+        return mSelectedTrack.getRenderingWidget();
+    }
+
+    /**
+     * Selects a subtitle track.  As a result, this track will receive
+     * in-band data from the {@link MediaPlayer}.  However, this does
+     * not change the subtitle visibility.
+     *
+     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
+     *
+     * @param track The subtitle track to select.  This must be one of the
+     *              tracks in {@link #getTracks}.
+     * @return true if the track was successfully selected.
+     */
+    public boolean selectTrack(SubtitleTrack track) {
+        if (track != null && !mTracks.contains(track)) {
+            return false;
+        }
+
+        processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_TRACK, track));
+        return true;
+    }
+
+    private void doSelectTrack(SubtitleTrack track) {
+        mTrackIsExplicit = true;
+        if (mSelectedTrack == track) {
+            return;
+        }
+
+        if (mSelectedTrack != null) {
+            mSelectedTrack.hide();
+            mSelectedTrack.setTimeProvider(null);
+        }
+
+        mSelectedTrack = track;
+        if (mAnchor != null) {
+            mAnchor.setSubtitleWidget(getRenderingWidget());
+        }
+
+        if (mSelectedTrack != null) {
+            mSelectedTrack.setTimeProvider(mTimeProvider);
+            mSelectedTrack.show();
+        }
+
+        if (mListener != null) {
+            mListener.onSubtitleTrackSelected(track);
+        }
+    }
+
+    /**
+     * @return the default subtitle track based on system preferences, or null,
+     * if no such track exists in this manager.
+     *
+     * Supports HLS-flags: AUTOSELECT, FORCED & DEFAULT.
+     *
+     * 1. If captioning is disabled, only consider FORCED tracks. Otherwise,
+     * consider all tracks, but prefer non-FORCED ones.
+     * 2. If user selected "Default" caption language:
+     *   a. If there is a considered track with DEFAULT=yes, returns that track
+     *      (favor the first one in the current language if there are more than
+     *      one default tracks, or the first in general if none of them are in
+     *      the current language).
+     *   b. Otherwise, if there is a track with AUTOSELECT=yes in the current
+     *      language, return that one.
+     *   c. If there are no default tracks, and no autoselectable tracks in the
+     *      current language, return null.
+     * 3. If there is a track with the caption language, select that one.  Prefer
+     * the one with AUTOSELECT=no.
+     *
+     * The default values for these flags are DEFAULT=no, AUTOSELECT=yes
+     * and FORCED=no.
+     */
+    public SubtitleTrack getDefaultTrack() {
+        SubtitleTrack bestTrack = null;
+        int bestScore = -1;
+
+        Locale selectedLocale = mCaptioningManager.getLocale();
+        Locale locale = selectedLocale;
+        if (locale == null) {
+            locale = Locale.getDefault();
+        }
+        boolean selectForced = !mCaptioningManager.isEnabled();
+
+        synchronized(mTracks) {
+            for (SubtitleTrack track: mTracks) {
+                MediaFormat format = track.getFormat();
+                String language = format.getString(MediaFormat.KEY_LANGUAGE);
+                boolean forced =
+                    format.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0;
+                boolean autoselect =
+                    format.getInteger(MediaFormat.KEY_IS_AUTOSELECT, 1) != 0;
+                boolean is_default =
+                    format.getInteger(MediaFormat.KEY_IS_DEFAULT, 0) != 0;
+
+                boolean languageMatches =
+                    (locale == null ||
+                    locale.getLanguage().equals("") ||
+                    locale.getISO3Language().equals(language) ||
+                    locale.getLanguage().equals(language));
+                // is_default is meaningless unless caption language is 'default'
+                int score = (forced ? 0 : 8) +
+                    (((selectedLocale == null) && is_default) ? 4 : 0) +
+                    (autoselect ? 0 : 2) + (languageMatches ? 1 : 0);
+
+                if (selectForced && !forced) {
+                    continue;
+                }
+
+                // we treat null locale/language as matching any language
+                if ((selectedLocale == null && is_default) ||
+                    (languageMatches &&
+                     (autoselect || forced || selectedLocale != null))) {
+                    if (score > bestScore) {
+                        bestScore = score;
+                        bestTrack = track;
+                    }
+                }
+            }
+        }
+        return bestTrack;
+    }
+
+    private boolean mTrackIsExplicit = false;
+    private boolean mVisibilityIsExplicit = false;
+
+    /** should be called from anchor thread */
+    public void selectDefaultTrack() {
+        processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_DEFAULT_TRACK));
+    }
+
+    private void doSelectDefaultTrack() {
+        if (mTrackIsExplicit) {
+            // If track selection is explicit, but visibility
+            // is not, it falls back to the captioning setting
+            if (!mVisibilityIsExplicit) {
+                if (mCaptioningManager.isEnabled() ||
+                    (mSelectedTrack != null &&
+                     mSelectedTrack.getFormat().getInteger(
+                            MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0)) {
+                    show();
+                } else if (mSelectedTrack != null
+                        && mSelectedTrack.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+                    hide();
+                }
+                mVisibilityIsExplicit = false;
+            }
+            return;
+        }
+
+        // We can have a default (forced) track even if captioning
+        // is not enabled.  This is handled by getDefaultTrack().
+        // Show this track unless subtitles were explicitly hidden.
+        SubtitleTrack track = getDefaultTrack();
+        if (track != null) {
+            selectTrack(track);
+            mTrackIsExplicit = false;
+            if (!mVisibilityIsExplicit) {
+                show();
+                mVisibilityIsExplicit = false;
+            }
+        }
+    }
+
+    /** must be called from anchor thread */
+    public void reset() {
+        checkAnchorLooper();
+        hide();
+        selectTrack(null);
+        mTracks.clear();
+        mTrackIsExplicit = false;
+        mVisibilityIsExplicit = false;
+        mCaptioningManager.removeCaptioningChangeListener(
+                mCaptioningChangeListener);
+    }
+
+    /**
+     * Adds a new, external subtitle track to the manager.
+     *
+     * @param format the format of the track that will include at least
+     *               the MIME type {@link MediaFormat@KEY_MIME}.
+     * @return the created {@link SubtitleTrack} object
+     */
+    public SubtitleTrack addTrack(MediaFormat format) {
+        synchronized(mRenderers) {
+            for (Renderer renderer: mRenderers) {
+                if (renderer.supports(format)) {
+                    SubtitleTrack track = renderer.createTrack(format);
+                    if (track != null) {
+                        synchronized(mTracks) {
+                            if (mTracks.size() == 0) {
+                                mCaptioningManager.addCaptioningChangeListener(
+                                        mCaptioningChangeListener);
+                            }
+                            mTracks.add(track);
+                        }
+                        return track;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Show the selected (or default) subtitle track.
+     *
+     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
+     */
+    public void show() {
+        processOnAnchor(mHandler.obtainMessage(WHAT_SHOW));
+    }
+
+    private void doShow() {
+        mShowing = true;
+        mVisibilityIsExplicit = true;
+        if (mSelectedTrack != null) {
+            mSelectedTrack.show();
+        }
+    }
+
+    /**
+     * Hide the selected (or default) subtitle track.
+     *
+     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
+     */
+    public void hide() {
+        processOnAnchor(mHandler.obtainMessage(WHAT_HIDE));
+    }
+
+    private void doHide() {
+        mVisibilityIsExplicit = true;
+        if (mSelectedTrack != null) {
+            mSelectedTrack.hide();
+        }
+        mShowing = false;
+    }
+
+    /**
+     * Interface for supporting a single or multiple subtitle types in {@link
+     * MediaPlayer}.
+     */
+    public abstract static class Renderer {
+        /**
+         * Called by {@link MediaPlayer}'s {@link SubtitleController} when a new
+         * subtitle track is detected, to see if it should use this object to
+         * parse and display this subtitle track.
+         *
+         * @param format the format of the track that will include at least
+         *               the MIME type {@link MediaFormat@KEY_MIME}.
+         *
+         * @return true if and only if the track format is supported by this
+         * renderer
+         */
+        public abstract boolean supports(MediaFormat format);
+
+        /**
+         * Called by {@link MediaPlayer}'s {@link SubtitleController} for each
+         * subtitle track that was detected and is supported by this object to
+         * create a {@link SubtitleTrack} object.  This object will be created
+         * for each track that was found.  If the track is selected for display,
+         * this object will be used to parse and display the track data.
+         *
+         * @param format the format of the track that will include at least
+         *               the MIME type {@link MediaFormat@KEY_MIME}.
+         * @return a {@link SubtitleTrack} object that will be used to parse
+         * and render the subtitle track.
+         */
+        public abstract SubtitleTrack createTrack(MediaFormat format);
+    }
+
+    /**
+     * Add support for a subtitle format in {@link MediaPlayer}.
+     *
+     * @param renderer a {@link SubtitleController.Renderer} object that adds
+     *                 support for a subtitle format.
+     */
+    public void registerRenderer(Renderer renderer) {
+        synchronized(mRenderers) {
+            // TODO how to get available renderers in the system
+            if (!mRenderers.contains(renderer)) {
+                // TODO should added renderers override existing ones (to allow replacing?)
+                mRenderers.add(renderer);
+            }
+        }
+    }
+
+    public boolean hasRendererFor(MediaFormat format) {
+        synchronized(mRenderers) {
+            // TODO how to get available renderers in the system
+            for (Renderer renderer: mRenderers) {
+                if (renderer.supports(format)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Subtitle anchor, an object that is able to display a subtitle renderer,
+     * e.g. a VideoView.
+     */
+    public interface Anchor {
+        /**
+         * Anchor should use the supplied subtitle rendering widget, or
+         * none if it is null.
+         */
+        public void setSubtitleWidget(RenderingWidget subtitleWidget);
+
+        /**
+         * Anchors provide the looper on which all track visibility changes
+         * (track.show/hide, setSubtitleWidget) will take place.
+         */
+        public Looper getSubtitleLooper();
+    }
+
+    private Anchor mAnchor;
+
+    /**
+     *  called from anchor's looper (if any, both when unsetting and
+     *  setting)
+     */
+    public void setAnchor(Anchor anchor) {
+        if (mAnchor == anchor) {
+            return;
+        }
+
+        if (mAnchor != null) {
+            checkAnchorLooper();
+            mAnchor.setSubtitleWidget(null);
+        }
+        mAnchor = anchor;
+        mHandler = null;
+        if (mAnchor != null) {
+            mHandler = new Handler(mAnchor.getSubtitleLooper(), mCallback);
+            checkAnchorLooper();
+            mAnchor.setSubtitleWidget(getRenderingWidget());
+        }
+    }
+
+    private void checkAnchorLooper() {
+        assert mHandler != null : "Should have a looper already";
+        assert Looper.myLooper() == mHandler.getLooper()
+                : "Must be called from the anchor's looper";
+    }
+
+    private void processOnAnchor(Message m) {
+        assert mHandler != null : "Should have a looper already";
+        if (Looper.myLooper() == mHandler.getLooper()) {
+            mHandler.dispatchMessage(m);
+        } else {
+            mHandler.sendMessage(m);
+        }
+    }
+
+    public interface Listener {
+        /**
+         * Called when a subtitle track has been selected.
+         *
+         * @param track selected subtitle track or null
+         */
+        public void onSubtitleTrackSelected(SubtitleTrack track);
+    }
+
+    private Listener mListener;
+}
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java
new file mode 100644
index 0000000..6b9064a
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java
@@ -0,0 +1,696 @@
+/*
+ * 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.subtitle;
+
+import android.graphics.Canvas;
+import android.media.MediaFormat;
+import android.media.MediaPlayer2.TrackInfo;
+import android.media.SubtitleData;
+import android.os.Handler;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Pair;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Vector;
+
+// Note: This is forked from android.media.SubtitleTrack since P
+/**
+ * A subtitle track abstract base class that is responsible for parsing and displaying
+ * an instance of a particular type of subtitle.
+ */
+public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeListener {
+    private static final String TAG = "SubtitleTrack";
+    private long mLastUpdateTimeMs;
+    private long mLastTimeMs;
+
+    private Runnable mRunnable;
+
+    final private LongSparseArray<Run> mRunsByEndTime = new LongSparseArray<Run>();
+    final private LongSparseArray<Run> mRunsByID = new LongSparseArray<Run>();
+
+    private CueList mCues;
+    final private Vector<Cue> mActiveCues = new Vector<Cue>();
+    protected boolean mVisible;
+
+    public boolean DEBUG = false;
+
+    protected Handler mHandler = new Handler();
+
+    private MediaFormat mFormat;
+
+    public SubtitleTrack(MediaFormat format) {
+        mFormat = format;
+        mCues = new CueList();
+        clearActiveCues();
+        mLastTimeMs = -1;
+    }
+
+    public final MediaFormat getFormat() {
+        return mFormat;
+    }
+
+    private long mNextScheduledTimeMs = -1;
+
+    public void onData(SubtitleData data) {
+        long runID = data.getStartTimeUs() + 1;
+        onData(data.getData(), true /* eos */, runID);
+        setRunDiscardTimeMs(
+                runID,
+                (data.getStartTimeUs() + data.getDurationUs()) / 1000);
+    }
+
+    /**
+     * Called when there is input data for the subtitle track.  The
+     * complete subtitle for a track can include multiple whole units
+     * (runs).  Each of these units can have multiple sections.  The
+     * contents of a run are submitted in sequential order, with eos
+     * indicating the last section of the run.  Calls from different
+     * runs must not be intermixed.
+     *
+     * @param data subtitle data byte buffer
+     * @param eos true if this is the last section of the run.
+     * @param runID mostly-unique ID for this run of data.  Subtitle cues
+     *              with runID of 0 are discarded immediately after
+     *              display.  Cues with runID of ~0 are discarded
+     *              only at the deletion of the track object.  Cues
+     *              with other runID-s are discarded at the end of the
+     *              run, which defaults to the latest timestamp of
+     *              any of its cues (with this runID).
+     */
+    protected abstract void onData(byte[] data, boolean eos, long runID);
+
+    /**
+     * Called when adding the subtitle rendering widget to the view hierarchy,
+     * as well as when showing or hiding the subtitle track, or when the video
+     * surface position has changed.
+     *
+     * @return the widget that renders this subtitle track. For most renderers
+     *         there should be a single shared instance that is used for all
+     *         tracks supported by that renderer, as at most one subtitle track
+     *         is visible at one time.
+     */
+    public abstract RenderingWidget getRenderingWidget();
+
+    /**
+     * Called when the active cues have changed, and the contents of the subtitle
+     * view should be updated.
+     */
+    public abstract void updateView(Vector<Cue> activeCues);
+
+    protected synchronized void updateActiveCues(boolean rebuild, long timeMs) {
+        // out-of-order times mean seeking or new active cues being added
+        // (during their own timespan)
+        if (rebuild || mLastUpdateTimeMs > timeMs) {
+            clearActiveCues();
+        }
+
+        for(Iterator<Pair<Long, Cue> > it =
+                mCues.entriesBetween(mLastUpdateTimeMs, timeMs).iterator(); it.hasNext(); ) {
+            Pair<Long, Cue> event = it.next();
+            Cue cue = event.second;
+
+            if (cue.mEndTimeMs == event.first) {
+                // remove past cues
+                if (DEBUG) Log.v(TAG, "Removing " + cue);
+                mActiveCues.remove(cue);
+                if (cue.mRunID == 0) {
+                    it.remove();
+                }
+            } else if (cue.mStartTimeMs == event.first) {
+                // add new cues
+                // TRICKY: this will happen in start order
+                if (DEBUG) Log.v(TAG, "Adding " + cue);
+                if (cue.mInnerTimesMs != null) {
+                    cue.onTime(timeMs);
+                }
+                mActiveCues.add(cue);
+            } else if (cue.mInnerTimesMs != null) {
+                // cue is modified
+                cue.onTime(timeMs);
+            }
+        }
+
+        /* complete any runs */
+        while (mRunsByEndTime.size() > 0 &&
+               mRunsByEndTime.keyAt(0) <= timeMs) {
+            removeRunsByEndTimeIndex(0); // removes element
+        }
+        mLastUpdateTimeMs = timeMs;
+    }
+
+    private void removeRunsByEndTimeIndex(int ix) {
+        Run run = mRunsByEndTime.valueAt(ix);
+        while (run != null) {
+            Cue cue = run.mFirstCue;
+            while (cue != null) {
+                mCues.remove(cue);
+                Cue nextCue = cue.mNextInRun;
+                cue.mNextInRun = null;
+                cue = nextCue;
+            }
+            mRunsByID.remove(run.mRunID);
+            Run nextRun = run.mNextRunAtEndTimeMs;
+            run.mPrevRunAtEndTimeMs = null;
+            run.mNextRunAtEndTimeMs = null;
+            run = nextRun;
+        }
+        mRunsByEndTime.removeAt(ix);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        /* remove all cues (untangle all cross-links) */
+        int size = mRunsByEndTime.size();
+        for(int ix = size - 1; ix >= 0; ix--) {
+            removeRunsByEndTimeIndex(ix);
+        }
+
+        super.finalize();
+    }
+
+    private synchronized void takeTime(long timeMs) {
+        mLastTimeMs = timeMs;
+    }
+
+    protected synchronized void clearActiveCues() {
+        if (DEBUG) Log.v(TAG, "Clearing " + mActiveCues.size() + " active cues");
+        mActiveCues.clear();
+        mLastUpdateTimeMs = -1;
+    }
+
+    protected void scheduleTimedEvents() {
+        /* get times for the next event */
+        if (mTimeProvider != null) {
+            mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs);
+            if (DEBUG) Log.d(TAG, "sched @" + mNextScheduledTimeMs + " after " + mLastTimeMs);
+            mTimeProvider.notifyAt(
+                    mNextScheduledTimeMs >= 0 ?
+                        (mNextScheduledTimeMs * 1000) : MediaTimeProvider.NO_TIME,
+                    this);
+        }
+    }
+
+    @Override
+    public void onTimedEvent(long timeUs) {
+        if (DEBUG) Log.d(TAG, "onTimedEvent " + timeUs);
+        synchronized (this) {
+            long timeMs = timeUs / 1000;
+            updateActiveCues(false, timeMs);
+            takeTime(timeMs);
+        }
+        updateView(mActiveCues);
+        scheduleTimedEvents();
+    }
+
+    @Override
+    public void onSeek(long timeUs) {
+        if (DEBUG) Log.d(TAG, "onSeek " + timeUs);
+        synchronized (this) {
+            long timeMs = timeUs / 1000;
+            updateActiveCues(true, timeMs);
+            takeTime(timeMs);
+        }
+        updateView(mActiveCues);
+        scheduleTimedEvents();
+    }
+
+    @Override
+    public void onStop() {
+        synchronized (this) {
+            if (DEBUG) Log.d(TAG, "onStop");
+            clearActiveCues();
+            mLastTimeMs = -1;
+        }
+        updateView(mActiveCues);
+        mNextScheduledTimeMs = -1;
+        mTimeProvider.notifyAt(MediaTimeProvider.NO_TIME, this);
+    }
+
+    protected MediaTimeProvider mTimeProvider;
+
+    public void show() {
+        if (mVisible) {
+            return;
+        }
+
+        mVisible = true;
+        RenderingWidget renderingWidget = getRenderingWidget();
+        if (renderingWidget != null) {
+            renderingWidget.setVisible(true);
+        }
+        if (mTimeProvider != null) {
+            mTimeProvider.scheduleUpdate(this);
+        }
+    }
+
+    public void hide() {
+        if (!mVisible) {
+            return;
+        }
+
+        if (mTimeProvider != null) {
+            mTimeProvider.cancelNotifications(this);
+        }
+        RenderingWidget renderingWidget = getRenderingWidget();
+        if (renderingWidget != null) {
+            renderingWidget.setVisible(false);
+        }
+        mVisible = false;
+    }
+
+    protected synchronized boolean addCue(Cue cue) {
+        mCues.add(cue);
+
+        if (cue.mRunID != 0) {
+            Run run = mRunsByID.get(cue.mRunID);
+            if (run == null) {
+                run = new Run();
+                mRunsByID.put(cue.mRunID, run);
+                run.mEndTimeMs = cue.mEndTimeMs;
+            } else if (run.mEndTimeMs < cue.mEndTimeMs) {
+                run.mEndTimeMs = cue.mEndTimeMs;
+            }
+
+            // link-up cues in the same run
+            cue.mNextInRun = run.mFirstCue;
+            run.mFirstCue = cue;
+        }
+
+        // if a cue is added that should be visible, need to refresh view
+        long nowMs = -1;
+        if (mTimeProvider != null) {
+            try {
+                nowMs = mTimeProvider.getCurrentTimeUs(
+                        false /* precise */, true /* monotonic */) / 1000;
+            } catch (IllegalStateException e) {
+                // handle as it we are not playing
+            }
+        }
+
+        if (DEBUG) Log.v(TAG, "mVisible=" + mVisible + ", " +
+                cue.mStartTimeMs + " <= " + nowMs + ", " +
+                cue.mEndTimeMs + " >= " + mLastTimeMs);
+
+        if (mVisible &&
+                cue.mStartTimeMs <= nowMs &&
+                // we don't trust nowMs, so check any cue since last callback
+                cue.mEndTimeMs >= mLastTimeMs) {
+            if (mRunnable != null) {
+                mHandler.removeCallbacks(mRunnable);
+            }
+            final SubtitleTrack track = this;
+            final long thenMs = nowMs;
+            mRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    // even with synchronized, it is possible that we are going
+                    // to do multiple updates as the runnable could be already
+                    // running.
+                    synchronized (track) {
+                        mRunnable = null;
+                        updateActiveCues(true, thenMs);
+                        updateView(mActiveCues);
+                    }
+                }
+            };
+            // delay update so we don't update view on every cue.  TODO why 10?
+            if (mHandler.postDelayed(mRunnable, 10 /* delay */)) {
+                if (DEBUG) Log.v(TAG, "scheduling update");
+            } else {
+                if (DEBUG) Log.w(TAG, "failed to schedule subtitle view update");
+            }
+            return true;
+        }
+
+        if (mVisible &&
+                cue.mEndTimeMs >= mLastTimeMs &&
+                (cue.mStartTimeMs < mNextScheduledTimeMs ||
+                 mNextScheduledTimeMs < 0)) {
+            scheduleTimedEvents();
+        }
+
+        return false;
+    }
+
+    public synchronized void setTimeProvider(MediaTimeProvider timeProvider) {
+        if (mTimeProvider == timeProvider) {
+            return;
+        }
+        if (mTimeProvider != null) {
+            mTimeProvider.cancelNotifications(this);
+        }
+        mTimeProvider = timeProvider;
+        if (mTimeProvider != null) {
+            mTimeProvider.scheduleUpdate(this);
+        }
+    }
+
+
+    static class CueList {
+        private static final String TAG = "CueList";
+        // simplistic, inefficient implementation
+        private SortedMap<Long, Vector<Cue> > mCues;
+        public boolean DEBUG = false;
+
+        private boolean addEvent(Cue cue, long timeMs) {
+            Vector<Cue> cues = mCues.get(timeMs);
+            if (cues == null) {
+                cues = new Vector<Cue>(2);
+                mCues.put(timeMs, cues);
+            } else if (cues.contains(cue)) {
+                // do not duplicate cues
+                return false;
+            }
+
+            cues.add(cue);
+            return true;
+        }
+
+        private void removeEvent(Cue cue, long timeMs) {
+            Vector<Cue> cues = mCues.get(timeMs);
+            if (cues != null) {
+                cues.remove(cue);
+                if (cues.size() == 0) {
+                    mCues.remove(timeMs);
+                }
+            }
+        }
+
+        public void add(Cue cue) {
+            // ignore non-positive-duration cues
+            if (cue.mStartTimeMs >= cue.mEndTimeMs)
+                return;
+
+            if (!addEvent(cue, cue.mStartTimeMs)) {
+                return;
+            }
+
+            long lastTimeMs = cue.mStartTimeMs;
+            if (cue.mInnerTimesMs != null) {
+                for (long timeMs: cue.mInnerTimesMs) {
+                    if (timeMs > lastTimeMs && timeMs < cue.mEndTimeMs) {
+                        addEvent(cue, timeMs);
+                        lastTimeMs = timeMs;
+                    }
+                }
+            }
+
+            addEvent(cue, cue.mEndTimeMs);
+        }
+
+        public void remove(Cue cue) {
+            removeEvent(cue, cue.mStartTimeMs);
+            if (cue.mInnerTimesMs != null) {
+                for (long timeMs: cue.mInnerTimesMs) {
+                    removeEvent(cue, timeMs);
+                }
+            }
+            removeEvent(cue, cue.mEndTimeMs);
+        }
+
+        public Iterable<Pair<Long, Cue>> entriesBetween(
+                final long lastTimeMs, final long timeMs) {
+            return new Iterable<Pair<Long, Cue> >() {
+                @Override
+                public Iterator<Pair<Long, Cue> > iterator() {
+                    if (DEBUG) Log.d(TAG, "slice (" + lastTimeMs + ", " + timeMs + "]=");
+                    try {
+                        return new EntryIterator(
+                                mCues.subMap(lastTimeMs + 1, timeMs + 1));
+                    } catch(IllegalArgumentException e) {
+                        return new EntryIterator(null);
+                    }
+                }
+            };
+        }
+
+        public long nextTimeAfter(long timeMs) {
+            SortedMap<Long, Vector<Cue>> tail = null;
+            try {
+                tail = mCues.tailMap(timeMs + 1);
+                if (tail != null) {
+                    return tail.firstKey();
+                } else {
+                    return -1;
+                }
+            } catch(IllegalArgumentException e) {
+                return -1;
+            } catch(NoSuchElementException e) {
+                return -1;
+            }
+        }
+
+        class EntryIterator implements Iterator<Pair<Long, Cue> > {
+            @Override
+            public boolean hasNext() {
+                return !mDone;
+            }
+
+            @Override
+            public Pair<Long, Cue> next() {
+                if (mDone) {
+                    throw new NoSuchElementException("");
+                }
+                mLastEntry = new Pair<Long, Cue>(
+                        mCurrentTimeMs, mListIterator.next());
+                mLastListIterator = mListIterator;
+                if (!mListIterator.hasNext()) {
+                    nextKey();
+                }
+                return mLastEntry;
+            }
+
+            @Override
+            public void remove() {
+                // only allow removing end tags
+                if (mLastListIterator == null ||
+                        mLastEntry.second.mEndTimeMs != mLastEntry.first) {
+                    throw new IllegalStateException("");
+                }
+
+                // remove end-cue
+                mLastListIterator.remove();
+                mLastListIterator = null;
+                if (mCues.get(mLastEntry.first).size() == 0) {
+                    mCues.remove(mLastEntry.first);
+                }
+
+                // remove rest of the cues
+                Cue cue = mLastEntry.second;
+                removeEvent(cue, cue.mStartTimeMs);
+                if (cue.mInnerTimesMs != null) {
+                    for (long timeMs: cue.mInnerTimesMs) {
+                        removeEvent(cue, timeMs);
+                    }
+                }
+            }
+
+            public EntryIterator(SortedMap<Long, Vector<Cue> > cues) {
+                if (DEBUG) Log.v(TAG, cues + "");
+                mRemainingCues = cues;
+                mLastListIterator = null;
+                nextKey();
+            }
+
+            private void nextKey() {
+                do {
+                    try {
+                        if (mRemainingCues == null) {
+                            throw new NoSuchElementException("");
+                        }
+                        mCurrentTimeMs = mRemainingCues.firstKey();
+                        mListIterator =
+                            mRemainingCues.get(mCurrentTimeMs).iterator();
+                        try {
+                            mRemainingCues =
+                                mRemainingCues.tailMap(mCurrentTimeMs + 1);
+                        } catch (IllegalArgumentException e) {
+                            mRemainingCues = null;
+                        }
+                        mDone = false;
+                    } catch (NoSuchElementException e) {
+                        mDone = true;
+                        mRemainingCues = null;
+                        mListIterator = null;
+                        return;
+                    }
+                } while (!mListIterator.hasNext());
+            }
+
+            private long mCurrentTimeMs;
+            private Iterator<Cue> mListIterator;
+            private boolean mDone;
+            private SortedMap<Long, Vector<Cue> > mRemainingCues;
+            private Iterator<Cue> mLastListIterator;
+            private Pair<Long,Cue> mLastEntry;
+        }
+
+        CueList() {
+            mCues = new TreeMap<Long, Vector<Cue>>();
+        }
+    }
+
+    public static class Cue {
+        public long mStartTimeMs;
+        public long mEndTimeMs;
+        public long[] mInnerTimesMs;
+        public long mRunID;
+
+        public Cue mNextInRun;
+
+        public void onTime(long timeMs) { }
+    }
+
+    /** update mRunsByEndTime (with default end time) */
+    protected void finishedRun(long runID) {
+        if (runID != 0 && runID != ~0) {
+            Run run = mRunsByID.get(runID);
+            if (run != null) {
+                run.storeByEndTimeMs(mRunsByEndTime);
+            }
+        }
+    }
+
+    /** update mRunsByEndTime with given end time */
+    public void setRunDiscardTimeMs(long runID, long timeMs) {
+        if (runID != 0 && runID != ~0) {
+            Run run = mRunsByID.get(runID);
+            if (run != null) {
+                run.mEndTimeMs = timeMs;
+                run.storeByEndTimeMs(mRunsByEndTime);
+            }
+        }
+    }
+
+    /** whether this is a text track who fires events instead getting rendered */
+    public int getTrackType() {
+        return getRenderingWidget() == null
+                ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT
+                : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
+    }
+
+
+    private static class Run {
+        public Cue mFirstCue;
+        public Run mNextRunAtEndTimeMs;
+        public Run mPrevRunAtEndTimeMs;
+        public long mEndTimeMs = -1;
+        public long mRunID = 0;
+        private long mStoredEndTimeMs = -1;
+
+        public void storeByEndTimeMs(LongSparseArray<Run> runsByEndTime) {
+            // remove old value if any
+            int ix = runsByEndTime.indexOfKey(mStoredEndTimeMs);
+            if (ix >= 0) {
+                if (mPrevRunAtEndTimeMs == null) {
+                    assert(this == runsByEndTime.valueAt(ix));
+                    if (mNextRunAtEndTimeMs == null) {
+                        runsByEndTime.removeAt(ix);
+                    } else {
+                        runsByEndTime.setValueAt(ix, mNextRunAtEndTimeMs);
+                    }
+                }
+                removeAtEndTimeMs();
+            }
+
+            // add new value
+            if (mEndTimeMs >= 0) {
+                mPrevRunAtEndTimeMs = null;
+                mNextRunAtEndTimeMs = runsByEndTime.get(mEndTimeMs);
+                if (mNextRunAtEndTimeMs != null) {
+                    mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = this;
+                }
+                runsByEndTime.put(mEndTimeMs, this);
+                mStoredEndTimeMs = mEndTimeMs;
+            }
+        }
+
+        public void removeAtEndTimeMs() {
+            Run prev = mPrevRunAtEndTimeMs;
+
+            if (mPrevRunAtEndTimeMs != null) {
+                mPrevRunAtEndTimeMs.mNextRunAtEndTimeMs = mNextRunAtEndTimeMs;
+                mPrevRunAtEndTimeMs = null;
+            }
+            if (mNextRunAtEndTimeMs != null) {
+                mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = prev;
+                mNextRunAtEndTimeMs = null;
+            }
+        }
+    }
+
+    /**
+     * Interface for rendering subtitles onto a Canvas.
+     */
+    public interface RenderingWidget {
+        /**
+         * Sets the widget's callback, which is used to send updates when the
+         * rendered data has changed.
+         *
+         * @param callback update callback
+         */
+        public void setOnChangedListener(OnChangedListener callback);
+
+        /**
+         * Sets the widget's size.
+         *
+         * @param width width in pixels
+         * @param height height in pixels
+         */
+        public void setSize(int width, int height);
+
+        /**
+         * Sets whether the widget should draw subtitles.
+         *
+         * @param visible true if subtitles should be drawn, false otherwise
+         */
+        public void setVisible(boolean visible);
+
+        /**
+         * Renders subtitles onto a {@link Canvas}.
+         *
+         * @param c canvas on which to render subtitles
+         */
+        public void draw(Canvas c);
+
+        /**
+         * Called when the widget is attached to a window.
+         */
+        public void onAttachedToWindow();
+
+        /**
+         * Called when the widget is detached from a window.
+         */
+        public void onDetachedFromWindow();
+
+        /**
+         * Callback used to send updates about changes to rendering data.
+         */
+        public interface OnChangedListener {
+            /**
+             * Called when the rendering data has changed.
+             *
+             * @param renderingWidget the widget whose data has changed
+             */
+            public void onChanged(RenderingWidget renderingWidget);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 07b8788..05a54d5 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -18,9 +18,7 @@
 
 import android.app.Notification;
 import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.media.DataSourceDesc;
+import android.content.pm.ApplicationInfo;
 import android.media.MediaBrowser2;
 import android.media.MediaBrowser2.BrowserCallback;
 import android.media.MediaController2;
@@ -31,17 +29,14 @@
 import android.media.MediaLibraryService2.MediaLibrarySession;
 import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
 import android.media.MediaPlaylistAgent;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 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;
-import android.media.PlaybackState2;
 import android.media.Rating2;
 import android.media.SessionToken2;
 import android.media.VolumeProvider2;
@@ -55,10 +50,8 @@
 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.PlaybackState2Provider;
 import android.media.update.SessionToken2Provider;
 import android.media.update.StaticProvider;
 import android.media.update.VideoView2Provider;
@@ -71,7 +64,7 @@
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
 
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
 import com.android.media.MediaBrowser2Impl;
 import com.android.media.MediaController2Impl;
 import com.android.media.MediaItem2Impl;
@@ -80,9 +73,7 @@
 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.PlaybackState2Impl;
 import com.android.media.Rating2Impl;
 import com.android.media.SessionToken2Impl;
 import com.android.media.VolumeProvider2Impl;
@@ -91,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();
     }
 
@@ -142,20 +134,7 @@
             Context context, ControllerInfo instance, int uid, int pid, String packageName,
             IInterface callback) {
         return new MediaSession2Impl.ControllerInfoImpl(context,
-                instance, uid, pid, packageName, (IMediaSession2Callback) callback);
-    }
-
-    @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);
+                instance, uid, pid, packageName, (IMediaController2) callback);
     }
 
     @Override
@@ -293,19 +272,6 @@
     }
 
     @Override
-    public PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance,
-            int state, long position, long updateTime, float speed, long bufferedPosition,
-            long activeItemId) {
-        return new PlaybackState2Impl(context, instance, state, position, updateTime, speed,
-                bufferedPosition, activeItemId);
-    }
-
-    @Override
-    public PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle) {
-        return PlaybackState2Impl.fromBundle(context, bundle);
-    }
-
-    @Override
     public MediaPlaylistAgentProvider createMediaPlaylistAgent(Context context,
             MediaPlaylistAgent instance) {
         return new MediaPlaylistAgentImpl(context, instance);
diff --git a/packages/MediaComponents/src/com/android/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 69febc2..9ae75b0 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -52,6 +52,7 @@
 import com.android.support.mediarouter.media.MediaRouter;
 import com.android.support.mediarouter.media.MediaRouteSelector;
 
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Formatter;
 import java.util.List;
@@ -69,18 +70,42 @@
     static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount";
     static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount";
     static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount";
+    static final String KEY_PLAYBACK_SPEED = "PlaybackSpeed";
+    static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex";
+    static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex";
     static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus";
 
     // TODO: Remove this once integrating with MediaSession2 & MediaMetadata2
     static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement";
     static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus";
 
-    // String for receiving command to show subtitle from MediaSession.
+    // String for sending command to show subtitle to MediaSession.
     static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
-    // String for receiving command to hide subtitle from MediaSession.
+    // String for sending command to hide subtitle to MediaSession.
     static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
     // TODO: remove once the implementation is revised
     public static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+    // String for sending command to select audio track to MediaSession.
+    static final String COMMAND_SELECT_AUDIO_TRACK = "SelectTrack";
+    // String for sending command to set playback speed to MediaSession.
+    static final String COMMAND_SET_PLAYBACK_SPEED = "SetPlaybackSpeed";
+    // String for sending command to mute audio to MediaSession.
+    static final String COMMAND_MUTE= "Mute";
+    // String for sending command to unmute audio to MediaSession.
+    static final String COMMAND_UNMUTE = "Unmute";
+
+    private static final int SETTINGS_MODE_AUDIO_TRACK = 0;
+    private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1;
+    private static final int SETTINGS_MODE_HELP = 2;
+    private static final int SETTINGS_MODE_SUBTITLE_TRACK = 3;
+    private static final int SETTINGS_MODE_VIDEO_QUALITY = 4;
+    private static final int SETTINGS_MODE_MAIN = 5;
+    private static final int PLAYBACK_SPEED_1x_INDEX = 3;
+
+    private static final int 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;
@@ -95,18 +120,25 @@
     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 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;
+    private int mSettingsMode;
+    private int mSelectedSubtitleTrackIndex;
+    private int mSelectedAudioTrackIndex;
+    private int mSelectedVideoQualityIndex;
+    private int mSelectedSpeedIndex;
+    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;
@@ -115,46 +147,70 @@
     private boolean mSubtitleIsEnabled;
     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;
 
+    // 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 mAspectRationButton;
+    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;
     private List<String> mSubtitleDescriptionsList;
     private List<String> mAudioTrackList;
     private List<String> mVideoQualityList;
-    private List<String> mPlaybackSpeedTextIdsList;
+    private List<String> mPlaybackSpeedTextList;
+    private List<Float> mPlaybackSpeedList;
 
     private CharSequence mPlayDescription;
     private CharSequence mPauseDescription;
     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);
@@ -163,10 +219,9 @@
 
     @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();
-        mRoot.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
         mInstance.addView(mRoot);
     }
 
@@ -217,15 +272,11 @@
                 }
                 break;
             case MediaControlView2.BUTTON_NEXT:
-                // TODO: this button is not visible unless its listener is manually set. Should this
-                // function still be provided?
                 if (mNextButton != null) {
                     mNextButton.setVisibility(visibility);
                 }
                 break;
             case MediaControlView2.BUTTON_PREV:
-                // TODO: this button is not visible unless its listener is manually set. Should this
-                // function still be provided?
                 if (mPrevButton != null) {
                     mPrevButton.setVisibility(visibility);
                 }
@@ -250,11 +301,6 @@
                     mMuteButton.setVisibility(visibility);
                 }
                 break;
-            case MediaControlView2.BUTTON_ASPECT_RATIO:
-                if (mAspectRationButton != null) {
-                    mAspectRationButton.setVisibility(visibility);
-                }
-                break;
             case MediaControlView2.BUTTON_SETTINGS:
                 if (mSettingsButton != null) {
                     mSettingsButton.setVisibility(visibility);
@@ -289,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);
 
@@ -363,7 +438,8 @@
         }
         mPlaybackState = mController.getPlaybackState();
         if (mPlaybackState != null) {
-            return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+            long bufferedPos = mPlaybackState.getBufferedPosition();
+            return (bufferedPos == -1) ? -1 : (int) (bufferedPos * 100 / mDuration);
         }
         return 0;
     }
@@ -409,42 +485,50 @@
         mReplayDescription =
                 mResources.getText(R.string.lockscreen_replay_button_content_description);
 
+        // 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);
 
-        mPlayPauseButton = v.findViewById(R.id.pause);
-        if (mPlayPauseButton != null) {
-            mPlayPauseButton.requestFocus();
-            mPlayPauseButton.setOnClickListener(mPlayPauseListener);
-            mPlayPauseButton.setColorFilter(R.color.gray);
-            mPlayPauseButton.setEnabled(false);
-        }
-        mFfwdButton = v.findViewById(R.id.ffwd);
-        if (mFfwdButton != null) {
-            mFfwdButton.setOnClickListener(mFfwdListener);
-            mFfwdButton.setColorFilter(R.color.gray);
-            mFfwdButton.setEnabled(false);
-        }
-        mRewButton = v.findViewById(R.id.rew);
-        if (mRewButton != null) {
-            mRewButton.setOnClickListener(mRewListener);
-            mRewButton.setColorFilter(R.color.gray);
-            mRewButton.setEnabled(false);
-        }
-        mNextButton = v.findViewById(R.id.next);
-        if (mNextButton != null) {
-            mNextButton.setOnClickListener(mNextListener);
-        }
-        mPrevButton = v.findViewById(R.id.prev);
-        if (mPrevButton != null) {
-            mPrevButton.setOnClickListener(mPrevListener);
+        // 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);
-            mSubtitleButton.setColorFilter(R.color.gray);
-            mSubtitleButton.setEnabled(false);
         }
         mFullScreenButton = v.findViewById(R.id.fullscreen);
         if (mFullScreenButton != null) {
@@ -455,51 +539,45 @@
         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);
         }
         mMuteButton = v.findViewById(R.id.mute);
-        mAspectRationButton = v.findViewById(R.id.aspect_ratio);
+        if (mMuteButton != null) {
+            mMuteButton.setOnClickListener(mMuteButtonListener);
+        }
         mSettingsButton = v.findViewById(R.id.settings);
         if (mSettingsButton != null) {
             mSettingsButton.setOnClickListener(mSettingsButtonListener);
         }
-
-        mProgress = v.findViewById(R.id.mediacontroller_progress);
-        if (mProgress != null) {
-            if (mProgress instanceof SeekBar) {
-                SeekBar seeker = (SeekBar) mProgress;
-                seeker.setOnSeekBarChangeListener(mSeekListener);
-            }
-            mProgress.setMax(MAX_PROGRESS);
+        mVideoQualityButton = v.findViewById(R.id.video_quality);
+        if (mVideoQualityButton != null) {
+            mVideoQualityButton.setOnClickListener(mVideoQualityListener);
         }
-
-        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);
         mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList,
-                mSettingsIconIdsList, false);
+                mSettingsIconIdsList);
+        mSubSettingsAdapter = new SubSettingsAdapter(null, 0);
         mSettingsListView.setAdapter(mSettingsAdapter);
         mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
-        int width = mResources.getDimensionPixelSize(R.dimen.MediaControlView2_settings_width);
-        mSettingsWindow = new PopupWindow(mSettingsListView, width,
+
+        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.mcv2_settings_offset);
+        mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth,
                 ViewGroup.LayoutParams.WRAP_CONTENT, true);
     }
 
@@ -574,7 +652,13 @@
         }
         if (mProgress != null && currentPosition != mDuration) {
             mProgress.setProgress(positionOnProgressBar);
-            mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+            // If the media is a local file, there is no need to set a buffer, so set secondary
+            // progress to maximum.
+            if (getBufferPercentage() < 0) {
+                mProgress.setSecondaryProgress(MAX_PROGRESS);
+            } else {
+                mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+            }
         }
 
         if (mEndTime != null) {
@@ -744,20 +828,30 @@
         }
     };
 
+    private final View.OnClickListener mBackListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            // TODO: implement
+        }
+    };
+
     private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            if (!mSubtitleIsEnabled) {
-                mSubtitleButton.setImageDrawable(
-                        mResources.getDrawable(R.drawable.ic_media_subtitle_enabled, null));
-                mController.sendCommand(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, null);
-                mSubtitleIsEnabled = true;
-            } else {
-                mSubtitleButton.setImageDrawable(
-                        mResources.getDrawable(R.drawable.ic_media_subtitle_disabled, null));
-                mController.sendCommand(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
-                mSubtitleIsEnabled = false;
-            }
+            mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK;
+            mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList);
+            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
+            displaySettingsWindow(mSubSettingsAdapter);
+        }
+    };
+
+    private final View.OnClickListener mVideoQualityListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mSettingsMode = SETTINGS_MODE_VIDEO_QUALITY;
+            mSubSettingsAdapter.setTexts(mVideoQualityList);
+            mSubSettingsAdapter.setCheckPosition(mSelectedVideoQualityIndex);
+            displaySettingsWindow(mSubSettingsAdapter);
         }
     };
 
@@ -797,20 +891,29 @@
         }
     };
 
+    private final View.OnClickListener mMuteButtonListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (!mIsMute) {
+                mMuteButton.setImageDrawable(
+                        mResources.getDrawable(R.drawable.ic_mute, null));
+                mIsMute = true;
+                mController.sendCommand(COMMAND_MUTE, null, null);
+            } else {
+                mMuteButton.setImageDrawable(
+                        mResources.getDrawable(R.drawable.ic_unmute, null));
+                mIsMute = false;
+                mController.sendCommand(COMMAND_UNMUTE, null, null);
+            }
+        }
+    };
+
     private final View.OnClickListener mSettingsButtonListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList,
-                    mSettingsSubTextsList, mSettingsIconIdsList, false);
-            mSettingsListView.setAdapter(mSettingsAdapter);
-            int itemHeight = mResources.getDimensionPixelSize(
-                    R.dimen.MediaControlView2_settings_height);
-            int totalHeight = mSettingsAdapter.getCount() * itemHeight;
-            int margin = (-1) * mResources.getDimensionPixelSize(
-                    R.dimen.MediaControlView2_settings_offset);
-            mSettingsWindow.dismiss();
-            mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
-                    Gravity.BOTTOM | Gravity.RIGHT);
+            mSettingsMode = SETTINGS_MODE_MAIN;
+            mSettingsAdapter.setSubTexts(mSettingsSubTextsList);
+            displaySettingsWindow(mSettingsAdapter);
         }
     };
 
@@ -818,85 +921,77 @@
             = new AdapterView.OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            switch (position) {
-                // change to identifiers
-                case 0:
-                    // TODO: add additional subtitle track details
-                    mSubtitleDescriptionsList = new ArrayList<String>();
-                    mSubtitleDescriptionsList.add(mResources.getString(
-                            R.string.MediaControlView2_subtitle_off_text));
-                    for (int i = 0; i < mSubtitleTrackCount; i++) {
-                        String track = mResources.getString(
-                                R.string.MediaControlView2_subtitle_track_number_text, i + 1);
-                        mSubtitleDescriptionsList.add(track);
+            switch (mSettingsMode) {
+                case SETTINGS_MODE_MAIN:
+                    if (position == SETTINGS_MODE_AUDIO_TRACK) {
+                        mSubSettingsAdapter.setTexts(mAudioTrackList);
+                        mSubSettingsAdapter.setCheckPosition(mSelectedAudioTrackIndex);
+                        mSettingsMode = SETTINGS_MODE_AUDIO_TRACK;
+                    } else if (position == SETTINGS_MODE_PLAYBACK_SPEED) {
+                        mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
+                        mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
+                        mSettingsMode = SETTINGS_MODE_PLAYBACK_SPEED;
+                    } else if (position == SETTINGS_MODE_HELP) {
+                        // TODO: implement this.
+                        mSettingsWindow.dismiss();
+                        return;
                     }
-                    mSettingsAdapter = new SettingsAdapter(mSubtitleDescriptionsList, null,
-                            null, true);
+                    displaySettingsWindow(mSubSettingsAdapter);
                     break;
-                case 1:
-                    // TODO: add additional audio track details
-                    mAudioTrackList = new ArrayList<String>();
-                    mAudioTrackList.add(mResources.getString(
-                            R.string.MediaControlView2_audio_track_none_text));
-                    for (int i = 0; i < mAudioTrackCount; i++) {
-                        String track = mResources.getString(
-                                R.string.MediaControlView2_audio_track_number_text, i + 1);
-                        mAudioTrackList.add(track);
+                case SETTINGS_MODE_AUDIO_TRACK:
+                    if (position != mSelectedAudioTrackIndex) {
+                        mSelectedAudioTrackIndex = position;
+                        if (mAudioTrackCount > 0) {
+                            Bundle extra = new Bundle();
+                            extra.putInt(KEY_SELECTED_AUDIO_INDEX, position);
+                            mController.sendCommand(COMMAND_SELECT_AUDIO_TRACK, extra, null);
+                        }
+                        mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+                                mSubSettingsAdapter.getMainText(position));
                     }
-                    mSettingsAdapter = new SettingsAdapter(mAudioTrackList, null,
-                            null, true);
+                    mSettingsWindow.dismiss();
                     break;
-                case 2:
-                    // TODO: add support for multiple quality video tracks
-                    mSettingsAdapter = new SettingsAdapter(mVideoQualityList, null,
-                            null, true);
+                case SETTINGS_MODE_PLAYBACK_SPEED:
+                    if (position != mSelectedSpeedIndex) {
+                        mSelectedSpeedIndex = position;
+                        Bundle extra = new Bundle();
+                        extra.putFloat(KEY_PLAYBACK_SPEED, mPlaybackSpeedList.get(position));
+                        mController.sendCommand(COMMAND_SET_PLAYBACK_SPEED, extra, null);
+                        mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED,
+                                mSubSettingsAdapter.getMainText(position));
+                    }
+                    mSettingsWindow.dismiss();
                     break;
-                case 3:
-                    // TODO: implement code to reflect change in speed.
-                    mSettingsAdapter = new SettingsAdapter(mPlaybackSpeedTextIdsList, null,
-                            null, true);
+                case SETTINGS_MODE_HELP:
+                    // TODO: implement this.
                     break;
-                default:
-                    return;
-            }
-            mSettingsListView.setAdapter(mSettingsAdapter);
-            int itemHeight = mResources.getDimensionPixelSize(
-                    R.dimen.MediaControlView2_settings_height);
-            int totalHeight = mSettingsAdapter.getCount() * itemHeight;
-            int margin = (-1) * mResources.getDimensionPixelSize(
-                    R.dimen.MediaControlView2_settings_offset);
-            mSettingsWindow.dismiss();
-            mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
-                    Gravity.BOTTOM | Gravity.RIGHT);
-        }
-    };
+                case SETTINGS_MODE_SUBTITLE_TRACK:
+                    if (position != mSelectedSubtitleTrackIndex) {
+                        mSelectedSubtitleTrackIndex = position;
+                        if (position > 0) {
+                            Bundle extra = new Bundle();
+                            extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, position - 1);
+                            mController.sendCommand(
+                                    MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, extra, null);
+                            mSubtitleButton.setImageDrawable(
+                                    mResources.getDrawable(R.drawable.ic_subtitle_on, null));
+                            mSubtitleIsEnabled = true;
+                        } else {
+                            mController.sendCommand(
+                                    MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
+                            mSubtitleButton.setImageDrawable(
+                                    mResources.getDrawable(R.drawable.ic_subtitle_off, null));
 
-    // 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);
+                            mSubtitleIsEnabled = false;
+                        }
+                    }
+                    mSettingsWindow.dismiss();
+                    break;
+                case SETTINGS_MODE_VIDEO_QUALITY:
+                    // TODO: add support for video quality
+                    mSelectedVideoQualityIndex = position;
+                    mSettingsWindow.dismiss();
+                    break;
             }
         }
     };
@@ -919,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);
@@ -938,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);
@@ -952,81 +1070,140 @@
         }
     }
 
+    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() {
-        if (mSettingsMainTextsList == null) {
-            mSettingsMainTextsList = new ArrayList<String>();
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_subtitle_text));
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_audio_track_text));
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_video_quality_text));
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_text));
-            mSettingsMainTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_help_text));
-        }
+        mSettingsMainTextsList = new ArrayList<String>();
+        mSettingsMainTextsList.add(
+                mResources.getString(R.string.MediaControlView2_audio_track_text));
+        mSettingsMainTextsList.add(
+                mResources.getString(R.string.MediaControlView2_playback_speed_text));
+        mSettingsMainTextsList.add(
+                mResources.getString(R.string.MediaControlView2_help_text));
 
-        // TODO: Update the following code to be dynamic.
-        if (mSettingsSubTextsList == null) {
-            mSettingsSubTextsList = new ArrayList<String>();
-            mSettingsSubTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_subtitle_off_text));
-            mSettingsSubTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_audio_track_none_text));
-            mSettingsSubTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
-            mSettingsSubTextsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
-            mSettingsSubTextsList.add(RESOURCE_EMPTY);
-        }
+        mSettingsSubTextsList = new ArrayList<String>();
+        mSettingsSubTextsList.add(
+                mResources.getString(R.string.MediaControlView2_audio_track_none_text));
+        mSettingsSubTextsList.add(
+                mResources.getStringArray(
+                        R.array.MediaControlView2_playback_speeds)[PLAYBACK_SPEED_1x_INDEX]);
+        mSettingsSubTextsList.add(RESOURCE_EMPTY);
 
-        if (mSettingsIconIdsList == null) {
-            mSettingsIconIdsList = new ArrayList<Integer>();
-            mSettingsIconIdsList.add(R.drawable.ic_closed_caption_off);
-            mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
-            mSettingsIconIdsList.add(R.drawable.ic_high_quality);
-            mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
-            mSettingsIconIdsList.add(R.drawable.ic_help);
-        }
+        mSettingsIconIdsList = new ArrayList<Integer>();
+        mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
+        mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
+        mSettingsIconIdsList.add(R.drawable.ic_help);
 
-        if (mSubtitleDescriptionsList == null) {
-            mSubtitleDescriptionsList = new ArrayList<String>();
-            mSubtitleDescriptionsList.add(
-                    mResources.getString(R.string.MediaControlView2_subtitle_off_text));
-        }
+        mAudioTrackList = new ArrayList<String>();
+        mAudioTrackList.add(
+                mResources.getString(R.string.MediaControlView2_audio_track_none_text));
 
-        if (mAudioTrackList == null) {
-            mAudioTrackList = new ArrayList<String>();
-            mAudioTrackList.add(
-                    mResources.getString(R.string.MediaControlView2_audio_track_none_text));
-        }
+        mVideoQualityList = new ArrayList<String>();
+        mVideoQualityList.add(
+                mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
 
-        if (mVideoQualityList == null) {
-            mVideoQualityList = new ArrayList<String>();
-            mVideoQualityList.add(
-                    mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
-        }
+        mPlaybackSpeedTextList = new ArrayList<String>(Arrays.asList(
+                mResources.getStringArray(R.array.MediaControlView2_playback_speeds)));
+        // Select the "1x" speed as the default value.
+        mSelectedSpeedIndex = PLAYBACK_SPEED_1x_INDEX;
 
-        if (mPlaybackSpeedTextIdsList == null) {
-            mPlaybackSpeedTextIdsList = new ArrayList<String>();
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_0_25x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_0_5x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_0_75x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_1_25x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_1_5x_text));
-            mPlaybackSpeedTextIdsList.add(
-                    mResources.getString(R.string.MediaControlView2_playback_speed_2x_text));
+        mPlaybackSpeedList = new ArrayList<Float>();
+        int[] speeds = mResources.getIntArray(R.array.speed_multiplied_by_100);
+        for (int i = 0; i < speeds.length; i++) {
+            float speed = (float) speeds[i] / 100.0f;
+            mPlaybackSpeedList.add(speed);
         }
     }
 
+    private void displaySettingsWindow(BaseAdapter adapter) {
+        // Set Adapter
+        mSettingsListView.setAdapter(adapter);
+
+        // 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);
+    }
+
     private class MediaControllerCallback extends MediaController.Callback {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -1065,16 +1242,13 @@
             if (mPlaybackActions != mPlaybackState.getActions()) {
                 long newActions = mPlaybackState.getActions();
                 if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
-                    mPlayPauseButton.clearColorFilter();
-                    mPlayPauseButton.setEnabled(true);
+                    mPlayPauseButton.setVisibility(View.VISIBLE);
                 }
                 if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
-                    mRewButton.clearColorFilter();
-                    mRewButton.setEnabled(true);
+                    mRewButton.setVisibility(View.VISIBLE);
                 }
                 if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
-                    mFfwdButton.clearColorFilter();
-                    mFfwdButton.setEnabled(true);
+                    mFfwdButton.setVisibility(View.VISIBLE);
                 }
                 if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
                     mSeekAvailable = true;
@@ -1122,16 +1296,42 @@
             switch (event) {
                 case EVENT_UPDATE_TRACK_STATUS:
                     mVideoTrackCount = extras.getInt(KEY_VIDEO_TRACK_COUNT);
+                    // If there is one or more audio tracks, and this information has not been
+                    // reflected into the Settings window yet, automatically check the first track.
+                    // Otherwise, the Audio Track selection will be defaulted to "None".
                     mAudioTrackCount = extras.getInt(KEY_AUDIO_TRACK_COUNT);
-                    int newSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
-                    if (newSubtitleTrackCount > 0) {
-                        mSubtitleButton.clearColorFilter();
-                        mSubtitleButton.setEnabled(true);
+                    mAudioTrackList = new ArrayList<String>();
+                    if (mAudioTrackCount > 0) {
+                        // TODO: add more text about track info.
+                        for (int i = 0; i < mAudioTrackCount; i++) {
+                            String track = mResources.getString(
+                                    R.string.MediaControlView2_audio_track_number_text, i + 1);
+                            mAudioTrackList.add(track);
+                        }
+                        // Change sub text inside the Settings window.
+                        mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+                                mAudioTrackList.get(0));
                     } else {
-                        mSubtitleButton.setColorFilter(R.color.gray);
+                        mAudioTrackList.add(mResources.getString(
+                                R.string.MediaControlView2_audio_track_none_text));
+                    }
+
+                    mSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
+                    mSubtitleDescriptionsList = new ArrayList<String>();
+                    if (mSubtitleTrackCount > 0) {
+                        mSubtitleButton.setVisibility(View.VISIBLE);
+                        mSubtitleButton.setEnabled(true);
+                        mSubtitleDescriptionsList.add(mResources.getString(
+                                R.string.MediaControlView2_subtitle_off_text));
+                        for (int i = 0; i < mSubtitleTrackCount; i++) {
+                            String track = mResources.getString(
+                                    R.string.MediaControlView2_subtitle_track_number_text, i + 1);
+                            mSubtitleDescriptionsList.add(track);
+                        }
+                    } else {
+                        mSubtitleButton.setVisibility(View.GONE);
                         mSubtitleButton.setEnabled(false);
                     }
-                    mSubtitleTrackCount = newSubtitleTrackCount;
                     break;
                 case EVENT_UPDATE_MEDIA_TYPE_STATUS:
                     boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
@@ -1145,17 +1345,29 @@
     }
 
     private class SettingsAdapter extends BaseAdapter {
-        List<Integer> mIconIds;
-        List<String> mMainTexts;
-        List<String> mSubTexts;
-        boolean mIsCheckable;
+        private List<Integer> mIconIds;
+        private List<String> mMainTexts;
+        private List<String> mSubTexts;
 
         public SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts,
-                @Nullable List<Integer> iconIds, boolean isCheckable) {
+                @Nullable List<Integer> iconIds) {
             mMainTexts = mainTexts;
             mSubTexts = subTexts;
             mIconIds = iconIds;
-            mIsCheckable = isCheckable;
+        }
+
+        public void updateSubTexts(List<String> subTexts) {
+            mSubTexts = subTexts;
+            notifyDataSetChanged();
+        }
+
+        public String getMainText(int position) {
+            if (mMainTexts != null) {
+                if (position < mMainTexts.size()) {
+                    return mMainTexts.get(position);
+                }
+            }
+            return RESOURCE_EMPTY;
         }
 
         @Override
@@ -1179,12 +1391,17 @@
 
         @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);
-            ImageView checkView = (ImageView) row.findViewById(R.id.check);
 
             // Set main text
             mainTextView.setText(mMainTexts.get(position));
@@ -1206,13 +1423,78 @@
                 // Otherwise, set main icon.
                 iconView.setImageDrawable(mResources.getDrawable(mIconIds.get(position), null));
             }
+            return row;
+        }
 
-            // Set check icon
-            // TODO: make the following code dynamic
-            if (!mIsCheckable) {
-                checkView.setVisibility(View.GONE);
+        public void setSubTexts(List<String> subTexts) {
+            mSubTexts = subTexts;
+        }
+    }
+
+    // TODO: extend this class from SettingsAdapter
+    private class SubSettingsAdapter extends BaseAdapter {
+        private List<String> mTexts;
+        private int mCheckPosition;
+
+        public SubSettingsAdapter(List<String> texts, int checkPosition) {
+            mTexts = texts;
+            mCheckPosition = checkPosition;
+        }
+
+        public String getMainText(int position) {
+            if (mTexts != null) {
+                if (position < mTexts.size()) {
+                    return mTexts.get(position);
+                }
+            }
+            return RESOURCE_EMPTY;
+        }
+
+        @Override
+        public int getCount() {
+            return (mTexts == null) ? 0 : mTexts.size();
+        }
+
+        @Override
+        public long getItemId(int position) {
+            // Auto-generated method stub--does not have any purpose here
+            // TODO: implement this.
+            return 0;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            // Auto-generated method stub--does not have any purpose here
+            // TODO: implement this.
+            return null;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup container) {
+            View row;
+            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);
+
+            textView.setText(mTexts.get(position));
+            if (position != mCheckPosition) {
+                checkView.setVisibility(View.INVISIBLE);
             }
             return row;
         }
+
+        public void setTexts(List<String> texts) {
+            mTexts = texts;
+        }
+
+        public void setCheckPosition(int checkPosition) {
+            mCheckPosition = checkPosition;
+        }
     }
 }
diff --git a/packages/MediaComponents/src/com/android/widget/SubtitleView.java b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
index 9071967..67b2cd1 100644
--- a/packages/MediaComponents/src/com/android/widget/SubtitleView.java
+++ b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
@@ -18,13 +18,14 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
-import android.media.SubtitleController.Anchor;
-import android.media.SubtitleTrack.RenderingWidget;
 import android.os.Looper;
 import android.support.annotation.Nullable;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
 
+import com.android.media.subtitle.SubtitleController.Anchor;
+import com.android.media.subtitle.SubtitleTrack.RenderingWidget;
+
 class SubtitleView extends FrameLayout implements Anchor {
     private static final String TAG = "SubtitleView";
 
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 0bb659d..46ae359 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -24,18 +24,14 @@
 import android.media.MediaMetadata;
 import android.media.MediaPlayer2;
 import android.media.MediaPlayer2.MediaPlayer2EventCallback;
+import android.media.MediaPlayer2.OnSubtitleDataListener;
 import android.media.MediaPlayer2Impl;
-import android.media.Cea708CaptionRenderer;
-import android.media.ClosedCaptionRenderer;
+import android.media.SubtitleData;
 import android.media.MediaItem2;
 import android.media.MediaMetadata2;
 import android.media.Metadata;
 import android.media.PlaybackParams;
-import android.media.SRTRenderer;
-import android.media.SubtitleController;
 import android.media.TimedText;
-import android.media.TtmlRenderer;
-import android.media.WebVttRenderer;
 import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession;
@@ -59,6 +55,9 @@
 import android.widget.VideoView2;
 
 import com.android.media.RoutePlayer;
+import com.android.media.subtitle.ClosedCaptionRenderer;
+import com.android.media.subtitle.SubtitleController;
+import com.android.media.subtitle.SubtitleTrack;
 import com.android.support.mediarouter.media.MediaItemStatus;
 import com.android.support.mediarouter.media.MediaControlIntent;
 import com.android.support.mediarouter.media.MediaRouter;
@@ -86,6 +85,7 @@
     private static final int STATE_PLAYBACK_COMPLETED = 5;
 
     private static final int INVALID_TRACK_INDEX = -1;
+    private static final float INVALID_SPEED = 0f;
 
     private AccessibilityManager mAccessibilityManager;
     private AudioManager mAudioManager;
@@ -123,7 +123,8 @@
 
     private ArrayList<Integer> mVideoTrackIndices;
     private ArrayList<Integer> mAudioTrackIndices;
-    private ArrayList<Integer> mSubtitleTrackIndices;
+    private ArrayList<Pair<Integer, SubtitleTrack>> mSubtitleTrackIndices;
+    private SubtitleController mSubtitleController;
 
     // selected video/audio/subtitle track index as MediaPlayer2 returns
     private int mSelectedVideoTrackIndex;
@@ -137,6 +138,9 @@
     // TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams().
     // Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
     private float mFallbackSpeed;  // keep the original speed before 'pause' is called.
+    private float mVolumeLevelFloat;
+    private int mVolumeLevel;
+
     private long mShowControllerIntervalMs;
 
     private MediaRouter mMediaRouter;
@@ -631,18 +635,11 @@
             mTextureView.setMediaPlayer(mMediaPlayer);
             mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer);
 
-            // TODO: create SubtitleController in MediaPlayer2, but we need
-            // a context for the subtitle renderers
             final Context context = mInstance.getContext();
-            final SubtitleController controller = new SubtitleController(
-                    context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
-            controller.registerRenderer(new WebVttRenderer(context));
-            controller.registerRenderer(new TtmlRenderer(context));
-            controller.registerRenderer(new Cea708CaptionRenderer(context));
-            controller.registerRenderer(new ClosedCaptionRenderer(context));
-            controller.registerRenderer(new SRTRenderer(context));
-            mMediaPlayer.setSubtitleAnchor(
-                    controller, (SubtitleController.Anchor) mSubtitleView);
+            // TODO: Add timely firing logic for more accurate sync between CC and video frame
+            mSubtitleController = new SubtitleController(context);
+            mSubtitleController.registerRenderer(new ClosedCaptionRenderer(context));
+            mSubtitleController.setAnchor((SubtitleController.Anchor) mSubtitleView);
             Executor executor = new Executor() {
                 @Override
                 public void execute(Runnable runnable) {
@@ -651,9 +648,10 @@
             };
             mMediaPlayer.setMediaPlayer2EventCallback(executor, mMediaPlayer2Callback);
 
-            mCurrentBufferPercentage = 0;
+            mCurrentBufferPercentage = -1;
             mMediaPlayer.setDataSource(dsd);
             mMediaPlayer.setAudioAttributes(mAudioAttributes);
+            mMediaPlayer.setOnSubtitleDataListener(mSubtitleListener);
             // we don't set the target state here either, but preserve the
             // target state that was there before.
             mCurrentState = STATE_PREPARING;
@@ -753,8 +751,12 @@
             && mCurrentState != STATE_PREPARING) {
             // TODO: this should be replaced with MediaPlayer2.getBufferedPosition() once it is
             // implemented.
-            mStateBuilder.setBufferedPosition(
-                    (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+            if (mCurrentBufferPercentage == -1) {
+                mStateBuilder.setBufferedPosition(-1);
+            } else {
+                mStateBuilder.setBufferedPosition(
+                        (long) (mCurrentBufferPercentage / 100.0 * mMediaPlayer.getDuration()));
+            }
         }
 
         // Set PlaybackState for MediaSession
@@ -855,7 +857,8 @@
         if (select) {
             if (mSubtitleTrackIndices.size() > 0) {
                 // TODO: make this selection dynamic
-                mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0);
+                mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0).first;
+                mSubtitleController.selectTrack(mSubtitleTrackIndices.get(0).second);
                 mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
                 mSubtitleView.setVisibility(View.VISIBLE);
             }
@@ -873,6 +876,7 @@
         mVideoTrackIndices = new ArrayList<>();
         mAudioTrackIndices = new ArrayList<>();
         mSubtitleTrackIndices = new ArrayList<>();
+        mSubtitleController.reset();
         for (int i = 0; i < trackInfos.size(); ++i) {
             int trackType = trackInfos.get(i).getTrackType();
             if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
@@ -881,9 +885,20 @@
                 mAudioTrackIndices.add(i);
             } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
                     || trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
-                  mSubtitleTrackIndices.add(i);
+                SubtitleTrack track = mSubtitleController.addTrack(trackInfos.get(i).getFormat());
+                if (track != null) {
+                    mSubtitleTrackIndices.add(new Pair<>(i, track));
+                }
             }
         }
+        // Select first tracks as default
+        if (mVideoTrackIndices.size() > 0) {
+            mSelectedVideoTrackIndex = 0;
+        }
+        if (mAudioTrackIndices.size() > 0) {
+            mSelectedAudioTrackIndex = 0;
+        }
+
         Bundle data = new Bundle();
         data.putInt(MediaControlView2Impl.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
         data.putInt(MediaControlView2Impl.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
@@ -894,6 +909,35 @@
         mMediaSession.sendSessionEvent(MediaControlView2Impl.EVENT_UPDATE_TRACK_STATUS, data);
     }
 
+    OnSubtitleDataListener mSubtitleListener =
+            new OnSubtitleDataListener() {
+                @Override
+                public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
+                                + ", getCurrentPosition: " + mp.getCurrentPosition()
+                                + ", getStartTimeUs(): " + data.getStartTimeUs()
+                                + ", diff: "
+                                + (data.getStartTimeUs()/1000 - mp.getCurrentPosition())
+                                + "ms, getDurationUs(): " + data.getDurationUs()
+                                );
+
+                    }
+                    final int index = data.getTrackIndex();
+                    if (index != mSelectedSubtitleTrackIndex) {
+                        Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
+                                + ", selected track index: " + mSelectedSubtitleTrackIndex);
+                        return;
+                    }
+                    for (Pair<Integer, SubtitleTrack> p : mSubtitleTrackIndices) {
+                        if (p.first == index) {
+                            SubtitleTrack track = p.second;
+                            track.onData(data);
+                        }
+                    }
+                }
+            };
+
     MediaPlayer2EventCallback mMediaPlayer2Callback =
             new MediaPlayer2EventCallback() {
                 @Override
@@ -1048,7 +1092,16 @@
             } else {
                 switch (command) {
                     case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
-                        mInstance.setSubtitleEnabled(true);
+                        int subtitleIndex = args.getInt(
+                                MediaControlView2Impl.KEY_SELECTED_SUBTITLE_INDEX,
+                                INVALID_TRACK_INDEX);
+                        if (subtitleIndex != INVALID_TRACK_INDEX) {
+                            int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex).first;
+                            if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
+                                mSelectedSubtitleTrackIndex = subtitleTrackIndex;
+                                mInstance.setSubtitleEnabled(true);
+                            }
+                        }
                         break;
                     case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
                         mInstance.setSubtitleEnabled(false);
@@ -1060,6 +1113,32 @@
                                     args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
                         }
                         break;
+                    case MediaControlView2Impl.COMMAND_SELECT_AUDIO_TRACK:
+                        int audioIndex = args.getInt(MediaControlView2Impl.KEY_SELECTED_AUDIO_INDEX,
+                                INVALID_TRACK_INDEX);
+                        if (audioIndex != INVALID_TRACK_INDEX) {
+                            int audioTrackIndex = mAudioTrackIndices.get(audioIndex);
+                            if (audioTrackIndex != mSelectedAudioTrackIndex) {
+                                mSelectedAudioTrackIndex = audioTrackIndex;
+                                mMediaPlayer.selectTrack(mSelectedAudioTrackIndex);
+                            }
+                        }
+                        break;
+                    case MediaControlView2Impl.COMMAND_SET_PLAYBACK_SPEED:
+                        float speed = args.getFloat(
+                                MediaControlView2Impl.KEY_PLAYBACK_SPEED, INVALID_SPEED);
+                        if (speed != INVALID_SPEED && speed != mSpeed) {
+                            mInstance.setSpeed(speed);
+                            mSpeed = speed;
+                        }
+                        break;
+                    case MediaControlView2Impl.COMMAND_MUTE:
+                        mVolumeLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+                        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
+                        break;
+                    case MediaControlView2Impl.COMMAND_UNMUTE:
+                        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeLevel, 0);
+                        break;
                 }
             }
             showController();
diff --git a/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 3134323..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;
@@ -3481,7 +3481,8 @@
                             if (diff > 0) {
                                 // notify of throttle end on debug log
                                 // but prevent spamming for bluetooth
-                                ALOGD_IF(!audio_is_a2dp_out_device(outDevice()),
+                                ALOGD_IF(!audio_is_a2dp_out_device(outDevice()) &&
+                                         !audio_is_hearing_aid_out_device(outDevice()),
                                         "mixer(%p) throttle end: throttle time(%u)", this, diff);
                                 mThreadThrottleEndMs = mThreadThrottleTimeMs;
                             }
@@ -8523,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);
@@ -8631,7 +8636,6 @@
     }
 
     if (volume != mHalVolFloat) {
-        mHalVolFloat = volume;
 
         // Convert volumes from float to 8.24
         uint32_t vol = (uint32_t)(volume * (1 << 24));
@@ -8644,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;
@@ -8658,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/common/include/Volume.h b/services/audiopolicy/common/include/Volume.h
index 1239fe0..4862684 100644
--- a/services/audiopolicy/common/include/Volume.h
+++ b/services/audiopolicy/common/include/Volume.h
@@ -125,6 +125,7 @@
         case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP:
         case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
         case AUDIO_DEVICE_OUT_USB_HEADSET:
+        case AUDIO_DEVICE_OUT_HEARING_AID:
             return DEVICE_CATEGORY_HEADSET;
         case AUDIO_DEVICE_OUT_LINE:
         case AUDIO_DEVICE_OUT_AUX_DIGITAL:
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 78a184b..5e5d38b 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -203,6 +203,16 @@
      */
     audio_io_handle_t getA2dpOutput() const;
 
+    /**
+     * returns true if primary HAL supports A2DP Offload
+     */
+    bool isA2dpOffloadedOnPrimary() const;
+
+    /**
+     * returns true if A2DP is supported (either via hardware offload or software encoding)
+     */
+    bool isA2dpSupported() const;
+
     sp<SwAudioOutputDescriptor> getOutputFromId(audio_port_handle_t id) const;
 
     sp<SwAudioOutputDescriptor> getPrimaryOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index caaa0f7..294a2a6 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -645,6 +645,29 @@
     return 0;
 }
 
+bool SwAudioOutputCollection::isA2dpOffloadedOnPrimary() const
+{
+    sp<SwAudioOutputDescriptor> primaryOutput = getPrimaryOutput();
+
+    if ((primaryOutput != NULL) && (primaryOutput->mProfile != NULL)
+        && (primaryOutput->mProfile->mModule != NULL)) {
+        sp<HwModule> primaryHwModule = primaryOutput->mProfile->mModule;
+        Vector <sp<IOProfile>> primaryHwModuleOutputProfiles =
+                                   primaryHwModule->getOutputProfiles();
+        for (size_t i = 0; i < primaryHwModuleOutputProfiles.size(); i++) {
+            if (primaryHwModuleOutputProfiles[i]->supportDevice(AUDIO_DEVICE_OUT_ALL_A2DP)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+bool SwAudioOutputCollection::isA2dpSupported() const
+{
+    return (isA2dpOffloadedOnPrimary() || (getA2dpOutput() != 0));
+}
+
 sp<SwAudioOutputDescriptor> SwAudioOutputCollection::getPrimaryOutput() const
 {
     for (size_t i = 0; i < size(); i++) {
diff --git a/services/audiopolicy/config/audio_policy_configuration.xml b/services/audiopolicy/config/audio_policy_configuration.xml
index 73efe8e..a75f1cb 100644
--- a/services/audiopolicy/config/audio_policy_configuration.xml
+++ b/services/audiopolicy/config/audio_policy_configuration.xml
@@ -182,6 +182,9 @@
         <!-- Remote Submix Audio HAL -->
         <xi:include href="r_submix_audio_policy_configuration.xml"/>
 
+        <!-- Hearing aid Audio HAL -->
+        <xi:include href="hearing_aid_audio_policy_configuration.xml"/>
+
     </modules>
     <!-- End of Modules section -->
 
diff --git a/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
new file mode 100644
index 0000000..3c48e88
--- /dev/null
+++ b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Hearing aid Audio HAL Audio Policy Configuration file -->
+<module name="hearing_aid" halVersion="2.0">
+    <mixPorts>
+        <mixPort name="hearing aid output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
+            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+                     samplingRates="24000,16000"
+                     channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+        </mixPort>
+    </mixPorts>
+    <devicePorts>
+        <devicePort tagName="BT Hearing Aid Out" type="AUDIO_DEVICE_OUT_HEARING_AID" role="sink"/>
+    </devicePorts>
+    <routes>
+        <route type="mix" sink="BT Hearing Aid Out" sources="hearing aid output"/>
+    </routes>
+</module>
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 5ec0475..977a396 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -306,8 +306,14 @@
             sp<AudioOutputDescriptor> primaryOutput = outputs.getPrimaryOutput();
             audio_devices_t availPrimaryInputDevices =
                  availableInputDevices.getDevicesFromHwModule(primaryOutput->getModuleHandle());
+
+            // TODO: getPrimaryOutput return only devices from first module in
+            // audio_policy_configuration.xml, hearing aid is not there, but it's
+            // a primary device
+            // FIXME: this is not the right way of solving this problem
             audio_devices_t availPrimaryOutputDevices =
-                    primaryOutput->supportedDevices() & availableOutputDevices.types();
+                (primaryOutput->supportedDevices() | AUDIO_DEVICE_OUT_HEARING_AID) &
+                availableOutputDevices.types();
 
             if (((availableInputDevices.types() &
                     AUDIO_DEVICE_IN_TELEPHONY_RX & ~AUDIO_DEVICE_BIT_IN) == 0) ||
@@ -332,10 +338,12 @@
             // FALL THROUGH
 
         default:    // FORCE_NONE
+            device = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+            if (device) break;
             // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
             if (!isInCall() &&
                     (mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
-                    (outputs.getA2dpOutput() != 0)) {
+                     outputs.isA2dpSupported()) {
                 device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
                 if (device) break;
                 device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
@@ -369,7 +377,7 @@
             // A2DP speaker when forcing to speaker output
             if (!isInCall() &&
                     (mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
-                    (outputs.getA2dpOutput() != 0)) {
+                     outputs.isA2dpSupported()) {
                 device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
                 if (device) break;
             }
@@ -481,9 +489,12 @@
                     outputDeviceTypesToIgnore);
             break;
         }
+        if (device2 == AUDIO_DEVICE_NONE) {
+            device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+        }
         if ((device2 == AUDIO_DEVICE_NONE) &&
                 (mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
-                (outputs.getA2dpOutput() != 0)) {
+                 outputs.isA2dpSupported()) {
             device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
             if (device2 == AUDIO_DEVICE_NONE) {
                 device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 42b199a..73d92ac 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -26,6 +26,7 @@
 
 #define AUDIO_POLICY_XML_CONFIG_FILE_PATH_MAX_LENGTH 128
 #define AUDIO_POLICY_XML_CONFIG_FILE_NAME "audio_policy_configuration.xml"
+#define AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME "audio_policy_a2dp_offload_configuration.xml"
 
 #include <inttypes.h>
 #include <math.h>
@@ -1265,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;
 }
 
@@ -1365,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();
         }
@@ -3517,11 +3529,14 @@
 
     for (int i = 0; i < kConfigLocationListSize; i++) {
         PolicySerializer serializer;
+        bool use_a2dp_offload_config =
+                 property_get_bool("persist.bluetooth.a2dp_offload.enable", false);
         snprintf(audioPolicyXmlConfigFile,
                  sizeof(audioPolicyXmlConfigFile),
                  "%s/%s",
                  kConfigLocationList[i],
-                 AUDIO_POLICY_XML_CONFIG_FILE_NAME);
+                 use_a2dp_offload_config ? AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME :
+                     AUDIO_POLICY_XML_CONFIG_FILE_NAME);
         ret = serializer.deserialize(audioPolicyXmlConfigFile, config);
         if (ret == NO_ERROR) {
             break;
@@ -4381,7 +4396,7 @@
 void AudioPolicyManager::checkA2dpSuspend()
 {
     audio_io_handle_t a2dpOutput = mOutputs.getA2dpOutput();
-    if (a2dpOutput == 0) {
+    if (a2dpOutput == 0 || mOutputs.isA2dpOffloadedOnPrimary()) {
         mA2dpSuspended = false;
         return;
     }
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index 9ab2d88..c49de8e 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1002,10 +1002,6 @@
         case HAL_PIXEL_FORMAT_BLOB:
         case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
         case HAL_PIXEL_FORMAT_YCbCr_420_888:
-        case HAL_PIXEL_FORMAT_YCbCr_422_888:
-        case HAL_PIXEL_FORMAT_YCbCr_444_888:
-        case HAL_PIXEL_FORMAT_FLEX_RGB_888:
-        case HAL_PIXEL_FORMAT_FLEX_RGBA_8888:
         case HAL_PIXEL_FORMAT_YCbCr_422_SP:
         case HAL_PIXEL_FORMAT_YCrCb_420_SP:
         case HAL_PIXEL_FORMAT_YCbCr_422_I:
diff --git a/services/camera/libcameraservice/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
diff --git a/services/mediacodec/seccomp_policy/mediacodec-x86.policy b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
index 1553ec7..bbbe552 100644
--- a/services/mediacodec/seccomp_policy/mediacodec-x86.policy
+++ b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
@@ -16,12 +16,14 @@
 mprotect: 1
 prctl: 1
 openat: 1
+open: 1
 getuid32: 1
 writev: 1
 ioctl: 1
 close: 1
 mmap2: 1
 fstat64: 1
+stat64: 1
 madvise: 1
 fstatat64: 1
 futex: 1