Merge "AudioPolicy: enable phone apps to mute VOICE_CALL stream"
diff --git a/camera/ndk/include/camera/NdkCameraDevice.h b/camera/ndk/include/camera/NdkCameraDevice.h
index 61deb46..c0eb5c1 100644
--- a/camera/ndk/include/camera/NdkCameraDevice.h
+++ b/camera/ndk/include/camera/NdkCameraDevice.h
@@ -251,6 +251,36 @@
      * @see ACameraDevice_createCaptureRequest
      */
     TEMPLATE_MANUAL = 6,
+
+    /**
+     * A template for selecting camera parameters that match TEMPLATE_PREVIEW as closely as
+     * possible while improving the camera output for motion tracking use cases.
+     *
+     * <p>This template is best used by applications that are frequently switching between motion
+     * tracking use cases and regular still capture use cases, to minimize the IQ changes
+     * when swapping use cases.</p>
+     *
+     * <p>This template is guaranteed to be supported on camera devices that support the
+     * {@link ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING} capability.</p>
+     *
+     * @see ACameraDevice_createCaptureRequest
+     */
+    TEMPLATE_MOTION_TRACKING_PREVIEW = 7,
+
+    /**
+     * A template for selecting camera parameters that maximize the quality of camera output for
+     * motion tracking use cases.
+     *
+     * <p>This template is best used by applications dedicated to motion tracking applications,
+     * which aren't concerned about fast switches between motion tracking and other use cases.</p>
+     *
+     * <p>This template is guaranteed to be supported on camera devices that support the
+     * {@link ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING} capability.</p>
+     *
+     * @see ACameraDevice_createCaptureRequest
+     */
+    TEMPLATE_MOTION_TRACKING_BEST = 8,
+
 } ACameraDevice_request_template;
 
 /**
@@ -760,4 +790,3 @@
 #endif /* _NDK_CAMERA_DEVICE_H */
 
 /** @} */
-
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 80d460f..6e861a6 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -837,10 +837,13 @@
      *
      * <p>This control (except for MANUAL) is only effective if
      * <code>ACAMERA_CONTROL_MODE != OFF</code> and any 3A routine is active.</p>
-     * <p>ZERO_SHUTTER_LAG will be supported if ACAMERA_REQUEST_AVAILABLE_CAPABILITIES
-     * contains PRIVATE_REPROCESSING or YUV_REPROCESSING. MANUAL will be supported if
-     * ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains MANUAL_SENSOR. Other intent values are
-     * always supported.</p>
+     * <p>All intents are supported by all devices, except that:
+     *   * ZERO_SHUTTER_LAG will be supported if ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains
+     * PRIVATE_REPROCESSING or YUV_REPROCESSING.
+     *   * MANUAL will be supported if ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains
+     * MANUAL_SENSOR.
+     *   * MOTION_TRACKING will be supported if ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains
+     * MOTION_TRACKING.</p>
      *
      * @see ACAMERA_CONTROL_MODE
      * @see ACAMERA_REQUEST_AVAILABLE_CAPABILITIES
@@ -1279,7 +1282,7 @@
      * <p>State       | Transition Cause | New State | Notes
      * :------------:|:----------------:|:---------:|:-----------------------:
      * INACTIVE      |                  | INACTIVE  | Camera device auto exposure algorithm is disabled</p>
-     * <p>When ACAMERA_CONTROL_AE_MODE is AE_MODE_ON_*:</p>
+     * <p>When ACAMERA_CONTROL_AE_MODE is AE_MODE_ON*:</p>
      * <p>State        | Transition Cause                             | New State      | Notes
      * :-------------:|:--------------------------------------------:|:--------------:|:-----------------:
      * INACTIVE       | Camera device initiates AE scan              | SEARCHING      | Values changing
@@ -1300,10 +1303,13 @@
      * LOCKED         | aeLock is ON and aePrecaptureTrigger is CANCEL| LOCKED        | Precapture trigger is ignored when AE is already locked
      * 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>
      * <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>
-     * <p>For example, for above AE modes (AE_MODE_ON_*), in addition to the state transitions
+     * <p>For example, for above AE modes (AE_MODE_ON*), in addition to the state transitions
      * listed in above table, it is also legal for the camera device to skip one or more
      * transient states between two results. See below table for examples:</p>
      * <p>State        | Transition Cause                                            | New State      | Notes
@@ -1316,6 +1322,7 @@
      * CONVERGED      | Camera device finished AE scan                              | FLASH_REQUIRED | Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.
      * FLASH_REQUIRED | Camera device finished AE scan                              | CONVERGED      | Converged after a new scan, transient states are skipped by camera device.</p>
      *
+     * @see ACAMERA_CONTROL_AE_AVAILABLE_MODES
      * @see ACAMERA_CONTROL_AE_LOCK
      * @see ACAMERA_CONTROL_AE_MODE
      * @see ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER
@@ -2235,34 +2242,31 @@
      * </ul></p>
      *
      * <p>The position of the camera device's lens optical center,
-     * as a three-dimensional vector <code>(x,y,z)</code>, relative to the
-     * optical center of the largest camera device facing in the
-     * same direction as this camera, in the <a href="https://developer.android.com/reference/android/hardware/SensorEvent.html">Android sensor coordinate
-     * axes</a>. Note that only the axis definitions are shared with
-     * the sensor coordinate system, but not the origin.</p>
-     * <p>If this device is the largest or only camera device with a
-     * given facing, then this position will be <code>(0, 0, 0)</code>; a
-     * camera device with a lens optical center located 3 cm 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 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 camera, and
-     * finally the inverse of ACAMERA_LENS_INTRINSIC_CALIBRATION
-     * of the destination camera. This obtains a
-     * radial-distortion-free coordinate in the destination
-     * camera pixel coordinates.</p>
-     * <p>To compare this against a real image from the destination
-     * camera, the destination camera image then needs to be
-     * corrected for radial distortion before comparison or
-     * sampling.</p>
+     * as a three-dimensional vector <code>(x,y,z)</code>.</p>
+     * <p>Prior to Android P, or when ACAMERA_LENS_POSE_REFERENCE is PRIMARY_CAMERA, this position
+     * is relative to the optical center of the largest camera device facing in the same
+     * direction as this camera, in the <a href="https://developer.android.com/reference/android/hardware/SensorEvent.html">Android sensor
+     * coordinate axes</a>. Note that only the axis definitions are shared with the sensor
+     * coordinate system, but not the origin.</p>
+     * <p>If this device is the largest or only camera device with a given facing, then this
+     * position will be <code>(0, 0, 0)</code>; a camera device with a lens optical center located 3 cm
+     * 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
+     * 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
+     * camera, and finally the inverse of ACAMERA_LENS_INTRINSIC_CALIBRATION of the destination
+     * camera. This obtains a radial-distortion-free coordinate in the destination camera pixel
+     * coordinates.</p>
+     * <p>To compare this against a real image from the destination camera, the destination camera
+     * image then needs to be corrected for radial distortion before comparison or sampling.</p>
+     * <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_INTRINSIC_CALIBRATION
+     * @see ACAMERA_LENS_POSE_REFERENCE
      * @see ACAMERA_LENS_POSE_ROTATION
      * @see ACAMERA_LENS_RADIAL_DISTORTION
      */
@@ -2433,6 +2437,26 @@
      */
     ACAMERA_LENS_RADIAL_DISTORTION =                            // float[6]
             ACAMERA_LENS_START + 11,
+    /**
+     * <p>The origin for ACAMERA_LENS_POSE_TRANSLATION.</p>
+     *
+     * @see ACAMERA_LENS_POSE_TRANSLATION
+     *
+     * <p>Type: byte (acamera_metadata_enum_android_lens_pose_reference_t)</p>
+     *
+     * <p>This tag may appear in:
+     * <ul>
+     *   <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+     * </ul></p>
+     *
+     * <p>Different calibration methods and use cases can produce better or worse results
+     * depending on the selected coordinate origin.</p>
+     * <p>For devices designed to support the MOTION_TRACKING capability, the GYROSCOPE origin
+     * makes device calibration and later usage by applications combining camera and gyroscope
+     * information together simpler.</p>
+     */
+    ACAMERA_LENS_POSE_REFERENCE =                               // byte (acamera_metadata_enum_android_lens_pose_reference_t)
+            ACAMERA_LENS_START + 12,
     ACAMERA_LENS_END,
 
     /**
@@ -2895,7 +2919,7 @@
      * time-consuming hardware re-configuration or internal camera pipeline
      * change. For performance reasons we advise clients to pass their initial
      * values as part of
-     * {@link ACameraDevice_createCaptureSessionWithSessionParameters }.i
+     * {@link ACameraDevice_createCaptureSessionWithSessionParameters }.
      * Once the camera capture session is enabled it is also recommended to avoid
      * changing them from their initial values set in
      * {@link ACameraDevice_createCaptureSessionWithSessionParameters }.
@@ -4908,6 +4932,23 @@
      */
     ACAMERA_INFO_SUPPORTED_HARDWARE_LEVEL =                     // byte (acamera_metadata_enum_android_info_supported_hardware_level_t)
             ACAMERA_INFO_START,
+    /**
+     * <p>A short string for manufacturer version information about the camera device, such as
+     * ISP hardware, sensors, etc.</p>
+     *
+     * <p>Type: byte</p>
+     *
+     * <p>This tag may appear in:
+     * <ul>
+     *   <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+     * </ul></p>
+     *
+     * <p>This can be used in <a href="https://developer.android.com/reference/android/media/ExifInterface.html#TAG_IMAGE_DESCRIPTION">TAG_IMAGE_DESCRIPTION</a>
+     * in jpeg EXIF. This key may be absent if no version information is available on the
+     * device.</p>
+     */
+    ACAMERA_INFO_VERSION =                                      // byte
+            ACAMERA_INFO_START + 1,
     ACAMERA_INFO_END,
 
     /**
@@ -5337,6 +5378,19 @@
      */
     ACAMERA_CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE                     = 4,
 
+    /**
+     * <p>An external flash has been turned on.</p>
+     * <p>It informs the camera device that an external flash has been turned on, and that
+     * metering (and continuous focus if active) should be quickly recaculated to account
+     * 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
+     * flash.</p>
+     */
+    ACAMERA_CONTROL_AE_MODE_ON_EXTERNAL_FLASH                        = 5,
+
 } acamera_metadata_enum_android_control_ae_mode_t;
 
 // ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER
@@ -5700,6 +5754,15 @@
      */
     ACAMERA_CONTROL_CAPTURE_INTENT_MANUAL                            = 6,
 
+    /**
+     * <p>This request is for a motion tracking use case, where
+     * the application will use camera and inertial sensor data to
+     * locate and track objects in the world.</p>
+     * <p>The camera device auto-exposure routine will limit the exposure time
+     * of the camera to no more than 20 milliseconds, to minimize motion blur.</p>
+     */
+    ACAMERA_CONTROL_CAPTURE_INTENT_MOTION_TRACKING                   = 7,
+
 } acamera_metadata_enum_android_control_capture_intent_t;
 
 // ACAMERA_CONTROL_EFFECT_MODE
@@ -6411,6 +6474,28 @@
 
 } acamera_metadata_enum_android_lens_state_t;
 
+// ACAMERA_LENS_POSE_REFERENCE
+typedef enum acamera_metadata_enum_acamera_lens_pose_reference {
+    /**
+     * <p>The value of ACAMERA_LENS_POSE_TRANSLATION is relative to the optical center of
+     * the largest camera device facing the same direction as this camera.</p>
+     * <p>This default value for API levels before Android P.</p>
+     *
+     * @see ACAMERA_LENS_POSE_TRANSLATION
+     */
+    ACAMERA_LENS_POSE_REFERENCE_PRIMARY_CAMERA                       = 0,
+
+    /**
+     * <p>The value of ACAMERA_LENS_POSE_TRANSLATION is relative to the position of the
+     * primary gyroscope of this Android device.</p>
+     * <p>This is the value reported by all devices that support the MOTION_TRACKING capability.</p>
+     *
+     * @see ACAMERA_LENS_POSE_TRANSLATION
+     */
+    ACAMERA_LENS_POSE_REFERENCE_GYROSCOPE                            = 1,
+
+} acamera_metadata_enum_android_lens_pose_reference_t;
+
 
 // ACAMERA_LENS_INFO_FOCUS_DISTANCE_CALIBRATION
 typedef enum acamera_metadata_enum_acamera_lens_info_focus_distance_calibration {
@@ -6743,6 +6828,7 @@
      * </ul>
      * </li>
      * <li>The ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE entry is listed by this device.</li>
+     * <li>As of Android P, the ACAMERA_LENS_POSE_REFERENCE entry is listed by this device.</li>
      * <li>A LIMITED camera with only the DEPTH_OUTPUT capability does not have to support
      *   normal YUV_420_888, JPEG, and PRIV-format outputs. It only has to support the DEPTH16
      *   format.</li>
@@ -6758,12 +6844,57 @@
      * @see ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE
      * @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,
 
+    /**
+     * <p>The device supports controls and metadata required for accurate motion tracking for
+     * use cases such as augmented reality, electronic image stabilization, and so on.</p>
+     * <p>This means this camera device has accurate optical calibration and timestamps relative
+     * to the inertial sensors.</p>
+     * <p>This capability requires the camera device to support the following:</p>
+     * <ul>
+     * <li>Capture request templates <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#TEMPLATE_MOTION_TRACKING_PREVIEW">CameraDevice#TEMPLATE_MOTION_TRACKING_PREVIEW</a> and <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#TEMPLATE_MOTION_TRACKING_BEST">CameraDevice#TEMPLATE_MOTION_TRACKING_BEST</a> are defined.</li>
+     * <li>The stream configurations listed in <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#createCaptureSession">CameraDevice#createCaptureSession</a> for MOTION_TRACKING are
+     *   supported, either at 30 or 60fps maximum frame rate.</li>
+     * <li>The following camera characteristics and capture result metadata are provided:<ul>
+     * <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li>
+     * <li>ACAMERA_LENS_RADIAL_DISTORTION</li>
+     * <li>ACAMERA_LENS_POSE_ROTATION</li>
+     * <li>ACAMERA_LENS_POSE_TRANSLATION</li>
+     * <li>ACAMERA_LENS_POSE_REFERENCE with value GYROSCOPE</li>
+     * </ul>
+     * </li>
+     * <li>The ACAMERA_SENSOR_INFO_TIMESTAMP_SOURCE field has value <code>REALTIME</code>. When compared to
+     *   timestamps from the device's gyroscopes, the clock difference for events occuring at
+     *   the same actual time instant will be less than 1 ms.</li>
+     * <li>The value of the ACAMERA_SENSOR_ROLLING_SHUTTER_SKEW field is accurate to within 1 ms.</li>
+     * <li>The value of ACAMERA_SENSOR_EXPOSURE_TIME is guaranteed to be available in the
+     *   capture result.</li>
+     * <li>The ACAMERA_CONTROL_CAPTURE_INTENT control supports MOTION_TRACKING to limit maximum
+     *   exposure to 20 milliseconds.</li>
+     * <li>The stream configurations required for MOTION_TRACKING (listed at <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#createCaptureSession">CameraDevice#createCaptureSession</a>) can operate at least at
+     *   30fps; optionally, they can operate at 60fps, and '[60, 60]' is listed in
+     *   ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES.</li>
+     * </ul>
+     *
+     * @see ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
+     * @see ACAMERA_CONTROL_CAPTURE_INTENT
+     * @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_SENSOR_EXPOSURE_TIME
+     * @see ACAMERA_SENSOR_INFO_TIMESTAMP_SOURCE
+     * @see ACAMERA_SENSOR_ROLLING_SHUTTER_SKEW
+     */
+    ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING           = 10,
+
 } acamera_metadata_enum_android_request_available_capabilities_t;
 
 
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 44ed034..bb517aa 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -622,7 +622,7 @@
     fprintf(stderr, "       -o playback audio\n");
     fprintf(stderr, "       -w(rite) filename (write to .mp4 file)\n");
     fprintf(stderr, "       -k seek test\n");
-    fprintf(stderr, "       -O(verride) name of the component\n");
+    fprintf(stderr, "       -N(ame) of the component\n");
     fprintf(stderr, "       -x display a histogram of decoding times/fps "
                     "(video only)\n");
     fprintf(stderr, "       -q don't show progress indicator\n");
@@ -708,7 +708,7 @@
     sp<ALooper> looper;
 
     int res;
-    while ((res = getopt(argc, argv, "haqn:lm:b:ptsrow:kO:xSTd:D:")) >= 0) {
+    while ((res = getopt(argc, argv, "haqn:lm:b:ptsrow:kN:xSTd:D:")) >= 0) {
         switch (res) {
             case 'a':
             {
@@ -737,7 +737,7 @@
                 break;
             }
 
-            case 'O':
+            case 'N':
             {
                 gComponentNameOverride.setTo(optarg);
                 break;
diff --git a/drm/libmediadrm/CryptoHal.cpp b/drm/libmediadrm/CryptoHal.cpp
index b9b3685..2114d40 100644
--- a/drm/libmediadrm/CryptoHal.cpp
+++ b/drm/libmediadrm/CryptoHal.cpp
@@ -332,10 +332,13 @@
             return status;
         }
         secure = false;
-    } else {
+    } else if (destination.mType == kDestinationTypeNativeHandle) {
         hDestination.type = BufferType::NATIVE_HANDLE;
         hDestination.secureMemory = hidl_handle(destination.mHandle);
         secure = true;
+    } else {
+        android_errorWriteLog(0x534e4554, "70526702");
+        return UNKNOWN_ERROR;
     }
 
     ::SharedBuffer hSource;
diff --git a/drm/libmediadrm/ICrypto.cpp b/drm/libmediadrm/ICrypto.cpp
index 8506d95..1d70a4e 100644
--- a/drm/libmediadrm/ICrypto.cpp
+++ b/drm/libmediadrm/ICrypto.cpp
@@ -16,14 +16,14 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "ICrypto"
-#include <utils/Log.h>
-
 #include <binder/Parcel.h>
 #include <binder/IMemory.h>
+#include <cutils/log.h>
 #include <media/ICrypto.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AString.h>
+#include <utils/Log.h>
 
 namespace android {
 
@@ -362,6 +362,17 @@
                     reply->writeInt32(BAD_VALUE);
                     return OK;
                 }
+                sp<IMemory> dest = destination.mSharedMemory;
+                if (totalSize > dest->size() ||
+                        (size_t)dest->offset() > dest->size() - totalSize) {
+                    reply->writeInt32(BAD_VALUE);
+                    android_errorWriteLog(0x534e4554, "71389378");
+                    return OK;
+                }
+            } else {
+                reply->writeInt32(BAD_VALUE);
+                android_errorWriteLog(0x534e4554, "70526702");
+                return OK;
             }
 
             AString errorDetailMsg;
diff --git a/drm/libmediadrm/PluginMetricsReporting.cpp b/drm/libmediadrm/PluginMetricsReporting.cpp
index 57ff5b8..cc7fb72 100644
--- a/drm/libmediadrm/PluginMetricsReporting.cpp
+++ b/drm/libmediadrm/PluginMetricsReporting.cpp
@@ -17,6 +17,7 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "PluginMetricsReporting"
 #include <utils/Log.h>
+#include <inttypes.h>
 
 #include <media/PluginMetricsReporting.h>
 
@@ -46,7 +47,7 @@
 
     // Report the package name.
     if (metricsGroup.has_app_package_name()) {
-      AString app_package_name(metricsGroup.app_package_name().c_str(),
+        std::string app_package_name(metricsGroup.app_package_name().c_str(),
                                metricsGroup.app_package_name().size());
       analyticsItem.setPkgName(app_package_name);
     }
@@ -81,10 +82,7 @@
 
     analyticsItem.setFinalized(true);
     if (!analyticsItem.selfrecord()) {
-      // Note the cast to int is because we build on 32 and 64 bit.
-      // The cast prevents a peculiar printf problem where one format cannot
-      // satisfy both.
-      ALOGE("selfrecord() returned false. sessioId %d", (int) sessionId);
+      ALOGE("selfrecord() returned false. sessioId %" PRId64, sessionId);
     }
 
     for (int i = 0; i < metricsGroup.metric_sub_group_size(); ++i) {
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp
index 7c43994..944002d 100644
--- a/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp
+++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp
@@ -142,25 +142,24 @@
     ssize_t index = mByteArrayProperties.indexOfKey(name);
     if (index < 0) {
         ALOGE("App requested unknown property: %s", name.string());
-        return android::BAD_VALUE;
+        return android::ERROR_DRM_CANNOT_HANDLE;
     }
     value = mByteArrayProperties.valueAt(index);
     return android::OK;
 }
 
 status_t DrmPlugin::setPropertyByteArray(
-        const String8& name, const Vector<uint8_t>& value) {
+        const String8& name, const Vector<uint8_t>& value)
+{
+    UNUSED(value);
     if (0 == name.compare(kDeviceIdKey)) {
         ALOGD("Cannot set immutable property: %s", name.string());
-        return android::BAD_VALUE;
+        return android::ERROR_DRM_CANNOT_HANDLE;
     }
 
-    ssize_t status = mByteArrayProperties.replaceValueFor(name, value);
-    if (status >= 0) {
-        return android::OK;
-    }
+    // Setting of undefined properties is not supported
     ALOGE("Failed to set property byte array, key=%s", name.string());
-    return android::BAD_VALUE;
+    return android::ERROR_DRM_CANNOT_HANDLE;
 }
 
 status_t DrmPlugin::getPropertyString(
@@ -168,7 +167,7 @@
     ssize_t index = mStringProperties.indexOfKey(name);
     if (index < 0) {
         ALOGE("App requested unknown property: %s", name.string());
-        return android::BAD_VALUE;
+        return android::ERROR_DRM_CANNOT_HANDLE;
     }
     value = mStringProperties.valueAt(index);
     return android::OK;
@@ -182,12 +181,18 @@
             kVendorKey.string(), kVersionKey.string());
     if (immutableKeys.contains(name.string())) {
         ALOGD("Cannot set immutable property: %s", name.string());
-        return android::BAD_VALUE;
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    ssize_t index = mStringProperties.indexOfKey(name);
+    if (index < 0) {
+        ALOGE("Cannot set undefined property string, key=%s", name.string());
+        return android::ERROR_DRM_CANNOT_HANDLE;
     }
 
     if (mStringProperties.add(name, value) < 0) {
         ALOGE("Failed to set property string, key=%s", name.string());
-        return android::BAD_VALUE;
+        return android::ERROR_DRM_UNKNOWN;
     }
     return android::OK;
 }
diff --git a/include/media/IMediaCodecService.h b/include/media/IMediaCodecService.h
deleted file mode 120000
index 37f6822..0000000
--- a/include/media/IMediaCodecService.h
+++ /dev/null
@@ -1 +0,0 @@
-../../media/libmedia/include/media/IMediaCodecService.h
\ No newline at end of file
diff --git a/include/media/MediaDefs.h b/include/media/MediaDefs.h
deleted file mode 120000
index 9850603..0000000
--- a/include/media/MediaDefs.h
+++ /dev/null
@@ -1 +0,0 @@
-../../media/libmedia/include/media/MediaDefs.h
\ No newline at end of file
diff --git a/include/media/MmapStreamInterface.h b/include/media/MmapStreamInterface.h
index d689e25..0196a0c 100644
--- a/include/media/MmapStreamInterface.h
+++ b/include/media/MmapStreamInterface.h
@@ -52,6 +52,9 @@
      * \param[in,out] deviceId audio device the stream should preferably be routed to/from
      *                       Requested as input,
      *                       Actual as output
+     * \param[in,out] sessionId audio sessionId for the stream
+     *                       Requested as input, may be AUDIO_SESSION_ALLOCATE
+     *                       Actual as output
      * \param[in] callback the MmapStreamCallback interface used by AudioFlinger to notify
      *                     condition changes affecting the stream operation
      * \param[out] interface the MmapStreamInterface interface controlling the created stream
@@ -66,6 +69,7 @@
                                            audio_config_base_t *config,
                                            const AudioClient& client,
                                            audio_port_handle_t *deviceId,
+                                           audio_session_t *sessionId,
                                            const sp<MmapStreamCallback>& callback,
                                            sp<MmapStreamInterface>& interface,
                                            audio_port_handle_t *handle);
diff --git a/media/OWNERS b/media/OWNERS
index 1605efd..d49eb8d 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -2,6 +2,7 @@
 dwkang@google.com
 elaurent@google.com
 essick@google.com
+hkuang@google.com
 hunga@google.com
 jmtrivi@google.com
 krocard@google.com
diff --git a/media/extractors/aac/AACExtractor.cpp b/media/extractors/aac/AACExtractor.cpp
index 716fe31..dfb54e2 100644
--- a/media/extractors/aac/AACExtractor.cpp
+++ b/media/extractors/aac/AACExtractor.cpp
@@ -288,6 +288,10 @@
     if (options && options->getSeekTo(&seekTimeUs, &mode)) {
         if (mFrameDurationUs > 0) {
             int64_t seekFrame = seekTimeUs / mFrameDurationUs;
+            if (seekFrame < 0 || seekFrame >= (int64_t)mOffsetVector.size()) {
+                android_errorWriteLog(0x534e4554, "70239507");
+                return ERROR_MALFORMED;
+            }
             mCurrentTimeUs = seekFrame * mFrameDurationUs;
 
             mOffset = mOffsetVector.itemAt(seekFrame);
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 8823c79..938bd5d 100644
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -5375,11 +5375,6 @@
     return NULL;
 }
 
-void MPEG4Extractor::populateMetrics() {
-    ALOGV("MPEG4Extractor::populateMetrics");
-    // write into mAnalyticsItem
-}
-
 static bool LegacySniffMPEG4(
         const sp<DataSource> &source, String8 *mimeType, float *confidence) {
     uint8_t header[8];
diff --git a/media/extractors/mp4/MPEG4Extractor.h b/media/extractors/mp4/MPEG4Extractor.h
index 76b549d..8bfecaa 100644
--- a/media/extractors/mp4/MPEG4Extractor.h
+++ b/media/extractors/mp4/MPEG4Extractor.h
@@ -69,8 +69,6 @@
 protected:
     virtual ~MPEG4Extractor();
 
-    virtual void populateMetrics();
-
 private:
 
     struct PsshInfo {
diff --git a/media/libaaudio/examples/loopback/Android.bp b/media/libaaudio/examples/loopback/Android.bp
index fa8fdc9..5b7d956 100644
--- a/media/libaaudio/examples/loopback/Android.bp
+++ b/media/libaaudio/examples/loopback/Android.bp
@@ -3,6 +3,10 @@
     gtest: false,
     srcs: ["src/loopback.cpp"],
     cflags: ["-Wall", "-Werror"],
-    shared_libs: ["libaaudio"],
+    static_libs: ["libsndfile"],
+    shared_libs: [
+        "libaaudio",
+        "libaudioutils",
+        ],
     header_libs: ["libaaudio_example_utils"],
 }
diff --git a/media/libaaudio/examples/loopback/jni/Android.mk b/media/libaaudio/examples/loopback/jni/Android.mk
index 1fe3def..aebe877 100644
--- a/media/libaaudio/examples/loopback/jni/Android.mk
+++ b/media/libaaudio/examples/loopback/jni/Android.mk
@@ -10,6 +10,7 @@
 # NDK recommends using this kind of relative path instead of an absolute path.
 LOCAL_SRC_FILES:= ../src/loopback.cpp
 LOCAL_CFLAGS := -Wall -Werror
-LOCAL_SHARED_LIBRARIES := libaaudio
+LOCAL_STATIC_LIBRARIES := libsndfile
+LOCAL_SHARED_LIBRARIES := libaaudio libaudioutils
 LOCAL_MODULE := aaudio_loopback
 include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
index 276b45f..b83851a 100644
--- a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
+++ b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
@@ -30,6 +30,8 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+#include <audio_utils/sndfile.h>
+
 // Tag for machine readable results as property = value pairs
 #define LOOPBACK_RESULT_TAG      "RESULT: "
 #define LOOPBACK_SAMPLE_RATE     48000
@@ -37,6 +39,7 @@
 #define MILLIS_PER_SECOND        1000
 
 #define MAX_ZEROTH_PARTIAL_BINS  40
+constexpr double MAX_ECHO_GAIN = 10.0; // based on experiments, otherwise autocorrelation too noisy
 
 static const float s_Impulse[] = {
         0.0f, 0.0f, 0.0f, 0.0f, 0.2f, // silence on each side of the impulse
@@ -156,6 +159,8 @@
                             const float *needle, int needleSize,
                             LatencyReport *report) {
     const double threshold = 0.1;
+    printf("measureLatencyFromEchos: haystackSize = %d, needleSize = %d\n",
+           haystackSize, needleSize);
 
     // Find first peak
     int first = (int) (findFirstMatch(haystack,
@@ -173,7 +178,7 @@
                                       needleSize,
                                       threshold) + 0.5);
 
-    printf("first = %d, again at %d\n", first, again);
+    printf("measureLatencyFromEchos: first = %d, again at %d\n", first, again);
     first = again;
 
     // Allocate results array
@@ -270,37 +275,60 @@
         return mData;
     }
 
+    void setSampleRate(int32_t sampleRate) {
+        mSampleRate = sampleRate;
+    }
+
+    int32_t getSampleRate() {
+        return mSampleRate;
+    }
+
     int save(const char *fileName, bool writeShorts = true) {
+        SNDFILE *sndFile = nullptr;
         int written = 0;
-        const int chunkSize = 64;
-        FILE *fid = fopen(fileName, "wb");
-        if (fid == NULL) {
+        SF_INFO info = {
+                .frames = mFrameCounter,
+                .samplerate = mSampleRate,
+                .channels = 1,
+                .format = SF_FORMAT_WAV | (writeShorts ? SF_FORMAT_PCM_16 : SF_FORMAT_FLOAT)
+        };
+
+        sndFile = sf_open(fileName, SFM_WRITE, &info);
+        if (sndFile == nullptr) {
+            printf("AudioRecording::save(%s) failed to open file\n", fileName);
             return -errno;
         }
 
-        if (writeShorts) {
-            int16_t buffer[chunkSize];
-            int32_t framesLeft = mFrameCounter;
-            int32_t cursor = 0;
-            while (framesLeft) {
-                int32_t framesToWrite = framesLeft < chunkSize ? framesLeft : chunkSize;
-                for (int i = 0; i < framesToWrite; i++) {
-                    buffer[i] = (int16_t) (mData[cursor++] * 32767);
-                }
-                written += fwrite(buffer, sizeof(int16_t), framesToWrite, fid);
-                framesLeft -= framesToWrite;
-            }
-        } else {
-            written = (int) fwrite(mData, sizeof(float), mFrameCounter, fid);
-        }
-        fclose(fid);
+        written = sf_writef_float(sndFile, mData, mFrameCounter);
+
+        sf_close(sndFile);
         return written;
     }
 
+    int load(const char *fileName) {
+        SNDFILE *sndFile = nullptr;
+        SF_INFO info;
+
+        sndFile = sf_open(fileName, SFM_READ, &info);
+        if (sndFile == nullptr) {
+            printf("AudioRecording::load(%s) failed to open file\n", fileName);
+            return -errno;
+        }
+
+        assert(info.channels == 1);
+
+        allocate(info.frames);
+        mFrameCounter = sf_readf_float(sndFile, mData, info.frames);
+
+        sf_close(sndFile);
+        return mFrameCounter;
+    }
+
 private:
     float  *mData = nullptr;
     int32_t mFrameCounter = 0;
     int32_t mMaxFrames = 0;
+    int32_t mSampleRate = 48000; // common default
 };
 
 // ====================================================================================
@@ -320,11 +348,25 @@
 
     virtual void printStatus() {};
 
+    virtual int getResult() {
+        return -1;
+    }
+
     virtual bool isDone() {
         return false;
     }
 
-    void setSampleRate(int32_t sampleRate) {
+    virtual int save(const char *fileName) {
+        (void) fileName;
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+
+    virtual int load(const char *fileName) {
+        (void) fileName;
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+
+    virtual void setSampleRate(int32_t sampleRate) {
         mSampleRate = sampleRate;
     }
 
@@ -395,7 +437,13 @@
 public:
 
     EchoAnalyzer() : LoopbackProcessor() {
-        audioRecorder.allocate(2 * LOOPBACK_SAMPLE_RATE);
+        mAudioRecording.allocate(2 * getSampleRate());
+        mAudioRecording.setSampleRate(getSampleRate());
+    }
+
+    void setSampleRate(int32_t sampleRate) override {
+        LoopbackProcessor::setSampleRate(sampleRate);
+        mAudioRecording.setSampleRate(sampleRate);
     }
 
     void reset() override {
@@ -406,8 +454,12 @@
         mState = STATE_INITIAL_SILENCE;
     }
 
+    virtual int getResult() {
+        return mState == STATE_DONE ? 0 : -1;
+    }
+
     virtual bool isDone() {
-        return mState == STATE_DONE;
+        return mState == STATE_DONE || mState == STATE_FAILED;
     }
 
     void setGain(float gain) {
@@ -423,31 +475,24 @@
         printf("EchoAnalyzer ---------------\n");
         printf(LOOPBACK_RESULT_TAG "measured.gain          = %f\n", mMeasuredLoopGain);
         printf(LOOPBACK_RESULT_TAG "echo.gain              = %f\n", mEchoGain);
-        printf(LOOPBACK_RESULT_TAG "frame.count            = %d\n", mFrameCounter);
         printf(LOOPBACK_RESULT_TAG "test.state             = %d\n", mState);
         if (mMeasuredLoopGain >= 0.9999) {
             printf("   ERROR - clipping, turn down volume slightly\n");
         } else {
             const float *needle = s_Impulse;
             int needleSize = (int) (sizeof(s_Impulse) / sizeof(float));
-            float *haystack = audioRecorder.getData();
-            int haystackSize = audioRecorder.size();
-            measureLatencyFromEchos(haystack, haystackSize, needle, needleSize, &latencyReport);
-            if (latencyReport.confidence < 0.01) {
-                printf("   ERROR - confidence too low = %f\n", latencyReport.confidence);
+            float *haystack = mAudioRecording.getData();
+            int haystackSize = mAudioRecording.size();
+            measureLatencyFromEchos(haystack, haystackSize, needle, needleSize, &mLatencyReport);
+            if (mLatencyReport.confidence < 0.01) {
+                printf("   ERROR - confidence too low = %f\n", mLatencyReport.confidence);
             } else {
-                double latencyMillis = 1000.0 * latencyReport.latencyInFrames / getSampleRate();
-                printf(LOOPBACK_RESULT_TAG "latency.frames        = %8.2f\n", latencyReport.latencyInFrames);
+                double latencyMillis = 1000.0 * mLatencyReport.latencyInFrames / getSampleRate();
+                printf(LOOPBACK_RESULT_TAG "latency.frames        = %8.2f\n", mLatencyReport.latencyInFrames);
                 printf(LOOPBACK_RESULT_TAG "latency.msec          = %8.2f\n", latencyMillis);
-                printf(LOOPBACK_RESULT_TAG "latency.confidence    = %8.6f\n", latencyReport.confidence);
+                printf(LOOPBACK_RESULT_TAG "latency.confidence    = %8.6f\n", mLatencyReport.confidence);
             }
         }
-
-        {
-#define ECHO_FILENAME "/data/oboe_echo.raw"
-            int written = audioRecorder.save(ECHO_FILENAME);
-            printf("Echo wrote %d mono samples to %s on Android device\n", written, ECHO_FILENAME);
-        }
     }
 
     void printStatus() override {
@@ -491,13 +536,18 @@
                 // If we get several in a row then go to next state.
                 if (peak > mPulseThreshold) {
                     if (mDownCounter-- <= 0) {
-                        nextState = STATE_WAITING_FOR_SILENCE;
                         //printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
                         //       mLoopCounter, peak);
                         mDownCounter = 8;
                         mMeasuredLoopGain = peak;  // assumes original pulse amplitude is one
                         // Calculate gain that will give us a nice decaying echo.
                         mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
+                        if (mEchoGain > MAX_ECHO_GAIN) {
+                            printf("ERROR - loop gain too low. Increase the volume.\n");
+                            nextState = STATE_FAILED;
+                        } else {
+                            nextState = STATE_WAITING_FOR_SILENCE;
+                        }
                     }
                 } else {
                     mDownCounter = 8;
@@ -524,14 +574,14 @@
                 break;
 
             case STATE_SENDING_PULSE:
-                audioRecorder.write(inputData, inputChannelCount, numFrames);
+                mAudioRecording.write(inputData, inputChannelCount, numFrames);
                 sendImpulse(outputData, outputChannelCount);
                 nextState = STATE_GATHERING_ECHOS;
                 //printf("%5d: switch to STATE_GATHERING_ECHOS\n", mLoopCounter);
                 break;
 
             case STATE_GATHERING_ECHOS:
-                numWritten = audioRecorder.write(inputData, inputChannelCount, numFrames);
+                numWritten = mAudioRecording.write(inputData, inputChannelCount, numFrames);
                 peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
                 if (peak > mMeasuredLoopGain) {
                     mMeasuredLoopGain = peak;  // AGC might be raising gain so adjust it on the fly.
@@ -565,6 +615,14 @@
         mLoopCounter++;
     }
 
+    int save(const char *fileName) override {
+        return mAudioRecording.save(fileName);
+    }
+
+    int load(const char *fileName) override {
+        return mAudioRecording.load(fileName);
+    }
+
 private:
 
     enum echo_state_t {
@@ -573,22 +631,22 @@
         STATE_WAITING_FOR_SILENCE,
         STATE_SENDING_PULSE,
         STATE_GATHERING_ECHOS,
-        STATE_DONE
+        STATE_DONE,
+        STATE_FAILED
     };
 
-    int           mDownCounter = 500;
-    int           mLoopCounter = 0;
-    float         mPulseThreshold = 0.02f;
-    float         mSilenceThreshold = 0.002f;
-    float         mMeasuredLoopGain = 0.0f;
-    float         mDesiredEchoGain = 0.95f;
-    float         mEchoGain = 1.0f;
-    echo_state_t  mState = STATE_INITIAL_SILENCE;
-    int32_t       mFrameCounter = 0;
+    int             mDownCounter = 500;
+    int             mLoopCounter = 0;
+    float           mPulseThreshold = 0.02f;
+    float           mSilenceThreshold = 0.002f;
+    float           mMeasuredLoopGain = 0.0f;
+    float           mDesiredEchoGain = 0.95f;
+    float           mEchoGain = 1.0f;
+    echo_state_t    mState = STATE_INITIAL_SILENCE;
 
-    AudioRecording     audioRecorder;
-    LatencyReport      latencyReport;
-    PeakDetector       mPeakDetector;
+    AudioRecording  mAudioRecording; // contains only the input after the gain detection burst
+    LatencyReport   mLatencyReport;
+    // PeakDetector    mPeakDetector;
 };
 
 
@@ -602,6 +660,10 @@
 class SineAnalyzer : public LoopbackProcessor {
 public:
 
+    virtual int getResult() {
+        return mState == STATE_LOCKED ? 0 : -1;
+    }
+
     void report() override {
         printf("SineAnalyzer ------------------\n");
         printf(LOOPBACK_RESULT_TAG "peak.amplitude     = %7.5f\n", mPeakAmplitude);
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index ac6024e..d23d907 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -37,10 +37,10 @@
 
 // Tag for machine readable results as property = value pairs
 #define RESULT_TAG              "RESULT: "
-#define SAMPLE_RATE             48000
 #define NUM_SECONDS             5
 #define NUM_INPUT_CHANNELS      1
-#define FILENAME                "/data/oboe_input.raw"
+#define FILENAME_ALL            "/data/loopback_all.wav"
+#define FILENAME_ECHOS          "/data/loopback_echos.wav"
 #define APP_VERSION             "0.1.22"
 
 struct LoopbackData {
@@ -61,7 +61,7 @@
 
     SineAnalyzer       sineAnalyzer;
     EchoAnalyzer       echoAnalyzer;
-    AudioRecording     audioRecorder;
+    AudioRecording     audioRecording;
     LoopbackProcessor *loopbackProcessor;
 };
 
@@ -126,7 +126,7 @@
             result = AAUDIO_CALLBACK_RESULT_STOP;
         } else if (framesRead > 0) {
 
-            myData->audioRecorder.write(myData->inputData,
+            myData->audioRecording.write(myData->inputData,
                                         myData->actualInputChannelCount,
                                         numFrames);
 
@@ -176,7 +176,8 @@
     printf("              p for _POWER_SAVING\n");
     printf("          -t{test}          select test mode\n");
     printf("              m for sine magnitude\n");
-    printf("              e for echo latency (default)\n\n");
+    printf("              e for echo latency (default)\n");
+    printf("              f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
     printf("          -x                use EXCLUSIVE mode for output\n");
     printf("          -X                use EXCLUSIVE mode for input\n");
     printf("Example:  aaudio_loopback -n2 -pl -Pl -x\n");
@@ -205,6 +206,7 @@
 enum {
     TEST_SINE_MAGNITUDE = 0,
     TEST_ECHO_LATENCY,
+    TEST_FILE_LATENCY,
 };
 
 static int parseTestMode(char c) {
@@ -217,6 +219,9 @@
         case 'e':
             testMode = TEST_ECHO_LATENCY;
             break;
+        case 'f':
+            testMode = TEST_FILE_LATENCY;
+            break;
         default:
             printf("ERROR in value test mode %c\n", c);
             break;
@@ -254,13 +259,13 @@
 int main(int argc, const char **argv)
 {
 
-    AAudioArgsParser     argParser;
-    AAudioSimplePlayer   player;
-    AAudioSimpleRecorder recorder;
-    LoopbackData         loopbackData;
-    AAudioStream        *outputStream = nullptr;
+    AAudioArgsParser      argParser;
+    AAudioSimplePlayer    player;
+    AAudioSimpleRecorder  recorder;
+    LoopbackData          loopbackData;
+    AAudioStream         *outputStream = nullptr;
 
-    aaudio_result_t      result = AAUDIO_OK;
+    aaudio_result_t       result = AAUDIO_OK;
     aaudio_sharing_mode_t requestedInputSharingMode     = AAUDIO_SHARING_MODE_SHARED;
     int                   requestedInputChannelCount = NUM_INPUT_CHANNELS;
     const aaudio_format_t requestedInputFormat = AAUDIO_FORMAT_PCM_I16;
@@ -268,6 +273,7 @@
     aaudio_format_t       actualInputFormat;
     aaudio_format_t       actualOutputFormat;
     aaudio_performance_mode_t inputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
+    int32_t               actualSampleRate = 0;
 
     int testMode = TEST_ECHO_LATENCY;
     double gain = 1.0;
@@ -324,7 +330,6 @@
 
     int32_t requestedDuration = argParser.getDurationSeconds();
     int32_t recordingDuration = std::min(60, requestedDuration);
-    loopbackData.audioRecorder.allocate(recordingDuration * SAMPLE_RATE);
 
     switch(testMode) {
         case TEST_SINE_MAGNITUDE:
@@ -334,6 +339,16 @@
             loopbackData.echoAnalyzer.setGain(gain);
             loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
             break;
+        case TEST_FILE_LATENCY: {
+            loopbackData.echoAnalyzer.setGain(gain);
+
+            loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
+            int read = loopbackData.loopbackProcessor->load(FILENAME_ECHOS);
+            printf("main() read %d mono samples from %s on Android device\n", read, FILENAME_ECHOS);
+            loopbackData.loopbackProcessor->report();
+            return 0;
+        }
+            break;
         default:
             exit(1);
             break;
@@ -344,7 +359,7 @@
     result = player.open(argParser, MyDataCallbackProc, MyErrorCallbackProc, &loopbackData);
     if (result != AAUDIO_OK) {
         fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
-        goto finish;
+        exit(1);
     }
     outputStream = player.getStream();
     argParser.compareWithStream(outputStream);
@@ -352,6 +367,10 @@
     actualOutputFormat = AAudioStream_getFormat(outputStream);
     assert(actualOutputFormat == AAUDIO_FORMAT_PCM_FLOAT);
 
+    actualSampleRate = AAudioStream_getSampleRate(outputStream);
+    loopbackData.audioRecording.allocate(recordingDuration * actualSampleRate);
+    loopbackData.audioRecording.setSampleRate(actualSampleRate);
+
     printf("INPUT stream ----------------------------------------\n");
     // Use different parameters for the input.
     argParser.setNumberOfBursts(AAUDIO_UNSPECIFIED);
@@ -380,7 +399,7 @@
 
     // Allocate a buffer for the audio data.
     loopbackData.inputFramesMaximum = 32 * framesPerBurst;
-    loopbackData.inputBuffersToDiscard = 100;
+    loopbackData.inputBuffersToDiscard = 200;
 
     loopbackData.inputData = new int16_t[loopbackData.inputFramesMaximum
                                          * loopbackData.actualInputChannelCount];
@@ -436,25 +455,31 @@
         }
     }
 
-    printf("input error = %d = %s\n",
-                loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
+    if (loopbackData.loopbackProcessor->getResult() < 0) {
+        printf("Test failed!\n");
+    } else {
+        printf("input error = %d = %s\n",
+               loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
 
-    printf("AAudioStream_getXRunCount %d\n", AAudioStream_getXRunCount(outputStream));
-    printf("framesRead    = %8d\n", (int) AAudioStream_getFramesRead(outputStream));
-    printf("framesWritten = %8d\n", (int) AAudioStream_getFramesWritten(outputStream));
-    printf("min numFrames = %8d\n", (int) loopbackData.minNumFrames);
-    printf("max numFrames = %8d\n", (int) loopbackData.maxNumFrames);
+        printf("AAudioStream_getXRunCount %d\n", AAudioStream_getXRunCount(outputStream));
+        printf("framesRead    = %8d\n", (int) AAudioStream_getFramesRead(outputStream));
+        printf("framesWritten = %8d\n", (int) AAudioStream_getFramesWritten(outputStream));
+        printf("min numFrames = %8d\n", (int) loopbackData.minNumFrames);
+        printf("max numFrames = %8d\n", (int) loopbackData.maxNumFrames);
 
-    if (loopbackData.inputError == AAUDIO_OK) {
-        if (testMode == TEST_SINE_MAGNITUDE) {
-            printAudioGraph(loopbackData.audioRecorder, 200);
+        if (loopbackData.inputError == AAUDIO_OK) {
+            if (testMode == TEST_SINE_MAGNITUDE) {
+                printAudioGraph(loopbackData.audioRecording, 200);
+            }
+            loopbackData.loopbackProcessor->report();
         }
-        loopbackData.loopbackProcessor->report();
-    }
 
-    {
-        int written = loopbackData.audioRecorder.save(FILENAME);
-        printf("main() wrote %d mono samples to %s on Android device\n", written, FILENAME);
+        int written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS);
+        printf("main() wrote %d mono samples to %s on Android device\n", written,
+               FILENAME_ECHOS);
+        printf("main() loopbackData.audioRecording.getSampleRate() = %d\n", loopbackData.audioRecording.getSampleRate());
+        written = loopbackData.audioRecording.save(FILENAME_ALL);
+        printf("main() wrote %d mono samples to %s on Android device\n", written, FILENAME_ALL);
     }
 
 finish:
diff --git a/media/libaaudio/examples/utils/AAudioSimplePlayer.h b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
index 3fafecf..54b77ba 100644
--- a/media/libaaudio/examples/utils/AAudioSimplePlayer.h
+++ b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
@@ -170,7 +170,6 @@
 
     aaudio_result_t close() {
         if (mStream != nullptr) {
-            printf("call AAudioStream_close(%p)\n", mStream);  fflush(stdout);
             AAudioStream_close(mStream);
             mStream = nullptr;
         }
diff --git a/media/libaaudio/examples/utils/AAudioSimpleRecorder.h b/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
index 1344273..869fad0 100644
--- a/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
+++ b/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
@@ -178,7 +178,6 @@
 
     aaudio_result_t close() {
         if (mStream != nullptr) {
-            printf("call AAudioStream_close(%p)\n", mStream);  fflush(stdout);
             AAudioStream_close(mStream);
             mStream = nullptr;
         }
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index 3c23736..5da8114 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -123,20 +123,185 @@
     /**
      * No particular performance needs. Default.
      */
-            AAUDIO_PERFORMANCE_MODE_NONE = 10,
+    AAUDIO_PERFORMANCE_MODE_NONE = 10,
 
     /**
      * Extending battery life is most important.
+     *
+     * This mode is not supported in input streams.
+     * Mode NONE will be used if this is requested.
      */
-            AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
+    AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
 
     /**
      * Reducing latency is most important.
      */
-            AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
+    AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
 };
 typedef int32_t aaudio_performance_mode_t;
 
+/**
+ * The USAGE attribute expresses "why" you are playing a sound, what is this sound used for.
+ * This information is used by certain platforms or routing policies
+ * to make more refined volume or routing decisions.
+ *
+ * Note that these match the equivalent values in AudioAttributes in the Android Java API.
+ */
+enum {
+    /**
+     * Use this for streaming media, music performance, video, podcasts, etcetera.
+     */
+    AAUDIO_USAGE_MEDIA = 1,
+
+    /**
+     * Use this for voice over IP, telephony, etcetera.
+     */
+    AAUDIO_USAGE_VOICE_COMMUNICATION = 2,
+
+    /**
+     * Use this for sounds associated with telephony such as busy tones, DTMF, etcetera.
+     */
+    AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING = 3,
+
+    /**
+     * Use this to demand the users attention.
+     */
+    AAUDIO_USAGE_ALARM = 4,
+
+    /**
+     * Use this for notifying the user when a message has arrived or some
+     * other background event has occured.
+     */
+    AAUDIO_USAGE_NOTIFICATION = 5,
+
+    /**
+     * Use this when the phone rings.
+     */
+    AAUDIO_USAGE_NOTIFICATION_RINGTONE = 6,
+
+    /**
+     * Use this to attract the users attention when, for example, the battery is low.
+     */
+    AAUDIO_USAGE_NOTIFICATION_EVENT = 10,
+
+    /**
+     * Use this for screen readers, etcetera.
+     */
+    AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY = 11,
+
+    /**
+     * Use this for driving or navigation directions.
+     */
+    AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12,
+
+    /**
+     * Use this for user interface sounds, beeps, etcetera.
+     */
+    AAUDIO_USAGE_ASSISTANCE_SONIFICATION = 13,
+
+    /**
+     * Use this for game audio and sound effects.
+     */
+    AAUDIO_USAGE_GAME = 14,
+
+    /**
+     * Use this for audio responses to user queries, audio instructions or help utterances.
+     */
+    AAUDIO_USAGE_ASSISTANT = 16
+};
+typedef int32_t aaudio_usage_t;
+
+/**
+ * The CONTENT_TYPE attribute describes "what" you are playing.
+ * It expresses the general category of the content. This information is optional.
+ * But in case it is known (for instance {@link #AAUDIO_CONTENT_TYPE_MOVIE} for a
+ * movie streaming service or {@link #AAUDIO_CONTENT_TYPE_SPEECH} for
+ * an audio book application) this information might be used by the audio framework to
+ * enforce audio focus.
+ *
+ * Note that these match the equivalent values in AudioAttributes in the Android Java API.
+ */
+enum {
+
+    /**
+     * Use this for spoken voice, audio books, etcetera.
+     */
+    AAUDIO_CONTENT_TYPE_SPEECH = 1,
+
+    /**
+     * Use this for pre-recorded or live music.
+     */
+    AAUDIO_CONTENT_TYPE_MUSIC = 2,
+
+    /**
+     * Use this for a movie or video soundtrack.
+     */
+    AAUDIO_CONTENT_TYPE_MOVIE = 3,
+
+    /**
+     * Use this for sound is designed to accompany a user action,
+     * such as a click or beep sound made when the user presses a button.
+     */
+    AAUDIO_CONTENT_TYPE_SONIFICATION = 4
+};
+typedef int32_t aaudio_content_type_t;
+
+/**
+ * Defines the audio source.
+ * An audio source defines both a default physical source of audio signal, and a recording
+ * configuration.
+ *
+ * Note that these match the equivalent values in MediaRecorder.AudioSource in the Android Java API.
+ */
+enum {
+    /**
+     * Use this preset when other presets do not apply.
+     */
+    AAUDIO_INPUT_PRESET_GENERIC = 1,
+
+    /**
+     * Use this preset when recording video.
+     */
+    AAUDIO_INPUT_PRESET_CAMCORDER = 5,
+
+    /**
+     * Use this preset when doing speech recognition.
+     */
+    AAUDIO_INPUT_PRESET_VOICE_RECOGNITION = 6,
+
+    /**
+     * Use this preset when doing telephony or voice messaging.
+     */
+    AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION = 7,
+
+    /**
+     * Use this preset to obtain an input with no effects.
+     * Note that this input will not have automatic gain control
+     * so the recorded volume may be very low.
+     */
+    AAUDIO_INPUT_PRESET_UNPROCESSED = 9,
+};
+typedef int32_t aaudio_input_preset_t;
+
+enum {
+    /**
+     * Do not allocate a session ID.
+     * Effects cannot be used with this stream.
+     * Default.
+     */
+    AAUDIO_SESSION_ID_NONE = -1,
+
+    /**
+     * Allocate a session ID that can be used to attach and control
+     * effects using the Java AudioEffects API.
+     * Note that the use of this flag may result in higher latency.
+     *
+     * Note that this matches the value of AudioManager.AUDIO_SESSION_ID_GENERATE.
+     */
+    AAUDIO_SESSION_ID_ALLOCATE = 0,
+};
+typedef int32_t aaudio_session_id_t;
+
 typedef struct AAudioStreamStruct         AAudioStream;
 typedef struct AAudioStreamBuilderStruct  AAudioStreamBuilder;
 
@@ -308,6 +473,78 @@
                                                 aaudio_performance_mode_t mode);
 
 /**
+ * Set the intended use case for the stream.
+ *
+ * The AAudio system will use this information to optimize the
+ * behavior of the stream.
+ * This could, for example, affect how volume and focus is handled for the stream.
+ *
+ * The default, if you do not call this function, is AAUDIO_USAGE_MEDIA.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param usage the desired usage, eg. AAUDIO_USAGE_GAME
+ */
+AAUDIO_API void AAudioStreamBuilder_setUsage(AAudioStreamBuilder* builder,
+                                                       aaudio_usage_t usage);
+
+/**
+ * Set the type of audio data that the stream will carry.
+ *
+ * The AAudio system will use this information to optimize the
+ * behavior of the stream.
+ * This could, for example, affect whether a stream is paused when a notification occurs.
+ *
+ * The default, if you do not call this function, is AAUDIO_CONTENT_TYPE_MUSIC.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param contentType the type of audio data, eg. AAUDIO_CONTENT_TYPE_SPEECH
+ */
+AAUDIO_API void AAudioStreamBuilder_setContentType(AAudioStreamBuilder* builder,
+                                             aaudio_content_type_t contentType);
+
+/**
+ * Set the input (capture) preset for the stream.
+ *
+ * The AAudio system will use this information to optimize the
+ * behavior of the stream.
+ * This could, for example, affect which microphones are used and how the
+ * recorded data is processed.
+ *
+ * The default, if you do not call this function, is AAUDIO_INPUT_PRESET_GENERIC.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param inputPreset the desired configuration for recording
+ */
+AAUDIO_API void AAudioStreamBuilder_setInputPreset(AAudioStreamBuilder* builder,
+                                                   aaudio_input_preset_t inputPreset);
+
+/** Set the requested session ID.
+ *
+ * The session ID can be used to associate a stream with effects processors.
+ * The effects are controlled using the Android AudioEffect Java API.
+ *
+ * The default, if you do not call this function, is AAUDIO_SESSION_ID_NONE.
+ *
+ * If set to AAUDIO_SESSION_ID_ALLOCATE then a session ID will be allocated
+ * when the stream is opened.
+ *
+ * The allocated session ID can be obtained by calling AAudioStream_getSessionId()
+ * and then used with this function when opening another stream.
+ * This allows effects to be shared between streams.
+ *
+ * Session IDs from AAudio can be used the Android Java APIs and vice versa.
+ * So a session ID from an AAudio stream can be passed to Java
+ * and effects applied using the Java AudioEffect API.
+ *
+ * Allocated session IDs will always be positive and nonzero.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param sessionId an allocated sessionID or AAUDIO_SESSION_ID_ALLOCATE
+ */
+AAUDIO_API void AAudioStreamBuilder_setSessionId(AAudioStreamBuilder* builder,
+                                                aaudio_session_id_t sessionId);
+
+/**
  * Return one of these values from the data callback function.
  */
 enum {
@@ -337,7 +574,13 @@
  * For an input stream, this function should read and process numFrames of data
  * from the audioData buffer.
  *
- * Note that this callback function should be considered a "real-time" function.
+ * The audio data is passed through the buffer. So do NOT call AAudioStream_read() or
+ * AAudioStream_write() on the stream that is making the callback.
+ *
+ * Note that numFrames can vary unless AAudioStreamBuilder_setFramesPerDataCallback()
+ * is called.
+ *
+ * Also note that this callback function should be considered a "real-time" function.
  * It must not do anything that could cause an unbounded delay because that can cause the
  * audio to glitch or pop.
  *
@@ -348,6 +591,7 @@
  * <li>any network operations such as streaming</li>
  * <li>use any mutexes or other synchronization primitives</li>
  * <li>sleep</li>
+ * <li>stop or close the stream</li>
  * </ul>
  *
  * If you need to move data, eg. MIDI commands, in or out of the callback function then
@@ -356,7 +600,7 @@
  * @param stream reference provided by AAudioStreamBuilder_openStream()
  * @param userData the same address that was passed to AAudioStreamBuilder_setCallback()
  * @param audioData a pointer to the audio data
- * @param numFrames the number of frames to be processed
+ * @param numFrames the number of frames to be processed, which can vary
  * @return AAUDIO_CALLBACK_RESULT_*
  */
 typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
@@ -431,18 +675,19 @@
         aaudio_result_t error);
 
 /**
- * Request that AAudio call this functions if any error occurs on a callback thread.
+ * Request that AAudio call this function if any error occurs or the stream is disconnected.
  *
  * It will be called, for example, if a headset or a USB device is unplugged causing the stream's
- * device to be unavailable.
- * In response, this function could signal or launch another thread to reopen a
- * stream on another device. Do not reopen the stream in this callback.
- *
- * This will not be called because of actions by the application, such as stopping
- * or closing a stream.
- *
+ * device to be unavailable or "disconnected".
  * Another possible cause of error would be a timeout or an unanticipated internal error.
  *
+ * In response, this function should signal or create another thread to stop
+ * and close this stream. The other thread could then reopen a stream on another device.
+ * Do not stop or close the stream, or reopen the new stream, directly from this callback.
+ *
+ * This callback will not be called because of actions by the application, such as stopping
+ * or closing a stream.
+ *
  * Note that the AAudio callbacks will never be called simultaneously from multiple threads.
  *
  * @param builder reference provided by AAudio_createStreamBuilder()
@@ -554,11 +799,13 @@
  * This will update the current client state.
  *
  * <pre><code>
- * aaudio_stream_state_t currentState;
- * aaudio_result_t result = AAudioStream_getState(stream, &currentState);
- * while (result == AAUDIO_OK && currentState != AAUDIO_STREAM_STATE_PAUSING) {
+ * aaudio_result_t result = AAUDIO_OK;
+ * aaudio_stream_state_t currentState = AAudioStream_getState(stream);
+ * aaudio_stream_state_t inputState = currentState;
+ * while (result == AAUDIO_OK && currentState != AAUDIO_STREAM_STATE_PAUSED) {
  *     result = AAudioStream_waitForStateChange(
- *                                   stream, currentState, &currentState, MY_TIMEOUT_NANOS);
+ *                                   stream, inputState, &currentState, MY_TIMEOUT_NANOS);
+ *     inputState = currentState;
  * }
  * </code></pre>
  *
@@ -683,10 +930,10 @@
  * This call can be used if the application needs to know the value of numFrames before
  * the stream is started. This is not normally necessary.
  *
- * If a specific size was requested by calling AAudioStreamBuilder_setCallbackSizeInFrames()
+ * If a specific size was requested by calling AAudioStreamBuilder_setFramesPerDataCallback()
  * then this will be the same size.
  *
- * If AAudioStreamBuilder_setCallbackSizeInFrames() was not called then this will
+ * If AAudioStreamBuilder_setFramesPerDataCallback() was not called then this will
  * return the size chosen by AAudio, or AAUDIO_UNSPECIFIED.
  *
  * AAUDIO_UNSPECIFIED indicates that the callback buffer size for this stream
@@ -794,6 +1041,28 @@
 AAUDIO_API int64_t AAudioStream_getFramesRead(AAudioStream* stream);
 
 /**
+ * Passes back the session ID associated with this stream.
+ *
+ * The session ID can be used to associate a stream with effects processors.
+ * The effects are controlled using the Android AudioEffect Java API.
+ *
+ * If AAudioStreamBuilder_setSessionId() was called with AAUDIO_SESSION_ID_ALLOCATE
+ * then a new session ID should be allocated once when the stream is opened.
+ *
+ * If AAudioStreamBuilder_setSessionId() was called with a previously allocated
+ * session ID then that value should be returned.
+ *
+ * If AAudioStreamBuilder_setSessionId() was not called then this function should
+ * return AAUDIO_SESSION_ID_NONE.
+ *
+ * The sessionID for a stream should not change once the stream has been opened.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @return session ID or AAUDIO_SESSION_ID_NONE
+ */
+AAUDIO_API aaudio_session_id_t AAudioStream_getSessionId(AAudioStream* stream);
+
+/**
  * Passes back the time at which a particular frame was presented.
  * This can be used to synchronize audio with video or MIDI.
  * It can also be used to align a recorded stream with a playback stream.
@@ -820,6 +1089,30 @@
                                       int64_t *framePosition,
                                       int64_t *timeNanoseconds);
 
+/**
+ * Return the use case for the stream.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @return frames read
+ */
+AAUDIO_API aaudio_usage_t AAudioStream_getUsage(AAudioStream* stream);
+
+/**
+ * Return the content type for the stream.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @return content type, for example AAUDIO_CONTENT_TYPE_MUSIC
+ */
+AAUDIO_API aaudio_content_type_t AAudioStream_getContentType(AAudioStream* stream);
+
+/**
+ * Return the input preset for the stream.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @return input preset, for example AAUDIO_INPUT_PRESET_CAMCORDER
+ */
+AAUDIO_API aaudio_input_preset_t AAudioStream_getInputPreset(AAudioStream* stream);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/media/libaaudio/libaaudio.map.txt b/media/libaaudio/libaaudio.map.txt
index 2ba5250..cbf5921 100644
--- a/media/libaaudio/libaaudio.map.txt
+++ b/media/libaaudio/libaaudio.map.txt
@@ -17,6 +17,10 @@
     AAudioStreamBuilder_setSharingMode;
     AAudioStreamBuilder_setDirection;
     AAudioStreamBuilder_setBufferCapacityInFrames;
+    AAudioStreamBuilder_setUsage;       # introduced=28
+    AAudioStreamBuilder_setContentType; # introduced=28
+    AAudioStreamBuilder_setInputPreset; # introduced=28
+    AAudioStreamBuilder_setSessionId;   # introduced=28
     AAudioStreamBuilder_openStream;
     AAudioStreamBuilder_delete;
     AAudioStream_close;
@@ -42,8 +46,12 @@
     AAudioStream_getFormat;
     AAudioStream_getSharingMode;
     AAudioStream_getDirection;
+    AAudioStream_getUsage;       # introduced=28
+    AAudioStream_getContentType; # introduced=28
+    AAudioStream_getInputPreset; # introduced=28
     AAudioStream_getFramesWritten;
     AAudioStream_getFramesRead;
+    AAudioStream_getSessionId;   # introduced=28
     AAudioStream_getTimestamp;
     AAudioStream_isMMapUsed;
   local:
diff --git a/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp b/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp
index 153fce3..959db61 100644
--- a/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp
+++ b/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp
@@ -50,6 +50,14 @@
     if (status != NO_ERROR) goto error;
     status = parcel->writeInt32(getBufferCapacity());
     if (status != NO_ERROR) goto error;
+    status = parcel->writeInt32((int32_t) getUsage());
+    if (status != NO_ERROR) goto error;
+    status = parcel->writeInt32((int32_t) getContentType());
+    if (status != NO_ERROR) goto error;
+    status = parcel->writeInt32((int32_t) getInputPreset());
+    if (status != NO_ERROR) goto error;
+    status = parcel->writeInt32(getSessionId());
+    if (status != NO_ERROR) goto error;
     return NO_ERROR;
 error:
     ALOGE("AAudioStreamConfiguration.writeToParcel(): write failed = %d", status);
@@ -69,16 +77,28 @@
     setSamplesPerFrame(value);
     status = parcel->readInt32(&value);
     if (status != NO_ERROR) goto error;
-    setSharingMode(value);
+    setSharingMode((aaudio_sharing_mode_t) value);
     status = parcel->readInt32(&value);
     if (status != NO_ERROR) goto error;
-    setFormat(value);
+    setFormat((aaudio_format_t) value);
     status = parcel->readInt32(&value);
     if (status != NO_ERROR) goto error;
     setDirection((aaudio_direction_t) value);
     status = parcel->readInt32(&value);
     if (status != NO_ERROR) goto error;
     setBufferCapacity(value);
+    status = parcel->readInt32(&value);
+    if (status != NO_ERROR) goto error;
+    setUsage((aaudio_usage_t) value);
+    status = parcel->readInt32(&value);
+    if (status != NO_ERROR) goto error;
+    setContentType((aaudio_content_type_t) value);
+    status = parcel->readInt32(&value);
+    if (status != NO_ERROR) goto error;
+    setInputPreset((aaudio_input_preset_t) value);
+    status = parcel->readInt32(&value);
+    if (status != NO_ERROR) goto error;
+    setSessionId(value);
     return NO_ERROR;
 error:
     ALOGE("AAudioStreamConfiguration.readFromParcel(): read failed = %d", status);
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 6d5a64f..4980e97 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -112,6 +112,10 @@
     request.getConfiguration().setDirection(getDirection());
     request.getConfiguration().setSharingMode(getSharingMode());
 
+    request.getConfiguration().setUsage(getUsage());
+    request.getConfiguration().setContentType(getContentType());
+    request.getConfiguration().setInputPreset(getInputPreset());
+
     request.getConfiguration().setBufferCapacity(builder.getBufferCapacity());
 
     mServiceStreamHandle = mServiceInterface.openStream(request, configurationOutput);
@@ -129,8 +133,13 @@
     setSampleRate(configurationOutput.getSampleRate());
     setSamplesPerFrame(configurationOutput.getSamplesPerFrame());
     setDeviceId(configurationOutput.getDeviceId());
+    setSessionId(configurationOutput.getSessionId());
     setSharingMode(configurationOutput.getSharingMode());
 
+    setUsage(configurationOutput.getUsage());
+    setContentType(configurationOutput.getContentType());
+    setInputPreset(configurationOutput.getInputPreset());
+
     // Save device format so we can do format conversion and volume scaling together.
     mDeviceFormat = configurationOutput.getFormat();
 
diff --git a/media/libaaudio/src/core/AAudioAudio.cpp b/media/libaaudio/src/core/AAudioAudio.cpp
index bb007ac..df0db79 100644
--- a/media/libaaudio/src/core/AAudioAudio.cpp
+++ b/media/libaaudio/src/core/AAudioAudio.cpp
@@ -177,13 +177,38 @@
     streamBuilder->setSharingMode(sharingMode);
 }
 
+AAUDIO_API void AAudioStreamBuilder_setUsage(AAudioStreamBuilder* builder,
+                                             aaudio_usage_t usage) {
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+    streamBuilder->setUsage(usage);
+}
+
+AAUDIO_API void AAudioStreamBuilder_setContentType(AAudioStreamBuilder* builder,
+                                                   aaudio_content_type_t contentType) {
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+    streamBuilder->setContentType(contentType);
+}
+
+AAUDIO_API void AAudioStreamBuilder_setInputPreset(AAudioStreamBuilder* builder,
+                                                   aaudio_input_preset_t inputPreset) {
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+    streamBuilder->setInputPreset(inputPreset);
+}
+
 AAUDIO_API void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder* builder,
-                                                        int32_t frames)
+                                                              int32_t frames)
 {
     AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
     streamBuilder->setBufferCapacity(frames);
 }
 
+AAUDIO_API void AAudioStreamBuilder_setSessionId(AAudioStreamBuilder* builder,
+                                                 aaudio_session_id_t sessionId)
+{
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+    streamBuilder->setSessionId(sessionId);
+}
+
 AAUDIO_API void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder* builder,
                                                     AAudioStream_dataCallback callback,
                                                     void *userData)
@@ -447,6 +472,30 @@
     return audioStream->getSharingMode();
 }
 
+AAUDIO_API aaudio_usage_t AAudioStream_getUsage(AAudioStream* stream)
+{
+    AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+    return audioStream->getUsage();
+}
+
+AAUDIO_API aaudio_content_type_t AAudioStream_getContentType(AAudioStream* stream)
+{
+    AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+    return audioStream->getContentType();
+}
+
+AAUDIO_API aaudio_input_preset_t AAudioStream_getInputPreset(AAudioStream* stream)
+{
+    AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+    return audioStream->getInputPreset();
+}
+
+AAUDIO_API int32_t AAudioStream_getSessionId(AAudioStream* stream)
+{
+    AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+    return audioStream->getSessionId();
+}
+
 AAUDIO_API int64_t AAudioStream_getFramesWritten(AAudioStream* stream)
 {
     AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.cpp b/media/libaaudio/src/core/AAudioStreamParameters.cpp
index 6400eb4..9645ea8 100644
--- a/media/libaaudio/src/core/AAudioStreamParameters.cpp
+++ b/media/libaaudio/src/core/AAudioStreamParameters.cpp
@@ -38,10 +38,14 @@
     mSamplesPerFrame = other.mSamplesPerFrame;
     mSampleRate      = other.mSampleRate;
     mDeviceId        = other.mDeviceId;
+    mSessionId       = other.mSessionId;
     mSharingMode     = other.mSharingMode;
     mAudioFormat     = other.mAudioFormat;
     mDirection       = other.mDirection;
     mBufferCapacity  = other.mBufferCapacity;
+    mUsage           = other.mUsage;
+    mContentType     = other.mContentType;
+    mInputPreset     = other.mInputPreset;
 }
 
 aaudio_result_t AAudioStreamParameters::validate() const {
@@ -56,6 +60,15 @@
         return AAUDIO_ERROR_OUT_OF_RANGE;
     }
 
+    // All Session ID values are legal.
+    switch (mSessionId) {
+        case AAUDIO_SESSION_ID_NONE:
+        case AAUDIO_SESSION_ID_ALLOCATE:
+            break;
+        default:
+            break;
+    }
+
     switch (mSharingMode) {
         case AAUDIO_SHARING_MODE_EXCLUSIVE:
         case AAUDIO_SHARING_MODE_SHARED:
@@ -98,16 +111,67 @@
             // break;
     }
 
+    switch (mUsage) {
+        case AAUDIO_UNSPECIFIED:
+        case AAUDIO_USAGE_MEDIA:
+        case AAUDIO_USAGE_VOICE_COMMUNICATION:
+        case AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING:
+        case AAUDIO_USAGE_ALARM:
+        case AAUDIO_USAGE_NOTIFICATION:
+        case AAUDIO_USAGE_NOTIFICATION_RINGTONE:
+        case AAUDIO_USAGE_NOTIFICATION_EVENT:
+        case AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY:
+        case AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+        case AAUDIO_USAGE_ASSISTANCE_SONIFICATION:
+        case AAUDIO_USAGE_GAME:
+        case AAUDIO_USAGE_ASSISTANT:
+            break; // valid
+        default:
+            ALOGE("usage not valid = %d", mUsage);
+            return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
+            // break;
+    }
+
+    switch (mContentType) {
+        case AAUDIO_UNSPECIFIED:
+        case AAUDIO_CONTENT_TYPE_MUSIC:
+        case AAUDIO_CONTENT_TYPE_MOVIE:
+        case AAUDIO_CONTENT_TYPE_SONIFICATION:
+        case AAUDIO_CONTENT_TYPE_SPEECH:
+            break; // valid
+        default:
+            ALOGE("content type not valid = %d", mContentType);
+            return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
+            // break;
+    }
+
+    switch (mInputPreset) {
+        case AAUDIO_UNSPECIFIED:
+        case AAUDIO_INPUT_PRESET_GENERIC:
+        case AAUDIO_INPUT_PRESET_CAMCORDER:
+        case AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION:
+        case AAUDIO_INPUT_PRESET_VOICE_RECOGNITION:
+        case AAUDIO_INPUT_PRESET_UNPROCESSED:
+            break; // valid
+        default:
+            ALOGE("input preset not valid = %d", mInputPreset);
+            return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
+            // break;
+    }
+
     return AAUDIO_OK;
 }
 
 void AAudioStreamParameters::dump() const {
     ALOGD("mDeviceId        = %6d", mDeviceId);
+    ALOGD("mSessionId       = %6d", mSessionId);
     ALOGD("mSampleRate      = %6d", mSampleRate);
     ALOGD("mSamplesPerFrame = %6d", mSamplesPerFrame);
     ALOGD("mSharingMode     = %6d", (int)mSharingMode);
     ALOGD("mAudioFormat     = %6d", (int)mAudioFormat);
     ALOGD("mDirection       = %6d", mDirection);
     ALOGD("mBufferCapacity  = %6d", mBufferCapacity);
+    ALOGD("mUsage           = %6d", mUsage);
+    ALOGD("mContentType     = %6d", mContentType);
+    ALOGD("mInputPreset     = %6d", mInputPreset);
 }
-
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.h b/media/libaaudio/src/core/AAudioStreamParameters.h
index 5e67c93..ce5dacd 100644
--- a/media/libaaudio/src/core/AAudioStreamParameters.h
+++ b/media/libaaudio/src/core/AAudioStreamParameters.h
@@ -88,6 +88,38 @@
         mDirection = direction;
     }
 
+    aaudio_usage_t getUsage() const {
+        return mUsage;
+    }
+
+    void setUsage(aaudio_usage_t usage) {
+        mUsage = usage;
+    }
+
+    aaudio_content_type_t getContentType() const {
+        return mContentType;
+    }
+
+    void setContentType(aaudio_content_type_t contentType) {
+        mContentType = contentType;
+    }
+
+    aaudio_input_preset_t getInputPreset() const {
+        return mInputPreset;
+    }
+
+    void setInputPreset(aaudio_input_preset_t inputPreset) {
+        mInputPreset = inputPreset;
+    }
+
+    aaudio_session_id_t getSessionId() const {
+        return mSessionId;
+    }
+
+    void setSessionId(aaudio_session_id_t sessionId) {
+        mSessionId = sessionId;
+    }
+
     int32_t calculateBytesPerFrame() const {
         return getSamplesPerFrame() * AAudioConvert_formatToSizeInBytes(getFormat());
     }
@@ -109,7 +141,11 @@
     aaudio_sharing_mode_t      mSharingMode     = AAUDIO_SHARING_MODE_SHARED;
     aaudio_format_t            mAudioFormat     = AAUDIO_FORMAT_UNSPECIFIED;
     aaudio_direction_t         mDirection       = AAUDIO_DIRECTION_OUTPUT;
+    aaudio_usage_t             mUsage           = AAUDIO_UNSPECIFIED;
+    aaudio_content_type_t      mContentType     = AAUDIO_UNSPECIFIED;
+    aaudio_input_preset_t      mInputPreset     = AAUDIO_UNSPECIFIED;
     int32_t                    mBufferCapacity  = AAUDIO_UNSPECIFIED;
+    aaudio_session_id_t        mSessionId       = AAUDIO_SESSION_ID_NONE;
 };
 
 } /* namespace aaudio */
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 8f5f5d3..82c0667 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -74,15 +74,28 @@
     }
 
     // Copy parameters from the Builder because the Builder may be deleted after this call.
+    // TODO AudioStream should be a subclass of AudioStreamParameters
     mSamplesPerFrame = builder.getSamplesPerFrame();
     mSampleRate = builder.getSampleRate();
     mDeviceId = builder.getDeviceId();
     mFormat = builder.getFormat();
     mSharingMode = builder.getSharingMode();
     mSharingModeMatchRequired = builder.isSharingModeMatchRequired();
-
     mPerformanceMode = builder.getPerformanceMode();
 
+    mUsage = builder.getUsage();
+    if (mUsage == AAUDIO_UNSPECIFIED) {
+        mUsage = AAUDIO_USAGE_MEDIA;
+    }
+    mContentType = builder.getContentType();
+    if (mContentType == AAUDIO_UNSPECIFIED) {
+        mContentType = AAUDIO_CONTENT_TYPE_MUSIC;
+    }
+    mInputPreset = builder.getInputPreset();
+    if (mInputPreset == AAUDIO_UNSPECIFIED) {
+        mInputPreset = AAUDIO_INPUT_PRESET_GENERIC;
+    }
+
     // callbacks
     mFramesPerDataCallback = builder.getFramesPerDataCallback();
     mDataCallbackProc = builder.getDataCallbackProc();
@@ -95,10 +108,14 @@
           mSampleRate, mSamplesPerFrame, mFormat,
           AudioStream_convertSharingModeToShortText(mSharingMode),
           (getDirection() == AAUDIO_DIRECTION_OUTPUT) ? "OUTPUT" : "INPUT");
-    ALOGI("open() device = %d, perfMode = %d, callback: %s with frames = %d",
-          mDeviceId, mPerformanceMode,
+    ALOGI("open() device = %d, sessionId = %d, perfMode = %d, callback: %s with frames = %d",
+          mDeviceId,
+          mSessionId,
+          mPerformanceMode,
           (isDataCallbackSet() ? "ON" : "OFF"),
           mFramesPerDataCallback);
+    ALOGI("open() usage = %d, contentType = %d, inputPreset = %d",
+          mUsage, mContentType, mInputPreset);
 
     return AAUDIO_OK;
 }
@@ -221,6 +238,17 @@
     if (err != 0) {
         return AAudioConvert_androidToAAudioResult(-errno);
     } else {
+        // Name the thread with an increasing index, "AAudio_#", for debugging.
+        static std::atomic<uint32_t> nextThreadIndex{1};
+        char name[16]; // max length for a pthread_name
+        uint32_t index = nextThreadIndex++;
+        // Wrap the index so that we do not hit the 16 char limit
+        // and to avoid hard-to-read large numbers.
+        index = index % 100000;  // arbitrary
+        snprintf(name, sizeof(name), "AAudio_%u", index);
+        err = pthread_setname_np(mThread, name);
+        ALOGW_IF((err != 0), "Could not set name of AAudio thread. err = %d", err);
+
         mHasThread = true;
         return AAUDIO_OK;
     }
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index b5d7fd5..42b585f 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -204,6 +204,22 @@
 
     virtual aaudio_direction_t getDirection() const = 0;
 
+    aaudio_usage_t getUsage() const {
+        return mUsage;
+    }
+
+    aaudio_content_type_t getContentType() const {
+        return mContentType;
+    }
+
+    aaudio_input_preset_t getInputPreset() const {
+        return mInputPreset;
+    }
+
+    int32_t getSessionId() const {
+        return mSessionId;
+    }
+
     /**
      * This is only valid after setSamplesPerFrame() and setFormat() have been called.
      */
@@ -408,6 +424,7 @@
 
     /**
      * This should not be called after the open() call.
+     * TODO for multiple setters: assert(mState == AAUDIO_STREAM_STATE_UNINITIALIZED)
      */
     void setSampleRate(int32_t sampleRate) {
         mSampleRate = sampleRate;
@@ -442,10 +459,15 @@
         mDeviceId = deviceId;
     }
 
+    void setSessionId(int32_t sessionId) {
+        mSessionId = sessionId;
+    }
+
     std::atomic<bool>    mCallbackEnabled{false};
 
     float                mDuckAndMuteVolume = 1.0f;
 
+
 protected:
 
     void setPeriodNanoseconds(int64_t periodNanoseconds) {
@@ -456,6 +478,27 @@
         return mPeriodNanoseconds.load(std::memory_order_acquire);
     }
 
+    /**
+     * This should not be called after the open() call.
+     */
+    void setUsage(aaudio_usage_t usage) {
+        mUsage = usage;
+    }
+
+    /**
+     * This should not be called after the open() call.
+     */
+    void setContentType(aaudio_content_type_t contentType) {
+        mContentType = contentType;
+    }
+
+    /**
+     * This should not be called after the open() call.
+     */
+    void setInputPreset(aaudio_input_preset_t inputPreset) {
+        mInputPreset = inputPreset;
+    }
+
 private:
 
     std::mutex                 mStreamLock;
@@ -472,6 +515,12 @@
     aaudio_stream_state_t       mState = AAUDIO_STREAM_STATE_UNINITIALIZED;
     aaudio_performance_mode_t   mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE;
 
+    aaudio_usage_t              mUsage           = AAUDIO_USAGE_MEDIA;
+    aaudio_content_type_t       mContentType     = AAUDIO_CONTENT_TYPE_MUSIC;
+    aaudio_input_preset_t       mInputPreset     = AAUDIO_INPUT_PRESET_GENERIC;
+
+    int32_t                     mSessionId = AAUDIO_UNSPECIFIED;
+
     // callback ----------------------------------
 
     AAudioStream_dataCallback   mDataCallbackProc = nullptr;  // external callback functions
diff --git a/media/libaaudio/src/core/AudioStreamBuilder.cpp b/media/libaaudio/src/core/AudioStreamBuilder.cpp
index f7cb8d6..293a6a8 100644
--- a/media/libaaudio/src/core/AudioStreamBuilder.cpp
+++ b/media/libaaudio/src/core/AudioStreamBuilder.cpp
@@ -141,6 +141,13 @@
     // TODO Support other performance settings in MMAP mode.
     // Disable MMAP if low latency not requested.
     if (getPerformanceMode() != AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) {
+        ALOGD("build() MMAP not available because AAUDIO_PERFORMANCE_MODE_LOW_LATENCY not used.");
+        allowMMap = false;
+    }
+
+    // SessionID and Effects are only supported in Legacy mode.
+    if (getSessionId() != AAUDIO_SESSION_ID_NONE) {
+        ALOGD("build() MMAP not available because sessionId used.");
         allowMMap = false;
     }
 
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index 55eed92..61a0f8a 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -104,11 +104,27 @@
                                            ? AUDIO_PORT_HANDLE_NONE
                                            : getDeviceId();
 
+    const audio_content_type_t contentType =
+            AAudioConvert_contentTypeToInternal(builder.getContentType());
+    const audio_source_t source =
+            AAudioConvert_inputPresetToAudioSource(builder.getInputPreset());
+
+    const audio_attributes_t attributes = {
+            .content_type = contentType,
+            .usage = AUDIO_USAGE_UNKNOWN, // only used for output
+            .source = source,
+            .flags = flags, // If attributes are set then the other flags parameter is ignored.
+            .tags = ""
+    };
+
+    aaudio_session_id_t requestedSessionId = builder.getSessionId();
+    audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
+
     mAudioRecord = new AudioRecord(
             mOpPackageName // const String16& opPackageName TODO does not compile
             );
     mAudioRecord->set(
-            AUDIO_SOURCE_VOICE_RECOGNITION,
+            AUDIO_SOURCE_DEFAULT, // ignored because we pass attributes below
             getSampleRate(),
             format,
             channelMask,
@@ -117,12 +133,12 @@
             callbackData,
             notificationFrames,
             false /*threadCanCallJava*/,
-            AUDIO_SESSION_ALLOCATE,
+            sessionId,
             streamTransferType,
             flags,
             AUDIO_UID_INVALID, // DEFAULT uid
             -1,                // DEFAULT pid
-            NULL,              // DEFAULT audio_attributes_t
+            &attributes,
             selectedDeviceId
             );
 
@@ -176,6 +192,13 @@
 
     setState(AAUDIO_STREAM_STATE_OPEN);
     setDeviceId(mAudioRecord->getRoutedDeviceId());
+
+    aaudio_session_id_t actualSessionId =
+            (requestedSessionId == AAUDIO_SESSION_ID_NONE)
+            ? AAUDIO_SESSION_ID_NONE
+            : (aaudio_session_id_t) mAudioRecord->getSessionId();
+    setSessionId(actualSessionId);
+
     mAudioRecord->addAudioDeviceCallback(mDeviceCallback);
 
     return AAUDIO_OK;
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 5113278..52c7822 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -121,9 +121,27 @@
                                            ? AUDIO_PORT_HANDLE_NONE
                                            : getDeviceId();
 
+    const audio_content_type_t contentType =
+            AAudioConvert_contentTypeToInternal(builder.getContentType());
+    const audio_usage_t usage =
+            AAudioConvert_usageToInternal(builder.getUsage());
+
+    const audio_attributes_t attributes = {
+            .content_type = contentType,
+            .usage = usage,
+            .source = AUDIO_SOURCE_DEFAULT, // only used for recording
+            .flags = flags, // If attributes are set then the other flags parameter is ignored.
+            .tags = ""
+    };
+
+    static_assert(AAUDIO_UNSPECIFIED == AUDIO_SESSION_ALLOCATE, "Session IDs should match");
+
+    aaudio_session_id_t requestedSessionId = builder.getSessionId();
+    audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
+
     mAudioTrack = new AudioTrack();
     mAudioTrack->set(
-            (audio_stream_type_t) AUDIO_STREAM_MUSIC,
+            AUDIO_STREAM_DEFAULT,  // ignored because we pass attributes below
             getSampleRate(),
             format,
             channelMask,
@@ -134,12 +152,12 @@
             notificationFrames,
             0,       // DEFAULT sharedBuffer*/,
             false,   // DEFAULT threadCanCallJava
-            AUDIO_SESSION_ALLOCATE,
+            sessionId,
             streamTransferType,
             NULL,    // DEFAULT audio_offload_info_t
             AUDIO_UID_INVALID, // DEFAULT uid
             -1,      // DEFAULT pid
-            NULL,    // DEFAULT audio_attributes_t
+            &attributes,
             // WARNING - If doNotReconnect set true then audio stops after plugging and unplugging
             // headphones a few times.
             false,   // DEFAULT doNotReconnect,
@@ -180,6 +198,13 @@
 
     setState(AAUDIO_STREAM_STATE_OPEN);
     setDeviceId(mAudioTrack->getRoutedDeviceId());
+
+    aaudio_session_id_t actualSessionId =
+            (requestedSessionId == AAUDIO_SESSION_ID_NONE)
+            ? AAUDIO_SESSION_ID_NONE
+            : (aaudio_session_id_t) mAudioTrack->getSessionId();
+    setSessionId(actualSessionId);
+
     mAudioTrack->addAudioDeviceCallback(mDeviceCallback);
 
     // Update performance mode based on the actual stream flags.
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index f709f41..2bee6e3 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -26,6 +26,7 @@
 #include "aaudio/AAudio.h"
 #include <aaudio/AAudioTesting.h>
 #include <math.h>
+#include <system/audio-base.h>
 
 #include "utility/AAudioUtilities.h"
 
@@ -249,6 +250,13 @@
     return result;
 }
 
+audio_session_t AAudioConvert_aaudioToAndroidSessionId(aaudio_session_id_t sessionId) {
+    // If not a valid sessionId then convert to a safe value of AUDIO_SESSION_ALLOCATE.
+    return (sessionId < AAUDIO_SESSION_ID_MIN)
+           ? AUDIO_SESSION_ALLOCATE
+           : (audio_session_t) sessionId;
+}
+
 audio_format_t AAudioConvert_aaudioToAndroidDataFormat(aaudio_format_t aaudioFormat) {
     audio_format_t androidFormat;
     switch (aaudioFormat) {
@@ -283,6 +291,61 @@
     return aaudioFormat;
 }
 
+// Make a message string from the condition.
+#define STATIC_ASSERT(condition) static_assert(condition, #condition)
+
+audio_usage_t AAudioConvert_usageToInternal(aaudio_usage_t usage) {
+    // The public aaudio_content_type_t constants are supposed to have the same
+    // values as the internal audio_content_type_t values.
+    STATIC_ASSERT(AAUDIO_USAGE_MEDIA == AUDIO_USAGE_MEDIA);
+    STATIC_ASSERT(AAUDIO_USAGE_VOICE_COMMUNICATION == AUDIO_USAGE_VOICE_COMMUNICATION);
+    STATIC_ASSERT(AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING
+                  == AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING);
+    STATIC_ASSERT(AAUDIO_USAGE_ALARM == AUDIO_USAGE_ALARM);
+    STATIC_ASSERT(AAUDIO_USAGE_NOTIFICATION == AUDIO_USAGE_NOTIFICATION);
+    STATIC_ASSERT(AAUDIO_USAGE_NOTIFICATION_RINGTONE
+                  == AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE);
+    STATIC_ASSERT(AAUDIO_USAGE_NOTIFICATION_EVENT == AUDIO_USAGE_NOTIFICATION_EVENT);
+    STATIC_ASSERT(AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY == AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY);
+    STATIC_ASSERT(AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
+                  == AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
+    STATIC_ASSERT(AAUDIO_USAGE_ASSISTANCE_SONIFICATION == AUDIO_USAGE_ASSISTANCE_SONIFICATION);
+    STATIC_ASSERT(AAUDIO_USAGE_GAME == AUDIO_USAGE_GAME);
+    STATIC_ASSERT(AAUDIO_USAGE_ASSISTANT == AUDIO_USAGE_ASSISTANT);
+    if (usage == AAUDIO_UNSPECIFIED) {
+        usage = AAUDIO_USAGE_MEDIA;
+    }
+    return (audio_usage_t) usage; // same value
+}
+
+audio_content_type_t AAudioConvert_contentTypeToInternal(aaudio_content_type_t contentType) {
+    // The public aaudio_content_type_t constants are supposed to have the same
+    // values as the internal audio_content_type_t values.
+    STATIC_ASSERT(AAUDIO_CONTENT_TYPE_MUSIC == AUDIO_CONTENT_TYPE_MUSIC);
+    STATIC_ASSERT(AAUDIO_CONTENT_TYPE_SPEECH == AUDIO_CONTENT_TYPE_SPEECH);
+    STATIC_ASSERT(AAUDIO_CONTENT_TYPE_SONIFICATION == AUDIO_CONTENT_TYPE_SONIFICATION);
+    STATIC_ASSERT(AAUDIO_CONTENT_TYPE_MOVIE == AUDIO_CONTENT_TYPE_MOVIE);
+    if (contentType == AAUDIO_UNSPECIFIED) {
+        contentType = AAUDIO_CONTENT_TYPE_MUSIC;
+    }
+    return (audio_content_type_t) contentType; // same value
+}
+
+audio_source_t AAudioConvert_inputPresetToAudioSource(aaudio_input_preset_t preset) {
+    // The public aaudio_input_preset_t constants are supposed to have the same
+    // values as the internal audio_source_t values.
+    STATIC_ASSERT(AAUDIO_UNSPECIFIED == AUDIO_SOURCE_DEFAULT);
+    STATIC_ASSERT(AAUDIO_INPUT_PRESET_GENERIC == AUDIO_SOURCE_MIC);
+    STATIC_ASSERT(AAUDIO_INPUT_PRESET_CAMCORDER == AUDIO_SOURCE_CAMCORDER);
+    STATIC_ASSERT(AAUDIO_INPUT_PRESET_VOICE_RECOGNITION == AUDIO_SOURCE_VOICE_RECOGNITION);
+    STATIC_ASSERT(AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION == AUDIO_SOURCE_VOICE_COMMUNICATION);
+    STATIC_ASSERT(AAUDIO_INPUT_PRESET_UNPROCESSED == AUDIO_SOURCE_UNPROCESSED);
+    if (preset == AAUDIO_UNSPECIFIED) {
+        preset = AAUDIO_INPUT_PRESET_GENERIC;
+    }
+    return (audio_source_t) preset; // same value
+}
+
 int32_t AAudioConvert_framesToBytes(int32_t numFrames,
                                             int32_t bytesPerFrame,
                                             int32_t *sizeInBytes) {
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index 3afa976..0c59f6d 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -27,6 +27,9 @@
 
 #include "aaudio/AAudio.h"
 
+
+constexpr aaudio_session_id_t AAUDIO_SESSION_ID_MIN = 1; // must be positive
+
 /**
  * Convert an AAudio result into the closest matching Android status.
  */
@@ -38,6 +41,13 @@
 aaudio_result_t AAudioConvert_androidToAAudioResult(android::status_t status);
 
 /**
+ * Convert an aaudio_session_id_t to a value that is safe to pass to AudioFlinger.
+ * @param sessionId
+ * @return safe value
+ */
+audio_session_t AAudioConvert_aaudioToAndroidSessionId(aaudio_session_id_t sessionId);
+
+/**
  * Convert an array of floats to an array of int16_t.
  *
  * @param source
@@ -167,6 +177,29 @@
 
 aaudio_format_t AAudioConvert_androidToAAudioDataFormat(audio_format_t format);
 
+
+/**
+ * Note that this function does not validate the passed in value.
+ * That is done somewhere else.
+ * @return internal value
+ */
+
+audio_usage_t AAudioConvert_usageToInternal(aaudio_usage_t usage);
+
+/**
+ * Note that this function does not validate the passed in value.
+ * That is done somewhere else.
+ * @return internal value
+ */
+audio_content_type_t AAudioConvert_contentTypeToInternal(aaudio_content_type_t contentType);
+
+/**
+ * Note that this function does not validate the passed in value.
+ * That is done somewhere else.
+ * @return internal audio source
+ */
+audio_source_t AAudioConvert_inputPresetToAudioSource(aaudio_input_preset_t preset);
+
 /**
  * @return the size of a sample of the given format in bytes or AAUDIO_ERROR_ILLEGAL_ARGUMENT
  */
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp
index 9f80695..45d417f 100644
--- a/media/libaaudio/tests/Android.bp
+++ b/media/libaaudio/tests/Android.bp
@@ -113,6 +113,18 @@
 }
 
 cc_test {
+    name: "test_session_id",
+    defaults: ["libaaudio_tests_defaults"],
+    srcs: ["test_session_id.cpp"],
+    shared_libs: [
+        "libaaudio",
+        "libbinder",
+        "libcutils",
+        "libutils",
+    ],
+}
+
+cc_test {
     name: "test_aaudio_monkey",
     defaults: ["libaaudio_tests_defaults"],
     srcs: ["test_aaudio_monkey.cpp"],
@@ -124,3 +136,15 @@
         "libutils",
     ],
 }
+
+cc_test {
+    name: "test_attributes",
+    defaults: ["libaaudio_tests_defaults"],
+    srcs: ["test_attributes.cpp"],
+    shared_libs: [
+        "libaaudio",
+        "libbinder",
+        "libcutils",
+        "libutils",
+    ],
+}
diff --git a/media/libaaudio/tests/test_attributes.cpp b/media/libaaudio/tests/test_attributes.cpp
new file mode 100644
index 0000000..9cbf113
--- /dev/null
+++ b/media/libaaudio/tests/test_attributes.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.
+ */
+
+// Test AAudio attributes such as Usage, ContentType and InputPreset.
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <aaudio/AAudio.h>
+#include <gtest/gtest.h>
+
+constexpr int64_t kNanosPerSecond = 1000000000;
+constexpr int kNumFrames = 256;
+constexpr int kChannelCount = 2;
+
+constexpr int32_t DONT_SET = -1000;
+
+static void checkAttributes(aaudio_performance_mode_t perfMode,
+                            aaudio_usage_t usage,
+                            aaudio_content_type_t contentType,
+                            aaudio_input_preset_t preset = DONT_SET,
+                            aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT) {
+
+    float *buffer = new float[kNumFrames * kChannelCount];
+
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    AAudioStream *aaudioStream = nullptr;
+
+    // Use an AAudioStreamBuilder to contain requested parameters.
+    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+    // Request stream properties.
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
+    AAudioStreamBuilder_setDirection(aaudioBuilder, direction);
+
+    // Set the attribute in the builder.
+    if (usage != DONT_SET) {
+        AAudioStreamBuilder_setUsage(aaudioBuilder, usage);
+    }
+    if (contentType != DONT_SET) {
+        AAudioStreamBuilder_setContentType(aaudioBuilder, contentType);
+    }
+    if (preset != DONT_SET) {
+        AAudioStreamBuilder_setInputPreset(aaudioBuilder, preset);
+    }
+
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+    AAudioStreamBuilder_delete(aaudioBuilder);
+
+    // Make sure we get the same attributes back from the stream.
+    aaudio_usage_t expectedUsage =
+            (usage == DONT_SET || usage == AAUDIO_UNSPECIFIED)
+            ? AAUDIO_USAGE_MEDIA // default
+            : usage;
+    EXPECT_EQ(expectedUsage, AAudioStream_getUsage(aaudioStream));
+
+    aaudio_content_type_t expectedContentType =
+            (contentType == DONT_SET || contentType == AAUDIO_UNSPECIFIED)
+            ? AAUDIO_CONTENT_TYPE_MUSIC // default
+            : contentType;
+    EXPECT_EQ(expectedContentType, AAudioStream_getContentType(aaudioStream));
+
+    aaudio_input_preset_t expectedPreset =
+            (preset == DONT_SET || preset == AAUDIO_UNSPECIFIED)
+            ? AAUDIO_INPUT_PRESET_GENERIC // default
+            : preset;
+    EXPECT_EQ(expectedPreset, AAudioStream_getInputPreset(aaudioStream));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
+
+    if (direction == AAUDIO_DIRECTION_INPUT) {
+        EXPECT_EQ(kNumFrames,
+                  AAudioStream_read(aaudioStream, buffer, kNumFrames, kNanosPerSecond));
+    } else {
+        EXPECT_EQ(kNumFrames,
+                  AAudioStream_write(aaudioStream, buffer, kNumFrames, kNanosPerSecond));
+    }
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
+    delete[] buffer;
+}
+
+static const aaudio_usage_t sUsages[] = {
+    DONT_SET,
+    AAUDIO_UNSPECIFIED,
+    AAUDIO_USAGE_MEDIA,
+    AAUDIO_USAGE_VOICE_COMMUNICATION,
+    AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING,
+    AAUDIO_USAGE_ALARM,
+    AAUDIO_USAGE_NOTIFICATION,
+    AAUDIO_USAGE_NOTIFICATION_RINGTONE,
+    AAUDIO_USAGE_NOTIFICATION_EVENT,
+    AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY,
+    AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+    AAUDIO_USAGE_ASSISTANCE_SONIFICATION,
+    AAUDIO_USAGE_GAME,
+    AAUDIO_USAGE_ASSISTANT
+};
+
+static const aaudio_content_type_t sContentypes[] = {
+    DONT_SET,
+    AAUDIO_UNSPECIFIED,
+    AAUDIO_CONTENT_TYPE_SPEECH,
+    AAUDIO_CONTENT_TYPE_MUSIC,
+    AAUDIO_CONTENT_TYPE_MOVIE,
+    AAUDIO_CONTENT_TYPE_SONIFICATION
+};
+
+static const aaudio_input_preset_t sInputPresets[] = {
+    DONT_SET,
+    AAUDIO_UNSPECIFIED,
+    AAUDIO_INPUT_PRESET_GENERIC,
+    AAUDIO_INPUT_PRESET_CAMCORDER,
+    AAUDIO_INPUT_PRESET_VOICE_RECOGNITION,
+    AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION,
+    AAUDIO_INPUT_PRESET_UNPROCESSED,
+};
+
+static void checkAttributesUsage(aaudio_performance_mode_t perfMode) {
+    for (aaudio_usage_t usage : sUsages) {
+        checkAttributes(perfMode, usage, DONT_SET);
+    }
+}
+
+static void checkAttributesContentType(aaudio_input_preset_t perfMode) {
+    for (aaudio_content_type_t contentType : sContentypes) {
+        checkAttributes(perfMode, DONT_SET, contentType);
+    }
+}
+
+static void checkAttributesInputPreset(aaudio_performance_mode_t perfMode) {
+    for (aaudio_input_preset_t inputPreset : sInputPresets) {
+        checkAttributes(perfMode,
+                        DONT_SET,
+                        DONT_SET,
+                        inputPreset,
+                        AAUDIO_DIRECTION_INPUT);
+    }
+}
+
+TEST(test_attributes, aaudio_usage_perfnone) {
+    checkAttributesUsage(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_attributes, aaudio_content_type_perfnone) {
+    checkAttributesContentType(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_attributes, aaudio_input_preset_perfnone) {
+    checkAttributesInputPreset(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_attributes, aaudio_usage_lowlat) {
+    checkAttributesUsage(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
+
+TEST(test_attributes, aaudio_content_type_lowlat) {
+    checkAttributesContentType(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
+
+TEST(test_attributes, aaudio_input_preset_lowlat) {
+    checkAttributesInputPreset(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
diff --git a/media/libaaudio/tests/test_session_id.cpp b/media/libaaudio/tests/test_session_id.cpp
new file mode 100644
index 0000000..d9072af
--- /dev/null
+++ b/media/libaaudio/tests/test_session_id.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+// Test AAudio SessionId, which is used to associate Effects with a stream
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <aaudio/AAudio.h>
+#include <gtest/gtest.h>
+
+constexpr int64_t kNanosPerSecond = 1000000000;
+constexpr int kNumFrames = 256;
+constexpr int kChannelCount = 2;
+
+// Test AAUDIO_SESSION_ID_NONE default
+static void checkSessionIdNone(aaudio_performance_mode_t perfMode) {
+
+    float *buffer = new float[kNumFrames * kChannelCount];
+
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+
+    AAudioStream *aaudioStream1 = nullptr;
+    int32_t       sessionId1 = 0;
+
+    // Use an AAudioStreamBuilder to contain requested parameters.
+    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+    // Request stream properties.
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
+
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1));
+
+    // Since we did not request or specify a SessionID, we should get NONE
+    sessionId1 = AAudioStream_getSessionId(aaudioStream1);
+    ASSERT_EQ(AAUDIO_SESSION_ID_NONE, sessionId1);
+
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1));
+
+    ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream1, buffer, kNumFrames, kNanosPerSecond));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream1));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream1));
+    delete[] buffer;
+    AAudioStreamBuilder_delete(aaudioBuilder);
+}
+
+TEST(test_session_id, aaudio_session_id_none_perfnone) {
+    checkSessionIdNone(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_session_id, aaudio_session_id_none_lowlat) {
+    checkSessionIdNone(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
+
+// Test AAUDIO_SESSION_ID_ALLOCATE
+static void checkSessionIdAllocate(aaudio_performance_mode_t perfMode,
+                                   aaudio_direction_t direction) {
+
+    float *buffer = new float[kNumFrames * kChannelCount];
+
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+
+    AAudioStream *aaudioStream1 = nullptr;
+    int32_t       sessionId1 = 0;
+    AAudioStream *aaudioStream2 = nullptr;
+    int32_t       sessionId2 = 0;
+
+    // Use an AAudioStreamBuilder to contain requested parameters.
+    ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+    // Request stream properties.
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
+    // This stream could be input or output.
+    AAudioStreamBuilder_setDirection(aaudioBuilder, direction);
+
+    // Ask AAudio to allocate a Session ID.
+    AAudioStreamBuilder_setSessionId(aaudioBuilder, AAUDIO_SESSION_ID_ALLOCATE);
+
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1));
+
+    // Get the allocated ID from the stream.
+    sessionId1 = AAudioStream_getSessionId(aaudioStream1);
+    ASSERT_LT(0, sessionId1); // Must be positive.
+
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1));
+
+    if (direction == AAUDIO_DIRECTION_INPUT) {
+        ASSERT_EQ(kNumFrames, AAudioStream_read(aaudioStream1,
+                                                buffer, kNumFrames, kNanosPerSecond));
+    } else {
+        ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream1,
+                                         buffer, kNumFrames, kNanosPerSecond));
+    }
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream1));
+
+    // Now open a second stream using the same session ID. ==================
+    AAudioStreamBuilder_setSessionId(aaudioBuilder, sessionId1);
+
+    // Reverse direction for second stream.
+    aaudio_direction_t otherDirection = (direction == AAUDIO_DIRECTION_OUTPUT)
+                                        ? AAUDIO_DIRECTION_INPUT
+                                        : AAUDIO_DIRECTION_OUTPUT;
+    AAudioStreamBuilder_setDirection(aaudioBuilder, otherDirection);
+
+    // Create an AAudioStream using the Builder.
+    ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream2));
+
+    // Get the allocated ID from the stream.
+    // It should match the ID that we set it to in the builder.
+    sessionId2 = AAudioStream_getSessionId(aaudioStream2);
+    ASSERT_EQ(sessionId1, sessionId2);
+
+    ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream2));
+
+    if (otherDirection == AAUDIO_DIRECTION_INPUT) {
+        ASSERT_EQ(kNumFrames, AAudioStream_read(aaudioStream2,
+                                                 buffer, kNumFrames, kNanosPerSecond));
+    } else {
+        ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream2,
+                                                 buffer, kNumFrames, kNanosPerSecond));
+    }
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream2));
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream2));
+
+
+    EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream1));
+    delete[] buffer;
+    AAudioStreamBuilder_delete(aaudioBuilder);
+}
+
+TEST(test_session_id, aaudio_session_id_alloc_perfnone_in) {
+    checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_DIRECTION_INPUT);
+}
+TEST(test_session_id, aaudio_session_id_alloc_perfnone_out) {
+    checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_DIRECTION_OUTPUT);
+}
+
+TEST(test_session_id, aaudio_session_id_alloc_lowlat_in) {
+    checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_DIRECTION_INPUT);
+}
+TEST(test_session_id, aaudio_session_id_alloc_lowlat_out) {
+    checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_DIRECTION_OUTPUT);
+}
diff --git a/media/libaudioclient/Android.bp b/media/libaudioclient/Android.bp
index bedde43..2df37a8 100644
--- a/media/libaudioclient/Android.bp
+++ b/media/libaudioclient/Android.bp
@@ -47,6 +47,8 @@
         "libdl",
         "libaudioutils",
         "libaudiomanager",
+        "libmedia_helper",
+        "libmediametrics",
     ],
     export_shared_lib_headers: ["libbinder"],
 
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index 0d4b462..bc294c5 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -26,6 +26,8 @@
 #include <utils/Log.h>
 #include <private/media/AudioTrackShared.h>
 #include <media/IAudioFlinger.h>
+#include <media/MediaAnalyticsItem.h>
+#include <media/TypeConverter.h>
 
 #define WAIT_PERIOD_MS          10
 
@@ -65,6 +67,34 @@
 
 // ---------------------------------------------------------------------------
 
+static std::string audioFormatTypeString(audio_format_t value) {
+    std::string formatType;
+    if (FormatConverter::toString(value, formatType)) {
+        return formatType;
+    }
+    char rawbuffer[16];  // room for "%d"
+    snprintf(rawbuffer, sizeof(rawbuffer), "%d", value);
+    return rawbuffer;
+}
+
+void AudioRecord::MediaMetrics::gather(const AudioRecord *record)
+{
+    // key for media statistics is defined in the header
+    // attrs for media statistics
+    static constexpr char kAudioRecordChannelCount[] = "android.media.audiorecord.channels";
+    static constexpr char kAudioRecordFormat[] = "android.media.audiorecord.format";
+    static constexpr char kAudioRecordLatency[] = "android.media.audiorecord.latency";
+    static constexpr char kAudioRecordSampleRate[] = "android.media.audiorecord.samplerate";
+
+    // constructor guarantees mAnalyticsItem is valid
+
+    mAnalyticsItem->setInt32(kAudioRecordLatency, record->mLatency);
+    mAnalyticsItem->setInt32(kAudioRecordSampleRate, record->mSampleRate);
+    mAnalyticsItem->setInt32(kAudioRecordChannelCount, record->mChannelCount);
+    mAnalyticsItem->setCString(kAudioRecordFormat,
+                               audioFormatTypeString(record->mFormat).c_str());
+}
+
 AudioRecord::AudioRecord(const String16 &opPackageName)
     : mActive(false), mStatus(NO_INIT), mOpPackageName(opPackageName),
       mSessionId(AUDIO_SESSION_ALLOCATE),
@@ -105,6 +135,8 @@
 
 AudioRecord::~AudioRecord()
 {
+    mMediaMetrics.gather(this);
+
     if (mStatus == NO_ERROR) {
         // Make sure that callback function exits in the case where
         // it is looping on buffer empty condition in obtainBuffer().
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 6d829a0..a3c66fe 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -31,6 +31,8 @@
 #include <media/IAudioFlinger.h>
 #include <media/AudioPolicyHelper.h>
 #include <media/AudioResamplerPublic.h>
+#include <media/MediaAnalyticsItem.h>
+#include <media/TypeConverter.h>
 
 #define WAIT_PERIOD_MS                  10
 #define WAIT_STREAM_END_TIMEOUT_SEC     120
@@ -157,6 +159,71 @@
 
 // ---------------------------------------------------------------------------
 
+static std::string audioContentTypeString(audio_content_type_t value) {
+    std::string contentType;
+    if (AudioContentTypeConverter::toString(value, contentType)) {
+        return contentType;
+    }
+    char rawbuffer[16];  // room for "%d"
+    snprintf(rawbuffer, sizeof(rawbuffer), "%d", value);
+    return rawbuffer;
+}
+
+static std::string audioUsageString(audio_usage_t value) {
+    std::string usage;
+    if (UsageTypeConverter::toString(value, usage)) {
+        return usage;
+    }
+    char rawbuffer[16];  // room for "%d"
+    snprintf(rawbuffer, sizeof(rawbuffer), "%d", value);
+    return rawbuffer;
+}
+
+void AudioTrack::MediaMetrics::gather(const AudioTrack *track)
+{
+
+    // key for media statistics is defined in the header
+    // attrs for media statistics
+    static constexpr char kAudioTrackStreamType[] = "android.media.audiotrack.streamtype";
+    static constexpr char kAudioTrackContentType[] = "android.media.audiotrack.type";
+    static constexpr char kAudioTrackUsage[] = "android.media.audiotrack.usage";
+    static constexpr char kAudioTrackSampleRate[] = "android.media.audiotrack.samplerate";
+    static constexpr char kAudioTrackChannelMask[] = "android.media.audiotrack.channelmask";
+#if 0
+    // XXX: disabled temporarily for b/72027185
+    static constexpr char kAudioTrackUnderrunFrames[] = "android.media.audiotrack.underrunframes";
+#endif
+    static constexpr char kAudioTrackStartupGlitch[] = "android.media.audiotrack.glitch.startup";
+
+    // constructor guarantees mAnalyticsItem is valid
+
+#if 0
+    // XXX: disabled temporarily for b/72027185
+    // must gather underrun info before cleaning mProxy information.
+    const int32_t underrunFrames = track->getUnderrunFrames();
+    if (underrunFrames != 0) {
+        mAnalyticsItem->setInt32(kAudioTrackUnderrunFrames, underrunFrames);
+    }
+#endif
+
+    if (track->mTimestampStartupGlitchReported) {
+        mAnalyticsItem->setInt32(kAudioTrackStartupGlitch, 1);
+    }
+
+    if (track->mStreamType != -1) {
+        // deprecated, but this will tell us who still uses it.
+        mAnalyticsItem->setInt32(kAudioTrackStreamType, track->mStreamType);
+    }
+    // XXX: consider including from mAttributes: source type
+    mAnalyticsItem->setCString(kAudioTrackContentType,
+                               audioContentTypeString(track->mAttributes.content_type).c_str());
+    mAnalyticsItem->setCString(kAudioTrackUsage,
+                               audioUsageString(track->mAttributes.usage).c_str());
+    mAnalyticsItem->setInt32(kAudioTrackSampleRate, track->mSampleRate);
+    mAnalyticsItem->setInt64(kAudioTrackChannelMask, track->mChannelMask);
+}
+
+
 AudioTrack::AudioTrack()
     : mStatus(NO_INIT),
       mState(STATE_STOPPED),
@@ -236,6 +303,9 @@
 
 AudioTrack::~AudioTrack()
 {
+    // pull together the numbers, before we clean up our structures
+    mMediaMetrics.gather(this);
+
     if (mStatus == NO_ERROR) {
         // Make sure that callback function exits in the case where
         // it is looping on buffer full condition in obtainBuffer().
diff --git a/media/libaudioclient/include/media/AudioRecord.h b/media/libaudioclient/include/media/AudioRecord.h
index 074e547..fea973a 100644
--- a/media/libaudioclient/include/media/AudioRecord.h
+++ b/media/libaudioclient/include/media/AudioRecord.h
@@ -21,6 +21,7 @@
 #include <cutils/sched_policy.h>
 #include <media/AudioSystem.h>
 #include <media/AudioTimestamp.h>
+#include <media/MediaAnalyticsItem.h>
 #include <media/Modulo.h>
 #include <utils/RefBase.h>
 #include <utils/threads.h>
@@ -688,6 +689,24 @@
                                               // activity and connected devices
     wp<AudioSystem::AudioDeviceCallback> mDeviceCallback;
 
+private:
+    class MediaMetrics {
+      public:
+        MediaMetrics() : mAnalyticsItem(new MediaAnalyticsItem("audiorecord")) {
+        }
+        ~MediaMetrics() {
+            // mAnalyticsItem alloc failure will be flagged in the constructor
+            // don't log empty records
+            if (mAnalyticsItem->count() > 0) {
+                mAnalyticsItem->setFinalized(true);
+                mAnalyticsItem->selfrecord();
+            }
+        }
+        void gather(const AudioRecord *record);
+      private:
+        std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem;
+    };
+    MediaMetrics mMediaMetrics;
 };
 
 }; // namespace android
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index 9fbd04b..c146db9 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -22,6 +22,7 @@
 #include <media/AudioTimestamp.h>
 #include <media/IAudioTrack.h>
 #include <media/AudioResamplerPublic.h>
+#include <media/MediaAnalyticsItem.h>
 #include <media/Modulo.h>
 #include <utils/threads.h>
 
@@ -1182,6 +1183,25 @@
     pid_t                   mClientPid;
 
     wp<AudioSystem::AudioDeviceCallback> mDeviceCallback;
+
+private:
+    class MediaMetrics {
+      public:
+        MediaMetrics() : mAnalyticsItem(new MediaAnalyticsItem("audiotrack")) {
+        }
+        ~MediaMetrics() {
+            // mAnalyticsItem alloc failure will be flagged in the constructor
+            // don't log empty records
+            if (mAnalyticsItem->count() > 0) {
+                mAnalyticsItem->setFinalized(true);
+                mAnalyticsItem->selfrecord();
+            }
+        }
+        void gather(const AudioTrack *track);
+      private:
+        std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem;
+    };
+    MediaMetrics mMediaMetrics;
 };
 
 }; // namespace android
diff --git a/media/libeffects/config/src/EffectsConfig.cpp b/media/libeffects/config/src/EffectsConfig.cpp
index 18c406d..4ed3ba8 100644
--- a/media/libeffects/config/src/EffectsConfig.cpp
+++ b/media/libeffects/config/src/EffectsConfig.cpp
@@ -255,8 +255,8 @@
     XMLDocument doc;
     doc.LoadFile(path);
     if (doc.Error()) {
-        ALOGE("Failed to parse %s: Tinyxml2 error (%d): %s %s", path,
-              doc.ErrorID(), doc.GetErrorStr1(), doc.GetErrorStr2());
+        ALOGE("Failed to parse %s: Tinyxml2 error (%d): %s", path,
+              doc.ErrorID(), doc.ErrorStr());
         return {nullptr, 0, path};
     }
 
diff --git a/media/libeffects/visualizer/EffectVisualizer.cpp b/media/libeffects/visualizer/EffectVisualizer.cpp
index c33f9f5..807f24d 100644
--- a/media/libeffects/visualizer/EffectVisualizer.cpp
+++ b/media/libeffects/visualizer/EffectVisualizer.cpp
@@ -594,7 +594,9 @@
                     deltaSmpl = CAPTURE_BUF_SIZE;
                 }
 
-                int32_t capturePoint = (int32_t)pContext->mCaptureIdx - deltaSmpl;
+                int32_t capturePoint;
+                //capturePoint = (int32_t)pContext->mCaptureIdx - deltaSmpl;
+                __builtin_sub_overflow((int32_t)pContext->mCaptureIdx, deltaSmpl, &capturePoint);
                 // a negative capturePoint means we wrap the buffer.
                 if (capturePoint < 0) {
                     uint32_t size = -capturePoint;
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index 0b4fd25..fd7400a 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -139,10 +139,18 @@
     },
 }
 
+filegroup {
+    name: "mediaupdateservice_aidl",
+    srcs: [
+        "aidl/android/media/IMediaExtractorUpdateService.aidl",
+    ],
+}
+
 cc_library_shared {
     name: "libmedia",
 
     srcs: [
+        ":mediaupdateservice_aidl",
         "IDataSource.cpp",
         "BufferingSettings.cpp",
         "mediaplayer.cpp",
@@ -179,6 +187,11 @@
         "StringArray.cpp",
     ],
 
+    aidl: {
+        local_include_dirs: ["aidl"],
+        export_aidl_headers: true,
+    },
+
     shared_libs: [
         "liblog",
         "libcutils",
@@ -222,6 +235,8 @@
         "-Wall",
     ],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "unsigned-integer-overflow",
@@ -306,6 +321,7 @@
 
     srcs: [
         "AudioParameter.cpp",
+        "JAudioTrack.cpp",
         "MediaPlayer2Factory.cpp",
         "MediaPlayer2Manager.cpp",
         "TestPlayerStub.cpp",
@@ -314,6 +330,7 @@
     ],
 
     shared_libs: [
+        "libandroid_runtime",
         "libaudioclient",
         "libbinder",
         "libcutils",
diff --git a/media/libmedia/JAudioTrack.cpp b/media/libmedia/JAudioTrack.cpp
new file mode 100644
index 0000000..b228d8b
--- /dev/null
+++ b/media/libmedia/JAudioTrack.cpp
@@ -0,0 +1,520 @@
+/*
+ * 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_TAG "JAudioTrack"
+
+#include "media/JAudioAttributes.h"
+#include "media/JAudioFormat.h"
+#include "media/JAudioTrack.h"
+
+#include <android_media_AudioErrors.h>
+#include <android_runtime/AndroidRuntime.h>
+
+namespace android {
+
+// TODO: Store Java class/methodID as a member variable in the class.
+// TODO: Add NULL && Exception checks after every JNI call.
+JAudioTrack::JAudioTrack(                             // < Usages of the arguments are below >
+        audio_stream_type_t streamType,               // AudioAudioAttributes
+        uint32_t sampleRate,                          // AudioFormat && bufferSizeInBytes
+        audio_format_t format,                        // AudioFormat && bufferSizeInBytes
+        audio_channel_mask_t channelMask,             // AudioFormat && bufferSizeInBytes
+        size_t frameCount,                            // bufferSizeInBytes
+        audio_session_t sessionId,                    // AudioTrack
+        const audio_attributes_t* pAttributes,        // AudioAttributes
+        float maxRequiredSpeed) {                     // bufferSizeInBytes
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass jAudioTrackCls = env->FindClass("android/media/AudioTrack");
+    mAudioTrackCls = (jclass) env->NewGlobalRef(jAudioTrackCls);
+
+    maxRequiredSpeed = std::min(std::max(maxRequiredSpeed, 1.0f), AUDIO_TIMESTRETCH_SPEED_MAX);
+
+    int bufferSizeInBytes = 0;
+    if (sampleRate == 0 || frameCount > 0) {
+        // Manually calculate buffer size.
+        bufferSizeInBytes = audio_channel_count_from_out_mask(channelMask)
+                * audio_bytes_per_sample(format) * (frameCount > 0 ? frameCount : 1);
+    } else if (sampleRate > 0) {
+        // Call Java AudioTrack::getMinBufferSize().
+        jmethodID jGetMinBufferSize =
+                env->GetStaticMethodID(mAudioTrackCls, "getMinBufferSize", "(III)I");
+        bufferSizeInBytes = env->CallStaticIntMethod(mAudioTrackCls, jGetMinBufferSize,
+                sampleRate, outChannelMaskFromNative(channelMask), audioFormatFromNative(format));
+    }
+    bufferSizeInBytes = (int) (bufferSizeInBytes * maxRequiredSpeed);
+
+    // Create a Java AudioTrack object through its Builder.
+    jclass jBuilderCls = env->FindClass("android/media/AudioTrack$Builder");
+    jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
+    jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);
+
+    jmethodID jSetAudioAttributes = env->GetMethodID(jBuilderCls, "setAudioAttributes",
+            "(Landroid/media/AudioAttributes;)Landroid/media/AudioTrack$Builder;");
+    jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetAudioAttributes,
+            JAudioAttributes::createAudioAttributesObj(env, pAttributes, streamType));
+
+    jmethodID jSetAudioFormat = env->GetMethodID(jBuilderCls, "setAudioFormat",
+            "(Landroid/media/AudioFormat;)Landroid/media/AudioTrack$Builder;");
+    jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetAudioFormat,
+            JAudioFormat::createAudioFormatObj(env, sampleRate, format, channelMask));
+
+    jmethodID jSetBufferSizeInBytes = env->GetMethodID(jBuilderCls, "setBufferSizeInBytes",
+            "(I)Landroid/media/AudioTrack$Builder;");
+    jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetBufferSizeInBytes, bufferSizeInBytes);
+
+    // We only use streaming mode of Java AudioTrack.
+    jfieldID jModeStream = env->GetStaticFieldID(mAudioTrackCls, "MODE_STREAM", "I");
+    jint transferMode = env->GetStaticIntField(mAudioTrackCls, jModeStream);
+    jmethodID jSetTransferMode = env->GetMethodID(jBuilderCls, "setTransferMode",
+            "(I)Landroid/media/AudioTrack$Builder;");
+    jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetTransferMode,
+            transferMode /* Java AudioTrack::MODE_STREAM */);
+
+    if (sessionId != 0) {
+        jmethodID jSetSessionId = env->GetMethodID(jBuilderCls, "setSessionId",
+                "(I)Landroid/media/AudioTrack$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetSessionId, sessionId);
+    }
+
+    jmethodID jBuild = env->GetMethodID(jBuilderCls, "build", "()Landroid/media/AudioTrack;");
+    mAudioTrackObj = env->CallObjectMethod(jBuilderObj, jBuild);
+}
+
+JAudioTrack::~JAudioTrack() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->DeleteGlobalRef(mAudioTrackCls);
+}
+
+size_t JAudioTrack::frameCount() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetBufferSizeInFrames = env->GetMethodID(
+            mAudioTrackCls, "getBufferSizeInFrames", "()I");
+    return env->CallIntMethod(mAudioTrackObj, jGetBufferSizeInFrames);
+}
+
+size_t JAudioTrack::channelCount() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetChannelCount = env->GetMethodID(mAudioTrackCls, "getChannelCount", "()I");
+    return env->CallIntMethod(mAudioTrackObj, jGetChannelCount);
+}
+
+status_t JAudioTrack::getPosition(uint32_t *position) {
+    if (position == NULL) {
+        return BAD_VALUE;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetPlaybackHeadPosition = env->GetMethodID(
+            mAudioTrackCls, "getPlaybackHeadPosition", "()I");
+    *position = env->CallIntMethod(mAudioTrackObj, jGetPlaybackHeadPosition);
+
+    return NO_ERROR;
+}
+
+bool JAudioTrack::getTimeStamp(AudioTimestamp& timestamp) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    jclass jAudioTimeStampCls = env->FindClass("android/media/AudioTimestamp");
+    jobject jAudioTimeStampObj = env->AllocObject(jAudioTimeStampCls);
+
+    jfieldID jFramePosition = env->GetFieldID(jAudioTimeStampCls, "framePosition", "L");
+    jfieldID jNanoTime = env->GetFieldID(jAudioTimeStampCls, "nanoTime", "L");
+
+    jmethodID jGetTimestamp = env->GetMethodID(mAudioTrackCls,
+            "getTimestamp", "(Landroid/media/AudioTimestamp)B");
+    bool success = env->CallBooleanMethod(mAudioTrackObj, jGetTimestamp, jAudioTimeStampObj);
+
+    if (!success) {
+        return false;
+    }
+
+    long long framePosition = env->GetLongField(jAudioTimeStampObj, jFramePosition);
+    long long nanoTime = env->GetLongField(jAudioTimeStampObj, jNanoTime);
+
+    struct timespec ts;
+    const long long secondToNano = 1000000000LL; // 1E9
+    ts.tv_sec = nanoTime / secondToNano;
+    ts.tv_nsec = nanoTime % secondToNano;
+    timestamp.mTime = ts;
+    timestamp.mPosition = (uint32_t) framePosition;
+
+    return true;
+}
+
+status_t JAudioTrack::setPlaybackRate(const AudioPlaybackRate &playbackRate) {
+    // TODO: existing native AudioTrack returns INVALID_OPERATION on offload/direct/fast tracks.
+    // Should we do the same thing?
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    jclass jPlaybackParamsCls = env->FindClass("android/media/PlaybackParams");
+    jmethodID jPlaybackParamsCtor = env->GetMethodID(jPlaybackParamsCls, "<init>", "()V");
+    jobject jPlaybackParamsObj = env->NewObject(jPlaybackParamsCls, jPlaybackParamsCtor);
+
+    jmethodID jSetAudioFallbackMode = env->GetMethodID(
+            jPlaybackParamsCls, "setAudioFallbackMode", "(I)Landroid/media/PlaybackParams;");
+    jPlaybackParamsObj = env->CallObjectMethod(
+            jPlaybackParamsObj, jSetAudioFallbackMode, playbackRate.mFallbackMode);
+
+    jmethodID jSetAudioStretchMode = env->GetMethodID(
+                jPlaybackParamsCls, "setAudioStretchMode", "(I)Landroid/media/PlaybackParams;");
+    jPlaybackParamsObj = env->CallObjectMethod(
+            jPlaybackParamsObj, jSetAudioStretchMode, playbackRate.mStretchMode);
+
+    jmethodID jSetPitch = env->GetMethodID(
+            jPlaybackParamsCls, "setPitch", "(F)Landroid/media/PlaybackParams;");
+    jPlaybackParamsObj = env->CallObjectMethod(jPlaybackParamsObj, jSetPitch, playbackRate.mPitch);
+
+    jmethodID jSetSpeed = env->GetMethodID(
+            jPlaybackParamsCls, "setSpeed", "(F)Landroid/media/PlaybackParams;");
+    jPlaybackParamsObj = env->CallObjectMethod(jPlaybackParamsObj, jSetSpeed, playbackRate.mSpeed);
+
+
+    // Set this Java PlaybackParams object into Java AudioTrack.
+    jmethodID jSetPlaybackParams = env->GetMethodID(
+            mAudioTrackCls, "setPlaybackParams", "(Landroid/media/PlaybackParams;)V");
+    env->CallVoidMethod(mAudioTrackObj, jSetPlaybackParams, jPlaybackParamsObj);
+    // TODO: Should we catch the Java IllegalArgumentException?
+
+    return NO_ERROR;
+}
+
+const AudioPlaybackRate JAudioTrack::getPlaybackRate() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    jmethodID jGetPlaybackParams = env->GetMethodID(
+            mAudioTrackCls, "getPlaybackParams", "()Landroid/media/PlaybackParams;");
+    jobject jPlaybackParamsObj = env->CallObjectMethod(mAudioTrackObj, jGetPlaybackParams);
+
+    AudioPlaybackRate playbackRate;
+    jclass jPlaybackParamsCls = env->FindClass("android/media/PlaybackParams");
+
+    jmethodID jGetAudioFallbackMode = env->GetMethodID(
+            jPlaybackParamsCls, "getAudioFallbackMode", "()I");
+    // TODO: Should we enable passing AUDIO_TIMESTRETCH_FALLBACK_CUT_REPEAT?
+    //       The enum is internal only, so it is not defined in PlaybackParmas.java.
+    // TODO: Is this right way to convert an int to an enum?
+    playbackRate.mFallbackMode = static_cast<AudioTimestretchFallbackMode>(
+            env->CallIntMethod(jPlaybackParamsObj, jGetAudioFallbackMode));
+
+    jmethodID jGetAudioStretchMode = env->GetMethodID(
+            jPlaybackParamsCls, "getAudioStretchMode", "()I");
+    playbackRate.mStretchMode = static_cast<AudioTimestretchStretchMode>(
+            env->CallIntMethod(jPlaybackParamsObj, jGetAudioStretchMode));
+
+    jmethodID jGetPitch = env->GetMethodID(jPlaybackParamsCls, "getPitch", "()F");
+    playbackRate.mPitch = env->CallFloatMethod(jPlaybackParamsObj, jGetPitch);
+
+    jmethodID jGetSpeed = env->GetMethodID(jPlaybackParamsCls, "getSpeed", "()F");
+    playbackRate.mSpeed = env->CallFloatMethod(jPlaybackParamsObj, jGetSpeed);
+
+    return playbackRate;
+}
+
+media::VolumeShaper::Status JAudioTrack::applyVolumeShaper(
+        const sp<media::VolumeShaper::Configuration>& configuration,
+        const sp<media::VolumeShaper::Operation>& operation) {
+
+    jobject jConfigurationObj = createVolumeShaperConfigurationObj(configuration);
+    jobject jOperationObj = createVolumeShaperOperationObj(operation);
+
+    if (jConfigurationObj == NULL || jOperationObj == NULL) {
+        return media::VolumeShaper::Status(BAD_VALUE);
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    jmethodID jCreateVolumeShaper = env->GetMethodID(mAudioTrackCls, "createVolumeShaper",
+            "(Landroid/media/VolumeShaper$Configuration;)Landroid/media/VolumeShaper;");
+    jobject jVolumeShaperObj = env->CallObjectMethod(
+            mAudioTrackObj, jCreateVolumeShaper, jConfigurationObj);
+
+    jclass jVolumeShaperCls = env->FindClass("android/media/VolumeShaper");
+    jmethodID jApply = env->GetMethodID(jVolumeShaperCls, "apply",
+            "(Landroid/media/VolumeShaper$Operation;)V");
+    env->CallVoidMethod(jVolumeShaperObj, jApply, jOperationObj);
+
+    return media::VolumeShaper::Status(NO_ERROR);
+}
+
+status_t JAudioTrack::setAuxEffectSendLevel(float level) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jSetAuxEffectSendLevel = env->GetMethodID(
+            mAudioTrackCls, "setAuxEffectSendLevel", "(F)I");
+    int result = env->CallIntMethod(mAudioTrackObj, jSetAuxEffectSendLevel, level);
+    return javaToNativeStatus(result);
+}
+
+status_t JAudioTrack::attachAuxEffect(int effectId) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jAttachAuxEffect = env->GetMethodID(mAudioTrackCls, "attachAuxEffect", "(I)I");
+    int result = env->CallIntMethod(mAudioTrackObj, jAttachAuxEffect, effectId);
+    return javaToNativeStatus(result);
+}
+
+status_t JAudioTrack::setVolume(float left, float right) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    // TODO: Java setStereoVolume is deprecated. Do we really need this method?
+    jmethodID jSetStereoVolume = env->GetMethodID(mAudioTrackCls, "setStereoVolume", "(FF)I");
+    int result = env->CallIntMethod(mAudioTrackObj, jSetStereoVolume, left, right);
+    return javaToNativeStatus(result);
+}
+
+status_t JAudioTrack::setVolume(float volume) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jSetVolume = env->GetMethodID(mAudioTrackCls, "setVolume", "(F)I");
+    int result = env->CallIntMethod(mAudioTrackObj, jSetVolume, volume);
+    return javaToNativeStatus(result);
+}
+
+status_t JAudioTrack::start() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jPlay = env->GetMethodID(mAudioTrackCls, "play", "()V");
+    // TODO: Should we catch the Java IllegalStateException from play()?
+    env->CallVoidMethod(mAudioTrackObj, jPlay);
+    return NO_ERROR;
+}
+
+ssize_t JAudioTrack::write(const void* buffer, size_t size, bool blocking) {
+    if (buffer == NULL) {
+        return BAD_VALUE;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jbyteArray jAudioData = env->NewByteArray(size);
+    env->SetByteArrayRegion(jAudioData, 0, size, (jbyte *) buffer);
+
+    jclass jByteBufferCls = env->FindClass("java/nio/ByteBuffer");
+    jmethodID jWrap = env->GetStaticMethodID(jByteBufferCls, "wrap", "([B)Ljava/nio/ByteBuffer;");
+    jobject jByteBufferObj = env->CallStaticObjectMethod(jByteBufferCls, jWrap, jAudioData);
+
+    int writeMode = 0;
+    if (blocking) {
+        jfieldID jWriteBlocking = env->GetStaticFieldID(mAudioTrackCls, "WRITE_BLOCKING", "I");
+        writeMode = env->GetStaticIntField(mAudioTrackCls, jWriteBlocking);
+    } else {
+        jfieldID jWriteNonBlocking = env->GetStaticFieldID(
+                mAudioTrackCls, "WRITE_NON_BLOCKING", "I");
+        writeMode = env->GetStaticIntField(mAudioTrackCls, jWriteNonBlocking);
+    }
+
+    jmethodID jWrite = env->GetMethodID(mAudioTrackCls, "write", "(Ljava/nio/ByteBuffer;II)I");
+    int result = env->CallIntMethod(mAudioTrackObj, jWrite, jByteBufferObj, size, writeMode);
+
+    if (result >= 0) {
+        return result;
+    } else {
+        return javaToNativeStatus(result);
+    }
+}
+
+void JAudioTrack::stop() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jStop = env->GetMethodID(mAudioTrackCls, "stop", "()V");
+    env->CallVoidMethod(mAudioTrackObj, jStop);
+    // TODO: Should we catch IllegalStateException?
+}
+
+// TODO: Is the right implementation?
+bool JAudioTrack::stopped() const {
+    return !isPlaying();
+}
+
+void JAudioTrack::flush() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jFlush = env->GetMethodID(mAudioTrackCls, "flush", "()V");
+    env->CallVoidMethod(mAudioTrackObj, jFlush);
+}
+
+void JAudioTrack::pause() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jPause = env->GetMethodID(mAudioTrackCls, "pause", "()V");
+    env->CallVoidMethod(mAudioTrackObj, jPause);
+    // TODO: Should we catch IllegalStateException?
+}
+
+bool JAudioTrack::isPlaying() const {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetPlayState = env->GetMethodID(mAudioTrackCls, "getPlayState", "()I");
+    int currentPlayState = env->CallIntMethod(mAudioTrackObj, jGetPlayState);
+
+    // TODO: In Java AudioTrack, there is no STOPPING state.
+    // This means while stopping, isPlaying() will return different value in two class.
+    //  - in existing native AudioTrack: true
+    //  - in JAudioTrack: false
+    // If not okay, also modify the implementation of stopped().
+    jfieldID jPlayStatePlaying = env->GetStaticFieldID(mAudioTrackCls, "PLAYSTATE_PLAYING", "I");
+    int statePlaying = env->GetStaticIntField(mAudioTrackCls, jPlayStatePlaying);
+    return currentPlayState == statePlaying;
+}
+
+uint32_t JAudioTrack::getSampleRate() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetSampleRate = env->GetMethodID(mAudioTrackCls, "getSampleRate", "()I");
+    return env->CallIntMethod(mAudioTrackObj, jGetSampleRate);
+}
+
+status_t JAudioTrack::getBufferDurationInUs(int64_t *duration) {
+    if (duration == nullptr) {
+        return BAD_VALUE;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetBufferSizeInFrames = env->GetMethodID(
+            mAudioTrackCls, "getBufferSizeInFrames", "()I");
+    int bufferSizeInFrames = env->CallIntMethod(mAudioTrackObj, jGetBufferSizeInFrames);
+
+    const double secondToMicro = 1000000LL; // 1E6
+    int sampleRate = JAudioTrack::getSampleRate();
+    float speed = JAudioTrack::getPlaybackRate().mSpeed;
+
+    *duration = (int64_t) (bufferSizeInFrames * secondToMicro / (sampleRate * speed));
+    return NO_ERROR;
+}
+
+audio_format_t JAudioTrack::format() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetAudioFormat = env->GetMethodID(mAudioTrackCls, "getAudioFormat", "()I");
+    int javaFormat = env->CallIntMethod(mAudioTrackObj, jGetAudioFormat);
+    return audioFormatToNative(javaFormat);
+}
+
+jobject JAudioTrack::createVolumeShaperConfigurationObj(
+        const sp<media::VolumeShaper::Configuration>& config) {
+
+    // TODO: Java VolumeShaper's setId() / setOptionFlags() are hidden.
+    if (config == NULL || config->getType() == media::VolumeShaper::Configuration::TYPE_ID) {
+        return NULL;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    // Referenced "android_media_VolumeShaper.h".
+    jfloatArray xarray = nullptr;
+    jfloatArray yarray = nullptr;
+    if (config->getType() == media::VolumeShaper::Configuration::TYPE_SCALE) {
+        // convert curve arrays
+        xarray = env->NewFloatArray(config->size());
+        yarray = env->NewFloatArray(config->size());
+        float * const x = env->GetFloatArrayElements(xarray, nullptr /* isCopy */);
+        float * const y = env->GetFloatArrayElements(yarray, nullptr /* isCopy */);
+        float *xptr = x, *yptr = y;
+        for (const auto &pt : *config.get()) {
+            *xptr++ = pt.first;
+            *yptr++ = pt.second;
+        }
+        env->ReleaseFloatArrayElements(xarray, x, 0 /* mode */);
+        env->ReleaseFloatArrayElements(yarray, y, 0 /* mode */);
+    }
+
+    jclass jBuilderCls = env->FindClass("android/media/VolumeShaper$Configuration$Builder");
+    jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
+    jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);
+
+    jmethodID jSetDuration = env->GetMethodID(jBuilderCls, "setDuration",
+            "(L)Landroid/media/VolumeShaper$Configuration$Builder;");
+    jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetDuration, (jlong) config->getDurationMs());
+
+    jmethodID jSetInterpolatorType = env->GetMethodID(jBuilderCls, "setInterpolatorType",
+            "(I)Landroid/media/VolumeShaper$Configuration$Builder;");
+    jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetInterpolatorType,
+            config->getInterpolatorType());
+
+    jmethodID jSetCurve = env->GetMethodID(jBuilderCls, "setCurve",
+            "([F[F)Landroid/media/VolumeShaper$Configuration$Builder;");
+    jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetCurve, xarray, yarray);
+
+    jmethodID jBuild = env->GetMethodID(jBuilderCls, "build",
+            "()Landroid/media/VolumeShaper$Configuration;");
+    return env->CallObjectMethod(jBuilderObj, jBuild);
+}
+
+jobject JAudioTrack::createVolumeShaperOperationObj(
+        const sp<media::VolumeShaper::Operation>& operation) {
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    jclass jBuilderCls = env->FindClass("android/media/VolumeShaper$Operation$Builder");
+    jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
+    jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);
+
+    // Set XOffset
+    jmethodID jSetXOffset = env->GetMethodID(jBuilderCls, "setXOffset",
+            "(F)Landroid/media/VolumeShaper$Operation$Builder;");
+    jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetXOffset, operation->getXOffset());
+
+    int32_t flags = operation->getFlags();
+
+    if (operation->getReplaceId() >= 0) {
+        jmethodID jReplace = env->GetMethodID(jBuilderCls, "replace",
+                "(IB)Landroid/media/VolumeShaper$Operation$Builder;");
+        bool join = (flags | media::VolumeShaper::Operation::FLAG_JOIN) != 0;
+        jBuilderObj = env->CallObjectMethod(jBuilderCls, jReplace, operation->getReplaceId(), join);
+    }
+
+    if (flags | media::VolumeShaper::Operation::FLAG_REVERSE) {
+        jmethodID jReverse = env->GetMethodID(jBuilderCls, "reverse",
+                "()Landroid/media/VolumeShaper$Operation$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderCls, jReverse);
+    }
+
+    // TODO: VolumeShaper Javadoc says "Do not call terminate() directly". Can we call this?
+    if (flags | media::VolumeShaper::Operation::FLAG_TERMINATE) {
+        jmethodID jTerminate = env->GetMethodID(jBuilderCls, "terminate",
+                "()Landroid/media/VolumeShaper$Operation$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderCls, jTerminate);
+    }
+
+    if (flags | media::VolumeShaper::Operation::FLAG_DELAY) {
+        jmethodID jDefer = env->GetMethodID(jBuilderCls, "defer",
+                "()Landroid/media/VolumeShaper$Operation$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderCls, jDefer);
+    }
+
+    if (flags | media::VolumeShaper::Operation::FLAG_CREATE_IF_NECESSARY) {
+        jmethodID jCreateIfNeeded = env->GetMethodID(jBuilderCls, "createIfNeeded",
+                "()Landroid/media/VolumeShaper$Operation$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderCls, jCreateIfNeeded);
+    }
+
+    // TODO: Handle error case (can it be NULL?)
+    jmethodID jBuild = env->GetMethodID(jBuilderCls, "build",
+            "()Landroid/media/VolumeShaper$Operation;");
+    return env->CallObjectMethod(jBuilderObj, jBuild);
+}
+
+status_t JAudioTrack::javaToNativeStatus(int javaStatus) {
+    switch (javaStatus) {
+    case AUDIO_JAVA_SUCCESS:
+        return NO_ERROR;
+    case AUDIO_JAVA_BAD_VALUE:
+        return BAD_VALUE;
+    case AUDIO_JAVA_INVALID_OPERATION:
+        return INVALID_OPERATION;
+    case AUDIO_JAVA_PERMISSION_DENIED:
+        return PERMISSION_DENIED;
+    case AUDIO_JAVA_NO_INIT:
+        return NO_INIT;
+    case AUDIO_JAVA_WOULD_BLOCK:
+        return WOULD_BLOCK;
+    case AUDIO_JAVA_DEAD_OBJECT:
+        return DEAD_OBJECT;
+    default:
+        return UNKNOWN_ERROR;
+    }
+}
+
+} // namespace android
diff --git a/media/libmedia/MediaPlayer2Manager.cpp b/media/libmedia/MediaPlayer2Manager.cpp
index c6ad99e..720c1e3 100644
--- a/media/libmedia/MediaPlayer2Manager.cpp
+++ b/media/libmedia/MediaPlayer2Manager.cpp
@@ -726,15 +726,14 @@
 }
 
 status_t MediaPlayer2Manager::Client::setDataSource(
-        const sp<IDataSource> &source) {
-    sp<DataSource> dataSource = CreateDataSourceFromIDataSource(source);
-    player2_type playerType = MediaPlayer2Factory::getPlayerType(this, dataSource);
+        const sp<DataSource> &source) {
+    player2_type playerType = MediaPlayer2Factory::getPlayerType(this, source);
     sp<MediaPlayer2Base> p = setDataSource_pre(playerType);
     if (p == NULL) {
         return NO_INIT;
     }
     // now set data source
-    return mStatus = setDataSource_post(p, p->setDataSource(dataSource));
+    return mStatus = setDataSource_post(p, p->setDataSource(source));
 }
 
 void MediaPlayer2Manager::Client::disconnectNativeWindow_l() {
diff --git a/media/libmedia/MediaPlayer2Manager.h b/media/libmedia/MediaPlayer2Manager.h
index d3ee044..b42cbbb 100644
--- a/media/libmedia/MediaPlayer2Manager.h
+++ b/media/libmedia/MediaPlayer2Manager.h
@@ -39,7 +39,7 @@
 struct AudioPlaybackRate;
 class AudioTrack;
 struct AVSyncSettings;
-class IDataSource;
+class DataSource;
 struct MediaHTTPService;
 class MediaPlayer2EngineClient;
 
@@ -297,7 +297,7 @@
         virtual status_t        setDataSource(int fd, int64_t offset, int64_t length);
 
         virtual status_t        setDataSource(const sp<IStreamSource> &source);
-        virtual status_t        setDataSource(const sp<IDataSource> &source);
+        virtual status_t        setDataSource(const sp<DataSource> &source);
 
 
         sp<MediaPlayer2Base>    setDataSource_pre(player2_type playerType);
diff --git a/media/libmedia/TypeConverter.cpp b/media/libmedia/TypeConverter.cpp
index e6c8f9c..9b06047 100644
--- a/media/libmedia/TypeConverter.cpp
+++ b/media/libmedia/TypeConverter.cpp
@@ -277,6 +277,16 @@
     TERMINATOR
 };
 
+template<>
+const AudioContentTypeConverter::Table AudioContentTypeConverter::mTable[] = {
+    MAKE_STRING_FROM_ENUM(AUDIO_CONTENT_TYPE_UNKNOWN),
+    MAKE_STRING_FROM_ENUM(AUDIO_CONTENT_TYPE_SPEECH),
+    MAKE_STRING_FROM_ENUM(AUDIO_CONTENT_TYPE_MUSIC),
+    MAKE_STRING_FROM_ENUM(AUDIO_CONTENT_TYPE_MOVIE),
+    MAKE_STRING_FROM_ENUM(AUDIO_CONTENT_TYPE_SONIFICATION),
+    TERMINATOR
+};
+
 template <>
 const UsageTypeConverter::Table UsageTypeConverter::mTable[] = {
     MAKE_STRING_FROM_ENUM(AUDIO_USAGE_UNKNOWN),
diff --git a/media/libmedia/aidl/android/media/IMediaExtractorUpdateService.aidl b/media/libmedia/aidl/android/media/IMediaExtractorUpdateService.aidl
new file mode 100644
index 0000000..57b1bc9
--- /dev/null
+++ b/media/libmedia/aidl/android/media/IMediaExtractorUpdateService.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * Service to reload extractor plugins when update package is installed/uninstalled.
+ * @hide
+ */
+interface IMediaExtractorUpdateService {
+    void loadPlugins(@utf8InCpp String apkPath);
+}
diff --git a/media/libmedia/exports.lds b/media/libmedia/exports.lds
new file mode 100644
index 0000000..b09fbce
--- /dev/null
+++ b/media/libmedia/exports.lds
@@ -0,0 +1,7 @@
+{
+    global:
+        *;
+    local:
+        _ZN7android13MidiIoWrapper*;
+        _ZTVN7android13MidiIoWrapperE;
+};
diff --git a/media/libmedia/include/media/JAudioAttributes.h b/media/libmedia/include/media/JAudioAttributes.h
new file mode 100644
index 0000000..fb11435
--- /dev/null
+++ b/media/libmedia/include/media/JAudioAttributes.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_JAUDIOATTRIBUTES_H
+#define ANDROID_JAUDIOATTRIBUTES_H
+
+#include <jni.h>
+#include <system/audio.h>
+
+namespace android {
+
+class JAudioAttributes {
+public:
+    /* Creates a Java AudioAttributes object. */
+    static jobject createAudioAttributesObj(JNIEnv *env,
+                                            const audio_attributes_t* pAttributes,
+                                            audio_stream_type_t streamType) {
+
+        jclass jBuilderCls = env->FindClass("android/media/AudioAttributes$Builder");
+        jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
+        jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);
+
+        if (pAttributes != NULL) {
+            // If pAttributes is not null, streamType is ignored.
+            jmethodID jSetUsage = env->GetMethodID(
+                    jBuilderCls, "setUsage", "(I)Landroid/media/AudioAttributes$Builder;");
+            jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetUsage, pAttributes->usage);
+
+            jmethodID jSetContentType = env->GetMethodID(jBuilderCls, "setContentType",
+                    "(I)Landroid/media/AudioAttributes$Builder;");
+            jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetContentType,
+                    pAttributes->content_type);
+
+            // TODO: Java AudioAttributes.Builder.setCapturePreset() is systemApi and hidden.
+            // Can we use this method?
+//            jmethodID jSetCapturePreset = env->GetMethodID(jBuilderCls, "setCapturePreset",
+//                    "(I)Landroid/media/AudioAttributes$Builder;");
+//            jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetCapturePreset,
+//                    pAttributes->source);
+
+            jmethodID jSetFlags = env->GetMethodID(jBuilderCls, "setFlags",
+                    "(I)Landroid/media/AudioAttributes$Builder;");
+            jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetFlags, pAttributes->flags);
+
+            // TODO: Handle the 'tags' (char[] to HashSet<String>).
+            // How to parse the char[]? Is there any example of it?
+            // Also, the addTags() method is hidden.
+        } else {
+            // Call AudioAttributes.Builder.setLegacyStreamType().build()
+            jmethodID jSetLegacyStreamType = env->GetMethodID(jBuilderCls, "setLegacyStreamType",
+                    "(I)Landroid/media/AudioAttributes$Builder;");
+            jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetLegacyStreamType, streamType);
+        }
+
+        jmethodID jBuild = env->GetMethodID(jBuilderCls, "build",
+                "()Landroid/media/AudioAttributes;");
+        return env->CallObjectMethod(jBuilderObj, jBuild);
+    }
+
+};
+
+} // namespace android
+
+#endif // ANDROID_JAUDIOATTRIBUTES_H
diff --git a/media/libmedia/include/media/JAudioFormat.h b/media/libmedia/include/media/JAudioFormat.h
new file mode 100644
index 0000000..00abdff
--- /dev/null
+++ b/media/libmedia/include/media/JAudioFormat.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_JAUDIOFORMAT_H
+#define ANDROID_JAUDIOFORMAT_H
+
+#include <android_media_AudioFormat.h>
+#include <jni.h>
+
+namespace android {
+
+class JAudioFormat {
+public:
+    /* Creates a Java AudioFormat object. */
+    static jobject createAudioFormatObj(JNIEnv *env,
+                                        uint32_t sampleRate,
+                                        audio_format_t format,
+                                        audio_channel_mask_t channelMask) {
+
+        jclass jBuilderCls = env->FindClass("android/media/AudioFormat$Builder");
+        jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
+        jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);
+
+        if (sampleRate == 0) {
+            jclass jAudioFormatCls = env->FindClass("android/media/AudioFormat");
+            jfieldID jSampleRateUnspecified =
+                    env->GetStaticFieldID(jAudioFormatCls, "SAMPLE_RATE_UNSPECIFIED", "I");
+            sampleRate = env->GetStaticIntField(jAudioFormatCls, jSampleRateUnspecified);
+        }
+
+        jmethodID jSetEncoding = env->GetMethodID(jBuilderCls, "setEncoding",
+                "(I)Landroid/media/AudioFormat$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetEncoding,
+                audioFormatFromNative(format));
+
+        jmethodID jSetSampleRate = env->GetMethodID(jBuilderCls, "setSampleRate",
+                "(I)Landroid/media/AudioFormat$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetSampleRate, sampleRate);
+
+        jmethodID jSetChannelMask = env->GetMethodID(jBuilderCls, "setChannelMask",
+                "(I)Landroid/media/AudioFormat$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetChannelMask,
+                outChannelMaskFromNative(channelMask));
+
+        jmethodID jBuild = env->GetMethodID(jBuilderCls, "build", "()Landroid/media/AudioFormat;");
+        return env->CallObjectMethod(jBuilderObj, jBuild);
+    }
+
+};
+
+} // namespace android
+
+#endif // ANDROID_JAUDIOFORMAT_H
diff --git a/media/libmedia/include/media/JAudioTrack.h b/media/libmedia/include/media/JAudioTrack.h
new file mode 100644
index 0000000..8af30b7
--- /dev/null
+++ b/media/libmedia/include/media/JAudioTrack.h
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_JAUDIOTRACK_H
+#define ANDROID_JAUDIOTRACK_H
+
+#include <jni.h>
+#include <media/AudioResamplerPublic.h>
+#include <media/VolumeShaper.h>
+#include <system/audio.h>
+#include <utils/Errors.h>
+
+#include <media/AudioTimestamp.h>   // It has dependency on audio.h/Errors.h, but doesn't
+                                    // include them in it. Therefore it is included here at last.
+
+namespace android {
+
+class JAudioTrack {
+public:
+
+    /* Creates an JAudioTrack object for non-offload mode.
+     * Once created, the track needs to be started before it can be used.
+     * Unspecified values are set to appropriate default values.
+     *
+     * Parameters:
+     *
+     * streamType:         Select the type of audio stream this track is attached to
+     *                     (e.g. AUDIO_STREAM_MUSIC).
+     * sampleRate:         Data source sampling rate in Hz.  Zero means to use the sink sample rate.
+     *                     A non-zero value must be specified if AUDIO_OUTPUT_FLAG_DIRECT is set.
+     *                     0 will not work with current policy implementation for direct output
+     *                     selection where an exact match is needed for sampling rate.
+     *                     (TODO: Check direct output after flags can be used in Java AudioTrack.)
+     * format:             Audio format. For mixed tracks, any PCM format supported by server is OK.
+     *                     For direct and offloaded tracks, the possible format(s) depends on the
+     *                     output sink.
+     *                     (TODO: How can we check whether a format is supported?)
+     * channelMask:        Channel mask, such that audio_is_output_channel(channelMask) is true.
+     * frameCount:         Minimum size of track PCM buffer in frames. This defines the
+     *                     application's contribution to the latency of the track.
+     *                     The actual size selected by the JAudioTrack could be larger if the
+     *                     requested size is not compatible with current audio HAL configuration.
+     *                     Zero means to use a default value.
+     * sessionId:          Specific session ID, or zero to use default.
+     * pAttributes:        If not NULL, supersedes streamType for use case selection.
+     * maxRequiredSpeed:   For PCM tracks, this creates an appropriate buffer size that will allow
+     *                     maxRequiredSpeed playback. Values less than 1.0f and greater than
+     *                     AUDIO_TIMESTRETCH_SPEED_MAX will be clamped.  For non-PCM tracks
+     *                     and direct or offloaded tracks, this parameter is ignored.
+     *                     (TODO: Handle this after offload / direct track is supported.)
+     *
+     * TODO: Revive removed arguments after offload mode is supported.
+     */
+    JAudioTrack(audio_stream_type_t streamType,
+                uint32_t sampleRate,
+                audio_format_t format,
+                audio_channel_mask_t channelMask,
+                size_t frameCount = 0,
+                audio_session_t sessionId  = AUDIO_SESSION_ALLOCATE,
+                const audio_attributes_t* pAttributes = NULL,
+                float maxRequiredSpeed = 1.0f);
+
+    /*
+       Temporarily removed constructor arguments:
+
+       // Q. Values are in audio-base.h, but where can we find explanation for them?
+       audio_output_flags_t flags,
+
+       // Q. May be used in AudioTrack.setPreferredDevice(AudioDeviceInfo)?
+       audio_port_handle_t selectedDeviceId,
+
+       // Should be deleted, since we don't use Binder anymore.
+       bool doNotReconnect,
+
+       // Do we need UID and PID?
+       uid_t uid,
+       pid_t pid,
+
+       // TODO: Uses these values when Java AudioTrack supports the offload mode.
+       callback_t cbf,
+       void* user,
+       int32_t notificationFrames,
+       const audio_offload_info_t *offloadInfo,
+
+       // Fixed to false, but what is this?
+       threadCanCallJava
+    */
+
+    virtual ~JAudioTrack();
+
+    size_t frameCount();
+    size_t channelCount();
+
+    /* Return the total number of frames played since playback start.
+     * The counter will wrap (overflow) periodically, e.g. every ~27 hours at 44.1 kHz.
+     * It is reset to zero by flush(), reload(), and stop().
+     *
+     * Parameters:
+     *
+     * position: Address where to return play head position.
+     *
+     * Returned status (from utils/Errors.h) can be:
+     *  - NO_ERROR: successful operation
+     *  - BAD_VALUE: position is NULL
+     */
+    status_t getPosition(uint32_t *position);
+
+    // TODO: Does this comment apply same to Java AudioTrack::getTimestamp?
+    // Changed the return type from status_t to bool, since Java AudioTrack::getTimestamp returns
+    // boolean. Will Java getTimestampWithStatus() be public?
+    /* Poll for a timestamp on demand.
+     * Use if EVENT_NEW_TIMESTAMP is not delivered often enough for your needs,
+     * or if you need to get the most recent timestamp outside of the event callback handler.
+     * Caution: calling this method too often may be inefficient;
+     * if you need a high resolution mapping between frame position and presentation time,
+     * consider implementing that at application level, based on the low resolution timestamps.
+     * Returns true if timestamp is valid.
+     * The timestamp parameter is undefined on return, if false is returned.
+     */
+    bool getTimeStamp(AudioTimestamp& timestamp);
+
+    /* Set source playback rate for timestretch
+     * 1.0 is normal speed: < 1.0 is slower, > 1.0 is faster
+     * 1.0 is normal pitch: < 1.0 is lower pitch, > 1.0 is higher pitch
+     *
+     * AUDIO_TIMESTRETCH_SPEED_MIN <= speed <= AUDIO_TIMESTRETCH_SPEED_MAX
+     * AUDIO_TIMESTRETCH_PITCH_MIN <= pitch <= AUDIO_TIMESTRETCH_PITCH_MAX
+     *
+     * Speed increases the playback rate of media, but does not alter pitch.
+     * Pitch increases the "tonal frequency" of media, but does not affect the playback rate.
+     */
+    status_t setPlaybackRate(const AudioPlaybackRate &playbackRate);
+
+    /* Return current playback rate */
+    const AudioPlaybackRate getPlaybackRate();
+
+    /* Sets the volume shaper object */
+    media::VolumeShaper::Status applyVolumeShaper(
+            const sp<media::VolumeShaper::Configuration>& configuration,
+            const sp<media::VolumeShaper::Operation>& operation);
+
+    /* Set the send level for this track. An auxiliary effect should be attached
+     * to the track with attachEffect(). Level must be >= 0.0 and <= 1.0.
+     */
+    status_t setAuxEffectSendLevel(float level);
+
+    /* Attach track auxiliary output to specified effect. Use effectId = 0
+     * to detach track from effect.
+     *
+     * Parameters:
+     *
+     * effectId: effectId obtained from AudioEffect::id().
+     *
+     * Returned status (from utils/Errors.h) can be:
+     *  - NO_ERROR: successful operation
+     *  - INVALID_OPERATION: The effect is not an auxiliary effect.
+     *  - BAD_VALUE: The specified effect ID is invalid.
+     */
+    status_t attachAuxEffect(int effectId);
+
+    /* Set volume for this track, mostly used for games' sound effects
+     * left and right volumes. Levels must be >= 0.0 and <= 1.0.
+     * This is the older API.  New applications should use setVolume(float) when possible.
+     */
+    status_t setVolume(float left, float right);
+
+    /* Set volume for all channels. This is the preferred API for new applications,
+     * especially for multi-channel content.
+     */
+    status_t setVolume(float volume);
+
+    // TODO: Does this comment equally apply to the Java AudioTrack::play()?
+    /* After it's created the track is not active. Call start() to
+     * make it active. If set, the callback will start being called.
+     * If the track was previously paused, volume is ramped up over the first mix buffer.
+     */
+    status_t start();
+
+    // TODO: Does this comment still applies? It seems not. (obtainBuffer, AudioFlinger, ...)
+    /* As a convenience we provide a write() interface to the audio buffer.
+     * Input parameter 'size' is in byte units.
+     * This is implemented on top of obtainBuffer/releaseBuffer. For best
+     * performance use callbacks. Returns actual number of bytes written >= 0,
+     * or one of the following negative status codes:
+     *      INVALID_OPERATION   AudioTrack is configured for static buffer or streaming mode
+     *      BAD_VALUE           size is invalid
+     *      WOULD_BLOCK         when obtainBuffer() returns same, or
+     *                          AudioTrack was stopped during the write
+     *      DEAD_OBJECT         when AudioFlinger dies or the output device changes and
+     *                          the track cannot be automatically restored.
+     *                          The application needs to recreate the AudioTrack
+     *                          because the audio device changed or AudioFlinger died.
+     *                          This typically occurs for direct or offload tracks
+     *                          or if mDoNotReconnect is true.
+     *      or any other error code returned by IAudioTrack::start() or restoreTrack_l().
+     * Default behavior is to only return when all data has been transferred. Set 'blocking' to
+     * false for the method to return immediately without waiting to try multiple times to write
+     * the full content of the buffer.
+     */
+    ssize_t write(const void* buffer, size_t size, bool blocking = true);
+
+    // TODO: Does this comment equally apply to the Java AudioTrack::stop()?
+    /* Stop a track.
+     * In static buffer mode, the track is stopped immediately.
+     * In streaming mode, the callback will cease being called.  Note that obtainBuffer() still
+     * works and will fill up buffers until the pool is exhausted, and then will return WOULD_BLOCK.
+     * In streaming mode the stop does not occur immediately: any data remaining in the buffer
+     * is first drained, mixed, and output, and only then is the track marked as stopped.
+     */
+    void stop();
+    bool stopped() const;
+
+    // TODO: Does this comment equally apply to the Java AudioTrack::flush()?
+    /* Flush a stopped or paused track. All previously buffered data is discarded immediately.
+     * This has the effect of draining the buffers without mixing or output.
+     * Flush is intended for streaming mode, for example before switching to non-contiguous content.
+     * This function is a no-op if the track is not stopped or paused, or uses a static buffer.
+     */
+    void flush();
+
+    // TODO: Does this comment equally apply to the Java AudioTrack::pause()?
+    // At least we are not using obtainBuffer.
+    /* Pause a track. After pause, the callback will cease being called and
+     * obtainBuffer returns WOULD_BLOCK. Note that obtainBuffer() still works
+     * and will fill up buffers until the pool is exhausted.
+     * Volume is ramped down over the next mix buffer following the pause request,
+     * and then the track is marked as paused. It can be resumed with ramp up by start().
+     */
+    void pause();
+
+    bool isPlaying() const;
+
+    /* Return current source sample rate in Hz.
+     * If specified as zero in constructor, this will be the sink sample rate.
+     */
+    uint32_t getSampleRate();
+
+    /* Returns the buffer duration in microseconds at current playback rate. */
+    status_t getBufferDurationInUs(int64_t *duration);
+
+    audio_format_t format();
+
+private:
+    jclass mAudioTrackCls;
+    jobject mAudioTrackObj;
+
+    /* Creates a Java VolumeShaper.Configuration object from VolumeShaper::Configuration */
+    jobject createVolumeShaperConfigurationObj(
+            const sp<media::VolumeShaper::Configuration>& config);
+
+    /* Creates a Java VolumeShaper.Operation object from VolumeShaper::Operation */
+    jobject createVolumeShaperOperationObj(
+            const sp<media::VolumeShaper::Operation>& operation);
+
+    status_t javaToNativeStatus(int javaStatus);
+};
+
+}; // namespace android
+
+#endif // ANDROID_JAUDIOTRACK_H
diff --git a/media/libmedia/include/media/MediaPlayer2Engine.h b/media/libmedia/include/media/MediaPlayer2Engine.h
index 0b43923..00f5fb1 100644
--- a/media/libmedia/include/media/MediaPlayer2Engine.h
+++ b/media/libmedia/include/media/MediaPlayer2Engine.h
@@ -32,13 +32,13 @@
 namespace android {
 
 struct ANativeWindowWrapper;
-class Parcel;
-class IDataSource;
+struct AVSyncSettings;
+struct AudioPlaybackRate;
+struct BufferingSettings;
+class DataSource;
 struct IStreamSource;
 struct MediaHTTPService;
-struct AudioPlaybackRate;
-struct AVSyncSettings;
-struct BufferingSettings;
+class Parcel;
 
 typedef MediaSource::ReadOptions::SeekMode MediaPlayer2SeekMode;
 
@@ -54,7 +54,7 @@
 
     virtual status_t        setDataSource(int fd, int64_t offset, int64_t length) = 0;
     virtual status_t        setDataSource(const sp<IStreamSource>& source) = 0;
-    virtual status_t        setDataSource(const sp<IDataSource>& source) = 0;
+    virtual status_t        setDataSource(const sp<DataSource>& source) = 0;
     virtual status_t        setVideoSurfaceTexture(const sp<ANativeWindowWrapper>& nww) = 0;
     virtual status_t        getBufferingSettings(
                                     BufferingSettings* buffering /* nonnull */) = 0;
diff --git a/media/libmedia/include/media/TypeConverter.h b/media/libmedia/include/media/TypeConverter.h
index 84e22b1..86f0d4c 100644
--- a/media/libmedia/include/media/TypeConverter.h
+++ b/media/libmedia/include/media/TypeConverter.h
@@ -80,6 +80,11 @@
     typedef audio_mode_t Type;
     typedef Vector<Type> Collection;
 };
+struct AudioContentTraits
+{
+    typedef audio_content_type_t Type;
+    typedef Vector<Type> Collection;
+};
 struct UsageTraits
 {
     typedef audio_usage_t Type;
@@ -226,6 +231,7 @@
 typedef TypeConverter<GainModeTraits> GainModeConverter;
 typedef TypeConverter<StreamTraits> StreamTypeConverter;
 typedef TypeConverter<AudioModeTraits> AudioModeConverter;
+typedef TypeConverter<AudioContentTraits> AudioContentTypeConverter;
 typedef TypeConverter<UsageTraits> UsageTypeConverter;
 typedef TypeConverter<SourceTraits> SourceTypeConverter;
 
@@ -240,6 +246,7 @@
 template<> const GainModeConverter::Table GainModeConverter::mTable[];
 template<> const StreamTypeConverter::Table StreamTypeConverter::mTable[];
 template<> const AudioModeConverter::Table AudioModeConverter::mTable[];
+template<> const AudioContentTypeConverter::Table AudioContentTypeConverter::mTable[];
 template<> const UsageTypeConverter::Table UsageTypeConverter::mTable[];
 template<> const SourceTypeConverter::Table SourceTypeConverter::mTable[];
 
diff --git a/media/libmedia/include/media/mediaplayer2.h b/media/libmedia/include/media/mediaplayer2.h
index 8327043..c96765f 100644
--- a/media/libmedia/include/media/mediaplayer2.h
+++ b/media/libmedia/include/media/mediaplayer2.h
@@ -35,6 +35,8 @@
 
 struct AVSyncSettings;
 struct ANativeWindowWrapper;
+class DataSource;
+struct MediaHTTPService;
 
 enum media2_event_type {
     MEDIA2_NOP               = 0, // interface test message
@@ -193,8 +195,6 @@
     virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) = 0;
 };
 
-struct MediaHTTPService;
-
 class MediaPlayer2 : public MediaPlayer2EngineClient
 {
 public:
@@ -208,7 +208,7 @@
                     const KeyedVector<String8, String8> *headers);
 
             status_t        setDataSource(int fd, int64_t offset, int64_t length);
-            status_t        setDataSource(const sp<IDataSource> &source);
+            status_t        setDataSource(const sp<DataSource> &source);
             status_t        setVideoSurfaceTexture(const sp<ANativeWindowWrapper>& nww);
             status_t        setListener(const sp<MediaPlayer2Listener>& listener);
             status_t        getBufferingSettings(BufferingSettings* buffering /* nonnull */);
diff --git a/media/libmedia/mediaplayer2.cpp b/media/libmedia/mediaplayer2.cpp
index 5a52abf..5c34d4a 100644
--- a/media/libmedia/mediaplayer2.cpp
+++ b/media/libmedia/mediaplayer2.cpp
@@ -33,7 +33,7 @@
 #include <media/AudioResamplerPublic.h>
 #include <media/AudioSystem.h>
 #include <media/AVSyncSettings.h>
-#include <media/IDataSource.h>
+#include <media/DataSource.h>
 #include <media/MediaAnalyticsItem.h>
 #include <media/NdkWrapper.h>
 
@@ -183,9 +183,9 @@
     return err;
 }
 
-status_t MediaPlayer2::setDataSource(const sp<IDataSource> &source)
+status_t MediaPlayer2::setDataSource(const sp<DataSource> &source)
 {
-    ALOGV("setDataSource(IDataSource)");
+    ALOGV("setDataSource(DataSource)");
     status_t err = UNKNOWN_ERROR;
     sp<MediaPlayer2Engine> player(MediaPlayer2Manager::get().create(this, mAudioSessionId));
     if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
diff --git a/media/libmedia/nuplayer2/GenericSource.cpp b/media/libmedia/nuplayer2/GenericSource.cpp
index f8bc4f1..011691a 100644
--- a/media/libmedia/nuplayer2/GenericSource.cpp
+++ b/media/libmedia/nuplayer2/GenericSource.cpp
@@ -354,6 +354,9 @@
         mLooper->unregisterHandler(id());
         mLooper->stop();
     }
+    if (mDataSource != NULL) {
+        mDataSource->close();
+    }
     resetDataSource();
 }
 
@@ -364,7 +367,9 @@
     if (mLooper == NULL) {
         mLooper = new ALooper;
         mLooper->setName("generic");
-        mLooper->start();
+        mLooper->start(false, /* runOnCallingThread */
+                       true,  /* canCallJava */
+                       PRIORITY_DEFAULT);
 
         mLooper->registerHandler(this);
     }
diff --git a/media/libmedia/nuplayer2/HTTPLiveSource.cpp b/media/libmedia/nuplayer2/HTTPLiveSource.cpp
index 14b67cad..e0e3df9 100644
--- a/media/libmedia/nuplayer2/HTTPLiveSource.cpp
+++ b/media/libmedia/nuplayer2/HTTPLiveSource.cpp
@@ -103,7 +103,8 @@
     if (mLiveLooper == NULL) {
         mLiveLooper = new ALooper;
         mLiveLooper->setName("http live");
-        mLiveLooper->start();
+        mLiveLooper->start(false, /* runOnCallingThread */
+                           true /* canCallJava */);
 
         mLiveLooper->registerHandler(this);
     }
diff --git a/media/libmedia/nuplayer2/NuPlayer2Driver.cpp b/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
index c4e42e7..629a7eb 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
@@ -40,6 +40,42 @@
 
 namespace android {
 
+struct ParcelWrapper : public RefBase {
+    static sp<ParcelWrapper> Create(const Parcel *p) {
+        if (p != NULL) {
+            sp<ParcelWrapper> pw = new ParcelWrapper();
+            if (pw->appendFrom(p) == OK) {
+                return pw;
+            }
+        }
+        return NULL;
+    }
+
+    const Parcel *getParcel() {
+        return mParcel;
+    }
+
+protected:
+    virtual ~ParcelWrapper() {
+        if (mParcel != NULL) {
+            delete mParcel;
+        }
+    }
+
+private:
+    ParcelWrapper()
+        : mParcel(NULL) { }
+
+    status_t appendFrom(const Parcel *p) {
+        if (mParcel == NULL) {
+            mParcel = new Parcel;
+        }
+        return mParcel->appendFrom(p, 0 /* start */, p->dataSize());
+    }
+
+    Parcel *mParcel;
+};
+
 // key for media statistics
 static const char *kKeyPlayer = "nuplayer2";
 // attrs for media statistics
@@ -390,7 +426,7 @@
 
         case STATE_PAUSED:
             mState = STATE_STOPPED;
-            sendNotifyOnLooper(MEDIA2_STOPPED);
+            notifyListener_l(MEDIA2_STOPPED);
             break;
 
         case STATE_PREPARED:
@@ -425,7 +461,7 @@
 
         case STATE_RUNNING:
             mState = STATE_PAUSED;
-            sendNotifyOnLooper(MEDIA2_PAUSED);
+            notifyListener_l(MEDIA2_PAUSED);
             mPlayer->pause();
             break;
 
@@ -449,7 +485,7 @@
         Mutex::Autolock autoLock(mLock);
         if (rate.mSpeed == 0.f && mState == STATE_RUNNING) {
             mState = STATE_PAUSED;
-            sendNotifyOnLooper(MEDIA2_PAUSED);
+            notifyListener_l(MEDIA2_PAUSED);
         } else if (rate.mSpeed != 0.f
                 && (mState == STATE_PAUSED
                     || mState == STATE_STOPPED_AND_PREPARED
@@ -487,7 +523,7 @@
             mAtEOS = false;
             mSeekInProgress = true;
             // seeks can take a while, so we essentially paused
-            sendNotifyOnLooper(MEDIA2_PAUSED);
+            notifyListener_l(MEDIA2_PAUSED);
             mPlayer->seekToAsync(seekTimeUs, mode, true /* needNotify */);
             break;
         }
@@ -660,7 +696,7 @@
         {
             CHECK(mIsAsyncPrepare);
 
-            sendNotifyOnLooper(MEDIA2_PREPARED);
+            notifyListener_l(MEDIA2_PREPARED);
             break;
         }
 
@@ -669,7 +705,7 @@
     }
 
     if (mState != STATE_STOPPED) {
-        sendNotifyOnLooper(MEDIA2_STOPPED);
+        notifyListener_l(MEDIA2_STOPPED);
     }
 
     mState = STATE_RESET_IN_PROGRESS;
@@ -952,8 +988,17 @@
     switch (msg->what()) {
         case kWhatNotifyListener: {
             int32_t msgId;
+            int32_t ext1 = 0;
+            int32_t ext2 = 0;
             CHECK(msg->findInt32("messageId", &msgId));
-            notifyListener(msgId);
+            msg->findInt32("ext1", &ext1);
+            msg->findInt32("ext2", &ext2);
+            sp<ParcelWrapper> in;
+            sp<RefBase> obj;
+            if (msg->findObject("parcel", &obj) && obj != NULL) {
+                in = static_cast<ParcelWrapper *>(obj.get());
+            }
+            sendEvent(msgId, ext1, ext2, (in == NULL ? NULL : in->getParcel()));
             break;
         }
         default:
@@ -1025,15 +1070,12 @@
             break;
     }
 
-    mLock.unlock();
-    sendEvent(msg, ext1, ext2, in);
-    mLock.lock();
-}
-
-void NuPlayer2Driver::sendNotifyOnLooper(int msgId) {
-    sp<AMessage> msg = new AMessage(kWhatNotifyListener, this);
-    msg->setInt32("messageId", msgId);
-    msg->post();
+    sp<AMessage> notify = new AMessage(kWhatNotifyListener, this);
+    notify->setInt32("messageId", msg);
+    notify->setInt32("ext1", ext1);
+    notify->setInt32("ext2", ext2);
+    notify->setObject("parcel", ParcelWrapper::Create(in));
+    notify->post();
 }
 
 void NuPlayer2Driver::notifySetDataSourceCompleted(status_t err) {
diff --git a/media/libmedia/nuplayer2/NuPlayer2Driver.h b/media/libmedia/nuplayer2/NuPlayer2Driver.h
index 4451349..d393f9d 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Driver.h
+++ b/media/libmedia/nuplayer2/NuPlayer2Driver.h
@@ -160,7 +160,6 @@
     status_t prepare_l();
     status_t start_l();
     void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0, const Parcel *in = NULL);
-    void sendNotifyOnLooper(int msgId);
 
     DISALLOW_EVIL_CONSTRUCTORS(NuPlayer2Driver);
 };
diff --git a/media/libmedia/omx/1.0/WOmxNode.cpp b/media/libmedia/omx/1.0/WOmxNode.cpp
index 0b40e8d..2cd8b76 100644
--- a/media/libmedia/omx/1.0/WOmxNode.cpp
+++ b/media/libmedia/omx/1.0/WOmxNode.cpp
@@ -151,7 +151,8 @@
                     hidl_handle const& outNativeHandle) {
                 fnStatus = toStatusT(status);
                 *buffer = outBuffer;
-                *native_handle = NativeHandle::create(
+                *native_handle = outNativeHandle.getNativeHandle() == nullptr ?
+                        nullptr : NativeHandle::create(
                         native_handle_clone(outNativeHandle), true);
             }));
     return transStatus == NO_ERROR ? fnStatus : transStatus;
diff --git a/media/libmediaextractor/Android.bp b/media/libmediaextractor/Android.bp
index c57cd41..dcdb320 100644
--- a/media/libmediaextractor/Android.bp
+++ b/media/libmediaextractor/Android.bp
@@ -1,5 +1,6 @@
 cc_library_shared {
     name: "libmediaextractor",
+
     include_dirs: [
         "frameworks/av/include",
         "frameworks/av/media/libmediaextractor/include",
@@ -14,7 +15,6 @@
     ],
 
     shared_libs: [
-        "libmediametrics",
         "libstagefright_foundation",
         "libutils",
         "libcutils",
diff --git a/media/libmediaextractor/MediaExtractor.cpp b/media/libmediaextractor/MediaExtractor.cpp
index 6ba7c0e..2241567 100644
--- a/media/libmediaextractor/MediaExtractor.cpp
+++ b/media/libmediaextractor/MediaExtractor.cpp
@@ -19,68 +19,26 @@
 #include <utils/Log.h>
 #include <pwd.h>
 
-#include <media/MediaAnalyticsItem.h>
 #include <media/MediaExtractor.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/MetaData.h>
 
 namespace android {
 
-// key for media statistics
-static const char *kKeyExtractor = "extractor";
-
 MediaExtractor::MediaExtractor() {
     if (!LOG_NDEBUG) {
         uid_t uid = getuid();
         struct passwd *pw = getpwuid(uid);
         ALOGV("extractor created in uid: %d (%s)", getuid(), pw->pw_name);
     }
-
-    mAnalyticsItem = NULL;
-    if (MEDIA_LOG) {
-        mAnalyticsItem = new MediaAnalyticsItem(kKeyExtractor);
-        (void) mAnalyticsItem->generateSessionID();
-    }
 }
 
-MediaExtractor::~MediaExtractor() {
-
-    // log the current record, provided it has some information worth recording
-    if (MEDIA_LOG) {
-        if (mAnalyticsItem != NULL) {
-            if (mAnalyticsItem->count() > 0) {
-                mAnalyticsItem->setFinalized(true);
-                mAnalyticsItem->selfrecord();
-            }
-        }
-    }
-    if (mAnalyticsItem != NULL) {
-        delete mAnalyticsItem;
-        mAnalyticsItem = NULL;
-    }
-}
+MediaExtractor::~MediaExtractor() {}
 
 sp<MetaData> MediaExtractor::getMetaData() {
     return new MetaData;
 }
 
-status_t MediaExtractor::getMetrics(Parcel *reply) {
-
-    if (mAnalyticsItem == NULL || reply == NULL) {
-        return UNKNOWN_ERROR;
-    }
-
-    populateMetrics();
-    mAnalyticsItem->writeToParcel(reply);
-
-    return OK;
-}
-
-void MediaExtractor::populateMetrics() {
-    ALOGV("MediaExtractor::populateMetrics");
-    // normally overridden in subclasses
-}
-
 uint32_t MediaExtractor::flags() const {
     return CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_PAUSE | CAN_SEEK;
 }
diff --git a/media/libmediaextractor/include/media/MediaExtractor.h b/media/libmediaextractor/include/media/MediaExtractor.h
index 2dcced3..f197b5e 100644
--- a/media/libmediaextractor/include/media/MediaExtractor.h
+++ b/media/libmediaextractor/include/media/MediaExtractor.h
@@ -24,14 +24,10 @@
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
-// still doing some on/off toggling here.
-#define MEDIA_LOG       1
-
 namespace android {
 
 class DataSource;
 class IMediaSource;
-class MediaAnalyticsItem;
 class MediaExtractorFactory;
 class MetaData;
 class Parcel;
@@ -55,8 +51,6 @@
     // returns an empty metadata object.
     virtual sp<MetaData> getMetaData();
 
-    status_t getMetrics(Parcel *reply);
-
     enum Flags {
         CAN_SEEK_BACKWARD  = 1,  // the "seek 10secs back button"
         CAN_SEEK_FORWARD   = 2,  // the "seek 10secs forward button"
@@ -123,14 +117,9 @@
     MediaExtractor();
     virtual ~MediaExtractor();
 
-    MediaAnalyticsItem *mAnalyticsItem;
-
-    virtual void populateMetrics();
-
 private:
     MediaExtractor(const MediaExtractor &);
     MediaExtractor &operator=(const MediaExtractor &);
-    friend class MediaExtractorFactory;
 };
 
 // purposely not defined anywhere so that this will fail to link if
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
index 6b063e8..423dfb8 100644
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ b/media/libmediametrics/MediaAnalyticsItem.cpp
@@ -29,8 +29,6 @@
 #include <utils/SortedVector.h>
 #include <utils/threads.h>
 
-#include <media/stagefright/foundation/AString.h>
-
 #include <binder/IServiceManager.h>
 #include <media/IMediaAnalyticsService.h>
 #include <media/MediaAnalyticsItem.h>
@@ -205,15 +203,11 @@
     return mUid;
 }
 
-MediaAnalyticsItem &MediaAnalyticsItem::setPkgName(AString pkgName) {
+MediaAnalyticsItem &MediaAnalyticsItem::setPkgName(const std::string &pkgName) {
     mPkgName = pkgName;
     return *this;
 }
 
-AString MediaAnalyticsItem::getPkgName() const {
-    return mPkgName;
-}
-
 MediaAnalyticsItem &MediaAnalyticsItem::setPkgVersionCode(int64_t pkgVersionCode) {
     mPkgVersionCode = pkgVersionCode;
     return *this;
@@ -727,11 +721,11 @@
 }
 
 
-AString MediaAnalyticsItem::toString() {
+std::string MediaAnalyticsItem::toString() {
    return toString(-1);
 }
 
-AString MediaAnalyticsItem::toString(int version) {
+std::string MediaAnalyticsItem::toString(int version) {
 
     // v0 : released with 'o'
     // v1 : bug fix (missing pid/finalized separator),
@@ -744,7 +738,7 @@
         version = PROTO_LAST;
     }
 
-    AString result;
+    std::string result;
     char buffer[512];
 
     if (version == PROTO_V0) {
@@ -841,7 +835,7 @@
 bool MediaAnalyticsItem::selfrecord(bool forcenew) {
 
     if (DEBUG_API) {
-        AString p = this->toString();
+        std::string p = this->toString();
         ALOGD("selfrecord of: %s [forcenew=%d]", p.c_str(), forcenew);
     }
 
@@ -850,13 +844,13 @@
     if (svc != NULL) {
         MediaAnalyticsItem::SessionID_t newid = svc->submit(this, forcenew);
         if (newid == SessionIDInvalid) {
-            AString p = this->toString();
+            std::string p = this->toString();
             ALOGW("Failed to record: %s [forcenew=%d]", p.c_str(), forcenew);
             return false;
         }
         return true;
     } else {
-        AString p = this->toString();
+        std::string p = this->toString();
         ALOGW("Unable to record: %s [forcenew=%d]", p.c_str(), forcenew);
         return false;
     }
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
index ec9b660..79ff093 100644
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ b/media/libmediametrics/include/MediaAnalyticsItem.h
@@ -18,6 +18,7 @@
 #define ANDROID_MEDIA_MEDIAANALYTICSITEM_H
 
 #include <cutils/properties.h>
+#include <string>
 #include <sys/types.h>
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
@@ -25,13 +26,10 @@
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
 
-#include <media/stagefright/foundation/AString.h>
-
 namespace android {
 
-
-
 class IMediaAnalyticsService;
+class Parcel;
 
 // the class interface
 //
@@ -66,7 +64,7 @@
         // values can be "component/component"
         // basic values: "video", "audio", "drm"
         // XXX: need to better define the format
-        typedef AString Key;
+        typedef std::string Key;
         static const Key kKeyNone;              // ""
         static const Key kKeyAny;               // "*"
 
@@ -170,8 +168,8 @@
         MediaAnalyticsItem &setUid(uid_t);
         uid_t getUid() const;
 
-        MediaAnalyticsItem &setPkgName(AString);
-        AString getPkgName() const;
+        MediaAnalyticsItem &setPkgName(const std::string &pkgName);
+        std::string getPkgName() const { return mPkgName; }
 
         MediaAnalyticsItem &setPkgVersionCode(int64_t);
         int64_t getPkgVersionCode() const;
@@ -180,8 +178,8 @@
         int32_t writeToParcel(Parcel *);
         int32_t readFromParcel(const Parcel&);
 
-        AString toString();
-        AString toString(int version);
+        std::string toString();
+        std::string toString(int version);
 
         // are we collecting analytics data
         static bool isEnabled();
@@ -204,7 +202,7 @@
         // to help validate that A doesn't mess with B's records
         pid_t     mPid;
         uid_t     mUid;
-        AString   mPkgName;
+        std::string   mPkgName;
         int64_t   mPkgVersionCode;
 
         // let's reuse a binder connection
diff --git a/media/libnblog/NBLog.cpp b/media/libnblog/NBLog.cpp
index c8c7195..d6fa3e3 100644
--- a/media/libnblog/NBLog.cpp
+++ b/media/libnblog/NBLog.cpp
@@ -259,7 +259,8 @@
     *(int*) (buffer + sizeof(entry) + sizeof(HistTsEntry)) = author;
     // Update lengths
     buffer[offsetof(entry, length)] = sizeof(HistTsEntryWithAuthor);
-    buffer[sizeof(buffer) + Entry::kPreviousLengthOffset] = sizeof(HistTsEntryWithAuthor);
+    buffer[offsetof(entry, data) + sizeof(HistTsEntryWithAuthor) + offsetof(ending, length)]
+        = sizeof(HistTsEntryWithAuthor);
     // Write new buffer into FIFO
     dst->write(buffer, sizeof(buffer));
     return EntryIterator(mEntry).next();
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index d9fdfe3..14ea2a8 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -551,6 +551,8 @@
       mDescribeHDRStaticInfoIndex((OMX_INDEXTYPE)0),
       mStateGeneration(0),
       mVendorExtensionsStatus(kExtensionsUnchecked) {
+    memset(&mLastHDRStaticInfo, 0, sizeof(mLastHDRStaticInfo));
+
     mUninitializedState = new UninitializedState(this);
     mLoadedState = new LoadedState(this);
     mLoadedToIdleState = new LoadedToIdleState(this);
@@ -6103,6 +6105,14 @@
             mCodec->mLastNativeWindowDataSpace = dataSpace;
             ALOGW_IF(err != NO_ERROR, "failed to set dataspace: %d", err);
         }
+        if (buffer->format()->contains("hdr-static-info")) {
+            HDRStaticInfo info;
+            if (ColorUtils::getHDRStaticInfoFromFormat(buffer->format(), &info)
+                && memcmp(&mCodec->mLastHDRStaticInfo, &info, sizeof(info))) {
+                setNativeWindowHdrMetadata(mCodec->mNativeWindow.get(), &info);
+                mCodec->mLastHDRStaticInfo = info;
+            }
+        }
 
         // save buffers sent to the surface so we can get render time when they return
         int64_t mediaTimeUs = -1;
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 26de4ca..c3d9c24 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -112,14 +112,12 @@
         "libRScpp",
         "libhidlbase",
         "libhidlmemory",
+        "libziparchive",
         "android.hidl.allocator@1.0",
         "android.hardware.cas.native@1.0",
         "android.hardware.media.omx@1.0",
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.mapper@2.0",
-
-        // XXX: hack
-        "libstagefright_soft_c2avcdec",
     ],
 
     static_libs: [
@@ -135,7 +133,6 @@
         "libstagefright_id3",
         "libFLAC",
 
-        // XXX: hack
         "libstagefright_codec2_vndk",
     ],
 
@@ -155,6 +152,8 @@
         "-Wall",
     ],
 
+    version_script: "exports.lds",
+
     product_variables: {
         debuggable: {
             // enable experiments only in userdebug and eng builds
@@ -215,6 +214,7 @@
         "libmedia_helper",
         "libstagefright_foundation",
         "libdl",
+        "libziparchive",
     ],
 
     static_libs: [
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index 068ca5f..0103abd 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -18,11 +18,10 @@
 #define LOG_TAG "CCodec"
 #include <utils/Log.h>
 
-// XXX: HACK
-#include "codecs/avcdec/C2SoftAvcDec.h"
-
 #include <thread>
 
+#include <C2PlatformSupport.h>
+
 #include <gui/Surface.h>
 #include <media/stagefright/CCodec.h>
 
@@ -181,8 +180,18 @@
     // TODO: use C2ComponentStore to create component
     mListener.reset(new CCodecListener(mChannel));
 
-    std::shared_ptr<C2Component> comp(new C2SoftAvcDec(componentName.c_str(), 0));
-    comp->setListener_vb(mListener, C2_DONT_BLOCK);
+    std::shared_ptr<C2Component> comp;
+    c2_status_t err = GetCodec2PlatformComponentStore()->createComponent(
+            componentName.c_str(), &comp);
+    if (err != C2_OK) {
+        Mutexed<State>::Locked state(mState);
+        state->mState = RELEASED;
+        state.unlock();
+        mCallback->onError(err, ACTION_CODE_FATAL);
+        state.lock();
+        return;
+    }
+    comp->setListener_vb(mListener, C2_MAY_BLOCK);
     {
         Mutexed<State>::Locked state(mState);
         if (state->mState != ALLOCATING) {
@@ -233,6 +242,26 @@
             setSurface(surface);
         }
 
+        // XXX: hack
+        bool audio = mime.startsWithIgnoreCase("audio/");
+        if (encoder) {
+            outputFormat->setString("mime", mime);
+            inputFormat->setString("mime", AStringPrintf("%s/raw", audio ? "audio" : "video"));
+            if (audio) {
+                inputFormat->setInt32("channel-count", 1);
+                inputFormat->setInt32("sample-rate", 44100);
+                outputFormat->setInt32("channel-count", 1);
+                outputFormat->setInt32("sample-rate", 44100);
+            }
+        } else {
+            inputFormat->setString("mime", mime);
+            outputFormat->setString("mime", AStringPrintf("%s/raw", audio ? "audio" : "video"));
+            if (audio) {
+                outputFormat->setInt32("channel-count", 2);
+                outputFormat->setInt32("sample-rate", 44100);
+            }
+        }
+
         // TODO
 
         return OK;
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index 9868cd4..eea9c78 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016, The Android Open Source Project
+ * Copyright 2017, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 #include <numeric>
 #include <thread>
 
+#include <C2AllocatorGralloc.h>
 #include <C2PlatformSupport.h>
 
 #include <android/hardware/cas/native/1.0/IDescrambler.h>
@@ -47,18 +48,11 @@
 using namespace hardware::cas::V1_0;
 using namespace hardware::cas::native::V1_0;
 
+namespace {
+
 // TODO: get this info from component
 const static size_t kMinBufferArraySize = 16;
 
-void CCodecBufferChannel::OutputBuffers::flush(
-        const std::list<std::unique_ptr<C2Work>> &flushedWork) {
-    (void) flushedWork;
-    // This is no-op by default unless we're in array mode where we need to keep
-    // track of the flushed work.
-}
-
-namespace {
-
 template <class T>
 ssize_t findBufferSlot(
         std::vector<T> *buffers,
@@ -76,16 +70,103 @@
     return std::distance(buffers->begin(), it);
 }
 
+sp<Codec2Buffer> allocateLinearBuffer(
+        const std::shared_ptr<C2BlockPool> &pool,
+        const sp<AMessage> &format,
+        size_t size,
+        const C2MemoryUsage &usage) {
+    std::shared_ptr<C2LinearBlock> block;
+
+    status_t err = pool->fetchLinearBlock(
+            size,
+            usage,
+            &block);
+    if (err != OK) {
+        return nullptr;
+    }
+
+    return Codec2Buffer::allocate(format, block);
+}
+
 class LinearBuffer : public C2Buffer {
 public:
     explicit LinearBuffer(C2ConstLinearBlock block) : C2Buffer({ block }) {}
 };
 
+class InputBuffersArray : public CCodecBufferChannel::InputBuffers {
+public:
+    InputBuffersArray() = default;
+
+    void add(
+            size_t index,
+            const sp<MediaCodecBuffer> &clientBuffer,
+            const std::shared_ptr<C2Buffer> &compBuffer,
+            bool available) {
+        if (mBufferArray.size() < index) {
+            mBufferArray.resize(index + 1);
+        }
+        mBufferArray[index].clientBuffer = clientBuffer;
+        mBufferArray[index].compBuffer = compBuffer;
+        mBufferArray[index].available = available;
+    }
+
+    bool isArrayMode() final { return true; }
+
+    std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
+        return nullptr;
+    }
+
+    void getArray(Vector<sp<MediaCodecBuffer>> *array) final {
+        array->clear();
+        for (const auto &entry : mBufferArray) {
+            array->push(entry.clientBuffer);
+        }
+    }
+
+    bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
+        for (size_t i = 0; i < mBufferArray.size(); ++i) {
+            if (mBufferArray[i].available) {
+                mBufferArray[i].available = false;
+                *index = i;
+                *buffer = mBufferArray[i].clientBuffer;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
+        for (size_t i = 0; i < mBufferArray.size(); ++i) {
+            if (!mBufferArray[i].available && mBufferArray[i].clientBuffer == buffer) {
+                mBufferArray[i].available = true;
+                return std::move(mBufferArray[i].compBuffer);
+            }
+        }
+        return nullptr;
+    }
+
+    void flush() override {
+        for (size_t i = 0; i < mBufferArray.size(); ++i) {
+            mBufferArray[i].available = true;
+            mBufferArray[i].compBuffer.reset();
+        }
+    }
+
+private:
+    struct Entry {
+        sp<MediaCodecBuffer> clientBuffer;
+        std::shared_ptr<C2Buffer> compBuffer;
+        bool available;
+    };
+
+    std::vector<Entry> mBufferArray;
+};
+
 class LinearInputBuffers : public CCodecBufferChannel::InputBuffers {
 public:
     using CCodecBufferChannel::InputBuffers::InputBuffers;
 
-    virtual bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
+    bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
         *buffer = nullptr;
         ssize_t ret = findBufferSlot<wp<Codec2Buffer>>(
                 &mBuffers, kMinBufferArraySize,
@@ -93,25 +174,20 @@
         if (ret < 0) {
             return false;
         }
-        std::shared_ptr<C2LinearBlock> block;
-
-        status_t err = mAlloc->fetchLinearBlock(
-                // TODO: proper max input size
-                65536,
-                { 0, C2MemoryUsage::kSoftwareWrite },
-                &block);
-        if (err != OK) {
+        // TODO: proper max input size and usage
+        // TODO: read usage from intf
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        sp<Codec2Buffer> newBuffer = allocateLinearBuffer(mPool, mFormat, 65536, usage);
+        if (newBuffer == nullptr) {
             return false;
         }
-
-        sp<Codec2Buffer> newBuffer = Codec2Buffer::allocate(mFormat, block);
         mBuffers[ret] = newBuffer;
         *index = ret;
         *buffer = newBuffer;
         return true;
     }
 
-    virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
+    std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
         auto it = std::find(mBuffers.begin(), mBuffers.end(), buffer);
         if (it == mBuffers.end()) {
             return nullptr;
@@ -122,80 +198,358 @@
         return std::make_shared<LinearBuffer>(codecBuffer->share());
     }
 
-    virtual void flush() override {
+    void flush() override {
+    }
+
+    std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
+        std::unique_ptr<InputBuffersArray> array(new InputBuffersArray);
+        // TODO
+        const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+        for (size_t i = 0; i < size; ++i) {
+            sp<Codec2Buffer> clientBuffer = mBuffers[i].promote();
+            bool available = false;
+            if (clientBuffer == nullptr) {
+                // TODO: proper max input size
+                // TODO: read usage from intf
+                C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+                clientBuffer = allocateLinearBuffer(mPool, mFormat, 65536, usage);
+                available = true;
+            }
+            array->add(
+                    i,
+                    clientBuffer,
+                    std::make_shared<LinearBuffer>(clientBuffer->share()),
+                    available);
+        }
+        return std::move(array);
     }
 
 private:
     // Buffers we passed to the client. The index of a buffer matches what
     // was passed in BufferCallback::onInputBufferAvailable().
     std::vector<wp<Codec2Buffer>> mBuffers;
-
-    // Buffer array we passed to the client. This only gets initialized at
-    // getInput/OutputBufferArray() and when this is set we can't add more
-    // buffers.
-    std::vector<sp<Codec2Buffer>> mBufferArray;
 };
 
-class GraphicOutputBuffers : public CCodecBufferChannel::OutputBuffers {
+// TODO: stub
+class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
+public:
+    using CCodecBufferChannel::InputBuffers::InputBuffers;
+
+    bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
+        (void)index;
+        (void)buffer;
+        return false;
+    }
+
+    std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
+        (void)buffer;
+        return nullptr;
+    }
+
+    void flush() override {
+    }
+
+    std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
+        return nullptr;
+    }
+};
+
+class OutputBuffersArray : public CCodecBufferChannel::OutputBuffers {
 public:
     using CCodecBufferChannel::OutputBuffers::OutputBuffers;
 
-    virtual bool registerBuffer(
+    void add(
+            size_t index,
+            const sp<MediaCodecBuffer> &clientBuffer,
+            const std::shared_ptr<C2Buffer> &compBuffer,
+            bool available) {
+        if (mBufferArray.size() < index) {
+            mBufferArray.resize(index + 1);
+        }
+        mBufferArray[index].clientBuffer = clientBuffer;
+        mBufferArray[index].compBuffer = compBuffer;
+        mBufferArray[index].available = available;
+    }
+
+    bool isArrayMode() final { return true; }
+
+    std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() final {
+        return nullptr;
+    }
+
+    bool registerBuffer(
+            const std::shared_ptr<C2Buffer> &buffer,
+            size_t *index,
+            sp<MediaCodecBuffer> *codecBuffer) final {
+        for (size_t i = 0; i < mBufferArray.size(); ++i) {
+            if (mBufferArray[i].available && copy(buffer, mBufferArray[i].clientBuffer)) {
+                *index = i;
+                *codecBuffer = mBufferArray[i].clientBuffer;
+                mBufferArray[i].compBuffer = buffer;
+                mBufferArray[i].available = false;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    bool registerCsd(
+            const C2StreamCsdInfo::output *csd,
+            size_t *index,
+            sp<MediaCodecBuffer> *codecBuffer) final {
+        for (size_t i = 0; i < mBufferArray.size(); ++i) {
+            if (mBufferArray[i].available
+                    && mBufferArray[i].clientBuffer->capacity() <= csd->flexCount()) {
+                memcpy(mBufferArray[i].clientBuffer->base(), csd->m.value, csd->flexCount());
+                *index = i;
+                *codecBuffer = mBufferArray[i].clientBuffer;
+                mBufferArray[i].available = false;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) final {
+        for (size_t i = 0; i < mBufferArray.size(); ++i) {
+            if (!mBufferArray[i].available && mBufferArray[i].clientBuffer == buffer) {
+                mBufferArray[i].available = true;
+                return std::move(mBufferArray[i].compBuffer);
+            }
+        }
+        return nullptr;
+    }
+
+    void flush(
+            const std::list<std::unique_ptr<C2Work>> &flushedWork) override {
+        (void) flushedWork;
+        for (size_t i = 0; i < mBufferArray.size(); ++i) {
+            mBufferArray[i].available = true;
+            mBufferArray[i].compBuffer.reset();
+        }
+    }
+
+    virtual bool copy(
+            const std::shared_ptr<C2Buffer> &buffer,
+            const sp<MediaCodecBuffer> &clientBuffer) = 0;
+
+    void getArray(Vector<sp<MediaCodecBuffer>> *array) final {
+        array->clear();
+        for (const auto &entry : mBufferArray) {
+            array->push(entry.clientBuffer);
+        }
+    }
+
+private:
+    struct Entry {
+        sp<MediaCodecBuffer> clientBuffer;
+        std::shared_ptr<C2Buffer> compBuffer;
+        bool available;
+    };
+
+    std::vector<Entry> mBufferArray;
+};
+
+class LinearOutputBuffersArray : public OutputBuffersArray {
+public:
+    using OutputBuffersArray::OutputBuffersArray;
+
+    bool copy(
+            const std::shared_ptr<C2Buffer> &buffer,
+            const sp<MediaCodecBuffer> &clientBuffer) final {
+        if (!buffer) {
+            clientBuffer->setRange(0u, 0u);
+            return true;
+        }
+        C2ReadView view = buffer->data().linearBlocks().front().map().get();
+        if (clientBuffer->capacity() < view.capacity()) {
+            return false;
+        }
+        clientBuffer->setRange(0u, view.capacity());
+        memcpy(clientBuffer->data(), view.data(), view.capacity());
+        return true;
+    }
+};
+
+class GraphicOutputBuffersArray : public OutputBuffersArray {
+public:
+    using OutputBuffersArray::OutputBuffersArray;
+
+    bool copy(
+            const std::shared_ptr<C2Buffer> &buffer,
+            const sp<MediaCodecBuffer> &clientBuffer) final {
+        if (!buffer) {
+            clientBuffer->setRange(0u, 0u);
+            return true;
+        }
+        clientBuffer->setRange(0u, 1u);
+        return true;
+    }
+};
+
+// Flexible in a sense that it does not have fixed array size.
+class FlexOutputBuffers : public CCodecBufferChannel::OutputBuffers {
+public:
+    using CCodecBufferChannel::OutputBuffers::OutputBuffers;
+
+    bool registerBuffer(
             const std::shared_ptr<C2Buffer> &buffer,
             size_t *index,
             sp<MediaCodecBuffer> *codecBuffer) override {
         *codecBuffer = nullptr;
         ssize_t ret = findBufferSlot<BufferInfo>(
                 &mBuffers,
-                kMinBufferArraySize,
-                [] (const auto &elem) { return elem.mClientBuffer.promote() == nullptr; });
+                std::numeric_limits<size_t>::max(),
+                [] (const auto &elem) { return elem.clientBuffer.promote() == nullptr; });
         if (ret < 0) {
             return false;
         }
         sp<MediaCodecBuffer> newBuffer = new MediaCodecBuffer(
                 mFormat,
-                buffer == nullptr ? kEmptyBuffer : kDummyBuffer);
+                convert(buffer));
         mBuffers[ret] = { newBuffer, buffer };
         *index = ret;
         *codecBuffer = newBuffer;
         return true;
     }
 
-    virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
+    bool registerCsd(
+            const C2StreamCsdInfo::output *csd,
+            size_t *index,
+            sp<MediaCodecBuffer> *codecBuffer) final {
+        *codecBuffer = nullptr;
+        ssize_t ret = findBufferSlot<BufferInfo>(
+                &mBuffers,
+                std::numeric_limits<size_t>::max(),
+                [] (const auto &elem) { return elem.clientBuffer.promote() == nullptr; });
+        if (ret < 0) {
+            return false;
+        }
+        sp<MediaCodecBuffer> newBuffer = new MediaCodecBuffer(
+                mFormat,
+                ABuffer::CreateAsCopy(csd->m.value, csd->flexCount()));
+        mBuffers[ret] = { newBuffer, nullptr };
+        *index = ret;
+        *codecBuffer = newBuffer;
+        return true;
+    }
+
+    std::shared_ptr<C2Buffer> releaseBuffer(
+            const sp<MediaCodecBuffer> &buffer) override {
         auto it = std::find_if(
                 mBuffers.begin(), mBuffers.end(),
                 [buffer] (const auto &elem) {
-                    return elem.mClientBuffer.promote() == buffer;
+                    return elem.clientBuffer.promote() == buffer;
                 });
         if (it == mBuffers.end()) {
             return nullptr;
         }
-        return it->mBufferRef;
+        return std::move(it->bufferRef);
     }
 
-private:
-    static const sp<ABuffer> kEmptyBuffer;
-    static const sp<ABuffer> kDummyBuffer;
+    void flush(
+            const std::list<std::unique_ptr<C2Work>> &flushedWork) override {
+        (void) flushedWork;
+        // This is no-op by default unless we're in array mode where we need to keep
+        // track of the flushed work.
+    }
 
+    virtual sp<ABuffer> convert(const std::shared_ptr<C2Buffer> &buffer) = 0;
+
+protected:
     struct BufferInfo {
         // wp<> of MediaCodecBuffer for MediaCodec.
-        wp<MediaCodecBuffer> mClientBuffer;
-        // Buffer reference to hold until mClientBuffer is valid.
-        std::shared_ptr<C2Buffer> mBufferRef;
+        wp<MediaCodecBuffer> clientBuffer;
+        // Buffer reference to hold until clientBuffer is valid.
+        std::shared_ptr<C2Buffer> bufferRef;
     };
     // Buffers we passed to the client. The index of a buffer matches what
     // was passed in BufferCallback::onInputBufferAvailable().
     std::vector<BufferInfo> mBuffers;
 };
 
-const sp<ABuffer> GraphicOutputBuffers::kEmptyBuffer = new ABuffer(nullptr, 0);
-const sp<ABuffer> GraphicOutputBuffers::kDummyBuffer = new ABuffer(nullptr, 1);
+class LinearOutputBuffers : public FlexOutputBuffers {
+public:
+    using FlexOutputBuffers::FlexOutputBuffers;
+
+    virtual sp<ABuffer> convert(const std::shared_ptr<C2Buffer> &buffer) override {
+        if (buffer == nullptr) {
+            return new ABuffer(nullptr, 0);
+        }
+        if (buffer->data().type() != C2BufferData::LINEAR) {
+            // We expect linear output buffers from the component.
+            return nullptr;
+        }
+        if (buffer->data().linearBlocks().size() != 1u) {
+            // We expect one and only one linear block from the component.
+            return nullptr;
+        }
+        C2ReadView view = buffer->data().linearBlocks().front().map().get();
+        if (view.error() != C2_OK) {
+            // Mapping the linear block failed
+            return nullptr;
+        }
+        return new ABuffer(
+                // XXX: the data is supposed to be read-only. We don't have
+                // const equivalent of ABuffer however...
+                const_cast<uint8_t *>(view.data()),
+                view.capacity());
+    }
+
+    std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
+        std::unique_ptr<OutputBuffersArray> array(new LinearOutputBuffersArray);
+
+        const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+        for (size_t i = 0; i < size; ++i) {
+            sp<MediaCodecBuffer> clientBuffer = mBuffers[i].clientBuffer.promote();
+            std::shared_ptr<C2Buffer> compBuffer = mBuffers[i].bufferRef;
+            bool available = false;
+            if (clientBuffer == nullptr) {
+                // TODO: proper max input size
+                clientBuffer = new MediaCodecBuffer(mFormat, new ABuffer(65536));
+                available = true;
+                compBuffer.reset();
+            }
+            array->add(i, clientBuffer, compBuffer, available);
+        }
+        return std::move(array);
+    }
+};
+
+class GraphicOutputBuffers : public FlexOutputBuffers {
+public:
+    using FlexOutputBuffers::FlexOutputBuffers;
+
+    sp<ABuffer> convert(const std::shared_ptr<C2Buffer> &buffer) override {
+        return buffer ? new ABuffer(nullptr, 1) : new ABuffer(nullptr, 0);
+    }
+
+    std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
+        std::unique_ptr<OutputBuffersArray> array(new GraphicOutputBuffersArray);
+
+        const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+        for (size_t i = 0; i < size; ++i) {
+            sp<MediaCodecBuffer> clientBuffer = mBuffers[i].clientBuffer.promote();
+            std::shared_ptr<C2Buffer> compBuffer = mBuffers[i].bufferRef;
+            bool available = false;
+            if (clientBuffer == nullptr) {
+                clientBuffer = new MediaCodecBuffer(mFormat, new ABuffer(nullptr, 1));
+                available = true;
+                compBuffer.reset();
+            }
+            array->add(i, clientBuffer, compBuffer, available);
+        }
+        return std::move(array);
+    }
+};
 
 }  // namespace
 
 CCodecBufferChannel::QueueGuard::QueueGuard(
         CCodecBufferChannel::QueueSync &sync) : mSync(sync) {
     std::unique_lock<std::mutex> l(mSync.mMutex);
+    // At this point it's guaranteed that mSync is not under state transition,
+    // as we are holding its mutex.
     if (mSync.mCount == -1) {
         mRunning = false;
     } else {
@@ -206,6 +560,8 @@
 
 CCodecBufferChannel::QueueGuard::~QueueGuard() {
     if (mRunning) {
+        // We are not holding mutex at this point so that QueueSync::stop() can
+        // keep holding the lock until mCount reaches zero.
         --mSync.mCount;
     }
 }
@@ -214,7 +570,7 @@
     std::unique_lock<std::mutex> l(mMutex);
     // If stopped, it goes to running state; otherwise no-op.
     int32_t expected = -1;
-    mCount.compare_exchange_strong(expected, 0);
+    (void)mCount.compare_exchange_strong(expected, 0);
 }
 
 void CCodecBufferChannel::QueueSync::stop() {
@@ -223,6 +579,11 @@
         // no-op
         return;
     }
+    // Holding mutex here blocks creation of additional QueueGuard objects, so
+    // mCount can only decrement. In other words, threads that acquired the lock
+    // are allowed to finish execution but additional threads trying to acquire
+    // the lock at this point will block, and then get QueueGuard at STOPPED
+    // state.
     int32_t expected = 0;
     while (!mCount.compare_exchange_weak(expected, -1)) {
         std::this_thread::yield();
@@ -232,8 +593,6 @@
 CCodecBufferChannel::CCodecBufferChannel(
         const std::function<void(status_t, enum ActionCode)> &onError)
     : mOnError(onError),
-      mInputBuffers(new LinearInputBuffers),
-      mOutputBuffers(new GraphicOutputBuffers),
       mFrameIndex(0u),
       mFirstValidFrameIndex(0u) {
 }
@@ -246,12 +605,50 @@
 
 void CCodecBufferChannel::setComponent(const std::shared_ptr<C2Component> &component) {
     mComponent = component;
-    // TODO: get pool ID from params
-    std::shared_ptr<C2BlockPool> pool;
-    c2_status_t err = GetCodec2BlockPool(C2BlockPool::BASIC_LINEAR, component, &pool);
-    if (err == C2_OK) {
+    C2StreamFormatConfig::input inputFormat(0u);
+    C2StreamFormatConfig::output outputFormat(0u);
+    c2_status_t err = mComponent->intf()->query_vb(
+            { &inputFormat, &outputFormat },
+            {},
+            C2_DONT_BLOCK,
+            nullptr);
+    if (err != C2_OK) {
+        // TODO: error
+        return;
+    }
+
+    {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
-        (*buffers)->setAlloc(pool);
+
+        bool graphic = (inputFormat.value == C2FormatVideo);
+        if (graphic) {
+            buffers->reset(new GraphicInputBuffers);
+        } else {
+            buffers->reset(new LinearInputBuffers);
+        }
+
+        ALOGV("graphic = %s", graphic ? "true" : "false");
+        std::shared_ptr<C2BlockPool> pool;
+        err = GetCodec2BlockPool(
+                graphic ? C2BlockPool::BASIC_GRAPHIC : C2BlockPool::BASIC_LINEAR,
+                component,
+                &pool);
+        if (err == C2_OK) {
+            (*buffers)->setPool(pool);
+        } else {
+            // TODO: error
+        }
+    }
+
+    {
+        Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+
+        bool graphic = (outputFormat.value == C2FormatVideo);
+        if (graphic) {
+            buffers->reset(new GraphicOutputBuffers);
+        } else {
+            buffers->reset(new LinearOutputBuffers);
+        }
     }
 }
 
@@ -314,17 +711,6 @@
 status_t CCodecBufferChannel::renderOutputBuffer(
         const sp<MediaCodecBuffer> &buffer, int64_t timestampNs) {
     ALOGV("renderOutputBuffer");
-    sp<MediaCodecBuffer> inBuffer;
-    size_t index;
-    {
-        Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
-        if (!(*buffers)->requestNewBuffer(&index, &inBuffer)) {
-            inBuffer = nullptr;
-        }
-    }
-    if (inBuffer != nullptr) {
-        mCallback->onInputBufferAvailable(index, inBuffer);
-    }
 
     std::shared_ptr<C2Buffer> c2Buffer;
     {
@@ -344,8 +730,9 @@
         return UNKNOWN_ERROR;
     }
 
+    native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(blocks.front().handle());
     sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(
-            blocks.front().handle(),
+            grallocHandle,
             GraphicBuffer::CLONE_HANDLE,
             blocks.front().width(),
             blocks.front().height(),
@@ -355,6 +742,7 @@
             (uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
             // TODO
             blocks.front().width()));
+    native_handle_delete(grallocHandle);
 
     status_t result = (*surface)->attachBuffer(graphicBuffer.get());
     if (result != OK) {
@@ -385,85 +773,38 @@
 }
 
 status_t CCodecBufferChannel::discardBuffer(const sp<MediaCodecBuffer> &buffer) {
-    ALOGV("discardBuffer");
+    ALOGV("discardBuffer: %p", buffer.get());
     {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
-        (void) (*buffers)->releaseBuffer(buffer);
+        (void)(*buffers)->releaseBuffer(buffer);
     }
     {
         Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
-        (void) (*buffers)->releaseBuffer(buffer);
+        (void)(*buffers)->releaseBuffer(buffer);
     }
     return OK;
 }
 
-#if 0
-void fillBufferArray_l(Mutexed<Buffers>::Locked &buffers) {
-    for (size_t i = 0; i < buffers->mClientBuffer.size(); ++i) {
-        sp<Codec2Buffer> buffer(buffers->mClientBuffer.get(i).promote());
-        if (buffer == nullptr) {
-            buffer = allocateBuffer_l(buffers->mAlloc);
-        }
-        buffers->mBufferArray.push_back(buffer);
-    }
-    while (buffers->mBufferArray.size() < kMinBufferArraySize) {
-        sp<Codec2Buffer> buffer = allocateBuffer_l(buffers->mAlloc);
-        // allocate buffer
-        buffers->mBufferArray.push_back(buffer);
-    }
-}
-#endif
-
 void CCodecBufferChannel::getInputBufferArray(Vector<sp<MediaCodecBuffer>> *array) {
-    (void) array;
-    // TODO
-#if 0
     array->clear();
-    Mutexed<Buffers>::Locked buffers(mInputBuffers);
+    Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
 
-    if (!buffers->isArrayMode()) {
-        // mBufferArray is empty.
-        fillBufferArray_l(buffers);
+    if (!(*buffers)->isArrayMode()) {
+        *buffers = (*buffers)->toArrayMode();
     }
 
-    for (const auto &buffer : buffers->mBufferArray) {
-        array->push_back(buffer);
-    }
-#endif
+    (*buffers)->getArray(array);
 }
 
 void CCodecBufferChannel::getOutputBufferArray(Vector<sp<MediaCodecBuffer>> *array) {
-    (void) array;
-    // TODO
-#if 0
     array->clear();
-    Mutexed<Buffers>::Locked buffers(mOutputBuffers);
+    Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
 
-    if (!buffers->isArrayMode()) {
-        if (linear) {
-            // mBufferArray is empty.
-            fillBufferArray_l(buffers);
-
-            // We need to replace the allocator so that the component only returns
-            // buffer from the array.
-            ArrayModeAllocator::Builder builder(buffers->mBufferArray);
-            for (size_t i = 0; i < buffers->mClientBuffer.size(); ++i) {
-                if (buffers->mClientBuffer.get(i).promote() != nullptr) {
-                    builder.markUsing(i);
-                }
-            }
-            buffers->mAlloc.reset(builder.build());
-        } else {
-            for (int i = 0; i < X; ++i) {
-                buffers->mBufferArray.push_back(dummy buffer);
-            }
-        }
+    if (!(*buffers)->isArrayMode()) {
+        *buffers = (*buffers)->toArrayMode();
     }
 
-    for (const auto &buffer : buffers->mBufferArray) {
-        array->push_back(buffer);
-    }
-#endif
+    (*buffers)->getArray(array);
 }
 
 void CCodecBufferChannel::start(const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat) {
@@ -513,6 +854,19 @@
 
 void CCodecBufferChannel::onWorkDone(std::vector<std::unique_ptr<C2Work>> workItems) {
     for (const auto &work : workItems) {
+        sp<MediaCodecBuffer> inBuffer;
+        size_t index;
+        {
+            Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
+            if (!(*buffers)->requestNewBuffer(&index, &inBuffer)) {
+                ALOGW("no new buffer available");
+                inBuffer = nullptr;
+            }
+        }
+        if (inBuffer != nullptr) {
+            mCallback->onInputBufferAvailable(index, inBuffer);
+        }
+
         if (work->result != OK) {
             ALOGE("work failed to complete: %d", work->result);
             mOnError(work->result, ACTION_CODE_FATAL);
@@ -539,7 +893,16 @@
         }
 
         const std::shared_ptr<C2Buffer> &buffer = worklet->output.buffers[0];
-        // TODO: transfer infos() into buffer metadata
+        const C2StreamCsdInfo::output *csdInfo = nullptr;
+        if (buffer) {
+            // TODO: transfer infos() into buffer metadata
+        }
+        for (const auto &info : worklet->output.infos) {
+            if (info->coreIndex() == C2StreamCsdInfo::output::CORE_INDEX) {
+                ALOGV("csd found");
+                csdInfo = static_cast<const C2StreamCsdInfo::output *>(info.get());
+            }
+        }
 
         int32_t flags = 0;
         if (worklet->output.flags & C2BufferPack::FLAG_END_OF_STREAM) {
@@ -547,15 +910,43 @@
             ALOGV("output EOS");
         }
 
-        size_t index;
         sp<MediaCodecBuffer> outBuffer;
-        Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
-        if (!(*buffers)->registerBuffer(buffer, &index, &outBuffer)) {
-            ALOGE("unable to register output buffer");
-            mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+        if (csdInfo != nullptr) {
+            Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+            if ((*buffers)->registerCsd(csdInfo, &index, &outBuffer)) {
+                outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp);
+                outBuffer->meta()->setInt32("flags", flags | MediaCodec::BUFFER_FLAG_CODECCONFIG);
+                ALOGV("csd index = %zu", index);
+
+                buffers.unlock();
+                mCallback->onOutputBufferAvailable(index, outBuffer);
+                buffers.lock();
+            } else {
+                ALOGE("unable to register output buffer");
+                buffers.unlock();
+                mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+                buffers.lock();
+                continue;
+            }
+        }
+
+        if (!buffer && !flags) {
+            ALOGV("Not reporting output buffer");
             continue;
         }
 
+        {
+            Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+            if (!(*buffers)->registerBuffer(buffer, &index, &outBuffer)) {
+                ALOGE("unable to register output buffer");
+
+                buffers.unlock();
+                mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+                buffers.lock();
+                continue;
+            }
+        }
+
         outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp);
         outBuffer->meta()->setInt32("flags", flags);
         ALOGV("index = %zu", index);
diff --git a/media/libstagefright/InterfaceUtils.cpp b/media/libstagefright/InterfaceUtils.cpp
index cf9fdf8..f174ba4 100644
--- a/media/libstagefright/InterfaceUtils.cpp
+++ b/media/libstagefright/InterfaceUtils.cpp
@@ -38,11 +38,12 @@
     return RemoteDataSource::wrap(source);
 }
 
-sp<IMediaExtractor> CreateIMediaExtractorFromMediaExtractor(const sp<MediaExtractor> &extractor) {
+sp<IMediaExtractor> CreateIMediaExtractorFromMediaExtractor(
+        const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin) {
     if (extractor == nullptr) {
         return nullptr;
     }
-    return RemoteMediaExtractor::wrap(extractor);
+    return RemoteMediaExtractor::wrap(extractor, plugin);
 }
 
 sp<MediaSource> CreateMediaSourceFromIMediaSource(const sp<IMediaSource> &source) {
@@ -52,11 +53,12 @@
     return new CallbackMediaSource(source);
 }
 
-sp<IMediaSource> CreateIMediaSourceFromMediaSource(const sp<MediaSource> &source) {
+sp<IMediaSource> CreateIMediaSourceFromMediaSource(
+        const sp<MediaSource> &source, const sp<RefBase> &plugin) {
     if (source == nullptr) {
         return nullptr;
     }
-    return RemoteMediaSource::wrap(source);
+    return RemoteMediaSource::wrap(source, plugin);
 }
 
 }  // namespace android
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 6ad11f4..77d9ce4 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -552,7 +552,7 @@
 //static
 sp<CodecBase> MediaCodec::GetCodecBase(const AString &name, bool nameIsType) {
     static bool ccodecEnabled = property_get_bool("debug.stagefright.ccodec", false);
-    if (ccodecEnabled && !nameIsType && name.startsWithIgnoreCase("codec2.")) {
+    if (ccodecEnabled && !nameIsType && name.startsWithIgnoreCase("c2.")) {
         return new CCodec;
     } else if (nameIsType || name.startsWithIgnoreCase("omx.")) {
         // at this time only ACodec specifies a mime type.
@@ -1832,6 +1832,12 @@
                                         mSurface.get(), (android_dataspace)dataSpace);
                                 ALOGW_IF(err != 0, "failed to set dataspace on surface (%d)", err);
                             }
+                            if (mOutputFormat->contains("hdr-static-info")) {
+                                HDRStaticInfo info;
+                                if (ColorUtils::getHDRStaticInfoFromFormat(mOutputFormat, &info)) {
+                                    setNativeWindowHdrMetadata(mSurface.get(), &info);
+                                }
+                            }
 
                             if (mime.startsWithIgnoreCase("video/")) {
                                 mSoftRenderer = new SoftwareRenderer(mSurface, mRotationDegrees);
diff --git a/media/libstagefright/MediaExtractorFactory.cpp b/media/libstagefright/MediaExtractorFactory.cpp
index e1e04eb..bbf87f0 100644
--- a/media/libstagefright/MediaExtractorFactory.cpp
+++ b/media/libstagefright/MediaExtractorFactory.cpp
@@ -15,7 +15,7 @@
  */
 
 //#define LOG_NDEBUG 0
-#define LOG_TAG "MediaExtractor"
+#define LOG_TAG "MediaExtractorFactory"
 #include <utils/Log.h>
 
 #include <binder/IServiceManager.h>
@@ -31,16 +31,14 @@
 #include <media/IMediaExtractorService.h>
 #include <cutils/properties.h>
 #include <utils/String8.h>
+#include <ziparchive/zip_archive.h>
 
 #include <dirent.h>
 #include <dlfcn.h>
 
 namespace android {
 
-// attrs for media statistics
-static const char *kExtractorMime = "android.media.mediaextractor.mime";
-static const char *kExtractorTracks = "android.media.mediaextractor.ntrk";
-static const char *kExtractorFormat = "android.media.mediaextractor.fmt";
+static const char *kSystemApkPath = "/system/app/MediaComponents/MediaComponents.apk";
 
 // static
 sp<IMediaExtractor> MediaExtractorFactory::Create(
@@ -50,8 +48,7 @@
     if (!property_get_bool("media.stagefright.extractremote", true)) {
         // local extractor
         ALOGW("creating media extractor in calling process");
-        sp<MediaExtractor> extractor = CreateFromService(source, mime);
-        return CreateIMediaExtractorFromMediaExtractor(extractor);
+        return CreateFromService(source, mime);
     } else {
         // remote extractor
         ALOGV("get service manager");
@@ -108,11 +105,12 @@
     return Create(*out, mime);
 }
 
-sp<MediaExtractor> MediaExtractorFactory::CreateFromService(
+sp<IMediaExtractor> MediaExtractorFactory::CreateFromService(
         const sp<DataSource> &source, const char *mime) {
 
-    ALOGV("MediaExtractorFactory::%s %s", __func__, mime);
-    RegisterDefaultSniffers();
+    ALOGV("MediaExtractorFactory::CreateFromService %s", mime);
+
+    UpdateExtractors(nullptr);
 
     // initialize source decryption if needed
     source->DrmInitialization(nullptr /* mime */);
@@ -122,7 +120,8 @@
     MediaExtractor::CreatorFunc creator = NULL;
     String8 tmp;
     float confidence;
-    creator = sniff(source, &tmp, &confidence, &meta);
+    sp<ExtractorPlugin> plugin;
+    creator = sniff(source, &tmp, &confidence, &meta, plugin);
     if (!creator) {
         ALOGV("FAILED to autodetect media content.");
         return NULL;
@@ -133,64 +132,69 @@
          mime, confidence);
 
     MediaExtractor *ret = creator(source, meta);
-
-    if (ret != NULL) {
-        // track the container format (mpeg, aac, wvm, etc)
-        if (MEDIA_LOG) {
-            if (ret->mAnalyticsItem != NULL) {
-                size_t ntracks = ret->countTracks();
-                ret->mAnalyticsItem->setCString(kExtractorFormat,  ret->name());
-                // tracks (size_t)
-                ret->mAnalyticsItem->setInt32(kExtractorTracks,  ntracks);
-                // metadata
-                sp<MetaData> pMetaData = ret->getMetaData();
-                if (pMetaData != NULL) {
-                    String8 xx = pMetaData->toString();
-                    // 'titl' -- but this verges into PII
-                    // 'mime'
-                    const char *mime = NULL;
-                    if (pMetaData->findCString(kKeyMIMEType, &mime)) {
-                        ret->mAnalyticsItem->setCString(kExtractorMime,  mime);
-                    }
-                    // what else is interesting and not already available?
-                }
-            }
-        }
-    }
-
-    return ret;
+    return CreateIMediaExtractorFromMediaExtractor(ret, plugin);
 }
 
-Mutex MediaExtractorFactory::gSnifferMutex;
-List<MediaExtractor::ExtractorDef> MediaExtractorFactory::gSniffers;
-bool MediaExtractorFactory::gSniffersRegistered = false;
+//static
+void MediaExtractorFactory::LoadPlugins(const ::std::string& apkPath) {
+    // TODO: Verify apk path with package manager in extractor process.
+    ALOGV("Load plugins from: %s", apkPath.c_str());
+    UpdateExtractors(apkPath.empty() ? nullptr : apkPath.c_str());
+}
+
+struct ExtractorPlugin : public RefBase {
+    MediaExtractor::ExtractorDef def;
+    void *libHandle;
+    String8 libPath;
+    String8 uuidString;
+
+    ExtractorPlugin(MediaExtractor::ExtractorDef definition, void *handle, String8 &path)
+        : def(definition), libHandle(handle), libPath(path) {
+        for (size_t i = 0; i < sizeof MediaExtractor::ExtractorDef::extractor_uuid; i++) {
+            uuidString.appendFormat("%02x", def.extractor_uuid.b[i]);
+        }
+    }
+    ~ExtractorPlugin() {
+        if (libHandle != nullptr) {
+            ALOGV("closing handle for %s %d", libPath.c_str(), def.extractor_version);
+            dlclose(libHandle);
+        }
+    }
+};
+
+Mutex MediaExtractorFactory::gPluginMutex;
+std::shared_ptr<List<sp<ExtractorPlugin>>> MediaExtractorFactory::gPlugins;
+bool MediaExtractorFactory::gPluginsRegistered = false;
 
 // static
 MediaExtractor::CreatorFunc MediaExtractorFactory::sniff(
-        const sp<DataSource> &source, String8 *mimeType, float *confidence, sp<AMessage> *meta) {
+        const sp<DataSource> &source, String8 *mimeType, float *confidence, sp<AMessage> *meta,
+        sp<ExtractorPlugin> &plugin) {
     *mimeType = "";
     *confidence = 0.0f;
     meta->clear();
 
+    std::shared_ptr<List<sp<ExtractorPlugin>>> plugins;
     {
-        Mutex::Autolock autoLock(gSnifferMutex);
-        if (!gSniffersRegistered) {
+        Mutex::Autolock autoLock(gPluginMutex);
+        if (!gPluginsRegistered) {
             return NULL;
         }
+        plugins = gPlugins;
     }
 
     MediaExtractor::CreatorFunc curCreator = NULL;
     MediaExtractor::CreatorFunc bestCreator = NULL;
-    for (List<MediaExtractor::ExtractorDef>::iterator it = gSniffers.begin();
-         it != gSniffers.end(); ++it) {
+    for (auto it = plugins->begin(); it != plugins->end(); ++it) {
         String8 newMimeType;
         float newConfidence;
         sp<AMessage> newMeta;
-        if ((curCreator = (*it).sniff(source, &newMimeType, &newConfidence, &newMeta))) {
+        if ((curCreator = (*it)->def.sniff(source, &newMimeType, &newConfidence, &newMeta))) {
             if (newConfidence > *confidence) {
                 *mimeType = newMimeType;
                 *confidence = newConfidence;
                 *meta = newMeta;
+                plugin = *it;
                 bestCreator = curCreator;
             }
         }
@@ -200,94 +204,127 @@
 }
 
 // static
-void MediaExtractorFactory::RegisterSniffer_l(const MediaExtractor::ExtractorDef &def) {
+void MediaExtractorFactory::RegisterExtractor(const sp<ExtractorPlugin> &plugin,
+        List<sp<ExtractorPlugin>> &pluginList) {
     // sanity check check struct version, uuid, name
-    if (def.def_version == 0 || def.def_version > MediaExtractor::EXTRACTORDEF_VERSION) {
-        ALOGE("don't understand extractor format %u, ignoring.", def.def_version);
+    if (plugin->def.def_version == 0
+            || plugin->def.def_version > MediaExtractor::EXTRACTORDEF_VERSION) {
+        ALOGE("don't understand extractor format %u, ignoring.", plugin->def.def_version);
         return;
     }
-    if (memcmp(&def.extractor_uuid, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) == 0) {
+    if (memcmp(&plugin->def.extractor_uuid, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) == 0) {
         ALOGE("invalid UUID, ignoring");
         return;
     }
-    if (def.extractor_name == NULL || strlen(def.extractor_name) == 0) {
+    if (plugin->def.extractor_name == NULL || strlen(plugin->def.extractor_name) == 0) {
         ALOGE("extractors should have a name, ignoring");
         return;
     }
 
-    for (List<MediaExtractor::ExtractorDef>::iterator it = gSniffers.begin();
-            it != gSniffers.end(); ++it) {
-        if (memcmp(&((*it).extractor_uuid), &def.extractor_uuid, 16) == 0) {
+    for (auto it = pluginList.begin(); it != pluginList.end(); ++it) {
+        if (memcmp(&((*it)->def.extractor_uuid), &plugin->def.extractor_uuid, 16) == 0) {
             // there's already an extractor with the same uuid
-            if ((*it).extractor_version < def.extractor_version) {
+            if ((*it)->def.extractor_version < plugin->def.extractor_version) {
                 // this one is newer, replace the old one
                 ALOGW("replacing extractor '%s' version %u with version %u",
-                        def.extractor_name,
-                        (*it).extractor_version,
-                        def.extractor_version);
-                gSniffers.erase(it);
+                        plugin->def.extractor_name,
+                        (*it)->def.extractor_version,
+                        plugin->def.extractor_version);
+                pluginList.erase(it);
                 break;
             } else {
                 ALOGW("ignoring extractor '%s' version %u in favor of version %u",
-                        def.extractor_name,
-                        def.extractor_version,
-                        (*it).extractor_version);
+                        plugin->def.extractor_name,
+                        plugin->def.extractor_version,
+                        (*it)->def.extractor_version);
                 return;
             }
         }
     }
-    ALOGV("registering extractor for %s", def.extractor_name);
-    gSniffers.push_back(def);
+    ALOGV("registering extractor for %s", plugin->def.extractor_name);
+    pluginList.push_back(plugin);
 }
 
-// static
-void MediaExtractorFactory::RegisterDefaultSniffers() {
-    Mutex::Autolock autoLock(gSnifferMutex);
-    if (gSniffersRegistered) {
-        return;
-    }
-
-    auto registerExtractors = [](const char *libDirPath) -> void {
-        DIR *libDir = opendir(libDirPath);
-        if (libDir) {
-            struct dirent* libEntry;
-            while ((libEntry = readdir(libDir))) {
-                String8 libPath = String8(libDirPath) + libEntry->d_name;
+//static
+void MediaExtractorFactory::RegisterExtractors(
+        const char *apkPath, List<sp<ExtractorPlugin>> &pluginList) {
+    ALOGV("search for plugins at %s", apkPath);
+    ZipArchiveHandle zipHandle;
+    int32_t ret = OpenArchive(apkPath, &zipHandle);
+    if (ret == 0) {
+        char abi[PROPERTY_VALUE_MAX];
+        property_get("ro.product.cpu.abi", abi, "arm64-v8a");
+        ZipString prefix(String8::format("lib/%s/", abi).c_str());
+        ZipString suffix("extractor.so");
+        void* cookie;
+        ret = StartIteration(zipHandle, &cookie, &prefix, &suffix);
+        if (ret == 0) {
+            ZipEntry entry;
+            ZipString name;
+            while (Next(cookie, &entry, &name) == 0) {
+                String8 libPath = String8(apkPath) + "!/" +
+                    String8(reinterpret_cast<const char*>(name.name), name.name_length);
                 void *libHandle = dlopen(libPath.string(), RTLD_NOW | RTLD_LOCAL);
                 if (libHandle) {
-                    MediaExtractor::GetExtractorDef getsniffer =
-                            (MediaExtractor::GetExtractorDef) dlsym(libHandle, "GETEXTRACTORDEF");
-                    if (getsniffer) {
+                    MediaExtractor::GetExtractorDef getDef =
+                        (MediaExtractor::GetExtractorDef) dlsym(libHandle, "GETEXTRACTORDEF");
+                    if (getDef) {
                         ALOGV("registering sniffer for %s", libPath.string());
-                        RegisterSniffer_l(getsniffer());
+                        RegisterExtractor(
+                                new ExtractorPlugin(getDef(), libHandle, libPath), pluginList);
                     } else {
                         ALOGW("%s does not contain sniffer", libPath.string());
                         dlclose(libHandle);
                     }
                 } else {
-                    ALOGW("couldn't dlopen(%s)", libPath.string());
+                    ALOGW("couldn't dlopen(%s) %s", libPath.string(), strerror(errno));
                 }
             }
-
-            closedir(libDir);
+            EndIteration(cookie);
         } else {
-            ALOGE("couldn't opendir(%s)", libDirPath);
+            ALOGW("couldn't find plugins from %s, %d", apkPath, ret);
         }
-    };
+        CloseArchive(zipHandle);
+    } else {
+        ALOGW("couldn't open(%s) %d", apkPath, ret);
+    }
+}
 
-    registerExtractors("/system/lib"
-#ifdef __LP64__
-            "64"
-#endif
-            "/extractors/");
+// static
+void MediaExtractorFactory::UpdateExtractors(const char *newUpdateApkPath) {
+    Mutex::Autolock autoLock(gPluginMutex);
+    if (newUpdateApkPath != nullptr) {
+        gPluginsRegistered = false;
+    }
+    if (gPluginsRegistered) {
+        return;
+    }
 
-    registerExtractors("/vendor/lib"
-#ifdef __LP64__
-            "64"
-#endif
-            "/extractors/");
+    std::shared_ptr<List<sp<ExtractorPlugin>>> newList(new List<sp<ExtractorPlugin>>());
 
-    gSniffersRegistered = true;
+    RegisterExtractors(kSystemApkPath, *newList);
+
+    if (newUpdateApkPath != nullptr) {
+        RegisterExtractors(newUpdateApkPath, *newList);
+    }
+
+    gPlugins = newList;
+    gPluginsRegistered = true;
+}
+
+status_t MediaExtractorFactory::dump(int fd, const Vector<String16>&) {
+    Mutex::Autolock autoLock(gPluginMutex);
+    String8 out;
+    out.append("Available extractors:\n");
+    for (auto it = gPlugins->begin(); it != gPlugins->end(); ++it) {
+        out.appendFormat("  %25s: uuid(%s), version(%u), path(%s)\n",
+                (*it)->def.extractor_name,
+                (*it)->uuidString.c_str(),
+                (*it)->def.extractor_version,
+                (*it)->libPath.c_str());
+    }
+    write(fd, out.string(), out.size());
+    return OK;
 }
 
 
diff --git a/media/libstagefright/RemoteMediaExtractor.cpp b/media/libstagefright/RemoteMediaExtractor.cpp
index 1dd7986..12654d9 100644
--- a/media/libstagefright/RemoteMediaExtractor.cpp
+++ b/media/libstagefright/RemoteMediaExtractor.cpp
@@ -14,16 +14,75 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
+#define LOG_TAG "RemoteMediaExtractor"
+#include <utils/Log.h>
+
 #include <media/stagefright/InterfaceUtils.h>
+#include <media/MediaAnalyticsItem.h>
 #include <media/MediaSource.h>
 #include <media/stagefright/RemoteMediaExtractor.h>
 
+// still doing some on/off toggling here.
+#define MEDIA_LOG       1
+
 namespace android {
 
-RemoteMediaExtractor::RemoteMediaExtractor(const sp<MediaExtractor> &extractor)
-    :mExtractor(extractor) {}
+// key for media statistics
+static const char *kKeyExtractor = "extractor";
 
-RemoteMediaExtractor::~RemoteMediaExtractor() {}
+// attrs for media statistics
+static const char *kExtractorMime = "android.media.mediaextractor.mime";
+static const char *kExtractorTracks = "android.media.mediaextractor.ntrk";
+static const char *kExtractorFormat = "android.media.mediaextractor.fmt";
+
+RemoteMediaExtractor::RemoteMediaExtractor(
+        const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin)
+    :mExtractor(extractor),
+    mExtractorPlugin(plugin) {
+
+    mAnalyticsItem = nullptr;
+    if (MEDIA_LOG) {
+        mAnalyticsItem = new MediaAnalyticsItem(kKeyExtractor);
+        (void) mAnalyticsItem->generateSessionID();
+
+        // track the container format (mpeg, aac, wvm, etc)
+        size_t ntracks = extractor->countTracks();
+        mAnalyticsItem->setCString(kExtractorFormat, extractor->name());
+        // tracks (size_t)
+        mAnalyticsItem->setInt32(kExtractorTracks, ntracks);
+        // metadata
+        sp<MetaData> pMetaData = extractor->getMetaData();
+        if (pMetaData != nullptr) {
+            String8 xx = pMetaData->toString();
+            // 'titl' -- but this verges into PII
+            // 'mime'
+            const char *mime = nullptr;
+            if (pMetaData->findCString(kKeyMIMEType, &mime)) {
+                mAnalyticsItem->setCString(kExtractorMime,  mime);
+            }
+            // what else is interesting and not already available?
+        }
+    }
+}
+
+RemoteMediaExtractor::~RemoteMediaExtractor() {
+    mExtractor = nullptr;
+    mExtractorPlugin = nullptr;
+    // log the current record, provided it has some information worth recording
+    if (MEDIA_LOG) {
+        if (mAnalyticsItem != nullptr) {
+            if (mAnalyticsItem->count() > 0) {
+                mAnalyticsItem->setFinalized(true);
+                mAnalyticsItem->selfrecord();
+            }
+        }
+    }
+    if (mAnalyticsItem != nullptr) {
+        delete mAnalyticsItem;
+        mAnalyticsItem = nullptr;
+    }
+}
 
 size_t RemoteMediaExtractor::countTracks() {
     return mExtractor->countTracks();
@@ -31,7 +90,8 @@
 
 sp<IMediaSource> RemoteMediaExtractor::getTrack(size_t index) {
     sp<MediaSource> source = mExtractor->getTrack(index);
-    return (source.get() == nullptr) ? nullptr : CreateIMediaSourceFromMediaSource(source);
+    return (source.get() == nullptr)
+            ? nullptr : CreateIMediaSourceFromMediaSource(source, mExtractorPlugin);
 }
 
 sp<MetaData> RemoteMediaExtractor::getTrackMetaData(size_t index, uint32_t flags) {
@@ -43,7 +103,12 @@
 }
 
 status_t RemoteMediaExtractor::getMetrics(Parcel *reply) {
-    return mExtractor->getMetrics(reply);
+    if (mAnalyticsItem == nullptr || reply == nullptr) {
+        return UNKNOWN_ERROR;
+    }
+
+    mAnalyticsItem->writeToParcel(reply);
+    return OK;
 }
 
 uint32_t RemoteMediaExtractor::flags() const {
@@ -73,11 +138,12 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 // static
-sp<IMediaExtractor> RemoteMediaExtractor::wrap(const sp<MediaExtractor> &extractor) {
+sp<IMediaExtractor> RemoteMediaExtractor::wrap(
+        const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin) {
     if (extractor.get() == nullptr) {
         return nullptr;
     }
-    return new RemoteMediaExtractor(extractor);
+    return new RemoteMediaExtractor(extractor, plugin);
 }
 
 }  // namespace android
diff --git a/media/libstagefright/RemoteMediaSource.cpp b/media/libstagefright/RemoteMediaSource.cpp
index 866d163..d97329c 100644
--- a/media/libstagefright/RemoteMediaSource.cpp
+++ b/media/libstagefright/RemoteMediaSource.cpp
@@ -19,10 +19,14 @@
 
 namespace android {
 
-RemoteMediaSource::RemoteMediaSource(const sp<MediaSource> &source)
-    :mSource(source) {}
+RemoteMediaSource::RemoteMediaSource(const sp<MediaSource> &source, const sp<RefBase> &plugin)
+    :mSource(source),
+    mExtractorPlugin(plugin) {}
 
-RemoteMediaSource::~RemoteMediaSource() {}
+RemoteMediaSource::~RemoteMediaSource() {
+    mSource = nullptr;
+    mExtractorPlugin = nullptr;
+}
 
 status_t RemoteMediaSource::start(MetaData *params) {
     return mSource->start(params);
@@ -51,11 +55,11 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 // static
-sp<IMediaSource> RemoteMediaSource::wrap(const sp<MediaSource> &source) {
+sp<IMediaSource> RemoteMediaSource::wrap(const sp<MediaSource> &source, const sp<RefBase> &plugin) {
     if (source.get() == nullptr) {
         return nullptr;
     }
-    return new RemoteMediaSource(source);
+    return new RemoteMediaSource(source, plugin);
 }
 
 }  // namespace android
diff --git a/media/libstagefright/SurfaceUtils.cpp b/media/libstagefright/SurfaceUtils.cpp
index b7c1598..9e11a94 100644
--- a/media/libstagefright/SurfaceUtils.cpp
+++ b/media/libstagefright/SurfaceUtils.cpp
@@ -18,8 +18,8 @@
 #define LOG_TAG "SurfaceUtils"
 #include <utils/Log.h>
 
+#include <media/hardware/VideoAPI.h>
 #include <media/stagefright/SurfaceUtils.h>
-
 #include <gui/Surface.h>
 
 namespace android {
@@ -128,6 +128,40 @@
     return NO_ERROR;
 }
 
+void setNativeWindowHdrMetadata(ANativeWindow *nativeWindow, HDRStaticInfo *info) {
+    struct android_smpte2086_metadata smpte2086_meta = {
+            .displayPrimaryRed = {
+                    info->sType1.mR.x * 0.00002f,
+                    info->sType1.mR.y * 0.00002f
+            },
+            .displayPrimaryGreen = {
+                    info->sType1.mG.x * 0.00002f,
+                    info->sType1.mG.y * 0.00002f
+            },
+            .displayPrimaryBlue = {
+                    info->sType1.mB.x * 0.00002f,
+                    info->sType1.mB.y * 0.00002f
+            },
+            .whitePoint = {
+                    info->sType1.mW.x * 0.00002f,
+                    info->sType1.mW.y * 0.00002f
+            },
+            .maxLuminance = (float) info->sType1.mMaxDisplayLuminance,
+            .minLuminance = info->sType1.mMinDisplayLuminance * 0.0001f
+    };
+
+    int err = native_window_set_buffers_smpte2086_metadata(nativeWindow, &smpte2086_meta);
+    ALOGW_IF(err != 0, "failed to set smpte2086 metadata on surface (%d)", err);
+
+    struct android_cta861_3_metadata cta861_meta = {
+            .maxContentLightLevel = (float) info->sType1.mMaxContentLightLevel,
+            .maxFrameAverageLightLevel = (float) info->sType1.mMaxFrameAverageLightLevel
+    };
+
+    err = native_window_set_buffers_cta861_3_metadata(nativeWindow, &cta861_meta);
+    ALOGW_IF(err != 0, "failed to set cta861_3 metadata on surface (%d)", err);
+}
+
 status_t pushBlankBuffersToNativeWindow(ANativeWindow *nativeWindow /* nonnull */) {
     status_t err = NO_ERROR;
     ANativeWindowBuffer* anb = NULL;
diff --git a/media/libstagefright/codec2/Android.bp b/media/libstagefright/codec2/Android.bp
index 74609e8..ee5c3eb 100644
--- a/media/libstagefright/codec2/Android.bp
+++ b/media/libstagefright/codec2/Android.bp
@@ -42,27 +42,22 @@
         "optional",
     ],
 
-    srcs: ["SimpleC2Component.cpp"],
+    srcs: [
+        "SimpleC2Component.cpp",
+        "SimpleC2Interface.cpp",
+    ],
 
     include_dirs: [
-        "frameworks/av/media/libstagefright/codec2/include",
     ],
 
     shared_libs: [
-        "android.hardware.graphics.allocator@2.0",
-        "android.hardware.graphics.mapper@2.0",
-        "libhidlbase",
-        "libion",
         "liblog",
         "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
         "libstagefright_foundation",
 	"libutils",
     ],
 
-    static_libs: [
-        "libstagefright_codec2_vndk",
-    ],
-
     sanitize: {
         misc_undefined: [
             "unsigned-integer-overflow",
diff --git a/media/libstagefright/codec2/SimpleC2Component.cpp b/media/libstagefright/codec2/SimpleC2Component.cpp
index 0e4a354..4d75a31 100644
--- a/media/libstagefright/codec2/SimpleC2Component.cpp
+++ b/media/libstagefright/codec2/SimpleC2Component.cpp
@@ -18,12 +18,39 @@
 #define LOG_TAG "SimpleC2Component"
 #include <media/stagefright/foundation/ADebug.h>
 
-#include <C2PlatformSupport.h>
+#include <inttypes.h>
 
+#include <C2PlatformSupport.h>
 #include <SimpleC2Component.h>
 
 namespace android {
 
+std::unique_ptr<C2Work> SimpleC2Component::WorkQueue::pop_front() {
+    std::unique_ptr<C2Work> work = std::move(mQueue.front().work);
+    mQueue.pop_front();
+    return work;
+}
+
+void SimpleC2Component::WorkQueue::push_back(std::unique_ptr<C2Work> work) {
+    mQueue.push_back({ std::move(work), NO_DRAIN });
+}
+
+bool SimpleC2Component::WorkQueue::empty() const {
+    return mQueue.empty();
+}
+
+void SimpleC2Component::WorkQueue::clear() {
+    mQueue.clear();
+}
+
+uint32_t SimpleC2Component::WorkQueue::drainMode() const {
+    return mQueue.front().drainMode;
+}
+
+void SimpleC2Component::WorkQueue::markDrain(uint32_t drainMode) {
+    mQueue.push_back({ nullptr, drainMode });
+}
+
 SimpleC2Component::SimpleC2Component(
         const std::shared_ptr<C2ComponentInterface> &intf)
     : mIntf(intf) {
@@ -55,7 +82,7 @@
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
         while (!items->empty()) {
-            queue->mQueue.push_back(std::move(items->front()));
+            queue->push_back(std::move(items->front()));
             items->pop_front();
         }
         queue->mCondition.broadcast();
@@ -79,10 +106,12 @@
     }
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
-        ++queue->mGeneration;
-        while (!queue->mQueue.empty()) {
-            flushedWork->push_back(std::move(queue->mQueue.front()));
-            queue->mQueue.pop_front();
+        queue->incGeneration();
+        while (!queue->empty()) {
+            std::unique_ptr<C2Work> work = queue->pop_front();
+            if (work) {
+                flushedWork->push_back(std::move(work));
+            }
         }
     }
     {
@@ -96,8 +125,10 @@
     return onFlush_sm();
 }
 
-c2_status_t SimpleC2Component::drain_nb(drain_mode_t drainThrough) {
-    (void) drainThrough;
+c2_status_t SimpleC2Component::drain_nb(drain_mode_t drainMode) {
+    if (drainMode == DRAIN_CHAIN) {
+        return C2_OMITTED;
+    }
     {
         Mutexed<ExecState>::Locked state(mExecState);
         if (state->mState != RUNNING) {
@@ -106,14 +137,11 @@
     }
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
-        if (!queue->mQueue.empty()) {
-            const std::unique_ptr<C2Work> &work = queue->mQueue.back();
-            work->input.flags = (C2BufferPack::flags_t)(work->input.flags | C2BufferPack::FLAG_END_OF_STREAM);
-            return C2_OK;
-        }
+        queue->markDrain(drainMode);
+        queue->mCondition.broadcast();
     }
 
-    return onDrain_nb();
+    return C2_OK;
 }
 
 c2_status_t SimpleC2Component::start() {
@@ -161,7 +189,7 @@
     }
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
-        queue->mQueue.clear();
+        queue->clear();
     }
     {
         Mutexed<PendingWork>::Locked pending(mPendingWork);
@@ -181,7 +209,7 @@
     }
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
-        queue->mQueue.clear();
+        queue->clear();
     }
     {
         Mutexed<PendingWork>::Locked pending(mPendingWork);
@@ -192,11 +220,13 @@
 }
 
 c2_status_t SimpleC2Component::release() {
+    std::thread releasing;
     {
         Mutexed<ExecState>::Locked state(mExecState);
-        mExitRequested = true;
-        state->mThread.join();
+        releasing = std::move(state->mThread);
     }
+    mExitRequested = true;
+    releasing.join();
     onRelease();
     return C2_OK;
 }
@@ -221,6 +251,7 @@
     {
         Mutexed<PendingWork>::Locked pending(mPendingWork);
         if (pending->count(frameIndex) == 0) {
+            ALOGW("unknown frame index: %" PRIu64, frameIndex);
             return;
         }
         work = std::move(pending->at(frameIndex));
@@ -230,34 +261,56 @@
         fillWork(work);
         Mutexed<ExecState>::Locked state(mExecState);
         state->mListener->onWorkDone_nb(shared_from_this(), vec(work));
+        ALOGV("returning pending work");
     }
 }
 
 void SimpleC2Component::processQueue() {
     std::unique_ptr<C2Work> work;
     uint64_t generation;
+    int32_t drainMode;
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
         nsecs_t deadline = systemTime() + ms2ns(250);
-        while (queue->mQueue.empty()) {
-            status_t err = queue.waitForConditionRelative(
-                    queue->mCondition, std::max(deadline - systemTime(), (nsecs_t)0));
+        while (queue->empty()) {
+            nsecs_t now = systemTime();
+            if (now >= deadline) {
+                return;
+            }
+            status_t err = queue.waitForConditionRelative(queue->mCondition, deadline - now);
             if (err == TIMED_OUT) {
                 return;
             }
         }
 
-        generation = queue->mGeneration;
-        work = std::move(queue->mQueue.front());
-        queue->mQueue.pop_front();
-    }
-    if (!work) {
-        return;
+        generation = queue->generation();
+        drainMode = queue->drainMode();
+        work = queue->pop_front();
     }
 
-    // TODO: grab pool ID from intf
     if (!mOutputBlockPool) {
-        c2_status_t err = GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, shared_from_this(), &mOutputBlockPool);
+        c2_status_t err = [this] {
+            // TODO: don't use query_vb
+            C2StreamFormatConfig::output outputFormat(0u);
+            c2_status_t err = intf()->query_vb(
+                    { &outputFormat },
+                    {},
+                    C2_DONT_BLOCK,
+                    nullptr);
+            if (err != C2_OK) {
+                return err;
+            }
+            err = GetCodec2BlockPool(
+                    (outputFormat.value == C2FormatVideo)
+                    ? C2BlockPool::BASIC_GRAPHIC
+                    : C2BlockPool::BASIC_LINEAR,
+                    shared_from_this(),
+                    &mOutputBlockPool);
+            if (err != C2_OK) {
+                return err;
+            }
+            return C2_OK;
+        }();
         if (err != C2_OK) {
             Mutexed<ExecState>::Locked state(mExecState);
             state->mListener->onError_nb(shared_from_this(), err);
@@ -265,10 +318,20 @@
         }
     }
 
-    bool done = process(work, mOutputBlockPool);
+    if (!work) {
+        c2_status_t err = drain(drainMode, mOutputBlockPool);
+        if (err != C2_OK) {
+            Mutexed<ExecState>::Locked state(mExecState);
+            state->mListener->onError_nb(shared_from_this(), err);
+        }
+        return;
+    }
+
+    process(work, mOutputBlockPool);
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
-        if (queue->mGeneration != generation) {
+        if (queue->generation() != generation) {
+            ALOGW("work form old generation: was %" PRIu64 " now %" PRIu64, queue->generation(), generation);
             work->result = C2_NOT_FOUND;
             queue.unlock();
             {
@@ -279,10 +342,12 @@
             return;
         }
     }
-    if (done) {
+    if (work->worklets_processed != 0u) {
         Mutexed<ExecState>::Locked state(mExecState);
+        ALOGV("returning this work");
         state->mListener->onWorkDone_nb(shared_from_this(), vec(work));
     } else {
+        ALOGV("queue pending work");
         std::unique_ptr<C2Work> unexpected;
         {
             Mutexed<PendingWork>::Locked pending(mPendingWork);
@@ -301,4 +366,45 @@
     }
 }
 
+namespace {
+
+class GraphicBuffer : public C2Buffer {
+public:
+    GraphicBuffer(
+            const std::shared_ptr<C2GraphicBlock> &block,
+            const C2Rect &crop)
+        : C2Buffer({ block->share(crop, ::android::C2Fence()) }) {}
+};
+
+
+class LinearBuffer : public C2Buffer {
+public:
+    LinearBuffer(
+            const std::shared_ptr<C2LinearBlock> &block, size_t offset, size_t size)
+        : C2Buffer({ block->share(offset, size, ::android::C2Fence()) }) {}
+};
+
+}  // namespace
+
+std::shared_ptr<C2Buffer> SimpleC2Component::createLinearBuffer(
+        const std::shared_ptr<C2LinearBlock> &block) {
+    return createLinearBuffer(block, block->offset(), block->size());
+}
+
+std::shared_ptr<C2Buffer> SimpleC2Component::createLinearBuffer(
+        const std::shared_ptr<C2LinearBlock> &block, size_t offset, size_t size) {
+    return std::make_shared<LinearBuffer>(block, offset, size);
+}
+
+std::shared_ptr<C2Buffer> SimpleC2Component::createGraphicBuffer(
+        const std::shared_ptr<C2GraphicBlock> &block) {
+    return createGraphicBuffer(block, C2Rect(0, 0, block->width(), block->height()));
+}
+
+std::shared_ptr<C2Buffer> SimpleC2Component::createGraphicBuffer(
+        const std::shared_ptr<C2GraphicBlock> &block,
+        const C2Rect &crop) {
+    return std::make_shared<GraphicBuffer>(block, crop);
+}
+
 } // namespace android
diff --git a/media/libstagefright/codec2/SimpleC2Interface.cpp b/media/libstagefright/codec2/SimpleC2Interface.cpp
new file mode 100644
index 0000000..f9cab26
--- /dev/null
+++ b/media/libstagefright/codec2/SimpleC2Interface.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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 "SimpleC2Interface"
+#include <utils/Log.h>
+
+#include <SimpleC2Interface.h>
+
+namespace android {
+
+c2_status_t SimpleC2Interface::query_vb(
+        const std::vector<C2Param* const> &stackParams,
+        const std::vector<C2Param::Index> &heapParamIndices,
+        c2_blocking_t mayBlock,
+        std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
+    (void)mayBlock;
+
+    for (C2Param* const param : stackParams) {
+        if (param->coreIndex() != C2StreamFormatConfig::CORE_INDEX
+                || !param->forStream()
+                || param->stream() != 0u) {
+            param->invalidate();
+            continue;
+        }
+        if (param->forInput()) {
+            param->updateFrom(mInputFormat);
+        } else {
+            param->updateFrom(mOutputFormat);
+        }
+    }
+    if (heapParams) {
+        heapParams->clear();
+        for (const auto &index : heapParamIndices) {
+            if (index.coreIndex() != C2StreamFormatConfig::CORE_INDEX
+                    || !index.forStream()
+                    || index.stream() != 0u) {
+                heapParams->push_back(nullptr);
+            }
+            if (index.forInput()) {
+                heapParams->push_back(C2Param::Copy(mInputFormat));
+            } else {
+                heapParams->push_back(C2Param::Copy(mOutputFormat));
+            }
+        }
+    }
+
+    return C2_OK;
+}
+
+} // namespace android
diff --git a/media/libstagefright/codec2/include/C2.h b/media/libstagefright/codec2/include/C2.h
index a01d674..fd99cce 100644
--- a/media/libstagefright/codec2/include/C2.h
+++ b/media/libstagefright/codec2/include/C2.h
@@ -191,16 +191,25 @@
 #define C2_INTERNAL __attribute__((internal_linkage))
 
 #define DEFINE_OTHER_COMPARISON_OPERATORS(type) \
-    inline bool operator!=(const type &other) { return !(*this == other); } \
-    inline bool operator<=(const type &other) { return (*this == other) || (*this < other); } \
-    inline bool operator>=(const type &other) { return !(*this < other); } \
-    inline bool operator>(const type &other) { return !(*this < other) && !(*this == other); }
+    inline bool operator!=(const type &other) const { return !(*this == other); } \
+    inline bool operator<=(const type &other) const { return (*this == other) || (*this < other); } \
+    inline bool operator>=(const type &other) const { return !(*this < other); } \
+    inline bool operator>(const type &other) const { return !(*this < other) && !(*this == other); }
 
 #define DEFINE_FIELD_BASED_COMPARISON_OPERATORS(type, field) \
     inline bool operator<(const type &other) const { return field < other.field; } \
     inline bool operator==(const type &other) const { return field == other.field; } \
     DEFINE_OTHER_COMPARISON_OPERATORS(type)
 
+#define DEFINE_FIELD_AND_MASK_BASED_COMPARISON_OPERATORS(type, field, mask) \
+    inline bool operator<(const type &other) const { \
+        return (field & mask) < (other.field & (mask)); \
+    } \
+    inline bool operator==(const type &other) const { \
+        return (field & mask) == (other.field & (mask)); \
+    } \
+    DEFINE_OTHER_COMPARISON_OPERATORS(type)
+
 /// \cond INTERNAL
 
 /// \defgroup utils_internal
@@ -213,7 +222,7 @@
 struct c2_types<T> {
     typedef typename std::decay<T>::type wide_type;
     typedef wide_type narrow_type;
-    typedef wide_type mintype;
+    typedef wide_type min_type; // type for min(T...)
 };
 
 /** specialization for two types */
@@ -229,7 +238,7 @@
     typedef typename std::decay<
             typename std::conditional<sizeof(T) < sizeof(U), T, U>::type>::type narrow_type;
     typedef typename std::conditional<
-            std::is_signed<T>::value, wide_type, narrow_type>::type mintype;
+            std::is_signed<T>::value, wide_type, narrow_type>::type min_type;
 };
 
 /// @}
@@ -249,7 +258,7 @@
     /** Narrowest type of the template parameter types. */
     typedef typename c2_types<typename c2_types<T, U>::narrow_type, V...>::narrow_type narrow_type;
     /** Type that accommodates the minimum value for any input for the template parameter types. */
-    typedef typename c2_types<typename c2_types<T, U>::mintype, V...>::mintype mintype;
+    typedef typename c2_types<typename c2_types<T, U>::min_type, V...>::min_type min_type;
 };
 
 /**
@@ -282,11 +291,11 @@
  *  \ingroup utils_internal
  * specialization for two values */
 template<typename T, typename U>
-inline constexpr typename c2_types<T, U>::mintype c2_min(const T a, const U b) {
+inline constexpr typename c2_types<T, U>::min_type c2_min(const T a, const U b) {
     typedef typename c2_types<T, U>::wide_type wide_type;
     return ({
         wide_type a_(a), b_(b);
-        static_cast<typename c2_types<T, U>::mintype>(a_ < b_ ? a_ : b_);
+        static_cast<typename c2_types<T, U>::min_type>(a_ < b_ ? a_ : b_);
     });
 }
 
@@ -302,12 +311,12 @@
  * @return the smallest of the input arguments.
  */
 template<typename T, typename U, typename... V>
-constexpr typename c2_types<T, U, V...>::mintype c2_min(const T a, const U b, const V ... c) {
-    typedef typename c2_types<U, V...>::mintype rest_type;
+constexpr typename c2_types<T, U, V...>::min_type c2_min(const T a, const U b, const V ... c) {
+    typedef typename c2_types<U, V...>::min_type rest_type;
     typedef typename c2_types<T, rest_type>::wide_type wide_type;
     return ({
         wide_type a_(a), b_(c2_min(b, c...));
-        static_cast<typename c2_types<T, rest_type>::mintype>(a_ < b_ ? a_ : b_);
+        static_cast<typename c2_types<T, rest_type>::min_type>(a_ < b_ ? a_ : b_);
     });
 }
 
diff --git a/media/libstagefright/codec2/include/C2Buffer.h b/media/libstagefright/codec2/include/C2Buffer.h
index d978e42..df9362c 100644
--- a/media/libstagefright/codec2/include/C2Buffer.h
+++ b/media/libstagefright/codec2/include/C2Buffer.h
@@ -23,8 +23,6 @@
 #include <list>
 #include <memory>
 
-typedef int C2Fence;
-
 #ifdef __ANDROID__
 
 // #include <system/window.h>
@@ -738,20 +736,20 @@
 /// \name Planar capacity interface
 /// @{
 public:
-    inline uint32_t width() const { return mWidth; }
-    inline uint32_t height() const { return mHeight; }
+    inline uint32_t width() const { return _mWidth; }
+    inline uint32_t height() const { return _mHeight; }
 
 protected:
     inline _C2PlanarCapacityAspect(uint32_t width, uint32_t height)
-      : mWidth(width), mHeight(height) { }
+      : _mWidth(width), _mHeight(height) { }
 
     inline _C2PlanarCapacityAspect(const _C2PlanarCapacityAspect *parent)
-        : mWidth(parent == nullptr ? 0 : parent->width()),
-          mHeight(parent == nullptr ? 0 : parent->height()) { }
+        : _mWidth(parent == nullptr ? 0 : parent->width()),
+          _mHeight(parent == nullptr ? 0 : parent->height()) { }
 
 private:
-    const uint32_t mWidth;
-    const uint32_t mHeight;
+    const uint32_t _mWidth;
+    const uint32_t _mHeight;
 /// @}
 };
 
@@ -762,25 +760,25 @@
  */
 struct C2Rect {
 // public:
-    uint32_t mLeft;
-    uint32_t mTop;
-    uint32_t mWidth;
-    uint32_t mHeight;
+    uint32_t left;
+    uint32_t top;
+    uint32_t width;
+    uint32_t height;
 
-    constexpr inline C2Rect(uint32_t width, uint32_t height)
-        : C2Rect(width, height, 0, 0) { }
+    constexpr inline C2Rect(uint32_t width_, uint32_t height_)
+        : C2Rect(width_, height_, 0, 0) { }
 
-    constexpr inline C2Rect(uint32_t width, uint32_t height, uint32_t left, uint32_t top)
-        : mLeft(left), mTop(top), mWidth(width), mHeight(height) { }
+    constexpr inline C2Rect(uint32_t width_, uint32_t height_, uint32_t left_, uint32_t top_)
+        : left(left_), top(top_), width(width_), height(height_) { }
 
     // utility methods
 
     inline bool isEmpty() const {
-        return mWidth == 0 || mHeight == 0;
+        return width == 0 || height == 0;
     }
 
     inline bool isValid() const {
-        return mLeft <= ~mWidth && mTop <= ~mHeight;
+        return left <= ~width && top <= ~height;
     }
 
     inline operator bool() const {
@@ -797,9 +795,9 @@
         } else if (other.isEmpty()) {
             return true;
         } else {
-            return mLeft <= other.mLeft && mTop <= other.mTop
-                    && mLeft + mWidth >= other.mLeft + other.mWidth
-                    && mTop + mHeight >= other.mTop + other.mHeight;
+            return left <= other.left && top <= other.top
+                    && left + width >= other.left + other.width
+                    && top + height >= other.top + other.height;
         }
     }
 
@@ -809,8 +807,8 @@
         } else if (isEmpty()) {
             return other.isEmpty();
         } else {
-            return mLeft == other.mLeft && mTop == other.mTop
-                    && mWidth == other.mWidth && mHeight == other.mHeight;
+            return left == other.left && top == other.top
+                    && width == other.width && height == other.height;
         }
     }
 
@@ -836,78 +834,104 @@
 };
 
 /**
- * C2PlaneInfo: information on the layout of flexible planes.
+ * C2PlaneInfo: information on the layout of a singe flexible plane.
  *
  * Public fields without getters/setters.
  */
 struct C2PlaneInfo {
-// public:
-    enum Channel : uint32_t {
-        Y,
-        R,
-        G,
-        B,
-        A,
-        Cr,
-        Cb,
-    } mChannel;
+//public:
+    enum channel_t : uint32_t {
+        CHANNEL_Y,  ///< luma
+        CHANNEL_R,  ///< red
+        CHANNEL_G,  ///< green
+        CHANNEL_B,  ///< blue
+        CHANNEL_A,  ///< alpha
+        CHANNEL_CR, ///< Cr
+        CHANNEL_CB, ///< Cb
+    } channel;
 
-    int32_t mColInc;               // column increment in bytes. may be negative
-    int32_t mRowInc;               // row increment in bytes. may be negative
-    uint32_t mHorizSubsampling;    // subsampling compared to width
-    uint32_t mVertSubsampling;     // subsampling compared to height
+    int32_t colInc;       ///< column increment in bytes. may be negative
+    int32_t rowInc;       ///< row increment in bytes. may be negative
+    uint32_t colSampling; ///< subsampling compared to width (must be a power of 2)
+    uint32_t rowSampling; ///< subsampling compared to height (must be a power of 2)
 
-    uint32_t mBitDepth;
-    uint32_t mAllocatedDepth;
+    uint32_t allocatedDepth; ///< size of each sample (must be a multiple of 8)
+    uint32_t bitDepth;       ///< significant bits per sample
+    /**
+     * the right shift of the significant bits in the sample. E.g. if a 10-bit significant
+     * value is laid out in a 16-bit allocation aligned to LSB (values 0-1023), rightShift
+     * would be 0 as the 16-bit value read from the sample does not need to be right shifted
+     * and can be used as is (after applying a 10-bit mask of 0x3FF).
+     *
+     * +--------+--------+
+     * |      VV|VVVVVVVV|
+     * +--------+--------+
+     *  15     8 7      0
+     *
+     * If the value is laid out aligned to MSB, rightShift would be 6, as the value read
+     * from the allocated sample must be right-shifted by 6 to get the actual sample value.
+     *
+     * +--------+--------+
+     * |VVVVVVVV|VV      |
+     * +--------+--------+
+     *  15     8 7      0
+     */
+    uint32_t rightShift;
+
+    enum endianness_t : uint32_t {
+        NATIVE,
+        LITTLE_END, // LITTLE_ENDIAN is reserved macro
+        BIG_END,    // BIG_ENDIAN is a reserved macro
+    } endianness; ///< endianness of the samples
 
     inline ssize_t minOffset(uint32_t width, uint32_t height) {
         ssize_t offs = 0;
-        if (width > 0 && mColInc < 0) {
-            offs += mColInc * (ssize_t)(width - 1);
+        if (width > 0 && colInc < 0) {
+            offs += colInc * (ssize_t)(width - 1);
         }
-        if (height > 0 && mRowInc < 0) {
-            offs += mRowInc * (ssize_t)(height - 1);
+        if (height > 0 && rowInc < 0) {
+            offs += rowInc * (ssize_t)(height - 1);
         }
         return offs;
     }
 
     inline ssize_t maxOffset(uint32_t width, uint32_t height, uint32_t allocatedDepth) {
         ssize_t offs = (allocatedDepth + 7) >> 3;
-        if (width > 0 && mColInc > 0) {
-            offs += mColInc * (ssize_t)(width - 1);
+        if (width > 0 && colInc > 0) {
+            offs += colInc * (ssize_t)(width - 1);
         }
-        if (height > 0 && mRowInc > 0) {
-            offs += mRowInc * (ssize_t)(height - 1);
+        if (height > 0 && rowInc > 0) {
+            offs += rowInc * (ssize_t)(height - 1);
         }
         return offs;
     }
 };
 
-struct C2PlaneLayout {
-public:
-    enum Type : uint32_t {
-        MEDIA_IMAGE_TYPE_UNKNOWN = 0,
-        MEDIA_IMAGE_TYPE_YUV = 0x100,
-        MEDIA_IMAGE_TYPE_YUVA,
-        MEDIA_IMAGE_TYPE_RGB,
-        MEDIA_IMAGE_TYPE_RGBA,
+struct C2PlanarLayout {
+//public:
+    enum type_t : uint32_t {
+        TYPE_UNKNOWN = 0,
+        TYPE_YUV = 0x100,
+        TYPE_YUVA,
+        TYPE_RGB,
+        TYPE_RGBA,
     };
 
-    Type mType;
-    uint32_t mNumPlanes;               // number of planes
+    type_t type;
+    uint32_t numPlanes;               // number of planes
 
-    enum PlaneIndex : uint32_t {
-        Y = 0,
-        U = 1,
-        V = 2,
-        R = 0,
-        G = 1,
-        B = 2,
-        A = 3,
+    enum plane_index_t : uint32_t {
+        PLANE_Y = 0,
+        PLANE_U = 1,
+        PLANE_V = 2,
+        PLANE_R = 0,
+        PLANE_G = 1,
+        PLANE_B = 2,
+        PLANE_A = 3,
         MAX_NUM_PLANES = 4,
     };
 
-    C2PlaneInfo mPlanes[MAX_NUM_PLANES];
+    C2PlaneInfo planes[MAX_NUM_PLANES];
 };
 
 /**
@@ -927,11 +951,11 @@
      *  Sets crop to crop intersected with [(0,0) .. (width, height)]
      */
     inline void setCrop_be(const C2Rect &crop) {
-        mCrop.mLeft = std::min(width(), crop.mLeft);
-        mCrop.mTop = std::min(height(), crop.mTop);
-        // It's guaranteed that mCrop.mLeft <= width() && mCrop.mTop <= height()
-        mCrop.mWidth = std::min(width() - mCrop.mLeft, crop.mWidth);
-        mCrop.mHeight = std::min(height() - mCrop.mTop, crop.mHeight);
+        mCrop.left = std::min(width(), crop.left);
+        mCrop.top = std::min(height(), crop.top);
+        // It's guaranteed that mCrop.left <= width() && mCrop.top <= height()
+        mCrop.width = std::min(width() - mCrop.left, crop.width);
+        mCrop.height = std::min(height() - mCrop.top, crop.height);
     }
 
     /**
@@ -940,8 +964,8 @@
      * \return true iff crop is within the dimensions of this object
      */
     inline bool setCrop(const C2Rect &crop) {
-        if (width() < crop.mWidth || height() < crop.mHeight
-                || width() - crop.mWidth < crop.mLeft || height() - crop.mHeight < crop.mTop) {
+        if (width() < crop.width || height() < crop.height
+                || width() - crop.width < crop.left || height() - crop.height < crop.top) {
             return false;
         }
         mCrop = crop;
@@ -1001,7 +1025,7 @@
     /**
      * \return layout of the graphic block to interpret the returned data.
      */
-    const C2PlaneLayout layout() const;
+    const C2PlanarLayout layout() const;
 
     /**
      * Returns a section of this view.
@@ -1022,7 +1046,7 @@
     C2GraphicView(
             const _C2PlanarCapacityAspect *parent,
             uint8_t *const *data,
-            const C2PlaneLayout& layout);
+            const C2PlanarLayout& layout);
     explicit C2GraphicView(c2_status_t error);
 
 private:
@@ -1327,25 +1351,26 @@
 // public:
     // TODO: match these to gralloc1.h
     enum Consumer : uint64_t {
-        kSoftwareRead        = GRALLOC_USAGE_SW_READ_OFTEN,
-        kRenderScriptRead    = GRALLOC_USAGE_RENDERSCRIPT,
-        kTextureRead         = GRALLOC_USAGE_HW_TEXTURE,
-        kHardwareComposer    = GRALLOC_USAGE_HW_COMPOSER,
-        kHardwareEncoder     = GRALLOC_USAGE_HW_VIDEO_ENCODER,
-        kProtectedRead       = GRALLOC_USAGE_PROTECTED,
+        // \todo do we need to distinguish often from rarely?
+        CPU_READ          = GRALLOC_USAGE_SW_READ_OFTEN,
+        RENDERSCRIPT_READ = GRALLOC_USAGE_RENDERSCRIPT,
+        HW_TEXTURE_READ   = GRALLOC_USAGE_HW_TEXTURE,
+        HW_COMPOSER_READ  = GRALLOC_USAGE_HW_COMPOSER,
+        HW_CODEC_READ     = GRALLOC_USAGE_HW_VIDEO_ENCODER,
+        READ_PROTECTED    = GRALLOC_USAGE_PROTECTED,
     };
 
     enum Producer : uint64_t {
-        kSoftwareWrite       = GRALLOC_USAGE_SW_WRITE_OFTEN,
-        kRenderScriptWrite   = GRALLOC_USAGE_RENDERSCRIPT,
-        kTextureWrite        = GRALLOC_USAGE_HW_RENDER,
-        kCompositionTarget   = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER,
-        kHardwareDecoder     = GRALLOC_USAGE_HW_VIDEO_ENCODER,
-        kProtectedWrite      = GRALLOC_USAGE_PROTECTED,
+        CPU_WRITE          = GRALLOC_USAGE_SW_WRITE_OFTEN,
+        RENDERSCRIPT_WRITE = GRALLOC_USAGE_RENDERSCRIPT,
+        HW_TEXTURE_WRITE   = GRALLOC_USAGE_HW_RENDER,
+        HW_COMPOSER_WRITE  = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER,
+        HW_CODEC_WRITE     = GRALLOC_USAGE_HW_VIDEO_ENCODER,
+        WRITE_PROTECTED    = GRALLOC_USAGE_PROTECTED,
     };
 
-    uint64_t mConsumer; // e.g. input
-    uint64_t mProducer; // e.g. output
+    uint64_t consumer; // e.g. input
+    uint64_t producer; // e.g. output
 };
 
 /**
@@ -1475,7 +1500,7 @@
     virtual c2_status_t map(
             C2Rect rect, C2MemoryUsage usage, int *fenceFd,
             // TODO: return <addr, size> buffers with plane sizes
-            C2PlaneLayout *layout /* nonnull */, uint8_t **addr /* nonnull */) = 0;
+            C2PlanarLayout *layout /* nonnull */, uint8_t **addr /* nonnull */) = 0;
 
     /**
      * Unmaps the last mapped rectangular section.
diff --git a/media/libstagefright/codec2/include/C2Config.h b/media/libstagefright/codec2/include/C2Config.h
index 0568432..83cb72c 100644
--- a/media/libstagefright/codec2/include/C2Config.h
+++ b/media/libstagefright/codec2/include/C2Config.h
@@ -41,7 +41,7 @@
 enum name : type { __VA_ARGS__ }; \
 DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(name, type, names, __VA_ARGS__)
 
-enum C2ParamIndexKind : C2Param::ParamIndex {
+enum C2ParamIndexKind : C2Param::type_index_t {
     /// domain
     kParamIndexDomain,
 
@@ -66,6 +66,8 @@
     kParamIndexMaxVideoSizeHint,
     kParamIndexVideoSizeTuning,
 
+    kParamIndexCsd,
+
     // video info
 
     kParamIndexStructStart = 0x1,
@@ -129,6 +131,8 @@
 
 typedef C2PortParam<C2Tuning, C2Uint64Array, kParamIndexBlockPools> C2PortBlockPoolsTuning;
 
+typedef C2StreamParam<C2Info, C2BlobValue, kParamIndexCsd> C2StreamCsdInfo;
+
 /*
    Component description fields:
 
@@ -232,12 +236,12 @@
 //   - critical parameters? (interlaced? profile? level?)
 
 struct C2VideoSizeStruct {
-    int32_t mWidth;     ///< video width
-    int32_t mHeight;    ///< video height
+    int32_t width;     ///< video width
+    int32_t height;    ///< video height
 
     DEFINE_AND_DESCRIBE_BASE_C2STRUCT(VideoSize)
-    C2FIELD(mWidth, "width")
-    C2FIELD(mHeight, "height")
+    C2FIELD(width, "width")
+    C2FIELD(height, "height")
 };
 
 // video size for video decoder [OUT]
diff --git a/media/libstagefright/codec2/include/C2Param.h b/media/libstagefright/codec2/include/C2Param.h
index f0b92a3..2a8c1b2 100644
--- a/media/libstagefright/codec2/include/C2Param.h
+++ b/media/libstagefright/codec2/include/C2Param.h
@@ -52,6 +52,9 @@
  *   - must be POD struct, e.g. no vtable (no virtual destructor)
  *   - must have the same size in 64-bit and 32-bit mode (no size_t)
  *   - as such, no pointer members
+ *   - some common member field names are reserved as they are defined as methods for all
+ *     parameters:
+ *     they are: size, type, kind, index and stream
  *
  * Behavior:
  * - Params can be global (not related to input or output), related to input or output,
@@ -94,17 +97,21 @@
 struct C2Param {
     // param index encompasses the following:
     //
-    // - type (setting, tuning, info, struct)
-    // - vendor extension flag
-    // - flexible parameter flag
-    // - direction (global, input, output)
-    // - stream flag
-    // - stream ID (usually 0)
+    // - kind (setting, tuning, info, struct)
+    // - scope
+    //   - direction (global, input, output)
+    //   - stream flag
+    //   - stream ID (usually 0)
+    // - and the parameter's type (core index)
+    //   - flexible parameter flag
+    //   - vendor extension flag
+    //   - type index (this includes the vendor extension flag)
     //
     // layout:
     //
+    //        kind : <------- scope -------> : <----- core index ----->
     //      +------+-----+---+------+--------+----|------+--------------+
-    //      | kind | dir | - |stream|streamID|flex|vendor|  core index  |
+    //      | kind | dir | - |stream|streamID|flex|vendor|  type index  |
     //      +------+-----+---+------+--------+----+------+--------------+
     //  bit: 31..30 29.28       25   24 .. 17  16    15   14    ..     0
     //
@@ -112,7 +119,7 @@
     /**
      * C2Param kinds, usable as bitmaps.
      */
-    enum Kind : uint32_t {
+    enum kind_t : uint32_t {
         NONE    = 0,
         STRUCT  = (1 << 0),
         INFO    = (1 << 1),
@@ -121,124 +128,141 @@
     };
 
     /**
-     * Parameter index is associated with each parameter. It is used to identify and distinguish
-     * global parameters, and also parameters on a given port or stream. They must be unique for the
-     * set of global parameters, as well as for the set of parameters on each port or each stream,
-     * but the same parameter index can be used for parameters on different streams or ports, as
-     * well as for global parameters and port/stream parameters.
+     * The parameter type index specifies the underlying parameter type of a parameter as
+     * an integer value.
      *
-     * Parameter index is also used to describe the layout of the parameter structures. Multiple
-     * parameter types can share the same layout, but the layout for all parameters with the same
-     * parameter index across all components must be identical.
+     * Parameter types are divided into two groups: platform types and vendor types.
+     *
+     * Platform types are defined by the platform and are common for all implementations.
+     *
+     * Vendor types are defined by each vendors, so they may differ between implementations.
+     * It is recommended that vendor types be the same for all implementations by a specific
+     * vendor.
      */
-    typedef uint32_t ParamIndex;
+    typedef uint32_t type_index_t;
+    enum : uint32_t {
+            TYPE_INDEX_VENDOR_START = 0x00008000, ///< vendor indices SHALL start after this
+    };
 
     /**
-     * base index (including the vendor extension bit) is a global index for
-     * C2 parameter structs. (e.g. the same indices cannot be reused for different
-     * structs for different components).
+     * Core index is the underlying parameter type for a parameter. It is used to describe the
+     * layout of the parameter structure regardless of the component or parameter kind/scope.
+     *
+     * It is used to identify and distinguish global parameters, and also parameters on a given
+     * port or stream. They must be unique for the set of global parameters, as well as for the
+     * set of parameters on each port or each stream, but the same core index can be used for
+     * parameters on different streams or ports, as well as for global parameters and port/stream
+     * parameters.
+     *
+     * Multiple parameter types can share the same layout.
+     *
+     * \note The layout for all parameters with the same core index across all components must
+     * be identical.
      */
-    struct BaseIndex {
+    struct CoreIndex {
+    //public:
+        enum : uint32_t {
+            IS_FLEX_FLAG = 0x00010000,
+        };
+
     protected:
         enum : uint32_t {
-            kTypeMask      = 0xC0000000,
-            kTypeStruct    = 0x00000000,
-            kTypeTuning    = 0x40000000,
-            kTypeSetting   = 0x80000000,
-            kTypeInfo      = 0xC0000000,
+            KIND_MASK      = 0xC0000000,
+            KIND_STRUCT    = 0x00000000,
+            KIND_TUNING    = 0x40000000,
+            KIND_SETTING   = 0x80000000,
+            KIND_INFO      = 0xC0000000,
 
-            kDirMask       = 0x30000000,
-            kDirGlobal     = 0x20000000,
-            kDirUndefined  = 0x30000000, // MUST have all bits set
-            kDirInput      = 0x00000000,
-            kDirOutput     = 0x10000000,
+            DIR_MASK       = 0x30000000,
+            DIR_GLOBAL     = 0x20000000,
+            DIR_UNDEFINED  = DIR_MASK, // MUST have all bits set
+            DIR_INPUT      = 0x00000000,
+            DIR_OUTPUT     = 0x10000000,
 
-            kStreamFlag    = 0x02000000,
-            kStreamIdMask  = 0x01FE0000,
-            kStreamIdShift = 17,
-            kStreamIdMax   = kStreamIdMask >> kStreamIdShift,
-            kStreamMask    = kStreamFlag | kStreamIdMask,
+            IS_STREAM_FLAG  = 0x02000000,
+            STREAM_ID_MASK  = 0x01FE0000,
+            STREAM_ID_SHIFT = 17,
+            MAX_STREAM_ID   = STREAM_ID_MASK >> STREAM_ID_SHIFT,
+            STREAM_MASK     = IS_STREAM_FLAG | STREAM_ID_MASK,
 
-            kFlexibleFlag  = 0x00010000,
-            kVendorFlag    = 0x00008000,
-            kParamMask     = 0x0000FFFF,
-            kCoreMask      = kParamMask | kFlexibleFlag,
+            IS_VENDOR_FLAG  = 0x00008000,
+            TYPE_INDEX_MASK = 0x0000FFFF,
+            CORE_MASK       = TYPE_INDEX_MASK | IS_FLEX_FLAG,
         };
 
     public:
-        enum : uint32_t {
-            kVendorStart = kVendorFlag, ///< vendor structs SHALL start after this
-            _kFlexibleFlag = kFlexibleFlag, // TODO: this is only needed for testing
-        };
-
         /// constructor/conversion from uint32_t
-        inline BaseIndex(uint32_t index) : mIndex(index) { }
+        inline CoreIndex(uint32_t index) : mIndex(index) { }
 
         // no conversion from uint64_t
-        inline BaseIndex(uint64_t index) = delete;
+        inline CoreIndex(uint64_t index) = delete;
 
         /// returns true iff this is a vendor extension parameter
-        inline bool isVendor() const { return mIndex & kVendorFlag; }
+        inline bool isVendor() const { return mIndex & IS_VENDOR_FLAG; }
 
         /// returns true iff this is a flexible parameter (with variable size)
-        inline bool isFlexible() const { return mIndex & kFlexibleFlag; }
+        inline bool isFlexible() const { return mIndex & IS_FLEX_FLAG; }
 
-        /// returns the core parameter type (index) for the underlying struct.
-        /// This is the combination of the parameter index and the flexible flag.
-        inline unsigned int coreIndex() const { return mIndex & kCoreMask; }
+        /// returns the core index
+        /// This is the combination of the parameter type index and the flexible flag.
+        inline uint32_t coreIndex() const { return mIndex & CORE_MASK; }
 
-        /// returns the parameter index for the underlying struct
-        inline ParamIndex paramIndex() const { return mIndex & kParamMask; }
+        /// returns the parameter type index
+        inline type_index_t typeIndex() const { return mIndex & TYPE_INDEX_MASK; }
 
-        DEFINE_FIELD_BASED_COMPARISON_OPERATORS(BaseIndex, mIndex)
+        DEFINE_FIELD_AND_MASK_BASED_COMPARISON_OPERATORS(CoreIndex, mIndex, CORE_MASK)
 
     protected:
         uint32_t mIndex;
     };
 
     /**
-     * type encompasses the parameter kind (tuning, setting, info), whether the
-     * parameter is global, input or output, and whether it is for a stream.
+     * Type encompasses the parameter's kind (tuning, setting, info), its scope (whether the
+     * parameter is global, input or output, and whether it is for a stream) and the its base
+     * index (which also determines its layout).
      */
-    struct Type : public BaseIndex {
+    struct Type : public CoreIndex {
+    //public:
         /// returns true iff this is a global parameter (not for input nor output)
-        inline bool isGlobal() const { return (mIndex & kDirMask) == kDirGlobal; }
+        inline bool isGlobal() const { return (mIndex & DIR_MASK) == DIR_GLOBAL; }
         /// returns true iff this is an input or input stream parameter
-        inline bool forInput() const { return (mIndex & kDirMask) == kDirInput; }
+        inline bool forInput() const { return (mIndex & DIR_MASK) == DIR_INPUT; }
         /// returns true iff this is an output or output stream parameter
-        inline bool forOutput() const { return (mIndex & kDirMask) == kDirOutput; }
+        inline bool forOutput() const { return (mIndex & DIR_MASK) == DIR_OUTPUT; }
 
         /// returns true iff this is a stream parameter
-        inline bool forStream() const { return mIndex & kStreamFlag; }
+        inline bool forStream() const { return mIndex & IS_STREAM_FLAG; }
         /// returns true iff this is a port (input or output) parameter
         inline bool forPort() const   { return !forStream() && !isGlobal(); }
 
         /// returns the parameter type: the parameter index without the stream ID
-        inline uint32_t type() const { return mIndex & (~kStreamIdMask); }
+        inline uint32_t type() const { return mIndex & (~STREAM_ID_MASK); }
 
-        /// return the kind of this param
-        inline Kind kind() const {
-            switch (mIndex & kTypeMask) {
-                case kTypeStruct: return STRUCT;
-                case kTypeInfo: return INFO;
-                case kTypeSetting: return SETTING;
-                case kTypeTuning: return TUNING;
+        /// return the kind (struct, info, setting or tuning) of this param
+        inline kind_t kind() const {
+            switch (mIndex & KIND_MASK) {
+                case KIND_STRUCT: return STRUCT;
+                case KIND_INFO: return INFO;
+                case KIND_SETTING: return SETTING;
+                case KIND_TUNING: return TUNING;
                 default: return NONE; // should not happen
             }
         }
 
         /// constructor/conversion from uint32_t
-        inline Type(uint32_t index) : BaseIndex(index) { }
+        inline Type(uint32_t index) : CoreIndex(index) { }
 
         // no conversion from uint64_t
         inline Type(uint64_t index) = delete;
 
+        DEFINE_FIELD_AND_MASK_BASED_COMPARISON_OPERATORS(Type, mIndex, ~STREAM_ID_MASK)
+
     private:
         friend struct C2Param;   // for setPort()
-        friend struct C2Tuning;  // for kTypeTuning
-        friend struct C2Setting; // for kTypeSetting
-        friend struct C2Info;    // for kTypeInfo
-        // for kDirGlobal
+        friend struct C2Tuning;  // for KIND_TUNING
+        friend struct C2Setting; // for KIND_SETTING
+        friend struct C2Info;    // for KIND_INFO
+        // for DIR_GLOBAL
         template<typename T, typename S, int I, class F> friend struct C2GlobalParam;
         template<typename T, typename S, int I, class F> friend struct C2PortParam;   // for kDir*
         template<typename T, typename S, int I, class F> friend struct C2StreamParam; // for kDir*
@@ -252,7 +276,7 @@
             if (isGlobal()) {
                 return false;
             } else {
-                mIndex = (mIndex & ~kDirMask) | (output ? kDirOutput : kDirInput);
+                mIndex = (mIndex & ~DIR_MASK) | (output ? DIR_OUTPUT : DIR_INPUT);
                 return true;
             }
         }
@@ -276,6 +300,8 @@
             return forStream() ? rawStream() : ~0U;
         }
 
+        DEFINE_FIELD_BASED_COMPARISON_OPERATORS(Index, mIndex)
+
     private:
         friend struct C2Param;           // for setStream, makeStreamId, isValid
         friend struct _C2ParamInspector; // for testing
@@ -287,23 +313,23 @@
         inline bool isValid() const {
             // there is no Type::isValid (even though some of this check could be
             // performed on types) as this is only used on index...
-            return (forStream() ? rawStream() < kStreamIdMax : rawStream() == 0)
-                    && (mIndex & kDirMask) != kDirUndefined;
+            return (forStream() ? rawStream() < MAX_STREAM_ID : rawStream() == 0)
+                    && (mIndex & DIR_MASK) != DIR_UNDEFINED;
         }
 
         /// returns the raw stream ID field
         inline unsigned rawStream() const {
-            return (mIndex & kStreamIdMask) >> kStreamIdShift;
+            return (mIndex & STREAM_ID_MASK) >> STREAM_ID_SHIFT;
         }
 
         /// returns the streamId bitfield for a given |stream|. If stream is invalid,
         /// returns an invalid bitfield.
         inline static uint32_t makeStreamId(unsigned stream) {
             // saturate stream ID (max value is invalid)
-            if (stream > kStreamIdMax) {
-                stream = kStreamIdMax;
+            if (stream > MAX_STREAM_ID) {
+                stream = MAX_STREAM_ID;
             }
-            return (stream << kStreamIdShift) & kStreamIdMask;
+            return (stream << STREAM_ID_SHIFT) & STREAM_ID_MASK;
         }
 
         /**
@@ -312,8 +338,8 @@
          */
         inline bool setStream(unsigned stream) {
             if (forStream()) {
-                mIndex = (mIndex & ~kStreamIdMask) | makeStreamId(stream);
-                return this->stream() < kStreamIdMax;
+                mIndex = (mIndex & ~STREAM_ID_MASK) | makeStreamId(stream);
+                return this->stream() < MAX_STREAM_ID;
             }
             return false;
         }
@@ -342,14 +368,17 @@
     inline unsigned stream() const { return _mIndex.stream(); }
 
     /// returns the parameter type: the parameter index without the stream ID
-    inline uint32_t type() const { return _mIndex.type(); }
+    inline Type type() const { return _mIndex.type(); }
 
     /// returns the index of this parameter
     /// \todo: should we restrict this to C2ParamField?
     inline uint32_t index() const { return (uint32_t)_mIndex; }
 
+    /// returns the core index of this parameter
+    inline CoreIndex coreIndex() const { return _mIndex.coreIndex(); }
+
     /// returns the kind of this parameter
-    inline Kind kind() const { return _mIndex.kind(); }
+    inline kind_t kind() const { return _mIndex.kind(); }
 
     /// returns the size of the parameter or 0 if the parameter is invalid
     inline size_t size() const { return _mSize; }
@@ -439,7 +468,7 @@
         } else if (o->_mIndex.isGlobal()) {
             return nullptr;
         } else {
-            return ((o->_mIndex.type() ^ type.mIndex) & ~Type::kDirMask) ? nullptr : o;
+            return ((o->_mIndex.type() ^ type.mIndex) & ~Type::DIR_MASK) ? nullptr : o;
         }
     }
 
@@ -467,10 +496,6 @@
 private:
     friend struct _C2ParamInspector; // for testing
 
-    /// returns the base type: the index for the underlying struct (for testing
-    /// as this can be gotten by the coreIndex enum)
-    inline uint32_t _coreIndex() const { return _mIndex.coreIndex(); }
-
     /// returns true iff |o| has the same size and index as this. This performs the
     /// basic check for equality.
     inline bool equals(const C2Param &o) const {
@@ -496,7 +521,7 @@
     template<typename ...Args>
     inline C2Setting(const Args(&... args)) : C2Param(args...) { }
 public: // TODO
-    enum : uint32_t { indexFlags = Type::kTypeSetting };
+    enum : uint32_t { PARAM_KIND = Type::KIND_SETTING };
 };
 
 /**
@@ -507,7 +532,7 @@
     template<typename ...Args>
     inline C2Tuning(const Args(&... args)) : C2Setting(args...) { }
 public: // TODO
-    enum : uint32_t { indexFlags = Type::kTypeTuning };
+    enum : uint32_t { PARAM_KIND = Type::KIND_TUNING };
 };
 
 /**
@@ -518,7 +543,7 @@
     template<typename ...Args>
     inline C2Info(const Args(&... args)) : C2Param(args...) { }
 public: // TODO
-    enum : uint32_t { indexFlags = Type::kTypeInfo };
+    enum : uint32_t { PARAM_KIND = Type::KIND_INFO };
 };
 
 /**
@@ -726,7 +751,7 @@
         template<typename T> const T &ref() const;
     };
 
-    enum Type {
+    enum type_t : uint32_t {
         NO_INIT,
         INT32,
         UINT32,
@@ -735,28 +760,28 @@
         FLOAT,
     };
 
-    template<typename T> static constexpr Type typeFor();
+    template<typename T> static constexpr type_t typeFor();
 
     // constructors - implicit
     template<typename T>
-    C2Value(T value)  : mType(typeFor<T>()), mValue(value) { }
+    C2Value(T value)  : _mType(typeFor<T>()), _mValue(value) { }
 
-    C2Value() : mType(NO_INIT) { }
+    C2Value() : _mType(NO_INIT) { }
 
-    inline Type type() const { return mType; }
+    inline type_t type() const { return _mType; }
 
     template<typename T>
     inline bool get(T *value) const {
-        if (mType == typeFor<T>()) {
-            *value = mValue.ref<T>();
+        if (_mType == typeFor<T>()) {
+            *value = _mValue.ref<T>();
             return true;
         }
         return false;
     }
 
 private:
-    Type mType;
-    Primitive mValue;
+    type_t _mType;
+    Primitive _mValue;
 };
 
 template<> inline const int32_t &C2Value::Primitive::ref<int32_t>() const { return i32; }
@@ -765,11 +790,11 @@
 template<> inline const uint64_t &C2Value::Primitive::ref<uint64_t>() const { return u64; }
 template<> inline const float &C2Value::Primitive::ref<float>() const { return fp; }
 
-template<> constexpr C2Value::Type C2Value::typeFor<int32_t>() { return INT32; }
-template<> constexpr C2Value::Type C2Value::typeFor<int64_t>() { return INT64; }
-template<> constexpr C2Value::Type C2Value::typeFor<uint32_t>() { return UINT32; }
-template<> constexpr C2Value::Type C2Value::typeFor<uint64_t>() { return UINT64; }
-template<> constexpr C2Value::Type C2Value::typeFor<float>() { return FLOAT; }
+template<> constexpr C2Value::type_t C2Value::typeFor<int32_t>() { return INT32; }
+template<> constexpr C2Value::type_t C2Value::typeFor<int64_t>() { return INT64; }
+template<> constexpr C2Value::type_t C2Value::typeFor<uint32_t>() { return UINT32; }
+template<> constexpr C2Value::type_t C2Value::typeFor<uint64_t>() { return UINT64; }
+template<> constexpr C2Value::type_t C2Value::typeFor<float>() { return FLOAT; }
 
 /**
  * field descriptor. A field is uniquely defined by an index into a parameter.
@@ -784,7 +809,7 @@
      * \note: only 32-bit and 64-bit fields are supported (e.g. no boolean, as that
      * is represented using INT32).
      */
-    enum Type : uint32_t {
+    enum type_t : uint32_t {
         // primitive types
         INT32   = C2Value::INT32,  ///< 32-bit signed integer
         UINT32  = C2Value::UINT32, ///< 32-bit unsigned integer
@@ -798,7 +823,7 @@
                         ///< however, bytes cannot be individually addressed by clients.
 
         // complex types
-        STRUCT_FLAG = 0x10000, ///< structs. Marked with this flag in addition to their coreIndex.
+        STRUCT_FLAG = 0x20000, ///< structs. Marked with this flag in addition to their coreIndex.
     };
 
     typedef std::pair<C2String, C2Value::Primitive> named_value_type;
@@ -816,7 +841,7 @@
     static named_values_type namedValuesFor(const B &);
 
     inline C2FieldDescriptor(uint32_t type, uint32_t length, C2StringLiteral name, size_t offset, size_t size)
-        : _mType((Type)type), _mLength(length), _mName(name), _mFieldId(offset, size) { }
+        : _mType((type_t)type), _mLength(length), _mName(name), _mFieldId(offset, size) { }
 
     template<typename T, class B=typename std::remove_extent<T>::type>
     inline C2FieldDescriptor(const T* offset, const char *name)
@@ -844,7 +869,7 @@
           _mFieldId(&(((S*)0)->*field)) {}
 
     /// returns the type of this field
-    inline Type type() const { return _mType; }
+    inline type_t type() const { return _mType; }
     /// returns the length of the field in case it is an array. Returns 0 for
     /// T[] arrays, returns 1 for T[1] arrays as well as if the field is not an array.
     inline size_t length() const { return _mLength; }
@@ -860,7 +885,7 @@
 #endif
 
 private:
-    const Type _mType;
+    const type_t _mType;
     const uint32_t _mLength; // the last member can be arbitrary length if it is T[] array,
                        // extending to the end of the parameter (this is marked with
                        // 0). T[0]-s are not fields.
@@ -874,27 +899,27 @@
     // 2) this is at parameter granularity.
 
     // type resolution
-    inline static Type getType(int32_t*)  { return INT32; }
-    inline static Type getType(uint32_t*) { return UINT32; }
-    inline static Type getType(int64_t*)  { return INT64; }
-    inline static Type getType(uint64_t*) { return UINT64; }
-    inline static Type getType(float*)    { return FLOAT; }
-    inline static Type getType(char*)     { return STRING; }
-    inline static Type getType(uint8_t*)  { return BLOB; }
+    inline static type_t getType(int32_t*)  { return INT32; }
+    inline static type_t getType(uint32_t*) { return UINT32; }
+    inline static type_t getType(int64_t*)  { return INT64; }
+    inline static type_t getType(uint64_t*) { return UINT64; }
+    inline static type_t getType(float*)    { return FLOAT; }
+    inline static type_t getType(char*)     { return STRING; }
+    inline static type_t getType(uint8_t*)  { return BLOB; }
 
     template<typename T,
              class=typename std::enable_if<std::is_enum<T>::value>::type>
-    inline static Type getType(T*) {
+    inline static type_t getType(T*) {
         typename std::underlying_type<T>::type underlying(0);
         return getType(&underlying);
     }
 
-    // verify C2Struct by having a fieldList and a coreIndex.
+    // verify C2Struct by having a FIELD_LIST and a CORE_INDEX.
     template<typename T,
-             class=decltype(T::coreIndex + 1), class=decltype(T::fieldList)>
-    inline static Type getType(T*) {
+             class=decltype(T::CORE_INDEX + 1), class=decltype(T::FIELD_LIST)>
+    inline static type_t getType(T*) {
         static_assert(!std::is_base_of<C2Param, T>::value, "cannot use C2Params as fields");
-        return (Type)(T::coreIndex | STRUCT_FLAG);
+        return (type_t)(T::CORE_INDEX | STRUCT_FLAG);
     }
 };
 
@@ -918,33 +943,33 @@
  */
 struct C2StructDescriptor {
 public:
-    /// Returns the parameter type
-    inline C2Param::BaseIndex coreIndex() const { return _mType.coreIndex(); }
+    /// Returns the core index of the struct
+    inline C2Param::CoreIndex coreIndex() const { return _mType.coreIndex(); }
 
-    // Returns the number of fields in this param (not counting any recursive fields).
-    // Must be at least 1 for valid params.
+    // Returns the number of fields in this struct (not counting any recursive fields).
+    // Must be at least 1 for valid structs.
     inline size_t numFields() const { return _mFields.size(); }
 
-    // Returns the list of immediate fields (not counting any recursive fields).
+    // Returns the list of direct fields (not counting any recursive fields).
     typedef std::vector<const C2FieldDescriptor>::const_iterator field_iterator;
     inline field_iterator cbegin() const { return _mFields.cbegin(); }
     inline field_iterator cend() const { return _mFields.cend(); }
 
-    // only supplying const iterator - but these are needed for range based loops
+    // only supplying const iterator - but these names are needed for range based loops
     inline field_iterator begin() const { return _mFields.cbegin(); }
     inline field_iterator end() const { return _mFields.cend(); }
 
     template<typename T>
     inline C2StructDescriptor(T*)
-        : C2StructDescriptor(T::coreIndex, T::fieldList) { }
+        : C2StructDescriptor(T::CORE_INDEX, T::FIELD_LIST) { }
 
     inline C2StructDescriptor(
-            C2Param::BaseIndex type,
+            C2Param::CoreIndex type,
             std::initializer_list<const C2FieldDescriptor> fields)
         : _mType(type), _mFields(fields) { }
 
 private:
-    const C2Param::BaseIndex _mType;
+    const C2Param::CoreIndex _mType;
     const std::vector<const C2FieldDescriptor> _mFields;
 };
 
@@ -984,7 +1009,7 @@
         : _mIsRequired(isRequired),
           _mIsPersistent(true),
           _mName(name),
-          _mType(T::typeIndex) { }
+          _mType(T::PARAM_TYPE) { }
 
     inline C2ParamDescriptor(
             bool isRequired, C2StringLiteral name, C2Param::Type type)
@@ -1001,29 +1026,29 @@
 };
 
 /// \ingroup internal
-/// Define a structure without coreIndex.
+/// Define a structure without CORE_INDEX.
 #define DEFINE_BASE_C2STRUCT(name) \
 public: \
     typedef C2##name##Struct _type; /**< type name shorthand */ \
-    const static std::initializer_list<const C2FieldDescriptor> fieldList; /**< structure fields */
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST; /**< structure fields */
 
-/// Define a structure with matching coreIndex.
+/// Define a structure with matching CORE_INDEX.
 #define DEFINE_C2STRUCT(name) \
 public: \
-    enum : uint32_t { coreIndex = kParamIndex##name }; \
+    enum : uint32_t { CORE_INDEX = kParamIndex##name }; \
     DEFINE_BASE_C2STRUCT(name)
 
-/// Define a flexible structure without coreIndex.
+/// Define a flexible structure without CORE_INDEX.
 #define DEFINE_BASE_FLEX_C2STRUCT(name, flexMember) \
 public: \
     FLEX(C2##name##Struct, flexMember) \
     DEFINE_BASE_C2STRUCT(name)
 
-/// Define a flexible structure with matching coreIndex.
+/// Define a flexible structure with matching CORE_INDEX.
 #define DEFINE_FLEX_C2STRUCT(name, flexMember) \
 public: \
     FLEX(C2##name##Struct, flexMember) \
-    enum : uint32_t { coreIndex = kParamIndex##name | C2Param::BaseIndex::_kFlexibleFlag }; \
+    enum : uint32_t { CORE_INDEX = kParamIndex##name | C2Param::CoreIndex::IS_FLEX_FLAG }; \
     DEFINE_BASE_C2STRUCT(name)
 
 #ifdef __C2_GENERATE_GLOBAL_VARS__
@@ -1031,12 +1056,12 @@
 /// Describe a structure of a templated structure.
 #define DESCRIBE_TEMPLATED_C2STRUCT(strukt, list) \
     template<> \
-    const std::initializer_list<const C2FieldDescriptor> strukt::fieldList = list;
+    const std::initializer_list<const C2FieldDescriptor> strukt::FIELD_LIST = list;
 
 /// \deprecated
 /// Describe the fields of a structure using an initializer list.
 #define DESCRIBE_C2STRUCT(name, list) \
-    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::fieldList = list;
+    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::FIELD_LIST = list;
 #else
 /// \if 0
 #define DESCRIBE_TEMPLATED_C2STRUCT(strukt, list)
@@ -1052,26 +1077,26 @@
  *
  *  ~~~~~~~~~~~~~ (.cpp)
  *  struct C2VideoWidthStruct {
- *      int32_t mWidth;
+ *      int32_t width;
  *      C2VideoWidthStruct() {} // optional default constructor
- *      C2VideoWidthStruct(int32_t _width) : mWidth(_width) {}
+ *      C2VideoWidthStruct(int32_t _width) : width(_width) {}
  *
  *      DEFINE_AND_DESCRIBE_C2STRUCT(VideoWidth)
- *      C2FIELD(mWidth, "width")
+ *      C2FIELD(width, "width")
  *  };
  *  ~~~~~~~~~~~~~
  *
  *  ~~~~~~~~~~~~~ (.cpp)
  *  struct C2VideoWidthStruct {
- *      int32_t mWidth;
+ *      int32_t width;
  *      C2VideoWidthStruct() = default; // optional default constructor
- *      C2VideoWidthStruct(int32_t _width) : mWidth(_width) {}
+ *      C2VideoWidthStruct(int32_t _width) : width(_width) {}
  *
  *      DEFINE_C2STRUCT(VideoWidth)
  *  } C2_PACK;
  *
  *  DESCRIBE_C2STRUCT(VideoWidth, {
- *      C2FIELD(mWidth, "width")
+ *      C2FIELD(width, "width")
  *  })
  *  ~~~~~~~~~~~~~
  *
@@ -1079,7 +1104,7 @@
  *
  *  ~~~~~~~~~~~~~ (.cpp)
  *  struct C2VideoFlexWidthsStruct {
- *      int32_t mWidths[];
+ *      int32_t widths[];
  *      C2VideoFlexWidthsStruct(); // must have a default constructor
  *
  *  private:
@@ -1088,7 +1113,7 @@
  *      //   C2VideoFlexWidthsGlobalParam::alloc_unique(size_t, int32_t);
  *      C2VideoFlexWidthsStruct(size_t flexCount, int32_t value) {
  *          for (size_t i = 0; i < flexCount; ++i) {
- *              mWidths[i] = value;
+ *              widths[i] = value;
  *          }
  *      }
  *
@@ -1099,12 +1124,12 @@
  *      template<unsigned N>
  *      C2VideoFlexWidthsStruct(size_t flexCount, const int32_t(&init)[N]) {
  *          for (size_t i = 0; i < flexCount; ++i) {
- *              mWidths[i] = init[i];
+ *              widths[i] = init[i];
  *          }
  *      }
  *
- *      DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(VideoFlexWidths, mWidths)
- *      C2FIELD(mWidths, "widths")
+ *      DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(VideoFlexWidths, widths)
+ *      C2FIELD(widths, "widths")
  *  };
  *  ~~~~~~~~~~~~~
  *
@@ -1130,29 +1155,29 @@
 #define C2SOLE_FIELD(member, name) \
   C2FieldDescriptor(&_type::member, name, 0)
 
-/// Define a structure with matching coreIndex and start describing its fields.
+/// Define a structure with matching CORE_INDEX and start describing its fields.
 /// This must be at the end of the structure definition.
 #define DEFINE_AND_DESCRIBE_C2STRUCT(name) \
     DEFINE_C2STRUCT(name) } C2_PACK; \
-    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::fieldList = {
+    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::FIELD_LIST = {
 
-/// Define a flexible structure with matching coreIndex and start describing its fields.
+/// Define a flexible structure with matching CORE_INDEX and start describing its fields.
 /// This must be at the end of the structure definition.
 #define DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(name, flexMember) \
     DEFINE_FLEX_C2STRUCT(name, flexMember) } C2_PACK; \
-    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::fieldList = {
+    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::FIELD_LIST = {
 
-/// Define a base structure (with no coreIndex) and start describing its fields.
+/// Define a base structure (with no CORE_INDEX) and start describing its fields.
 /// This must be at the end of the structure definition.
 #define DEFINE_AND_DESCRIBE_BASE_C2STRUCT(name) \
     DEFINE_BASE_C2STRUCT(name) } C2_PACK; \
-    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::fieldList = {
+    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::FIELD_LIST = {
 
-/// Define a flexible base structure (with no coreIndex) and start describing its fields.
+/// Define a flexible base structure (with no CORE_INDEX) and start describing its fields.
 /// This must be at the end of the structure definition.
 #define DEFINE_AND_DESCRIBE_BASE_FLEX_C2STRUCT(name, flexMember) \
     DEFINE_BASE_FLEX_C2STRUCT(name, flexMember) } C2_PACK; \
-    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::fieldList = {
+    const std::initializer_list<const C2FieldDescriptor> C2##name##Struct::FIELD_LIST = {
 
 #else
 /// \if 0
@@ -1162,19 +1187,19 @@
 #define C2FIELD(member, name)
 /// \deprecated
 #define C2SOLE_FIELD(member, name)
-/// Define a structure with matching coreIndex and start describing its fields.
+/// Define a structure with matching CORE_INDEX and start describing its fields.
 /// This must be at the end of the structure definition.
 #define DEFINE_AND_DESCRIBE_C2STRUCT(name) \
     DEFINE_C2STRUCT(name) }  C2_PACK; namespace ignored {
-/// Define a flexible structure with matching coreIndex and start describing its fields.
+/// Define a flexible structure with matching CORE_INDEX and start describing its fields.
 /// This must be at the end of the structure definition.
 #define DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(name, flexMember) \
     DEFINE_FLEX_C2STRUCT(name, flexMember) } C2_PACK; namespace ignored {
-/// Define a base structure (with no coreIndex) and start describing its fields.
+/// Define a base structure (with no CORE_INDEX) and start describing its fields.
 /// This must be at the end of the structure definition.
 #define DEFINE_AND_DESCRIBE_BASE_C2STRUCT(name) \
     DEFINE_BASE_C2STRUCT(name) } C2_PACK; namespace ignored {
-/// Define a flexible base structure (with no coreIndex) and start describing its fields.
+/// Define a flexible base structure (with no CORE_INDEX) and start describing its fields.
 /// This must be at the end of the structure definition.
 #define DEFINE_AND_DESCRIBE_BASE_FLEX_C2STRUCT(name, flexMember) \
     DEFINE_BASE_FLEX_C2STRUCT(name, flexMember) } C2_PACK; namespace ignored {
@@ -1194,7 +1219,7 @@
     /**
      *  Describes a parameter structure.
      *
-     *  \param[in] coreIndex the base index of the parameter structure containing at least the
+     *  \param[in] coreIndex the core index of the parameter structure containing at least the
      *  core index
      *
      *  \return the description of the parameter structure
@@ -1208,7 +1233,7 @@
      *  descriptions, but we want to conserve memory if client only wants the description
      *  of a few indices.
      */
-    virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::BaseIndex coreIndex) = 0;
+    virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::CoreIndex coreIndex) = 0;
 
 protected:
     virtual ~C2ParamReflector() = default;
@@ -1225,14 +1250,14 @@
  */
 struct C2FieldSupportedValues {
 //public:
-    enum Type {
+    enum type_t {
         EMPTY,      ///< no supported values
         RANGE,      ///< a numeric range that can be continuous or discrete
         VALUES,     ///< a list of values
         FLAGS       ///< a list of flags that can be OR-ed
     };
 
-    Type type;
+    type_t type;
 
     typedef C2Value::Primitive Primitive;
 
diff --git a/media/libstagefright/codec2/include/C2ParamDef.h b/media/libstagefright/codec2/include/C2ParamDef.h
index 8656629..b5834f2 100644
--- a/media/libstagefright/codec2/include/C2ParamDef.h
+++ b/media/libstagefright/codec2/include/C2ParamDef.h
@@ -59,33 +59,33 @@
                         || decltype(_C2Comparable_impl::__testNE<S>(0))::value> {
 };
 
-///  Helper class that checks if a type has a coreIndex constant.
+///  Helper class that checks if a type has a CORE_INDEX constant.
 struct C2_HIDE _C2CoreIndexHelper_impl
 {
-    template<typename S, int=S::coreIndex>
+    template<typename S, int=S::CORE_INDEX>
     static std::true_type __testCoreIndex(int);
     template<typename>
     static std::false_type __testCoreIndex(...);
 };
 
-/// Helper template that verifies a type's coreIndex and creates it if the type does not have one.
+/// Helper template that verifies a type's CORE_INDEX and creates it if the type does not have one.
 template<typename S, int CoreIndex,
         bool HasBase=decltype(_C2CoreIndexHelper_impl::__testCoreIndex<S>(0))::value>
 struct C2_HIDE C2CoreIndexOverride {
-    // TODO: what if we allow structs without coreIndex?
-    static_assert(CoreIndex == S::coreIndex, "coreIndex differs from structure");
+    // TODO: what if we allow structs without CORE_INDEX?
+    static_assert(CoreIndex == S::CORE_INDEX, "CORE_INDEX differs from structure");
 };
 
-/// Specialization for types without a coreIndex.
+/// Specialization for types without a CORE_INDEX.
 template<typename S, int CoreIndex>
 struct C2_HIDE C2CoreIndexOverride<S, CoreIndex, false> {
 public:
     enum : uint32_t {
-        coreIndex = CoreIndex, ///< coreIndex override.
+        CORE_INDEX = CoreIndex, ///< CORE_INDEX override.
     };
 };
 
-/// Helper template that adds a coreIndex to a type if it does not have one.
+/// Helper template that adds a CORE_INDEX to a type if it does not have one.
 template<typename S, int CoreIndex>
 struct C2_HIDE C2AddCoreIndex : public S, public C2CoreIndexOverride<S, CoreIndex> {};
 
@@ -94,9 +94,9 @@
  *
  * Features:
  *  - verify default constructor, no virtual methods, and no equality operators.
- *  - expose typeIndex, and non-flex flexSize.
+ *  - expose PARAM_TYPE, and non-flex FLEX_SIZE.
  */
-template<typename S, int CoreIndex, unsigned TypeIndex>
+template<typename S, int CoreIndex, unsigned TypeFlags>
 struct C2_HIDE C2StructCheck {
     static_assert(
             std::is_default_constructible<S>::value, "C2 structure must have default constructor");
@@ -105,31 +105,31 @@
 
 public:
     enum : uint32_t {
-        typeIndex = CoreIndex | TypeIndex
+        PARAM_TYPE = CoreIndex | TypeFlags
     };
 
 protected:
     enum : uint32_t {
-        flexSize = 0, // TODO: is this still needed? this may be confusing.
+        FLEX_SIZE = 0, // TODO: is this still needed? this may be confusing.
     };
 };
 
-/// Helper class that checks if a type has an integer flexSize member.
+/// Helper class that checks if a type has an integer FLEX_SIZE member.
 struct C2_HIDE _C2Flexible_impl {
-    /// specialization for types that have a flexSize member
-    template<typename S, unsigned=S::flexSize>
+    /// specialization for types that have a FLEX_SIZE member
+    template<typename S, unsigned=S::FLEX_SIZE>
     static std::true_type __testFlexSize(int);
     template<typename>
     static std::false_type __testFlexSize(...);
 };
 
-/// Helper template that returns if a type has an integer flexSize member.
+/// Helper template that returns if a type has an integer FLEX_SIZE member.
 template<typename S>
 struct C2_HIDE _C2Flexible
     : public std::integral_constant<bool, decltype(_C2Flexible_impl::__testFlexSize<S>(0))::value> {
 };
 
-/// Macro to test if a type is flexible (has a flexSize member).
+/// Macro to test if a type is flexible (has a FLEX_SIZE member).
 #define IF_FLEXIBLE(S) ENABLE_IF(_C2Flexible<S>::value)
 /// Shorthand for std::enable_if
 #define ENABLE_IF(cond) typename std::enable_if<cond>::type
@@ -137,76 +137,76 @@
 /// Helper template that exposes the flexible subtype of a struct.
 template<typename S, typename E=void>
 struct C2_HIDE _C2FlexHelper {
-    typedef void flexType;
-    enum : uint32_t { flexSize = 0 };
+    typedef void FlexType;
+    enum : uint32_t { FLEX_SIZE = 0 };
 };
 
 /// Specialization for flexible types.
 template<typename S>
 struct C2_HIDE _C2FlexHelper<S,
         typename std::enable_if<!std::is_void<typename S::flexMemberType>::value>::type> {
-    typedef typename _C2FlexHelper<typename S::flexMemberType>::flexType flexType;
-    enum : uint32_t { flexSize = _C2FlexHelper<typename S::flexMemberType>::flexSize };
+    typedef typename _C2FlexHelper<typename S::flexMemberType>::FlexType FlexType;
+    enum : uint32_t { FLEX_SIZE = _C2FlexHelper<typename S::flexMemberType>::FLEX_SIZE };
 };
 
 /// Specialization for flex arrays.
 template<typename S>
 struct C2_HIDE _C2FlexHelper<S[],
-        typename std::enable_if<std::is_void<typename _C2FlexHelper<S>::flexType>::value>::type> {
-    typedef S flexType;
-    enum : uint32_t { flexSize = sizeof(S) };
+        typename std::enable_if<std::is_void<typename _C2FlexHelper<S>::FlexType>::value>::type> {
+    typedef S FlexType;
+    enum : uint32_t { FLEX_SIZE = sizeof(S) };
 };
 
 /**
  * \brief Helper class to check flexible struct requirements and add common operations.
  *
  * Features:
- *  - expose coreIndex and fieldList (this is normally inherited from the struct, but flexible
+ *  - expose CORE_INDEX and FIELD_LIST (this is normally inherited from the struct, but flexible
  *    structs cannot be base classes and thus inherited from)
  *  - disable copy assignment and construction (TODO: this is already done in the FLEX macro for the
  *    flexible struct, so may not be needed here)
  */
-template<typename S, int ParamIndex, unsigned TypeIndex>
+template<typename S, int ParamIndex, unsigned TypeFlags>
 struct C2_HIDE C2FlexStructCheck :
-// add flexible flag as C2StructCheck defines typeIndex
-        public C2StructCheck<S, ParamIndex | C2Param::BaseIndex::_kFlexibleFlag, TypeIndex> {
+// add flexible flag as C2StructCheck defines PARAM_TYPE
+        public C2StructCheck<S, ParamIndex | C2Param::CoreIndex::IS_FLEX_FLAG, TypeFlags> {
 public:
     enum : uint32_t {
         /// \hideinitializer
-        coreIndex = ParamIndex | C2Param::BaseIndex::_kFlexibleFlag, ///< flexible struct core-index
+        CORE_INDEX = ParamIndex | C2Param::CoreIndex::IS_FLEX_FLAG, ///< flexible struct core-index
     };
 
-    const static std::initializer_list<const C2FieldDescriptor> fieldList; // TODO assign here
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST; // TODO assign here
 
     // default constructor needed because of the disabled copy constructor
     inline C2FlexStructCheck() = default;
 
 protected:
     // cannot copy flexible params
-    C2FlexStructCheck(const C2FlexStructCheck<S, ParamIndex, TypeIndex> &) = delete;
-    C2FlexStructCheck& operator= (const C2FlexStructCheck<S, ParamIndex, TypeIndex> &) = delete;
+    C2FlexStructCheck(const C2FlexStructCheck<S, ParamIndex, TypeFlags> &) = delete;
+    C2FlexStructCheck& operator= (const C2FlexStructCheck<S, ParamIndex, TypeFlags> &) = delete;
 
     // constants used for helper methods
     enum : uint32_t {
         /// \hideinitializer
-        flexSize = _C2FlexHelper<S>::flexSize, ///< size of flexible type
+        FLEX_SIZE = _C2FlexHelper<S>::FLEX_SIZE, ///< size of flexible type
         /// \hideinitializer
-        maxSize = (uint32_t)std::min((size_t)UINT32_MAX, SIZE_MAX), // TODO: is this always u32 max?
+        MAX_SIZE = (uint32_t)std::min((size_t)UINT32_MAX, SIZE_MAX), // TODO: is this always u32 max?
         /// \hideinitializer
-        baseSize = sizeof(S) + sizeof(C2Param), ///< size of the base param
+        BASE_SIZE = sizeof(S) + sizeof(C2Param), ///< size of the base param
     };
 
     /// returns the allocated size of this param with flexCount, or 0 if it would overflow.
-    inline static size_t calcSize(size_t flexCount, size_t size = baseSize) {
-        if (flexCount <= (maxSize - size) / S::flexSize) {
-            return size + S::flexSize * flexCount;
+    inline static size_t calcSize(size_t flexCount, size_t size = BASE_SIZE) {
+        if (flexCount <= (MAX_SIZE - size) / S::FLEX_SIZE) {
+            return size + S::FLEX_SIZE * flexCount;
         }
         return 0;
     }
 
     /// dynamic new operator usable for params of type S
     inline void* operator new(size_t size, size_t flexCount) noexcept {
-        // TODO: assert(size == baseSize);
+        // TODO: assert(size == BASE_SIZE);
         size = calcSize(flexCount, size);
         if (size > 0) {
             return ::operator new(size);
@@ -216,22 +216,22 @@
 };
 
 // TODO: this probably does not work.
-/// Expose fieldList from subClass;
-template<typename S, int ParamIndex, unsigned TypeIndex>
+/// Expose FIELD_LIST from subClass;
+template<typename S, int ParamIndex, unsigned TypeFlags>
 const std::initializer_list<const C2FieldDescriptor>
-C2FlexStructCheck<S, ParamIndex, TypeIndex>::fieldList = S::fieldList;
+C2FlexStructCheck<S, ParamIndex, TypeFlags>::FIELD_LIST = S::FIELD_LIST;
 
 /// Define From() cast operators for params.
-#define DEFINE_CAST_OPERATORS(_type) \
-    inline static _type* From(C2Param *other) { \
-        return (_type*)C2Param::ifSuitable( \
-                other, sizeof(_type),_type::typeIndex, _type::flexSize, \
-                (_type::typeIndex & T::Index::kDirUndefined) != T::Index::kDirUndefined); \
+#define DEFINE_CAST_OPERATORS(_Type) \
+    inline static _Type* From(C2Param *other) { \
+        return (_Type*)C2Param::ifSuitable( \
+                other, sizeof(_Type),_Type::PARAM_TYPE, _Type::FLEX_SIZE, \
+                (_Type::PARAM_TYPE & T::Index::DIR_UNDEFINED) != T::Index::DIR_UNDEFINED); \
     } \
-    inline static const _type* From(const C2Param *other) { \
-        return const_cast<const _type*>(From(const_cast<C2Param *>(other))); \
+    inline static const _Type* From(const C2Param *other) { \
+        return const_cast<const _Type*>(From(const_cast<C2Param *>(other))); \
     } \
-    inline static _type* From(std::nullptr_t) { return nullptr; } \
+    inline static _Type* From(std::nullptr_t) { return nullptr; } \
 
 /**
  * Define flexible allocators (alloc_shared or alloc_unique) for flexible params.
@@ -241,38 +241,38 @@
  *  - P::alloc_xyz(args..., std::initializer_list<T>): allocate for size of (and with) initializer
  *    list.
  */
-#define DEFINE_FLEXIBLE_ALLOC(_type, S, ptr) \
+#define DEFINE_FLEXIBLE_ALLOC(_Type, S, ptr) \
     template<typename ...Args> \
-    inline static std::ptr##_ptr<_type> alloc_##ptr(size_t flexCount, const Args(&... args)) { \
-        return std::ptr##_ptr<_type>(new(flexCount) _type(flexCount, args...)); \
+    inline static std::ptr##_ptr<_Type> alloc_##ptr(size_t flexCount, const Args(&... args)) { \
+        return std::ptr##_ptr<_Type>(new(flexCount) _Type(flexCount, args...)); \
     } \
     /* NOTE: unfortunately this is not supported by clang yet */ \
-    template<typename ...Args, typename U=typename S::flexType, unsigned N> \
-    inline static std::ptr##_ptr<_type> alloc_##ptr(const Args(&... args), const U(&init)[N]) { \
-        return std::ptr##_ptr<_type>(new(N) _type(N, args..., init)); \
+    template<typename ...Args, typename U=typename S::FlexType, unsigned N> \
+    inline static std::ptr##_ptr<_Type> alloc_##ptr(const Args(&... args), const U(&init)[N]) { \
+        return std::ptr##_ptr<_Type>(new(N) _Type(N, args..., init)); \
     } \
     /* so for now, specialize for no args */ \
-    template<typename U=typename S::flexType, unsigned N> \
-    inline static std::ptr##_ptr<_type> alloc_##ptr(const U(&init)[N]) { \
-        return std::ptr##_ptr<_type>(new(N) _type(N, init)); \
+    template<typename U=typename S::FlexType, unsigned N> \
+    inline static std::ptr##_ptr<_Type> alloc_##ptr(const U(&init)[N]) { \
+        return std::ptr##_ptr<_Type>(new(N) _Type(N, init)); \
     } \
-    template<typename ...Args, typename U=typename S::flexType> \
-    inline static std::ptr##_ptr<_type> alloc_##ptr( \
+    template<typename ...Args, typename U=typename S::FlexType> \
+    inline static std::ptr##_ptr<_Type> alloc_##ptr( \
             const Args(&... args), const std::initializer_list<U> &init) { \
-        return std::ptr##_ptr<_type>(new(init.size()) _type(init.size(), args..., init)); \
+        return std::ptr##_ptr<_Type>(new(init.size()) _Type(init.size(), args..., init)); \
     } \
 
 /**
  * Define flexible methods alloc_shared, alloc_unique and flexCount.
  */
-#define DEFINE_FLEXIBLE_METHODS(_type, S) \
-    DEFINE_FLEXIBLE_ALLOC(_type, S, shared) \
-    DEFINE_FLEXIBLE_ALLOC(_type, S, unique) \
+#define DEFINE_FLEXIBLE_METHODS(_Type, S) \
+    DEFINE_FLEXIBLE_ALLOC(_Type, S, shared) \
+    DEFINE_FLEXIBLE_ALLOC(_Type, S, unique) \
     inline size_t flexCount() const { \
-        static_assert(sizeof(_type) == _type::baseSize, "incorrect baseSize"); \
+        static_assert(sizeof(_Type) == _Type::BASE_SIZE, "incorrect BASE_SIZE"); \
         size_t sz = this->size(); \
-        if (sz >= sizeof(_type)) { \
-            return (sz - sizeof(_type)) / _type::flexSize; \
+        if (sz >= sizeof(_Type)) { \
+            return (sz - sizeof(_Type)) / _Type::FLEX_SIZE; \
         } \
         return 0; \
     } \
@@ -289,11 +289,11 @@
     typedef decltype(m) flexMemberType; \
 public: \
     /* constexpr static flexMemberType cls::* flexMember = &cls::m; */ \
-    typedef typename _C2FlexHelper<flexMemberType>::flexType flexType; \
+    typedef typename _C2FlexHelper<flexMemberType>::FlexType FlexType; \
     static_assert(\
-            !std::is_void<flexType>::value, \
+            !std::is_void<FlexType>::value, \
             "member is not flexible, or a flexible array of a flexible type"); \
-    enum : uint32_t { flexSize = _C2FlexHelper<flexMemberType>::flexSize }; \
+    enum : uint32_t { FLEX_SIZE = _C2FlexHelper<flexMemberType>::FLEX_SIZE }; \
     /** \endif */ \
 
 /// @}
@@ -313,18 +313,18 @@
  * \tparam ParamIndex optional parameter index override. Must be specified for base/reused
  * structures.
  */
-template<typename T, typename S, int ParamIndex=S::coreIndex, class Flex=void>
+template<typename T, typename S, int ParamIndex=S::CORE_INDEX, class Flex=void>
 struct C2_HIDE C2GlobalParam : public T, public S, public C2CoreIndexOverride<S, ParamIndex>,
-        public C2StructCheck<S, ParamIndex, T::indexFlags | T::Type::kDirGlobal> {
+        public C2StructCheck<S, ParamIndex, T::PARAM_KIND | T::Type::DIR_GLOBAL> {
 private:
-    typedef C2GlobalParam<T, S, ParamIndex> _type;
+    typedef C2GlobalParam<T, S, ParamIndex> _Type;
 
 public:
     /// Wrapper around base structure's constructor.
     template<typename ...Args>
-    inline C2GlobalParam(const Args(&... args)) : T(sizeof(_type), _type::typeIndex), S(args...) { }
+    inline C2GlobalParam(const Args(&... args)) : T(sizeof(_Type), _Type::PARAM_TYPE), S(args...) { }
 
-    DEFINE_CAST_OPERATORS(_type)
+    DEFINE_CAST_OPERATORS(_Type)
 };
 
 /**
@@ -344,20 +344,20 @@
  */
 template<typename T, typename S, int ParamIndex>
 struct C2_HIDE C2GlobalParam<T, S, ParamIndex, IF_FLEXIBLE(S)>
-    : public T, public C2FlexStructCheck<S, ParamIndex, T::indexFlags | T::Type::kDirGlobal> {
+    : public T, public C2FlexStructCheck<S, ParamIndex, T::PARAM_KIND | T::Type::DIR_GLOBAL> {
 private:
-    typedef C2GlobalParam<T, S, ParamIndex> _type;
+    typedef C2GlobalParam<T, S, ParamIndex> _Type;
 
     /// Wrapper around base structure's constructor.
     template<typename ...Args>
     inline C2GlobalParam(size_t flexCount, const Args(&... args))
-        : T(_type::calcSize(flexCount), _type::typeIndex), m(flexCount, args...) { }
+        : T(_Type::calcSize(flexCount), _Type::PARAM_TYPE), m(flexCount, args...) { }
 
 public:
     S m; ///< wrapped flexible structure
 
-    DEFINE_FLEXIBLE_METHODS(_type, S)
-    DEFINE_CAST_OPERATORS(_type)
+    DEFINE_FLEXIBLE_METHODS(_Type, S)
+    DEFINE_CAST_OPERATORS(_Type)
 };
 
 /**
@@ -379,30 +379,30 @@
  * There are 3 flavors of port parameters: unspecified, input and output. Parameters with
  * unspecified port expose a setPort method, and add an initial port parameter to the constructor.
  */
-template<typename T, typename S, int ParamIndex=S::coreIndex, class Flex=void>
+template<typename T, typename S, int ParamIndex=S::CORE_INDEX, class Flex=void>
 struct C2_HIDE C2PortParam : public T, public S, public C2CoreIndexOverride<S, ParamIndex>,
-        private C2StructCheck<S, ParamIndex, T::indexFlags | T::Index::kDirUndefined> {
+        private C2StructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_UNDEFINED> {
 private:
-    typedef C2PortParam<T, S, ParamIndex> _type;
+    typedef C2PortParam<T, S, ParamIndex> _Type;
 
 public:
     /// Default constructor.
-    inline C2PortParam() : T(sizeof(_type), _type::typeIndex) { }
+    inline C2PortParam() : T(sizeof(_Type), _Type::PARAM_TYPE) { }
     template<typename ...Args>
     /// Wrapper around base structure's constructor while specifying port/direction.
     inline C2PortParam(bool _output, const Args(&... args))
-        : T(sizeof(_type), _output ? output::typeIndex : input::typeIndex), S(args...) { }
+        : T(sizeof(_Type), _output ? output::PARAM_TYPE : input::PARAM_TYPE), S(args...) { }
     /// Set port/direction.
     inline void setPort(bool output) { C2Param::setPort(output); }
 
-    DEFINE_CAST_OPERATORS(_type)
+    DEFINE_CAST_OPERATORS(_Type)
 
     /// Specialization for an input port parameter.
     struct input : public T, public S, public C2CoreIndexOverride<S, ParamIndex>,
-            public C2StructCheck<S, ParamIndex, T::indexFlags | T::Index::kDirInput> {
+            public C2StructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_INPUT> {
         /// Wrapper around base structure's constructor.
         template<typename ...Args>
-        inline input(const Args(&... args)) : T(sizeof(_type), input::typeIndex), S(args...) { }
+        inline input(const Args(&... args)) : T(sizeof(_Type), input::PARAM_TYPE), S(args...) { }
 
         DEFINE_CAST_OPERATORS(input)
 
@@ -410,10 +410,10 @@
 
     /// Specialization for an output port parameter.
     struct output : public T, public S, public C2CoreIndexOverride<S, ParamIndex>,
-            public C2StructCheck<S, ParamIndex, T::indexFlags | T::Index::kDirOutput> {
+            public C2StructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_OUTPUT> {
         /// Wrapper around base structure's constructor.
         template<typename ...Args>
-        inline output(const Args(&... args)) : T(sizeof(_type), output::typeIndex), S(args...) { }
+        inline output(const Args(&... args)) : T(sizeof(_Type), output::PARAM_TYPE), S(args...) { }
 
         DEFINE_CAST_OPERATORS(output)
     };
@@ -440,16 +440,16 @@
  */
 template<typename T, typename S, int ParamIndex>
 struct C2_HIDE C2PortParam<T, S, ParamIndex, IF_FLEXIBLE(S)>
-    : public T, public C2FlexStructCheck<S, ParamIndex, T::indexFlags | T::Type::kDirUndefined> {
+    : public T, public C2FlexStructCheck<S, ParamIndex, T::PARAM_KIND | T::Type::DIR_UNDEFINED> {
 private:
-    typedef C2PortParam<T, S, ParamIndex> _type;
+    typedef C2PortParam<T, S, ParamIndex> _Type;
 
     /// Default constructor for basic allocation: new(flexCount) P.
-    inline C2PortParam(size_t flexCount) : T(_type::calcSize(flexCount), _type::typeIndex) { }
+    inline C2PortParam(size_t flexCount) : T(_Type::calcSize(flexCount), _Type::PARAM_TYPE) { }
     template<typename ...Args>
     /// Wrapper around base structure's constructor while also specifying port/direction.
     inline C2PortParam(size_t flexCount, bool _output, const Args(&... args))
-        : T(_type::calcSize(flexCount), _output ? output::typeIndex : input::typeIndex),
+        : T(_Type::calcSize(flexCount), _output ? output::PARAM_TYPE : input::PARAM_TYPE),
           m(flexCount, args...) { }
 
 public:
@@ -458,17 +458,17 @@
 
     S m; ///< wrapped flexible structure
 
-    DEFINE_FLEXIBLE_METHODS(_type, S)
-    DEFINE_CAST_OPERATORS(_type)
+    DEFINE_FLEXIBLE_METHODS(_Type, S)
+    DEFINE_CAST_OPERATORS(_Type)
 
     /// Specialization for an input port parameter.
     struct input : public T,
-            public C2FlexStructCheck<S, ParamIndex, T::indexFlags | T::Index::kDirInput> {
+            public C2FlexStructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_INPUT> {
     private:
         /// Wrapper around base structure's constructor while also specifying port/direction.
         template<typename ...Args>
         inline input(size_t flexCount, const Args(&... args))
-            : T(_type::calcSize(flexCount), input::typeIndex), m(flexCount, args...) { }
+            : T(_Type::calcSize(flexCount), input::PARAM_TYPE), m(flexCount, args...) { }
 
     public:
         S m; ///< wrapped flexible structure
@@ -479,12 +479,12 @@
 
     /// Specialization for an output port parameter.
     struct output : public T,
-            public C2FlexStructCheck<S, ParamIndex, T::indexFlags | T::Index::kDirOutput> {
+            public C2FlexStructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_OUTPUT> {
     private:
         /// Wrapper around base structure's constructor while also specifying port/direction.
         template<typename ...Args>
         inline output(size_t flexCount, const Args(&... args))
-            : T(_type::calcSize(flexCount), output::typeIndex), m(flexCount, args...) { }
+            : T(_Type::calcSize(flexCount), output::PARAM_TYPE), m(flexCount, args...) { }
 
     public:
         S m; ///< wrapped flexible structure
@@ -515,39 +515,39 @@
  * parameters with unspecified port expose a setPort method, and add an additional initial port
  * parameter to the constructor.
  */
-template<typename T, typename S, int ParamIndex=S::coreIndex, class Flex=void>
+template<typename T, typename S, int ParamIndex=S::CORE_INDEX, class Flex=void>
 struct C2_HIDE C2StreamParam : public T, public S, public C2CoreIndexOverride<S, ParamIndex>,
         private C2StructCheck<S, ParamIndex,
-                T::indexFlags | T::Index::kStreamFlag | T::Index::kDirUndefined> {
+                T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Index::DIR_UNDEFINED> {
 private:
-    typedef C2StreamParam<T, S, ParamIndex> _type;
+    typedef C2StreamParam<T, S, ParamIndex> _Type;
 
 public:
     /// Default constructor. Port/direction and stream-ID is undefined.
-    inline C2StreamParam() : T(sizeof(_type), _type::typeIndex) { }
+    inline C2StreamParam() : T(sizeof(_Type), _Type::PARAM_TYPE) { }
     /// Wrapper around base structure's constructor while also specifying port/direction and
     /// stream-ID.
     template<typename ...Args>
     inline C2StreamParam(bool _output, unsigned stream, const Args(&... args))
-        : T(sizeof(_type), _output ? output::typeIndex : input::typeIndex, stream),
+        : T(sizeof(_Type), _output ? output::PARAM_TYPE : input::PARAM_TYPE, stream),
           S(args...) { }
     /// Set port/direction.
     inline void setPort(bool output) { C2Param::setPort(output); }
     /// Set stream-id. \retval true if the stream-id was successfully set.
     inline bool setStream(unsigned stream) { return C2Param::setStream(stream); }
 
-    DEFINE_CAST_OPERATORS(_type)
+    DEFINE_CAST_OPERATORS(_Type)
 
     /// Specialization for an input stream parameter.
     struct input : public T, public S, public C2CoreIndexOverride<S, ParamIndex>,
             public C2StructCheck<S, ParamIndex,
-                    T::indexFlags | T::Index::kStreamFlag | T::Type::kDirInput> {
+                    T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Type::DIR_INPUT> {
         /// Default constructor. Stream-ID is undefined.
-        inline input() : T(sizeof(_type), input::typeIndex) { }
+        inline input() : T(sizeof(_Type), input::PARAM_TYPE) { }
         /// Wrapper around base structure's constructor while also specifying stream-ID.
         template<typename ...Args>
         inline input(unsigned stream, const Args(&... args))
-            : T(sizeof(_type), input::typeIndex, stream), S(args...) { }
+            : T(sizeof(_Type), input::PARAM_TYPE, stream), S(args...) { }
         /// Set stream-id. \retval true if the stream-id was successfully set.
         inline bool setStream(unsigned stream) { return C2Param::setStream(stream); }
 
@@ -557,13 +557,13 @@
     /// Specialization for an output stream parameter.
     struct output : public T, public S, public C2CoreIndexOverride<S, ParamIndex>,
             public C2StructCheck<S, ParamIndex,
-                    T::indexFlags | T::Index::kStreamFlag | T::Type::kDirOutput> {
+                    T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Type::DIR_OUTPUT> {
         /// Default constructor. Stream-ID is undefined.
-        inline output() : T(sizeof(_type), output::typeIndex) { }
+        inline output() : T(sizeof(_Type), output::PARAM_TYPE) { }
         /// Wrapper around base structure's constructor while also specifying stream-ID.
         template<typename ...Args>
         inline output(unsigned stream, const Args(&... args))
-            : T(sizeof(_type), output::typeIndex, stream), S(args...) { }
+            : T(sizeof(_Type), output::PARAM_TYPE, stream), S(args...) { }
         /// Set stream-id. \retval true if the stream-id was successfully set.
         inline bool setStream(unsigned stream) { return C2Param::setStream(stream); }
 
@@ -596,16 +596,16 @@
 struct C2_HIDE C2StreamParam<T, S, ParamIndex, IF_FLEXIBLE(S)>
     : public T,
       public C2FlexStructCheck<S, ParamIndex,
-              T::indexFlags | T::Index::kStreamFlag | T::Index::kDirUndefined> {
+              T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Index::DIR_UNDEFINED> {
 private:
-    typedef C2StreamParam<T, S, ParamIndex> _type;
+    typedef C2StreamParam<T, S, ParamIndex> _Type;
     /// Default constructor. Port/direction and stream-ID is undefined.
-    inline C2StreamParam(size_t flexCount) : T(_type::calcSize(flexCount), _type::typeIndex, 0u) { }
+    inline C2StreamParam(size_t flexCount) : T(_Type::calcSize(flexCount), _Type::PARAM_TYPE, 0u) { }
     /// Wrapper around base structure's constructor while also specifying port/direction and
     /// stream-ID.
     template<typename ...Args>
     inline C2StreamParam(size_t flexCount, bool _output, unsigned stream, const Args(&... args))
-        : T(_type::calcSize(flexCount), _output ? output::typeIndex : input::typeIndex, stream),
+        : T(_Type::calcSize(flexCount), _output ? output::PARAM_TYPE : input::PARAM_TYPE, stream),
           m(flexCount, args...) { }
 
 public:
@@ -616,20 +616,20 @@
     /// Set stream-id. \retval true if the stream-id was successfully set.
     inline bool setStream(unsigned stream) { return C2Param::setStream(stream); }
 
-    DEFINE_FLEXIBLE_METHODS(_type, S)
-    DEFINE_CAST_OPERATORS(_type)
+    DEFINE_FLEXIBLE_METHODS(_Type, S)
+    DEFINE_CAST_OPERATORS(_Type)
 
     /// Specialization for an input stream parameter.
     struct input : public T,
             public C2FlexStructCheck<S, ParamIndex,
-                    T::indexFlags | T::Index::kStreamFlag | T::Type::kDirInput> {
+                    T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Type::DIR_INPUT> {
     private:
         /// Default constructor. Stream-ID is undefined.
-        inline input(size_t flexCount) : T(_type::calcSize(flexCount), input::typeIndex) { }
+        inline input(size_t flexCount) : T(_Type::calcSize(flexCount), input::PARAM_TYPE) { }
         /// Wrapper around base structure's constructor while also specifying stream-ID.
         template<typename ...Args>
         inline input(size_t flexCount, unsigned stream, const Args(&... args))
-            : T(_type::calcSize(flexCount), input::typeIndex, stream), m(flexCount, args...) { }
+            : T(_Type::calcSize(flexCount), input::PARAM_TYPE, stream), m(flexCount, args...) { }
 
     public:
         S m; ///< wrapped flexible structure
@@ -644,14 +644,14 @@
     /// Specialization for an output stream parameter.
     struct output : public T,
             public C2FlexStructCheck<S, ParamIndex,
-                    T::indexFlags | T::Index::kStreamFlag | T::Type::kDirOutput> {
+                    T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Type::DIR_OUTPUT> {
     private:
         /// Default constructor. Stream-ID is undefined.
-        inline output(size_t flexCount) : T(_type::calcSize(flexCount), output::typeIndex) { }
+        inline output(size_t flexCount) : T(_Type::calcSize(flexCount), output::PARAM_TYPE) { }
         /// Wrapper around base structure's constructor while also specifying stream-ID.
         template<typename ...Args>
         inline output(size_t flexCount, unsigned stream, const Args(&... args))
-            : T(_type::calcSize(flexCount), output::typeIndex, stream), m(flexCount, args...) { }
+            : T(_Type::calcSize(flexCount), output::PARAM_TYPE, stream), m(flexCount, args...) { }
 
     public:
         S m; ///< wrapped flexible structure
@@ -672,11 +672,11 @@
  */
 template<typename T>
 struct C2SimpleValueStruct {
-    T mValue; ///< simple value of the structure
+    T value; ///< simple value of the structure
     // Default constructor.
     inline C2SimpleValueStruct() = default;
     // Constructor with an initial value.
-    inline C2SimpleValueStruct(T value) : mValue(value) {}
+    inline C2SimpleValueStruct(T value) : value(value) {}
     DEFINE_BASE_C2STRUCT(SimpleValue)
 };
 
@@ -704,16 +704,16 @@
  */
 template<typename T>
 struct C2ConstMemoryBlock : public C2MemoryBlock<T> {
-    virtual const T * data() const { return mData; }
-    virtual size_t size() const { return mSize; }
+    virtual const T * data() const { return _mData; }
+    virtual size_t size() const { return _mSize; }
 
     /// Constructor.
     template<unsigned N>
-    inline constexpr C2ConstMemoryBlock(const T(&init)[N]) : mData(init), mSize(N) {}
+    inline constexpr C2ConstMemoryBlock(const T(&init)[N]) : _mData(init), _mSize(N) {}
 
 private:
-    const T *mData;
-    const size_t mSize;
+    const T *_mData;
+    const size_t _mSize;
 };
 
 /// \addtogroup internal
@@ -770,30 +770,30 @@
  * constructed on its own as it's size is 0.
  *
  * \internal This is different from C2SimpleArrayStruct<T[]> simply because its member has the name
- * as mValue to reflect this is a single value.
+ * as value to reflect this is a single value.
  */
 template<typename T>
 struct C2SimpleValueStruct<T[]> {
     static_assert(std::is_same<T, char>::value || std::is_same<T, uint8_t>::value,
                   "C2SimpleValueStruct<T[]> is only for BLOB or STRING");
-    T mValue[];
+    T value[];
 
     inline C2SimpleValueStruct() = default;
     DEFINE_BASE_C2STRUCT(SimpleValue)
-    FLEX(C2SimpleValueStruct, mValue)
+    FLEX(C2SimpleValueStruct, value)
 
 private:
     inline C2SimpleValueStruct(size_t flexCount, const C2MemoryBlock<T> &block) {
-        _C2ValueArrayHelper::init(mValue, flexCount, block);
+        _C2ValueArrayHelper::init(value, flexCount, block);
     }
 
     inline C2SimpleValueStruct(size_t flexCount, const std::initializer_list<T> &init) {
-        _C2ValueArrayHelper::init(mValue, flexCount, init);
+        _C2ValueArrayHelper::init(value, flexCount, init);
     }
 
     template<unsigned N>
     inline C2SimpleValueStruct(size_t flexCount, const T(&init)[N]) {
-        _C2ValueArrayHelper::init(mValue, flexCount, init);
+        _C2ValueArrayHelper::init(value, flexCount, init);
     }
 };
 
@@ -812,30 +812,30 @@
     static_assert(!std::is_same<T, char>::value && !std::is_same<T, uint8_t>::value,
                   "use C2SimpleValueStruct<T[]> is for BLOB or STRING");
 
-    T mValues[]; ///< array member
+    T values[]; ///< array member
     /// Default constructor
     inline C2SimpleArrayStruct() = default;
-    DEFINE_BASE_FLEX_C2STRUCT(SimpleArray, mValues)
-    //FLEX(C2SimpleArrayStruct, mValues)
+    DEFINE_BASE_FLEX_C2STRUCT(SimpleArray, values)
+    //FLEX(C2SimpleArrayStruct, values)
 
 private:
     /// Construct from a C2MemoryBlock.
     /// Used only by the flexible parameter allocators (alloc_unique & alloc_shared).
     inline C2SimpleArrayStruct(size_t flexCount, const C2MemoryBlock<T> &block) {
-        _C2ValueArrayHelper::init(mValues, flexCount, block);
+        _C2ValueArrayHelper::init(values, flexCount, block);
     }
 
     /// Construct from an initializer list.
     /// Used only by the flexible parameter allocators (alloc_unique & alloc_shared).
     inline C2SimpleArrayStruct(size_t flexCount, const std::initializer_list<T> &init) {
-        _C2ValueArrayHelper::init(mValues, flexCount, init);
+        _C2ValueArrayHelper::init(values, flexCount, init);
     }
 
     /// Construct from another flexible array.
     /// Used only by the flexible parameter allocators (alloc_unique & alloc_shared).
     template<unsigned N>
     inline C2SimpleArrayStruct(size_t flexCount, const T(&init)[N]) {
-        _C2ValueArrayHelper::init(mValues, flexCount, init);
+        _C2ValueArrayHelper::init(values, flexCount, init);
     }
 };
 
@@ -851,54 +851,54 @@
  *   typedef C2PortParam<C2Tuning, C2Int32Value, kParamIndexMyIntegerPortParam>
  *           C2MyIntegerPortParamTuning;
  *
- * They contain a single member (mValue or mValues) that is described as "value" or "values".
+ * They contain a single member (value or values) that is described as "value" or "values".
  */
-/// A 32-bit signed integer parameter in mValue, described as "value"
+/// A 32-bit signed integer parameter in value, described as "value"
 typedef C2SimpleValueStruct<int32_t> C2Int32Value;
-/// A 32-bit signed integer array parameter in mValues, described as "values"
+/// A 32-bit signed integer array parameter in values, described as "values"
 typedef C2SimpleArrayStruct<int32_t> C2Int32Array;
-/// A 32-bit unsigned integer parameter in mValue, described as "value"
+/// A 32-bit unsigned integer parameter in value, described as "value"
 typedef C2SimpleValueStruct<uint32_t> C2Uint32Value;
-/// A 32-bit unsigned integer array parameter in mValues, described as "values"
+/// A 32-bit unsigned integer array parameter in values, described as "values"
 typedef C2SimpleArrayStruct<uint32_t> C2Uint32Array;
-/// A 64-bit signed integer parameter in mValue, described as "value"
+/// A 64-bit signed integer parameter in value, described as "value"
 typedef C2SimpleValueStruct<int64_t> C2Int64Value;
-/// A 64-bit signed integer array parameter in mValues, described as "values"
+/// A 64-bit signed integer array parameter in values, described as "values"
 typedef C2SimpleArrayStruct<int64_t> C2Int64Array;
-/// A 64-bit unsigned integer parameter in mValue, described as "value"
+/// A 64-bit unsigned integer parameter in value, described as "value"
 typedef C2SimpleValueStruct<uint64_t> C2Uint64Value;
-/// A 64-bit unsigned integer array parameter in mValues, described as "values"
+/// A 64-bit unsigned integer array parameter in values, described as "values"
 typedef C2SimpleArrayStruct<uint64_t> C2Uint64Array;
-/// A float parameter in mValue, described as "value"
+/// A float parameter in value, described as "value"
 typedef C2SimpleValueStruct<float> C2FloatValue;
-/// A float array parameter in mValues, described as "values"
+/// A float array parameter in values, described as "values"
 typedef C2SimpleArrayStruct<float> C2FloatArray;
-/// A blob flexible parameter in mValue, described as "value"
+/// A blob flexible parameter in value, described as "value"
 typedef C2SimpleValueStruct<uint8_t[]> C2BlobValue;
-/// A string flexible parameter in mValue, described as "value"
+/// A string flexible parameter in value, described as "value"
 typedef C2SimpleValueStruct<char[]> C2StringValue;
 
 #if 1
 template<typename T>
-const std::initializer_list<const C2FieldDescriptor> C2SimpleValueStruct<T>::fieldList = { C2FIELD(mValue, "value") };
+const std::initializer_list<const C2FieldDescriptor> C2SimpleValueStruct<T>::FIELD_LIST = { C2FIELD(value, "value") };
 template<typename T>
-const std::initializer_list<const C2FieldDescriptor> C2SimpleValueStruct<T[]>::fieldList = { C2FIELD(mValue, "value") };
+const std::initializer_list<const C2FieldDescriptor> C2SimpleValueStruct<T[]>::FIELD_LIST = { C2FIELD(value, "value") };
 template<typename T>
-const std::initializer_list<const C2FieldDescriptor> C2SimpleArrayStruct<T>::fieldList = { C2FIELD(mValues, "values") };
+const std::initializer_list<const C2FieldDescriptor> C2SimpleArrayStruct<T>::FIELD_LIST = { C2FIELD(values, "values") };
 #else
 // This seem to be able to be handled by the template above
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<int32_t>, { C2FIELD(mValue, "value") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<uint32_t>, { C2FIELD(mValue, "value") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<int64_t>, { C2FIELD(mValue, "value") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<uint64_t>, { C2FIELD(mValue, "value") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<float>, { C2FIELD(mValue, "value") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<uint8_t[]>, { C2FIELD(mValue, "value") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<char[]>, { C2FIELD(mValue, "value") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<int32_t>, { C2FIELD(mValues, "values") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<uint32_t>, { C2FIELD(mValues, "values") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<int64_t>, { C2FIELD(mValues, "values") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<uint64_t>, { C2FIELD(mValues, "values") });
-DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<float>, { C2FIELD(mValues, "values") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<int32_t>, { C2FIELD(value, "value") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<uint32_t>, { C2FIELD(value, "value") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<int64_t>, { C2FIELD(value, "value") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<uint64_t>, { C2FIELD(value, "value") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<float>, { C2FIELD(value, "value") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<uint8_t[]>, { C2FIELD(value, "value") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleValueStruct<char[]>, { C2FIELD(value, "value") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<int32_t>, { C2FIELD(values, "values") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<uint32_t>, { C2FIELD(values, "values") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<int64_t>, { C2FIELD(values, "values") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<uint64_t>, { C2FIELD(values, "values") });
+DESCRIBE_TEMPLATED_C2STRUCT(C2SimpleArrayStruct<float>, { C2FIELD(values, "values") });
 #endif
 
 /// @}
diff --git a/media/libstagefright/codec2/include/SimpleC2Component.h b/media/libstagefright/codec2/include/SimpleC2Component.h
index 48b8382..a4b6ee1 100644
--- a/media/libstagefright/codec2/include/SimpleC2Component.h
+++ b/media/libstagefright/codec2/include/SimpleC2Component.h
@@ -84,23 +84,28 @@
     virtual c2_status_t onFlush_sm() = 0;
 
     /**
-     * Drain the component.
-     */
-    virtual c2_status_t onDrain_nb() = 0;
-
-    /**
      * Process the given work and finish pending work using finish().
      *
      * \param[in,out]   work    the work to process
      * \param[in]       pool    the pool to use for allocating output blocks.
-     *
-     * \retval true             |work| is done and ready for return to client
-     * \retval false            more data is needed for the |work| to be done;
-     *                          mark |work| as pending.
      */
-    virtual bool process(
+    virtual void process(
             const std::unique_ptr<C2Work> &work,
-            std::shared_ptr<C2BlockPool> pool) = 0;
+            const std::shared_ptr<C2BlockPool> &pool) = 0;
+
+    /**
+     * Drain the component and finish pending work using finish().
+     *
+     * \param[in]   drainMode   mode of drain.
+     * \param[in]   pool        the pool to use for allocating output blocks.
+     *
+     * \retval C2_OK            The component has drained all pending output
+     *                          work.
+     * \retval C2_OMITTED       Unsupported mode (e.g. DRAIN_CHAIN)
+     */
+    virtual c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) = 0;
 
     // for derived classes
     /**
@@ -116,6 +121,21 @@
      */
     void finish(uint64_t frameIndex, std::function<void(const std::unique_ptr<C2Work> &)> fillWork);
 
+    std::shared_ptr<C2Buffer> createLinearBuffer(
+            const std::shared_ptr<C2LinearBlock> &block);
+
+    std::shared_ptr<C2Buffer> createLinearBuffer(
+            const std::shared_ptr<C2LinearBlock> &block, size_t offset, size_t size);
+
+    std::shared_ptr<C2Buffer> createGraphicBuffer(
+            const std::shared_ptr<C2GraphicBlock> &block);
+
+    std::shared_ptr<C2Buffer> createGraphicBuffer(
+            const std::shared_ptr<C2GraphicBlock> &block,
+            const C2Rect &crop);
+
+    static constexpr uint32_t NO_DRAIN = ~0u;
+
 private:
     const std::shared_ptr<C2ComponentInterface> mIntf;
     std::atomic_bool mExitRequested;
@@ -135,10 +155,30 @@
     };
     Mutexed<ExecState> mExecState;
 
-    struct WorkQueue {
+    class WorkQueue {
+    public:
+        inline WorkQueue() : mGeneration(0ul) {}
+
+        inline uint64_t generation() const { return mGeneration; }
+        inline void incGeneration() { ++mGeneration; }
+
+        std::unique_ptr<C2Work> pop_front();
+        void push_back(std::unique_ptr<C2Work> work);
+        bool empty() const;
+        uint32_t drainMode() const;
+        void markDrain(uint32_t drainMode);
+        void clear();
+
         Condition mCondition;
-        std::list<std::unique_ptr<C2Work>> mQueue;
+
+    private:
+        struct Entry {
+            std::unique_ptr<C2Work> work;
+            uint32_t drainMode;
+        };
+
         uint64_t mGeneration;
+        std::list<Entry> mQueue;
     };
     Mutexed<WorkQueue> mWorkQueue;
 
diff --git a/media/libstagefright/codec2/include/SimpleC2Interface.h b/media/libstagefright/codec2/include/SimpleC2Interface.h
new file mode 100644
index 0000000..3796b0b
--- /dev/null
+++ b/media/libstagefright/codec2/include/SimpleC2Interface.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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 SIMPLE_C2_INTERFACE_H_
+#define SIMPLE_C2_INTERFACE_H_
+
+#include <C2Component.h>
+
+namespace android {
+
+class SimpleC2Interface : public C2ComponentInterface {
+public:
+    class Builder {
+    public:
+        inline Builder(
+                const char *name,
+                c2_node_id_t id)
+            : mIntf(new SimpleC2Interface(name, id)) {}
+
+        inline Builder(
+                const char *name,
+                c2_node_id_t id,
+                std::function<void(::android::SimpleC2Interface*)> deleter)
+            : mIntf(new SimpleC2Interface(name, id), deleter) {}
+
+        inline Builder &inputFormat(C2FormatKind input) {
+            mIntf->mInputFormat.value = input;
+            return *this;
+        }
+
+        inline Builder &outputFormat(C2FormatKind output) {
+            mIntf->mOutputFormat.value = output;
+            return *this;
+        }
+
+        inline std::shared_ptr<SimpleC2Interface> build() {
+            return mIntf;
+        }
+    private:
+        std::shared_ptr<SimpleC2Interface> mIntf;
+    };
+
+    virtual ~SimpleC2Interface() = default;
+
+    // From C2ComponentInterface
+    inline C2String getName() const override { return mName; }
+    inline c2_node_id_t getId() const override { return mId; }
+    c2_status_t query_vb(
+            const std::vector<C2Param* const> &stackParams,
+            const std::vector<C2Param::Index> &heapParamIndices,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2Param>>* const heapParams) const override;
+    inline c2_status_t config_vb(
+            const std::vector<C2Param* const> &,
+            c2_blocking_t,
+            std::vector<std::unique_ptr<C2SettingResult>>* const) override {
+        return C2_OMITTED;
+    }
+    inline c2_status_t createTunnel_sm(c2_node_id_t) override { return C2_OMITTED; }
+    inline c2_status_t releaseTunnel_sm(c2_node_id_t) override { return C2_OMITTED; }
+    inline c2_status_t querySupportedParams_nb(
+            std::vector<std::shared_ptr<C2ParamDescriptor>> * const) const override {
+        return C2_OMITTED;
+    }
+    c2_status_t querySupportedValues_vb(
+            std::vector<C2FieldSupportedValuesQuery> &,
+            c2_blocking_t) const override {
+        return C2_OMITTED;
+    }
+
+private:
+    inline SimpleC2Interface(const char *name, c2_node_id_t id)
+        : mName(name), mId(id), mInputFormat(0u), mOutputFormat(0u) {}
+
+    const C2String mName;
+    const c2_node_id_t mId;
+    C2StreamFormatConfig::input mInputFormat;
+    C2StreamFormatConfig::output mOutputFormat;
+
+    SimpleC2Interface() = delete;
+};
+
+}  // namespace android
+
+#endif  // SIMPLE_C2_INTERFACE_H_
diff --git a/media/libstagefright/codec2/tests/Android.bp b/media/libstagefright/codec2/tests/Android.bp
index cf75061..f26fbd0 100644
--- a/media/libstagefright/codec2/tests/Android.bp
+++ b/media/libstagefright/codec2/tests/Android.bp
@@ -42,23 +42,14 @@
     ],
 
     include_dirs: [
-        "frameworks/av/media/libstagefright/codec2/include",
-        "frameworks/av/media/libstagefright/codec2/vndk/include",
     ],
 
     shared_libs: [
-        "android.hardware.graphics.allocator@2.0",
-        "android.hardware.graphics.mapper@2.0",
         "libcutils",
-        "libhidlbase",
-        "libion",
         "liblog",
         "libstagefright_codec2",
-        "libutils",
-    ],
-
-    static_libs: [
         "libstagefright_codec2_vndk",
+        "libutils",
     ],
 
     cflags: [
@@ -80,22 +71,15 @@
     ],
 
     include_dirs: [
-        "frameworks/av/media/libstagefright/codec2/include",
-        "frameworks/av/media/libstagefright/codec2/vndk/include",
         "frameworks/native/include/media/openmax",
     ],
 
     shared_libs: [
         "libcutils",
-        "libhidlbase",
-        "libion",
         "liblog",
         "libstagefright_codec2",
-        "libutils",
-    ],
-
-    static_libs: [
         "libstagefright_codec2_vndk",
+        "libutils",
     ],
 
     cflags: [
diff --git a/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp b/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
index 8333fb8..339f927 100644
--- a/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
+++ b/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
@@ -38,7 +38,7 @@
 template <class T> std::unique_ptr<T> alloc_unique_cstr(const char *cstr) {
     size_t len = strlen(cstr);
     std::unique_ptr<T> ptr = T::alloc_unique(len);
-    memcpy(ptr->m.mValue, cstr, len);
+    memcpy(ptr->m.value, cstr, len);
     return ptr;
 }
 
@@ -202,7 +202,7 @@
 template <typename T>
 c2_status_t C2CompIntfTest::queryOnHeap(
         const T &p, std::vector<std::unique_ptr<C2Param>> *const heapParams) {
-    uint32_t index = p.type();
+    uint32_t index = p.index() & ~0x03FE0000;
     if (p.forStream()) {
         index |= ((p.stream() << 17) & 0x01FE0000) | 0x02000000;
     }
@@ -369,12 +369,12 @@
     const auto &c2FSV = validValueInfos;
 
     switch (c2FSV.type) {
-    case C2FieldSupportedValues::Type::EMPTY: {
+    case C2FieldSupportedValues::type_t::EMPTY: {
         invalidValues->emplace_back(TField(0));
         // TODO(hiroh) : Should other invalid values be tested?
         break;
     }
-    case C2FieldSupportedValues::Type::RANGE: {
+    case C2FieldSupportedValues::type_t::RANGE: {
         const auto &range = c2FSV.range;
         auto rmin = prim2Value(range.min);
         auto rmax = prim2Value(range.max);
@@ -448,7 +448,7 @@
         }
         break;
     }
-    case C2FieldSupportedValues::Type::VALUES: {
+    case C2FieldSupportedValues::type_t::VALUES: {
         for (const C2Value::Primitive &prim : c2FSV.values) {
             validValues->emplace_back(prim2Value(prim));
         }
@@ -462,7 +462,7 @@
         }
         break;
     }
-    case C2FieldSupportedValues::Type::FLAGS: {
+    case C2FieldSupportedValues::type_t::FLAGS: {
         // TODO(hiroh) : Implement the case that param.type is FLAGS.
         break;
     }
@@ -605,21 +605,21 @@
 
 #define TEST_VSSTRUCT_WRITABLE_FIELD(TParam_, field_type_name_)         \
     do {                                                                \
-        TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, mWidth); \
-        TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, mHeight);\
+        TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, width);  \
+        TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, height); \
     } while (0)
 
 #define TEST_U32_WRITABLE_FIELD(TParam_, field_type_name_)              \
-  TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, mValue)
+  TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, value)
 
 #define TEST_ENUM_WRITABLE_FIELD(TParam_, field_type_name_)             \
-  TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, mValue)
+  TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, value)
 
 // TODO(hiroh): Support parameters based on char[] and uint32_t[].
 //#define TEST_STRING_WRITABLE_FIELD(TParam_, field_type_name_)
-// TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, m.mValue)
+// TEST_GENERAL_WRITABLE_FIELD(TParam_, field_type_name_, m.value)
 //#define TEST_U32ARRAY_WRITABLE_FIELD(Tparam_, field_type_name_)
-// TEST_GENERAL_WRITABLE_FIELD(Tparam_, uint32_t[], field_type_name_, mValues)
+// TEST_GENERAL_WRITABLE_FIELD(Tparam_, uint32_t[], field_type_name_, values)
 
 #define EACH_TEST(TParam_, field_type_name_, test_name)                 \
     do {                                                                \
diff --git a/media/libstagefright/codec2/tests/C2Param_test.cpp b/media/libstagefright/codec2/tests/C2Param_test.cpp
index 05f8a96..8ebc584 100644
--- a/media/libstagefright/codec2/tests/C2Param_test.cpp
+++ b/media/libstagefright/codec2/tests/C2Param_test.cpp
@@ -57,11 +57,11 @@
     *os << "*" << fd.length() << ")";
 }
 
-enum C2ParamIndexType {
+enum C2ParamIndexType : C2Param::type_index_t {
     kParamIndexNumber,
     kParamIndexNumbers,
     kParamIndexNumber2,
-    kParamIndexVendorStart = C2Param::BaseIndex::kVendorStart,
+    kParamIndexVendorStart = C2Param::TYPE_INDEX_VENDOR_START,
     kParamIndexVendorNumbers,
 };
 
@@ -91,11 +91,11 @@
 };
 
 struct C2SizeStruct {
-    int32_t mNumber;
-    int32_t mHeight;
-    enum : uint32_t { coreIndex = kParamIndexSize };                        // <= needed for C2FieldDescriptor
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;  // <= needed for C2FieldDescriptor
-    const static FD::Type TYPE = (FD::Type)(coreIndex | FD::STRUCT_FLAG);
+    int32_t width;
+    int32_t height;
+    enum : uint32_t { CORE_INDEX = kParamIndexSize };                        // <= needed for C2FieldDescriptor
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;  // <= needed for C2FieldDescriptor
+    const static FD::type_t TYPE = (FD::type_t)(CORE_INDEX | FD::STRUCT_FLAG);
 };
 
 DEFINE_NO_NAMED_VALUES_FOR(C2SizeStruct)
@@ -110,22 +110,22 @@
 }
 
 struct C2TestStruct_A {
-    int32_t mSigned32;
-    int64_t mSigned64[2];
-    uint32_t mUnsigned32[1];
-    uint64_t mUnsigned64;
-    float mFloat;
-    C2SizeStruct mSize[3];
-    uint8_t mBlob[100];
-    char mString[100];
-    bool mYesNo[100];
+    int32_t signed32;
+    int64_t signed64[2];
+    uint32_t unsigned32[1];
+    uint64_t unsigned64;
+    float fp32;
+    C2SizeStruct sz[3];
+    uint8_t blob[100];
+    char string[100];
+    bool yesNo[100];
 
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;
-    // enum : uint32_t { coreIndex = kParamIndexTest };
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;
+    // enum : uint32_t { CORE_INDEX = kParamIndexTest };
     // typedef C2TestStruct_A _type;
 } __attribute__((packed));
 
-const std::initializer_list<const C2FieldDescriptor> C2TestStruct_A::fieldList =
+const std::initializer_list<const C2FieldDescriptor> C2TestStruct_A::FIELD_LIST =
     { { FD::INT32,    1, "s32",   0, 4 },
       { FD::INT64,    2, "s64",   4, 8 },
       { FD::UINT32,   1, "u32",  20, 4 },
@@ -137,7 +137,7 @@
       { FD::BLOB,   100, "y-n", 260, 1 } };
 
 TEST_P(C2ParamTest_ParamFieldList, VerifyStruct) {
-    std::vector<const C2FieldDescriptor> fields = GetParam(), expected = C2TestStruct_A::fieldList;
+    std::vector<const C2FieldDescriptor> fields = GetParam(), expected = C2TestStruct_A::FIELD_LIST;
 
     // verify first field descriptor
     EXPECT_EQ(FD::INT32, fields[0].type());
@@ -158,34 +158,34 @@
     }
 }
 
-INSTANTIATE_TEST_CASE_P(InitializerList, C2ParamTest_ParamFieldList, ::testing::Values(C2TestStruct_A::fieldList));
+INSTANTIATE_TEST_CASE_P(InitializerList, C2ParamTest_ParamFieldList, ::testing::Values(C2TestStruct_A::FIELD_LIST));
 
 // define fields using C2FieldDescriptor pointer constructor
 const std::initializer_list<const C2FieldDescriptor> C2TestStruct_A_FD_PTR_fieldList =
-    { C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mSigned32,   "s32"),
-      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mSigned64,   "s64"),
-      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mUnsigned32, "u32"),
-      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mUnsigned64, "u64"),
-      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mFloat,      "fp"),
-      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mSize,       "size"),
-      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mBlob,       "blob"),
-      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mString,     "str"),
-    //  C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->mYesNo,      "y-n")
+    { C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->signed32,   "s32"),
+      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->signed64,   "s64"),
+      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->unsigned32, "u32"),
+      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->unsigned64, "u64"),
+      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->fp32,      "fp"),
+      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->sz,       "size"),
+      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->blob,       "blob"),
+      C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->string,     "str"),
+    //  C2FieldDescriptor(&((C2TestStruct_A*)(nullptr))->yesNo,      "y-n")
     };
 
 INSTANTIATE_TEST_CASE_P(PointerConstructor, C2ParamTest_ParamFieldList, ::testing::Values(C2TestStruct_A_FD_PTR_fieldList));
 
 // define fields using C2FieldDescriptor member-pointer constructor
 const std::initializer_list<const C2FieldDescriptor> C2TestStruct_A_FD_MEM_PTR_fieldList =
-    { C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mSigned32,   "s32"),
-      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mSigned64,   "s64"),
-      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mUnsigned32, "u32"),
-      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mUnsigned64, "u64"),
-      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mFloat,      "fp"),
-      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mSize,       "size"),
-      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mBlob,       "blob"),
-      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mString,     "str"),
-    //  C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::mYesNo,      "y-n")
+    { C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::signed32,   "s32"),
+      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::signed64,   "s64"),
+      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::unsigned32, "u32"),
+      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::unsigned64, "u64"),
+      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::fp32,      "fp"),
+      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::sz,       "size"),
+      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::blob,       "blob"),
+      C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::string,     "str"),
+    //  C2FieldDescriptor((C2TestStruct_A*)0, &C2TestStruct_A::yesNo,      "y-n")
     };
 
 INSTANTIATE_TEST_CASE_P(MemberPointerConstructor, C2ParamTest_ParamFieldList, ::testing::Values(C2TestStruct_A_FD_MEM_PTR_fieldList));
@@ -193,75 +193,75 @@
 // Test 2. define a structure with two-step helper methods
 
 struct C2TestAStruct {
-    int32_t mSigned32;
-    int64_t mSigned64[2];
-    uint32_t mUnsigned32[1];
-    uint64_t mUnsigned64;
-    float mFloat;
-    C2SizeStruct mSize[3];
-    uint8_t mBlob[100];
-    char mString[100];
-    bool mYesNo[100];
+    int32_t signed32;
+    int64_t signed64[2];
+    uint32_t unsigned32[1];
+    uint64_t unsigned64;
+    float fp32;
+    C2SizeStruct sz[3];
+    uint8_t blob[100];
+    char string[100];
+    bool yesNo[100];
 
 private: // test access level
     DEFINE_C2STRUCT(TestA)
 } C2_PACK;
 
 DESCRIBE_C2STRUCT(TestA, {
-    C2FIELD(mSigned32, "s32")
-    C2FIELD(mSigned64, "s64")
-    C2FIELD(mUnsigned32, "u32")
-    C2FIELD(mUnsigned64, "u64")
-    C2FIELD(mFloat, "fp")
-    C2FIELD(mSize, "size")
-    C2FIELD(mBlob, "blob")
-    C2FIELD(mString, "str")
-    // C2FIELD(mYesNo, "y-n")
+    C2FIELD(signed32, "s32")
+    C2FIELD(signed64, "s64")
+    C2FIELD(unsigned32, "u32")
+    C2FIELD(unsigned64, "u64")
+    C2FIELD(fp32, "fp")
+    C2FIELD(sz, "size")
+    C2FIELD(blob, "blob")
+    C2FIELD(string, "str")
+    // C2FIELD(yesNo, "y-n")
 }) // ; optional
 
-INSTANTIATE_TEST_CASE_P(DescribeStruct2Step, C2ParamTest_ParamFieldList, ::testing::Values(C2TestAStruct::fieldList));
+INSTANTIATE_TEST_CASE_P(DescribeStruct2Step, C2ParamTest_ParamFieldList, ::testing::Values(C2TestAStruct::FIELD_LIST));
 
 // Test 3. define a structure with one-step helper method
 
 struct C2TestBStruct {
-    int32_t mSigned32;
-    int64_t mSigned64[2];
-    uint32_t mUnsigned32[1];
-    uint64_t mUnsigned64;
-    float mFloat;
-    C2SizeStruct mSize[3];
-    uint8_t mBlob[100];
-    char mString[100];
-    bool mYesNo[100];
+    int32_t signed32;
+    int64_t signed64[2];
+    uint32_t unsigned32[1];
+    uint64_t unsigned64;
+    float fp32;
+    C2SizeStruct sz[3];
+    uint8_t blob[100];
+    char string[100];
+    bool yesNo[100];
 
 private: // test access level
     DEFINE_AND_DESCRIBE_C2STRUCT(TestB)
 
-    C2FIELD(mSigned32, "s32")
-    C2FIELD(mSigned64, "s64")
-    C2FIELD(mUnsigned32, "u32")
-    C2FIELD(mUnsigned64, "u64")
-    C2FIELD(mFloat, "fp")
-    C2FIELD(mSize, "size")
-    C2FIELD(mBlob, "blob")
-    C2FIELD(mString, "str")
-    // C2FIELD(mYesNo, "y-n")
+    C2FIELD(signed32, "s32")
+    C2FIELD(signed64, "s64")
+    C2FIELD(unsigned32, "u32")
+    C2FIELD(unsigned64, "u64")
+    C2FIELD(fp32, "fp")
+    C2FIELD(sz, "size")
+    C2FIELD(blob, "blob")
+    C2FIELD(string, "str")
+    // C2FIELD(yesNo, "y-n")
 };
 
-INSTANTIATE_TEST_CASE_P(DescribeStruct1Step, C2ParamTest_ParamFieldList, ::testing::Values(C2TestBStruct::fieldList));
+INSTANTIATE_TEST_CASE_P(DescribeStruct1Step, C2ParamTest_ParamFieldList, ::testing::Values(C2TestBStruct::FIELD_LIST));
 
 // Test 4. flexible members
 
 template<typename T>
 class C2ParamTest_FlexParamFieldList : public ::testing::Test {
 protected:
-    using Type=FD::Type;
+    using type_t=FD::type_t;
 
     // static std::initializer_list<std::initializer_list<const C2FieldDescriptor>>
     static std::vector<std::vector<const C2FieldDescriptor>>
             GetLists();
 
-    constexpr static Type flexType =
+    constexpr static type_t FlexType =
             std::is_same<T, int32_t>::value ? FD::INT32 :
             std::is_same<T, int64_t>::value ? FD::INT64 :
             std::is_same<T, uint32_t>::value ? FD::UINT32 :
@@ -269,8 +269,8 @@
             std::is_same<T, float>::value ? FD::FLOAT :
             std::is_same<T, uint8_t>::value ? FD::BLOB :
             std::is_same<T, char>::value ? FD::STRING :
-            std::is_same<T, C2SizeStruct>::value ? C2SizeStruct::TYPE : (Type)0;
-    constexpr static size_t flexSize = sizeof(T);
+            std::is_same<T, C2SizeStruct>::value ? C2SizeStruct::TYPE : (type_t)0;
+    constexpr static size_t FLEX_SIZE = sizeof(T);
 };
 
 typedef ::testing::Types<int32_t, int64_t, C2SizeStruct> FlexTypes;
@@ -282,11 +282,11 @@
         if (fields.size() > 1) {
             EXPECT_EQ(2u, fields.size());
             EXPECT_EQ(C2FieldDescriptor(FD::INT32, 1, "s32", 0, 4), fields[0]);
-            EXPECT_EQ(C2FieldDescriptor(this->flexType, 0, "flex", 4, this->flexSize),
+            EXPECT_EQ(C2FieldDescriptor(this->FlexType, 0, "flex", 4, this->FLEX_SIZE),
                       fields[1]);
         } else {
             EXPECT_EQ(1u, fields.size());
-            EXPECT_EQ(C2FieldDescriptor(this->flexType, 0, "flex", 0, this->flexSize),
+            EXPECT_EQ(C2FieldDescriptor(this->FlexType, 0, "flex", 0, this->FLEX_SIZE),
                       fields[0]);
         }
     }
@@ -295,33 +295,33 @@
 struct C2TestStruct_FlexS32 {
     int32_t mFlex[];
 
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;
-    // enum : uint32_t { coreIndex = kParamIndexTestFlex, flexSize = 4 };
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;
+    // enum : uint32_t { CORE_INDEX = kParamIndexTestFlex, FLEX_SIZE = 4 };
     // typedef C2TestStruct_FlexS32 _type;
-    // typedef int32_t flexType;
+    // typedef int32_t FlexType;
 };
 
-const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexS32::fieldList = {
+const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexS32::FIELD_LIST = {
     { FD::INT32, 0, "flex", 0, 4 }
 };
 
 struct C2TestStruct_FlexEndS32 {
-    int32_t mSigned32;
+    int32_t signed32;
     int32_t mFlex[];
 
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;
-    // enum : uint32_t { coreIndex = kParamIndexTestFlexEnd, flexSize = 4 };
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;
+    // enum : uint32_t { CORE_INDEX = kParamIndexTestFlexEnd, FLEX_SIZE = 4 };
     // typedef C2TestStruct_FlexEnd _type;
-    // typedef int32_t flexType;
+    // typedef int32_t FlexType;
 };
 
-const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexEndS32::fieldList = {
+const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexEndS32::FIELD_LIST = {
     { FD::INT32, 1, "s32", 0, 4 },
     { FD::INT32, 0, "flex", 4, 4 },
 };
 
 const static std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexEndS32_ptr_fieldList = {
-    C2FieldDescriptor(&((C2TestStruct_FlexEndS32*)0)->mSigned32, "s32"),
+    C2FieldDescriptor(&((C2TestStruct_FlexEndS32*)0)->signed32, "s32"),
     C2FieldDescriptor(&((C2TestStruct_FlexEndS32*)0)->mFlex, "flex"),
 };
 
@@ -335,7 +335,7 @@
 };
 
 struct C2TestFlexEndS32Struct {
-    int32_t mSigned32;
+    int32_t signed32;
     int32_t mFlexSigned32[];
 private: // test access level
     C2TestFlexEndS32Struct() {}
@@ -344,7 +344,7 @@
 } C2_PACK;
 
 DESCRIBE_C2STRUCT(TestFlexEndS32, {
-    C2FIELD(mSigned32, "s32")
+    C2FIELD(signed32, "s32")
     C2FIELD(mFlexSigned32, "flex")
 }) // ; optional
 
@@ -353,38 +353,38 @@
 //std::initializer_list<std::initializer_list<const C2FieldDescriptor>>
 C2ParamTest_FlexParamFieldList<int32_t>::GetLists() {
     return {
-        C2TestStruct_FlexS32::fieldList,
-        C2TestStruct_FlexEndS32::fieldList,
+        C2TestStruct_FlexS32::FIELD_LIST,
+        C2TestStruct_FlexEndS32::FIELD_LIST,
         C2TestStruct_FlexEndS32_ptr_fieldList,
-        C2TestFlexS32Struct::fieldList,
-        C2TestFlexEndS32Struct::fieldList,
+        C2TestFlexS32Struct::FIELD_LIST,
+        C2TestFlexEndS32Struct::FIELD_LIST,
     };
 }
 
 struct C2TestStruct_FlexS64 {
     int64_t mFlexSigned64[];
 
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;
-    // enum : uint32_t { coreIndex = kParamIndexTestFlexS64, flexSize = 8 };
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;
+    // enum : uint32_t { CORE_INDEX = kParamIndexTestFlexS64, FLEX_SIZE = 8 };
     // typedef C2TestStruct_FlexS64 _type;
-    // typedef int64_t flexType;
+    // typedef int64_t FlexType;
 };
 
-const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexS64::fieldList = {
+const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexS64::FIELD_LIST = {
     { FD::INT64, 0, "flex", 0, 8 }
 };
 
 struct C2TestStruct_FlexEndS64 {
-    int32_t mSigned32;
+    int32_t signed32;
     int64_t mSigned64Flex[];
 
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;
-    // enum : uint32_t { coreIndex = C2TestStruct_FlexEndS64, flexSize = 8 };
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;
+    // enum : uint32_t { CORE_INDEX = C2TestStruct_FlexEndS64, FLEX_SIZE = 8 };
     // typedef C2TestStruct_FlexEndS64 _type;
-    // typedef int64_t flexType;
+    // typedef int64_t FlexType;
 };
 
-const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexEndS64::fieldList = {
+const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexEndS64::FIELD_LIST = {
     { FD::INT32, 1, "s32", 0, 4 },
     { FD::INT64, 0, "flex", 4, 8 },
 };
@@ -398,7 +398,7 @@
 };
 
 struct C2TestFlexEndS64Struct {
-    int32_t mSigned32;
+    int32_t signed32;
     int64_t mFlexSigned64[];
     C2TestFlexEndS64Struct() {}
 
@@ -406,7 +406,7 @@
 } C2_PACK;
 
 DESCRIBE_C2STRUCT(TestFlexEndS64, {
-    C2FIELD(mSigned32, "s32")
+    C2FIELD(signed32, "s32")
     C2FIELD(mFlexSigned64, "flex")
 }) // ; optional
 
@@ -415,37 +415,37 @@
 //std::initializer_list<std::initializer_list<const C2FieldDescriptor>>
 C2ParamTest_FlexParamFieldList<int64_t>::GetLists() {
     return {
-        C2TestStruct_FlexS64::fieldList,
-        C2TestStruct_FlexEndS64::fieldList,
-        C2TestFlexS64Struct::fieldList,
-        C2TestFlexEndS64Struct::fieldList,
+        C2TestStruct_FlexS64::FIELD_LIST,
+        C2TestStruct_FlexEndS64::FIELD_LIST,
+        C2TestFlexS64Struct::FIELD_LIST,
+        C2TestFlexEndS64Struct::FIELD_LIST,
     };
 }
 
 struct C2TestStruct_FlexSize {
     C2SizeStruct mFlexSize[];
 
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;
-    // enum : uint32_t { coreIndex = kParamIndexTestFlexSize, flexSize = 8 };
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;
+    // enum : uint32_t { CORE_INDEX = kParamIndexTestFlexSize, FLEX_SIZE = 8 };
     // typedef C2TestStruct_FlexSize _type;
-    // typedef C2SizeStruct flexType;
+    // typedef C2SizeStruct FlexType;
 };
 
-const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexSize::fieldList = {
+const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexSize::FIELD_LIST = {
     { C2SizeStruct::TYPE, 0, "flex", 0, sizeof(C2SizeStruct) }
 };
 
 struct C2TestStruct_FlexEndSize {
-    int32_t mSigned32;
+    int32_t signed32;
     C2SizeStruct mSizeFlex[];
 
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;
-    // enum : uint32_t { coreIndex = C2TestStruct_FlexEndSize, flexSize = 8 };
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;
+    // enum : uint32_t { CORE_INDEX = C2TestStruct_FlexEndSize, FLEX_SIZE = 8 };
     // typedef C2TestStruct_FlexEndSize _type;
-    // typedef C2SizeStruct flexType;
+    // typedef C2SizeStruct FlexType;
 };
 
-const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexEndSize::fieldList = {
+const std::initializer_list<const C2FieldDescriptor> C2TestStruct_FlexEndSize::FIELD_LIST = {
     { FD::INT32, 1, "s32", 0, 4 },
     { C2SizeStruct::TYPE, 0, "flex", 4, sizeof(C2SizeStruct) },
 };
@@ -459,7 +459,7 @@
 };
 
 struct C2TestFlexEndSizeStruct {
-    int32_t mSigned32;
+    int32_t signed32;
     C2SizeStruct mFlexSize[];
     C2TestFlexEndSizeStruct() {}
 
@@ -467,12 +467,12 @@
 } C2_PACK;
 
 DESCRIBE_C2STRUCT(TestFlexEndSize, {
-    C2FIELD(mSigned32, "s32")
+    C2FIELD(signed32, "s32")
     C2FIELD(mFlexSize, "flex")
 }) // ; optional
 
 struct C2TestBaseFlexEndSizeStruct {
-    int32_t mSigned32;
+    int32_t signed32;
     C2SizeStruct mFlexSize[];
     C2TestBaseFlexEndSizeStruct() {}
 
@@ -480,17 +480,17 @@
 } C2_PACK;
 
 DESCRIBE_C2STRUCT(TestBaseFlexEndSize, {
-    C2FIELD(mSigned32, "s32")
+    C2FIELD(signed32, "s32")
     C2FIELD(mFlexSize, "flex")
 }) // ; optional
 
 struct C2TestBaseFlexEndSize2Struct {
-    int32_t mSigned32;
+    int32_t signed32;
     C2SizeStruct mFlexSize[];
     C2TestBaseFlexEndSize2Struct() {}
 
     DEFINE_AND_DESCRIBE_BASE_FLEX_C2STRUCT(TestBaseFlexEndSize2, mFlexSize)
-    C2FIELD(mSigned32, "s32")
+    C2FIELD(signed32, "s32")
     C2FIELD(mFlexSize, "flex")
 };
 
@@ -499,65 +499,65 @@
 //std::initializer_list<std::initializer_list<const C2FieldDescriptor>>
 C2ParamTest_FlexParamFieldList<C2SizeStruct>::GetLists() {
     return {
-        C2TestStruct_FlexSize::fieldList,
-        C2TestStruct_FlexEndSize::fieldList,
-        C2TestFlexSizeStruct::fieldList,
-        C2TestFlexEndSizeStruct::fieldList,
-        C2TestBaseFlexEndSizeStruct::fieldList,
-        C2TestBaseFlexEndSize2Struct::fieldList,
+        C2TestStruct_FlexSize::FIELD_LIST,
+        C2TestStruct_FlexEndSize::FIELD_LIST,
+        C2TestFlexSizeStruct::FIELD_LIST,
+        C2TestFlexEndSizeStruct::FIELD_LIST,
+        C2TestBaseFlexEndSizeStruct::FIELD_LIST,
+        C2TestBaseFlexEndSize2Struct::FIELD_LIST,
     };
 }
 
 TEST_F(C2ParamTest, FieldId) {
     // pointer constructor
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&((C2TestStruct_A*)0)->mSigned32));
-    EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId(&((C2TestStruct_A*)0)->mSigned64));
-    EXPECT_EQ(_C2FieldId(20, 4), _C2FieldId(&((C2TestStruct_A*)0)->mUnsigned32));
-    EXPECT_EQ(_C2FieldId(24, 8), _C2FieldId(&((C2TestStruct_A*)0)->mUnsigned64));
-    EXPECT_EQ(_C2FieldId(32, 4), _C2FieldId(&((C2TestStruct_A*)0)->mFloat));
-    EXPECT_EQ(_C2FieldId(36, 8), _C2FieldId(&((C2TestStruct_A*)0)->mSize));
-    EXPECT_EQ(_C2FieldId(60, 1), _C2FieldId(&((C2TestStruct_A*)0)->mBlob));
-    EXPECT_EQ(_C2FieldId(160, 1), _C2FieldId(&((C2TestStruct_A*)0)->mString));
-    EXPECT_EQ(_C2FieldId(260, 1), _C2FieldId(&((C2TestStruct_A*)0)->mYesNo));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&((C2TestStruct_A*)0)->signed32));
+    EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId(&((C2TestStruct_A*)0)->signed64));
+    EXPECT_EQ(_C2FieldId(20, 4), _C2FieldId(&((C2TestStruct_A*)0)->unsigned32));
+    EXPECT_EQ(_C2FieldId(24, 8), _C2FieldId(&((C2TestStruct_A*)0)->unsigned64));
+    EXPECT_EQ(_C2FieldId(32, 4), _C2FieldId(&((C2TestStruct_A*)0)->fp32));
+    EXPECT_EQ(_C2FieldId(36, 8), _C2FieldId(&((C2TestStruct_A*)0)->sz));
+    EXPECT_EQ(_C2FieldId(60, 1), _C2FieldId(&((C2TestStruct_A*)0)->blob));
+    EXPECT_EQ(_C2FieldId(160, 1), _C2FieldId(&((C2TestStruct_A*)0)->string));
+    EXPECT_EQ(_C2FieldId(260, 1), _C2FieldId(&((C2TestStruct_A*)0)->yesNo));
 
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&((C2TestFlexEndSizeStruct*)0)->mSigned32));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&((C2TestFlexEndSizeStruct*)0)->signed32));
     EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId(&((C2TestFlexEndSizeStruct*)0)->mFlexSize));
 
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&((C2TestBaseFlexEndSizeStruct*)0)->mSigned32));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&((C2TestBaseFlexEndSizeStruct*)0)->signed32));
     EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId(&((C2TestBaseFlexEndSizeStruct*)0)->mFlexSize));
 
     // member pointer constructor
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mSigned32));
-    EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mSigned64));
-    EXPECT_EQ(_C2FieldId(20, 4), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mUnsigned32));
-    EXPECT_EQ(_C2FieldId(24, 8), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mUnsigned64));
-    EXPECT_EQ(_C2FieldId(32, 4), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mFloat));
-    EXPECT_EQ(_C2FieldId(36, 8), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mSize));
-    EXPECT_EQ(_C2FieldId(60, 1), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mBlob));
-    EXPECT_EQ(_C2FieldId(160, 1), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mString));
-    EXPECT_EQ(_C2FieldId(260, 1), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::mYesNo));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::signed32));
+    EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::signed64));
+    EXPECT_EQ(_C2FieldId(20, 4), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::unsigned32));
+    EXPECT_EQ(_C2FieldId(24, 8), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::unsigned64));
+    EXPECT_EQ(_C2FieldId(32, 4), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::fp32));
+    EXPECT_EQ(_C2FieldId(36, 8), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::sz));
+    EXPECT_EQ(_C2FieldId(60, 1), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::blob));
+    EXPECT_EQ(_C2FieldId(160, 1), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::string));
+    EXPECT_EQ(_C2FieldId(260, 1), _C2FieldId((C2TestStruct_A*)0, &C2TestStruct_A::yesNo));
 
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId((C2TestFlexEndSizeStruct*)0, &C2TestFlexEndSizeStruct::mSigned32));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId((C2TestFlexEndSizeStruct*)0, &C2TestFlexEndSizeStruct::signed32));
     EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId((C2TestFlexEndSizeStruct*)0, &C2TestFlexEndSizeStruct::mFlexSize));
 
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId((C2TestBaseFlexEndSizeStruct*)0, &C2TestBaseFlexEndSizeStruct::mSigned32));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId((C2TestBaseFlexEndSizeStruct*)0, &C2TestBaseFlexEndSizeStruct::signed32));
     EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId((C2TestBaseFlexEndSizeStruct*)0, &C2TestBaseFlexEndSizeStruct::mFlexSize));
 
     // member pointer sans type pointer
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&C2TestStruct_A::mSigned32));
-    EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId(&C2TestStruct_A::mSigned64));
-    EXPECT_EQ(_C2FieldId(20, 4), _C2FieldId(&C2TestStruct_A::mUnsigned32));
-    EXPECT_EQ(_C2FieldId(24, 8), _C2FieldId(&C2TestStruct_A::mUnsigned64));
-    EXPECT_EQ(_C2FieldId(32, 4), _C2FieldId(&C2TestStruct_A::mFloat));
-    EXPECT_EQ(_C2FieldId(36, 8), _C2FieldId(&C2TestStruct_A::mSize));
-    EXPECT_EQ(_C2FieldId(60, 1), _C2FieldId(&C2TestStruct_A::mBlob));
-    EXPECT_EQ(_C2FieldId(160, 1), _C2FieldId(&C2TestStruct_A::mString));
-    EXPECT_EQ(_C2FieldId(260, 1), _C2FieldId(&C2TestStruct_A::mYesNo));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&C2TestStruct_A::signed32));
+    EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId(&C2TestStruct_A::signed64));
+    EXPECT_EQ(_C2FieldId(20, 4), _C2FieldId(&C2TestStruct_A::unsigned32));
+    EXPECT_EQ(_C2FieldId(24, 8), _C2FieldId(&C2TestStruct_A::unsigned64));
+    EXPECT_EQ(_C2FieldId(32, 4), _C2FieldId(&C2TestStruct_A::fp32));
+    EXPECT_EQ(_C2FieldId(36, 8), _C2FieldId(&C2TestStruct_A::sz));
+    EXPECT_EQ(_C2FieldId(60, 1), _C2FieldId(&C2TestStruct_A::blob));
+    EXPECT_EQ(_C2FieldId(160, 1), _C2FieldId(&C2TestStruct_A::string));
+    EXPECT_EQ(_C2FieldId(260, 1), _C2FieldId(&C2TestStruct_A::yesNo));
 
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&C2TestFlexEndSizeStruct::mSigned32));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&C2TestFlexEndSizeStruct::signed32));
     EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId(&C2TestFlexEndSizeStruct::mFlexSize));
 
-    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&C2TestBaseFlexEndSizeStruct::mSigned32));
+    EXPECT_EQ(_C2FieldId(0, 4), _C2FieldId(&C2TestBaseFlexEndSizeStruct::signed32));
     EXPECT_EQ(_C2FieldId(4, 8), _C2FieldId(&C2TestBaseFlexEndSizeStruct::mFlexSize));
 
     typedef C2GlobalParam<C2Info, C2TestAStruct> C2TestAInfo;
@@ -565,38 +565,38 @@
     typedef C2GlobalParam<C2Info, C2TestBaseFlexEndSizeStruct, kParamIndexTestFlexEndSize> C2TestFlexEndSizeInfoFromBase;
 
     // pointer constructor in C2Param
-    EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&((C2TestAInfo*)0)->mSigned32));
-    EXPECT_EQ(_C2FieldId(12, 8), _C2FieldId(&((C2TestAInfo*)0)->mSigned64));
-    EXPECT_EQ(_C2FieldId(28, 4), _C2FieldId(&((C2TestAInfo*)0)->mUnsigned32));
-    EXPECT_EQ(_C2FieldId(32, 8), _C2FieldId(&((C2TestAInfo*)0)->mUnsigned64));
-    EXPECT_EQ(_C2FieldId(40, 4), _C2FieldId(&((C2TestAInfo*)0)->mFloat));
-    EXPECT_EQ(_C2FieldId(44, 8), _C2FieldId(&((C2TestAInfo*)0)->mSize));
-    EXPECT_EQ(_C2FieldId(68, 1), _C2FieldId(&((C2TestAInfo*)0)->mBlob));
-    EXPECT_EQ(_C2FieldId(168, 1), _C2FieldId(&((C2TestAInfo*)0)->mString));
-    EXPECT_EQ(_C2FieldId(268, 1), _C2FieldId(&((C2TestAInfo*)0)->mYesNo));
+    EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&((C2TestAInfo*)0)->signed32));
+    EXPECT_EQ(_C2FieldId(12, 8), _C2FieldId(&((C2TestAInfo*)0)->signed64));
+    EXPECT_EQ(_C2FieldId(28, 4), _C2FieldId(&((C2TestAInfo*)0)->unsigned32));
+    EXPECT_EQ(_C2FieldId(32, 8), _C2FieldId(&((C2TestAInfo*)0)->unsigned64));
+    EXPECT_EQ(_C2FieldId(40, 4), _C2FieldId(&((C2TestAInfo*)0)->fp32));
+    EXPECT_EQ(_C2FieldId(44, 8), _C2FieldId(&((C2TestAInfo*)0)->sz));
+    EXPECT_EQ(_C2FieldId(68, 1), _C2FieldId(&((C2TestAInfo*)0)->blob));
+    EXPECT_EQ(_C2FieldId(168, 1), _C2FieldId(&((C2TestAInfo*)0)->string));
+    EXPECT_EQ(_C2FieldId(268, 1), _C2FieldId(&((C2TestAInfo*)0)->yesNo));
 
-    EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&((C2TestFlexEndSizeInfo*)0)->m.mSigned32));
+    EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&((C2TestFlexEndSizeInfo*)0)->m.signed32));
     EXPECT_EQ(_C2FieldId(12, 8), _C2FieldId(&((C2TestFlexEndSizeInfo*)0)->m.mFlexSize));
 
-    EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&((C2TestFlexEndSizeInfoFromBase*)0)->m.mSigned32));
+    EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&((C2TestFlexEndSizeInfoFromBase*)0)->m.signed32));
     EXPECT_EQ(_C2FieldId(12, 8), _C2FieldId(&((C2TestFlexEndSizeInfoFromBase*)0)->m.mFlexSize));
 
     // member pointer in C2Param
-    EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mSigned32));
-    EXPECT_EQ(_C2FieldId(12, 8), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mSigned64));
-    EXPECT_EQ(_C2FieldId(28, 4), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mUnsigned32));
-    EXPECT_EQ(_C2FieldId(32, 8), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mUnsigned64));
-    EXPECT_EQ(_C2FieldId(40, 4), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mFloat));
-    EXPECT_EQ(_C2FieldId(44, 8), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mSize));
-    EXPECT_EQ(_C2FieldId(68, 1), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mBlob));
-    EXPECT_EQ(_C2FieldId(168, 1), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mString));
-    EXPECT_EQ(_C2FieldId(268, 1), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::mYesNo));
+    EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::signed32));
+    EXPECT_EQ(_C2FieldId(12, 8), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::signed64));
+    EXPECT_EQ(_C2FieldId(28, 4), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::unsigned32));
+    EXPECT_EQ(_C2FieldId(32, 8), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::unsigned64));
+    EXPECT_EQ(_C2FieldId(40, 4), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::fp32));
+    EXPECT_EQ(_C2FieldId(44, 8), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::sz));
+    EXPECT_EQ(_C2FieldId(68, 1), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::blob));
+    EXPECT_EQ(_C2FieldId(168, 1), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::string));
+    EXPECT_EQ(_C2FieldId(268, 1), _C2FieldId((C2TestAInfo*)0, &C2TestAInfo::yesNo));
 
     // NOTE: cannot use a member pointer for flex params due to introduction of 'm'
-    // EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&C2TestFlexEndSizeInfo::m.mSigned32));
+    // EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&C2TestFlexEndSizeInfo::m.signed32));
     // EXPECT_EQ(_C2FieldId(12, 8), _C2FieldId(&C2TestFlexEndSizeInfo::m.mFlexSize));
 
-    // EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&C2TestFlexEndSizeInfoFromBase::m.mSigned32));
+    // EXPECT_EQ(_C2FieldId(8, 4), _C2FieldId(&C2TestFlexEndSizeInfoFromBase::m.signed32));
     // EXPECT_EQ(_C2FieldId(12, 8), _C2FieldId(&C2TestFlexEndSizeInfoFromBase::m.mFlexSize));
 
 
@@ -631,33 +631,33 @@
 };
 
 void compiledStatic_arrayTypePropagationTest() {
-    (void)S32(&((C2TestFlexEndS32Struct *)0)->mSigned32);
+    (void)S32(&((C2TestFlexEndS32Struct *)0)->signed32);
     (void)FLX(&((C2TestFlexEndS32Struct *)0)->mFlexSigned32, (int32_t*)0);
     (void)FLX(&((C2TestFlexS32Struct *)0)->mFlexSigned32, (int32_t*)0);
 
     typedef C2GlobalParam<C2Info, C2TestAStruct> C2TestAInfo;
 
     // TRICKY: &derivedClass::baseMember has type of baseClass::*
-    static_assert(std::is_same<decltype(&C2TestAInfo::mSigned32), int32_t C2TestAStruct::*>::value,
+    static_assert(std::is_same<decltype(&C2TestAInfo::signed32), int32_t C2TestAStruct::*>::value,
                   "base member pointer should have base class in type");
 
     // therefore, member pointer expands to baseClass::* in templates
-    (void)MP(&C2TestAInfo::mSigned32,
+    (void)MP(&C2TestAInfo::signed32,
              (C2TestAStruct*)0 /* expected */, (C2TestAInfo*)0 /* unexpected */);
     // but can be cast to derivedClass::*
-    (void)MP((int32_t C2TestAInfo::*)&C2TestAInfo::mSigned32,
+    (void)MP((int32_t C2TestAInfo::*)&C2TestAInfo::signed32,
              (C2TestAInfo*)0 /* expected */, (C2TestAStruct*)0 /* unexpected */);
 
     // TRICKY: baseClass::* does not autoconvert to derivedClass::* even in templates
-    // (void)MP(&C2TestAInfo::mSigned32, (C2TestAInfo*)0);
+    // (void)MP(&C2TestAInfo::signed32, (C2TestAInfo*)0);
 }
 
 TEST_F(C2ParamTest, MemberPointerCast) {
     typedef C2GlobalParam<C2Info, C2TestAStruct> C2TestAInfo;
 
-    static_assert(offsetof(C2TestAInfo, mSigned32) == 8, "offset should be 8");
-    constexpr int32_t C2TestAStruct::* s32ptr = &C2TestAInfo::mSigned32;
-    constexpr int32_t C2TestAInfo::* s32ptr_derived = (int32_t C2TestAStruct::*)&C2TestAInfo::mSigned32;
+    static_assert(offsetof(C2TestAInfo, signed32) == 8, "offset should be 8");
+    constexpr int32_t C2TestAStruct::* s32ptr = &C2TestAInfo::signed32;
+    constexpr int32_t C2TestAInfo::* s32ptr_derived = (int32_t C2TestAStruct::*)&C2TestAInfo::signed32;
     constexpr int32_t C2TestAInfo::* s32ptr_cast2derived = (int32_t C2TestAInfo::*)s32ptr;
     C2TestAInfo *info = (C2TestAInfo *)256;
     C2TestAStruct *strukt = (C2TestAStruct *)info;
@@ -674,11 +674,11 @@
     EXPECT_EQ(264u, (uintptr_t)strukt_s32);
 
     typedef C2GlobalParam<C2Info, C2TestFlexEndSizeStruct> C2TestFlexEndSizeInfo;
-    static_assert(offsetof(C2TestFlexEndSizeInfo, m.mSigned32) == 8, "offset should be 8");
+    static_assert(offsetof(C2TestFlexEndSizeInfo, m.signed32) == 8, "offset should be 8");
     static_assert(offsetof(C2TestFlexEndSizeInfo, m.mFlexSize) == 12, "offset should be 12");
 
     typedef C2GlobalParam<C2Info, C2TestBaseFlexEndSizeStruct, kParamIndexTestFlexEndSize> C2TestFlexEndSizeInfoFromBase;
-    static_assert(offsetof(C2TestFlexEndSizeInfoFromBase, m.mSigned32) == 8, "offset should be 8");
+    static_assert(offsetof(C2TestFlexEndSizeInfoFromBase, m.signed32) == 8, "offset should be 8");
     static_assert(offsetof(C2TestFlexEndSizeInfoFromBase, m.mFlexSize) == 12, "offset should be 12");
 }
 
@@ -711,6 +711,8 @@
 };
 static_assert(sizeof(C2NumbersStruct) == 0, "C2NumbersStruct has incorrect size");
 
+typedef C2GlobalParam<C2Info, C2NumberStruct> C2NumberInfo;
+
 typedef C2GlobalParam<C2Tuning, C2NumberStruct> C2NumberTuning;
 typedef   C2PortParam<C2Tuning, C2NumberStruct> C2NumberPortTuning;
 typedef C2StreamParam<C2Tuning, C2NumberStruct> C2NumberStreamTuning;
@@ -724,7 +726,7 @@
 
 void test() {
     C2NumberStruct s(10);
-    (void)C2NumberStruct::fieldList;
+    (void)C2NumberStruct::FIELD_LIST;
 };
 
 typedef C2StreamParam<C2Tuning, C2Int64Value, kParamIndexNumberB> C2NumberConfig4;
@@ -733,19 +735,19 @@
 
 void test3() {
     C2NumberConfig3 s(10);
-    s.mValue = 11;
+    s.value = 11;
     s = 12;
-    (void)C2NumberConfig3::fieldList;
+    (void)C2NumberConfig3::FIELD_LIST;
     std::shared_ptr<C2VideoNameConfig> n = C2VideoNameConfig::alloc_shared(25);
-    strcpy(n->m.mValue, "lajos");
+    strcpy(n->m.value, "lajos");
     C2NumberConfig4 t(false, 0, 11);
-    t.mValue = 15;
+    t.value = 15;
 };
 
 struct C2NumbersStruct {
     int32_t mNumbers[];
-    enum { coreIndex = kParamIndexNumber };
-    const static std::initializer_list<const C2FieldDescriptor> fieldList;
+    enum { CORE_INDEX = kParamIndexNumber };
+    const static std::initializer_list<const C2FieldDescriptor> FIELD_LIST;
     C2NumbersStruct() {}
 
     FLEX(C2NumbersStruct, mNumbers);
@@ -756,13 +758,13 @@
 
 typedef C2GlobalParam<C2Info, C2NumbersStruct> C2NumbersInfo;
 
-const std::initializer_list<const C2FieldDescriptor> C2NumbersStruct::fieldList =
+const std::initializer_list<const C2FieldDescriptor> C2NumbersStruct::FIELD_LIST =
 //    { { FD::INT32, 0, "widths" } };
     { C2FieldDescriptor(&((C2NumbersStruct*)(nullptr))->mNumbers, "number") };
 
 typedef C2PortParam<C2Tuning, C2NumberStruct> C2NumberConfig;
 
-std::list<const C2FieldDescriptor> myList = C2NumberConfig::fieldList;
+std::list<const C2FieldDescriptor> myList = C2NumberConfig::FIELD_LIST;
 
     std::unique_ptr<android::C2ParamDescriptor> __test_describe(uint32_t paramType) {
         std::list<const C2FieldDescriptor> fields = describeC2Params<C2NumberConfig>();
@@ -778,7 +780,7 @@
 
         C2Param::Index index(paramType);
         switch (paramType) {
-        case C2NumberConfig::coreIndex:
+        case C2NumberConfig::CORE_INDEX:
             return std::unique_ptr<C2ParamDescriptor>(new C2ParamDescriptor{
                 true /* isRequired */,
                 "number",
@@ -836,13 +838,13 @@
 void _C2ParamInspector::StaticTest() {
     typedef C2Param::Index I;
 
-    // C2NumberStruct: coreIndex = kIndex                          (args)
-    static_assert(C2NumberStruct::coreIndex == kParamIndexNumber, "bad index");
+    // C2NumberStruct: CORE_INDEX = kIndex                          (args)
+    static_assert(C2NumberStruct::CORE_INDEX == kParamIndexNumber, "bad index");
     static_assert(sizeof(C2NumberStruct) == 4, "bad size");
 
     // C2NumberTuning:             kIndex | tun | global           (args)
-    static_assert(C2NumberTuning::coreIndex == kParamIndexNumber, "bad index");
-    static_assert(C2NumberTuning::typeIndex == (kParamIndexNumber | I::kTypeTuning | I::kDirGlobal), "bad index");
+    static_assert(C2NumberTuning::CORE_INDEX == kParamIndexNumber, "bad index");
+    static_assert(C2NumberTuning::PARAM_TYPE == (kParamIndexNumber | I::KIND_TUNING | I::DIR_GLOBAL), "bad index");
     static_assert(sizeof(C2NumberTuning) == 12, "bad size");
 
     static_assert(offsetof(C2NumberTuning, _mSize) == 0, "bad size");
@@ -853,14 +855,14 @@
     static_assert(sizeof(C2NumberPortTuning) == 12, "bad size");
     // C2NumberPortTuning::input:  kIndex | tun | port | input     (args)
     // C2NumberPortTuning::output: kIndex | tun | port | output    (args)
-    static_assert(C2NumberPortTuning::input::coreIndex ==
+    static_assert(C2NumberPortTuning::input::CORE_INDEX ==
                   kParamIndexNumber, "bad index");
-    static_assert(C2NumberPortTuning::input::typeIndex ==
-                  (kParamIndexNumber | I::kTypeTuning | I::kDirInput), "bad index");
-    static_assert(C2NumberPortTuning::output::coreIndex ==
+    static_assert(C2NumberPortTuning::input::PARAM_TYPE ==
+                  (kParamIndexNumber | I::KIND_TUNING | I::DIR_INPUT), "bad index");
+    static_assert(C2NumberPortTuning::output::CORE_INDEX ==
                   kParamIndexNumber, "bad index");
-    static_assert(C2NumberPortTuning::output::typeIndex ==
-                  (kParamIndexNumber | I::kTypeTuning | I::kDirOutput), "bad index");
+    static_assert(C2NumberPortTuning::output::PARAM_TYPE ==
+                  (kParamIndexNumber | I::KIND_TUNING | I::DIR_OUTPUT), "bad index");
     static_assert(sizeof(C2NumberPortTuning::input) == 12, "bad size");
     static_assert(sizeof(C2NumberPortTuning::output) == 12, "bad size");
     static_assert(offsetof(C2NumberPortTuning::input, _mSize) == 0, "bad size");
@@ -874,14 +876,14 @@
     static_assert(sizeof(C2NumberStreamTuning) == 12u, "bad size");
     // C2NumberStreamTuning::input kIndex | tun | str | input      (int, args)
     // C2NumberStreamTuning::output kIx   | tun | str | output     (int, args)
-    static_assert(C2NumberStreamTuning::input::coreIndex ==
+    static_assert(C2NumberStreamTuning::input::CORE_INDEX ==
                   kParamIndexNumber, "bad index");
-    static_assert(C2NumberStreamTuning::input::typeIndex ==
-                  (kParamIndexNumber | I::kTypeTuning | I::kDirInput | I::kStreamFlag), "bad index");
-    static_assert(C2NumberStreamTuning::output::coreIndex ==
+    static_assert(C2NumberStreamTuning::input::PARAM_TYPE ==
+                  (kParamIndexNumber | I::KIND_TUNING | I::DIR_INPUT | I::IS_STREAM_FLAG), "bad index");
+    static_assert(C2NumberStreamTuning::output::CORE_INDEX ==
                   kParamIndexNumber, "bad index");
-    static_assert(C2NumberStreamTuning::output::typeIndex ==
-                  (kParamIndexNumber | I::kTypeTuning | I::kDirOutput | I::kStreamFlag), "bad index");
+    static_assert(C2NumberStreamTuning::output::PARAM_TYPE ==
+                  (kParamIndexNumber | I::KIND_TUNING | I::DIR_OUTPUT | I::IS_STREAM_FLAG), "bad index");
     static_assert(sizeof(C2NumberStreamTuning::input) == 12u, "bad size");
     static_assert(sizeof(C2NumberStreamTuning::output) == 12u, "bad size");
     static_assert(offsetof(C2NumberStreamTuning::input, _mSize) == 0, "bad size");
@@ -901,13 +903,13 @@
 
     typedef C2Param::Index I;
 
-    // C2MyStruct has no coreIndex
-    //static_assert(C2MyStruct::coreIndex == kParamIndexMy, "bad index");
+    // C2MyStruct has no CORE_INDEX
+    //static_assert(C2MyStruct::CORE_INDEX == kParamIndexMy, "bad index");
     static_assert(sizeof(C2MyStruct) == 4, "bad size");
 
     // C2MySetting:             kIndex | tun | global           (args)
-    static_assert(C2MySetting::coreIndex == kParamIndexMy, "bad index");
-    static_assert(C2MySetting::typeIndex == (kParamIndexMy | I::kTypeSetting | I::kDirGlobal), "bad index");
+    static_assert(C2MySetting::CORE_INDEX == kParamIndexMy, "bad index");
+    static_assert(C2MySetting::PARAM_TYPE == (kParamIndexMy | I::KIND_SETTING | I::DIR_GLOBAL), "bad index");
     static_assert(sizeof(C2MySetting) == 12, "bad size");
 
     static_assert(offsetof(C2MySetting, _mSize) == 0, "bad size");
@@ -918,14 +920,14 @@
     static_assert(sizeof(C2MyPortSetting) == 12, "bad size");
     // C2MyPortSetting::input:  kIndex | tun | port | input     (args)
     // C2MyPortSetting::output: kIndex | tun | port | output    (args)
-    static_assert(C2MyPortSetting::input::coreIndex ==
+    static_assert(C2MyPortSetting::input::CORE_INDEX ==
                   kParamIndexMy, "bad index");
-    static_assert(C2MyPortSetting::input::typeIndex ==
-                  (kParamIndexMy | I::kTypeSetting | I::kDirInput), "bad index");
-    static_assert(C2MyPortSetting::output::coreIndex ==
+    static_assert(C2MyPortSetting::input::PARAM_TYPE ==
+                  (kParamIndexMy | I::KIND_SETTING | I::DIR_INPUT), "bad index");
+    static_assert(C2MyPortSetting::output::CORE_INDEX ==
                   kParamIndexMy, "bad index");
-    static_assert(C2MyPortSetting::output::typeIndex ==
-                  (kParamIndexMy | I::kTypeSetting | I::kDirOutput), "bad index");
+    static_assert(C2MyPortSetting::output::PARAM_TYPE ==
+                  (kParamIndexMy | I::KIND_SETTING | I::DIR_OUTPUT), "bad index");
     static_assert(sizeof(C2MyPortSetting::input) == 12, "bad size");
     static_assert(sizeof(C2MyPortSetting::output) == 12, "bad size");
     static_assert(offsetof(C2MyPortSetting::input, _mSize) == 0, "bad size");
@@ -939,14 +941,14 @@
     static_assert(sizeof(C2MyStreamSetting) == 12u, "bad size");
     // C2MyStreamSetting::input kIndex | tun | str | input      (int, args)
     // C2MyStreamSetting::output kIx   | tun | str | output     (int, args)
-    static_assert(C2MyStreamSetting::input::coreIndex ==
+    static_assert(C2MyStreamSetting::input::CORE_INDEX ==
                   kParamIndexMy, "bad index");
-    static_assert(C2MyStreamSetting::input::typeIndex ==
-                  (kParamIndexMy | I::kTypeSetting | I::kDirInput | I::kStreamFlag), "bad index");
-    static_assert(C2MyStreamSetting::output::coreIndex ==
+    static_assert(C2MyStreamSetting::input::PARAM_TYPE ==
+                  (kParamIndexMy | I::KIND_SETTING | I::DIR_INPUT | I::IS_STREAM_FLAG), "bad index");
+    static_assert(C2MyStreamSetting::output::CORE_INDEX ==
                   kParamIndexMy, "bad index");
-    static_assert(C2MyStreamSetting::output::typeIndex ==
-                  (kParamIndexMy | I::kTypeSetting | I::kDirOutput | I::kStreamFlag), "bad index");
+    static_assert(C2MyStreamSetting::output::PARAM_TYPE ==
+                  (kParamIndexMy | I::KIND_SETTING | I::DIR_OUTPUT | I::IS_STREAM_FLAG), "bad index");
     static_assert(sizeof(C2MyStreamSetting::input) == 12u, "bad size");
     static_assert(sizeof(C2MyStreamSetting::output) == 12u, "bad size");
     static_assert(offsetof(C2MyStreamSetting::input, _mSize) == 0, "bad size");
@@ -960,13 +962,13 @@
 void _C2ParamInspector::StaticFlexTest() {
     typedef C2Param::Index I;
 
-    // C2NumbersStruct: coreIndex = kIndex                          (args)
-    static_assert(C2NumbersStruct::coreIndex == (I::kFlexibleFlag | kParamIndexNumbers), "bad index");
+    // C2NumbersStruct: CORE_INDEX = kIndex                          (args)
+    static_assert(C2NumbersStruct::CORE_INDEX == (I::IS_FLEX_FLAG | kParamIndexNumbers), "bad index");
     static_assert(sizeof(C2NumbersStruct) == 0, "bad size");
 
     // C2NumbersTuning:             kIndex | tun | global           (args)
-    static_assert(C2NumbersTuning::coreIndex == (I::kFlexibleFlag | kParamIndexNumbers), "bad index");
-    static_assert(C2NumbersTuning::typeIndex == (I::kFlexibleFlag | kParamIndexNumbers | I::kTypeTuning | I::kDirGlobal), "bad index");
+    static_assert(C2NumbersTuning::CORE_INDEX == (I::IS_FLEX_FLAG | kParamIndexNumbers), "bad index");
+    static_assert(C2NumbersTuning::PARAM_TYPE == (I::IS_FLEX_FLAG | kParamIndexNumbers | I::KIND_TUNING | I::DIR_GLOBAL), "bad index");
     static_assert(sizeof(C2NumbersTuning) == 8, "bad size");
 
     static_assert(offsetof(C2NumbersTuning, _mSize) == 0, "bad size");
@@ -977,14 +979,14 @@
     static_assert(sizeof(C2NumbersPortTuning) == 8, "bad size");
     // C2NumbersPortTuning::input:  kIndex | tun | port | input     (args)
     // C2NumbersPortTuning::output: kIndex | tun | port | output    (args)
-    static_assert(C2NumbersPortTuning::input::coreIndex ==
-                  (I::kFlexibleFlag | kParamIndexNumbers), "bad index");
-    static_assert(C2NumbersPortTuning::input::typeIndex ==
-                  (I::kFlexibleFlag | kParamIndexNumbers | I::kTypeTuning | I::kDirInput), "bad index");
-    static_assert(C2NumbersPortTuning::output::coreIndex ==
-                  (I::kFlexibleFlag | kParamIndexNumbers), "bad index");
-    static_assert(C2NumbersPortTuning::output::typeIndex ==
-                  (I::kFlexibleFlag | kParamIndexNumbers | I::kTypeTuning | I::kDirOutput), "bad index");
+    static_assert(C2NumbersPortTuning::input::CORE_INDEX ==
+                  (I::IS_FLEX_FLAG | kParamIndexNumbers), "bad index");
+    static_assert(C2NumbersPortTuning::input::PARAM_TYPE ==
+                  (I::IS_FLEX_FLAG | kParamIndexNumbers | I::KIND_TUNING | I::DIR_INPUT), "bad index");
+    static_assert(C2NumbersPortTuning::output::CORE_INDEX ==
+                  (I::IS_FLEX_FLAG | kParamIndexNumbers), "bad index");
+    static_assert(C2NumbersPortTuning::output::PARAM_TYPE ==
+                  (I::IS_FLEX_FLAG | kParamIndexNumbers | I::KIND_TUNING | I::DIR_OUTPUT), "bad index");
     static_assert(sizeof(C2NumbersPortTuning::input) == 8, "bad size");
     static_assert(sizeof(C2NumbersPortTuning::output) == 8, "bad size");
     static_assert(offsetof(C2NumbersPortTuning::input, _mSize) == 0, "bad size");
@@ -998,14 +1000,14 @@
     static_assert(sizeof(C2NumbersStreamTuning) == 8, "bad size");
     // C2NumbersStreamTuning::input kIndex | tun | str | input      (int, args)
     // C2NumbersStreamTuning::output kIx   | tun | str | output     (int, args)
-    static_assert(C2NumbersStreamTuning::input::coreIndex ==
-                  (I::kFlexibleFlag | kParamIndexNumbers), "bad index");
-    static_assert(C2NumbersStreamTuning::input::typeIndex ==
-                  (I::kFlexibleFlag | kParamIndexNumbers | I::kTypeTuning | I::kDirInput | I::kStreamFlag), "bad index");
-    static_assert(C2NumbersStreamTuning::output::coreIndex ==
-                  (I::kFlexibleFlag | kParamIndexNumbers), "bad index");
-    static_assert(C2NumbersStreamTuning::output::typeIndex ==
-                  (I::kFlexibleFlag | kParamIndexNumbers | I::kTypeTuning | I::kDirOutput | I::kStreamFlag), "bad index");
+    static_assert(C2NumbersStreamTuning::input::CORE_INDEX ==
+                  (I::IS_FLEX_FLAG | kParamIndexNumbers), "bad index");
+    static_assert(C2NumbersStreamTuning::input::PARAM_TYPE ==
+                  (I::IS_FLEX_FLAG | kParamIndexNumbers | I::KIND_TUNING | I::DIR_INPUT | I::IS_STREAM_FLAG), "bad index");
+    static_assert(C2NumbersStreamTuning::output::CORE_INDEX ==
+                  (I::IS_FLEX_FLAG | kParamIndexNumbers), "bad index");
+    static_assert(C2NumbersStreamTuning::output::PARAM_TYPE ==
+                  (I::IS_FLEX_FLAG | kParamIndexNumbers | I::KIND_TUNING | I::DIR_OUTPUT | I::IS_STREAM_FLAG), "bad index");
     static_assert(sizeof(C2NumbersStreamTuning::input) == 8, "bad size");
     static_assert(sizeof(C2NumbersStreamTuning::output) == 8, "bad size");
     static_assert(offsetof(C2NumbersStreamTuning::input, _mSize) == 0, "bad size");
@@ -1034,60 +1036,60 @@
 
     typedef C2Param::Index I;
 
-    // C2MyStruct has no coreIndex
-    //static_assert(C2MyStruct::coreIndex == (I::kFlexibleFlag | kParamIndexMy), "bad index");
+    // C2MyStruct has no CORE_INDEX
+    //static_assert(C2MyStruct::CORE_INDEX == (I::IS_FLEX_FLAG | kParamIndexMy), "bad index");
     static_assert(sizeof(C2MyStruct) == 4, "bad size");
 
     // C2MyInfo:             kIndex | tun | global           (args)
-    static_assert_equals(C2MyInfo::coreIndex, (I::kFlexibleFlag | kParamIndexMy), "bad index");
-    static_assert_equals(C2MyInfo::typeIndex, (I::kFlexibleFlag | kParamIndexMy | I::kTypeInfo | I::kDirGlobal), "bad index");
+    static_assert_equals(C2MyInfo::CORE_INDEX, (I::IS_FLEX_FLAG | kParamIndexMy), "bad index");
+    static_assert_equals(C2MyInfo::PARAM_TYPE, (I::IS_FLEX_FLAG | kParamIndexMy | I::KIND_INFO | I::DIR_GLOBAL), "bad index");
     static_assert(sizeof(C2MyInfo) == 12, "bad size");
 
     static_assert(offsetof(C2MyInfo, _mSize) == 0, "bad size");
     static_assert(offsetof(C2MyInfo, _mIndex) == 4, "bad offset");
-    static_assert(offsetof(C2MyInfo, m.mSigned32) == 8, "bad offset");
+    static_assert(offsetof(C2MyInfo, m.signed32) == 8, "bad offset");
 
     // C2MyPortInfo:         kIndex | tun | port             (bool, args)
     static_assert(sizeof(C2MyPortInfo) == 12, "bad size");
     // C2MyPortInfo::input:  kIndex | tun | port | input     (args)
     // C2MyPortInfo::output: kIndex | tun | port | output    (args)
-    static_assert(C2MyPortInfo::input::coreIndex ==
-                  (I::kFlexibleFlag | kParamIndexMy), "bad index");
-    static_assert(C2MyPortInfo::input::typeIndex ==
-                  (I::kFlexibleFlag | kParamIndexMy | I::kTypeInfo | I::kDirInput), "bad index");
-    static_assert(C2MyPortInfo::output::coreIndex ==
-                  (I::kFlexibleFlag | kParamIndexMy), "bad index");
-    static_assert(C2MyPortInfo::output::typeIndex ==
-                  (I::kFlexibleFlag | kParamIndexMy | I::kTypeInfo | I::kDirOutput), "bad index");
+    static_assert(C2MyPortInfo::input::CORE_INDEX ==
+                  (I::IS_FLEX_FLAG | kParamIndexMy), "bad index");
+    static_assert(C2MyPortInfo::input::PARAM_TYPE ==
+                  (I::IS_FLEX_FLAG | kParamIndexMy | I::KIND_INFO | I::DIR_INPUT), "bad index");
+    static_assert(C2MyPortInfo::output::CORE_INDEX ==
+                  (I::IS_FLEX_FLAG | kParamIndexMy), "bad index");
+    static_assert(C2MyPortInfo::output::PARAM_TYPE ==
+                  (I::IS_FLEX_FLAG | kParamIndexMy | I::KIND_INFO | I::DIR_OUTPUT), "bad index");
     static_assert(sizeof(C2MyPortInfo::input) == 12, "bad size");
     static_assert(sizeof(C2MyPortInfo::output) == 12, "bad size");
     static_assert(offsetof(C2MyPortInfo::input, _mSize) == 0, "bad size");
     static_assert(offsetof(C2MyPortInfo::input, _mIndex) == 4, "bad offset");
-    static_assert(offsetof(C2MyPortInfo::input, m.mSigned32) == 8, "bad offset");
+    static_assert(offsetof(C2MyPortInfo::input, m.signed32) == 8, "bad offset");
     static_assert(offsetof(C2MyPortInfo::output, _mSize) == 0, "bad size");
     static_assert(offsetof(C2MyPortInfo::output, _mIndex) == 4, "bad offset");
-    static_assert(offsetof(C2MyPortInfo::output, m.mSigned32) == 8, "bad offset");
+    static_assert(offsetof(C2MyPortInfo::output, m.signed32) == 8, "bad offset");
 
     // C2MyStreamInfo:       kIndex | tun | str              (bool, uint, args)
     static_assert(sizeof(C2MyStreamInfo) == 12, "bad size");
     // C2MyStreamInfo::input kIndex | tun | str | input      (int, args)
     // C2MyStreamInfo::output kIx   | tun | str | output     (int, args)
-    static_assert(C2MyStreamInfo::input::coreIndex ==
-                  (I::kFlexibleFlag | kParamIndexMy), "bad index");
-    static_assert(C2MyStreamInfo::input::typeIndex ==
-                  (I::kFlexibleFlag | kParamIndexMy | I::kTypeInfo | I::kDirInput | I::kStreamFlag), "bad index");
-    static_assert(C2MyStreamInfo::output::coreIndex ==
-                  (I::kFlexibleFlag | kParamIndexMy), "bad index");
-    static_assert(C2MyStreamInfo::output::typeIndex ==
-                  (I::kFlexibleFlag | kParamIndexMy | I::kTypeInfo | I::kDirOutput | I::kStreamFlag), "bad index");
+    static_assert(C2MyStreamInfo::input::CORE_INDEX ==
+                  (I::IS_FLEX_FLAG | kParamIndexMy), "bad index");
+    static_assert(C2MyStreamInfo::input::PARAM_TYPE ==
+                  (I::IS_FLEX_FLAG | kParamIndexMy | I::KIND_INFO | I::DIR_INPUT | I::IS_STREAM_FLAG), "bad index");
+    static_assert(C2MyStreamInfo::output::CORE_INDEX ==
+                  (I::IS_FLEX_FLAG | kParamIndexMy), "bad index");
+    static_assert(C2MyStreamInfo::output::PARAM_TYPE ==
+                  (I::IS_FLEX_FLAG | kParamIndexMy | I::KIND_INFO | I::DIR_OUTPUT | I::IS_STREAM_FLAG), "bad index");
     static_assert(sizeof(C2MyStreamInfo::input) == 12, "bad size");
     static_assert(sizeof(C2MyStreamInfo::output) == 12, "bad size");
     static_assert(offsetof(C2MyStreamInfo::input, _mSize) == 0, "bad size");
     static_assert(offsetof(C2MyStreamInfo::input, _mIndex) == 4, "bad offset");
-    static_assert(offsetof(C2MyStreamInfo::input, m.mSigned32) == 8, "bad offset");
+    static_assert(offsetof(C2MyStreamInfo::input, m.signed32) == 8, "bad offset");
     static_assert(offsetof(C2MyStreamInfo::output, _mSize) == 0, "bad size");
     static_assert(offsetof(C2MyStreamInfo::output, _mIndex) == 4, "bad offset");
-    static_assert(offsetof(C2MyStreamInfo::output, m.mSigned32) == 8, "bad offset");
+    static_assert(offsetof(C2MyStreamInfo::output, m.signed32) == 8, "bad offset");
 }
 
 TEST_F(C2ParamTest, ParamOpsTest) {
@@ -1098,17 +1100,27 @@
         EXPECT_EQ(100, str.mNumber);
         bstr.mNumber = 100;
 
-        C2Param::BaseIndex index = C2NumberStruct::coreIndex;
+        C2Param::CoreIndex index = C2NumberStruct::CORE_INDEX;
         EXPECT_FALSE(index.isVendor());
         EXPECT_FALSE(index.isFlexible());
         EXPECT_EQ(index.coreIndex(), kParamIndexNumber);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumber);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumber);
     }
 
     const C2NumberTuning tun(100);
     C2NumberTuning btun;
 
     {
+      C2NumberInfo inf(100);
+      std::unique_ptr<C2NumbersTuning> tun_ = C2NumbersTuning::alloc_unique(1);
+
+      EXPECT_EQ(tun.coreIndex(), inf.coreIndex());
+      EXPECT_NE(tun.coreIndex(), tun_->coreIndex());
+      EXPECT_NE(tun.type(), inf.type());
+      EXPECT_NE(tun.type(), tun_->type());
+    }
+
+    {
         // flags & invariables
         for (const auto &p : { tun, btun }) {
             EXPECT_TRUE((bool)p);
@@ -1135,18 +1147,18 @@
         EXPECT_EQ(tun, btun);
 
         // index
-        EXPECT_EQ(C2Param::Type(tun.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(tun.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(tun.type(), C2NumberTuning::typeIndex);
+        EXPECT_EQ(C2Param::Type(tun.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(tun.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(tun.type(), C2NumberTuning::PARAM_TYPE);
         EXPECT_EQ(tun.stream(), ~0u);
 
-        C2Param::BaseIndex index = C2NumberTuning::coreIndex;
+        C2Param::CoreIndex index = C2NumberTuning::CORE_INDEX;
         EXPECT_FALSE(index.isVendor());
         EXPECT_FALSE(index.isFlexible());
         EXPECT_EQ(index.coreIndex(), kParamIndexNumber);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumber);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumber);
 
-        C2Param::Type type = C2NumberTuning::typeIndex;
+        C2Param::Type type = C2NumberTuning::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_FALSE(type.isFlexible());
         EXPECT_TRUE(type.isGlobal());
@@ -1176,6 +1188,22 @@
     const C2NumberPortTuning::output outp2(100);
     C2NumberPortTuning::output boutp2;
 
+    EXPECT_EQ(inp1.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(outp1.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(binp1.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(boutp1.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(inp2.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(outp2.coreIndex(), tun.coreIndex());
+
+    EXPECT_EQ(inp1.type(), inp2.type());
+    EXPECT_EQ(outp1.type(), outp2.type());
+    EXPECT_NE(inp1.type(), outp1.type());
+    EXPECT_NE(inp2.type(), outp2.type());
+    EXPECT_NE(inp1.type(), binp1.type());
+    EXPECT_NE(outp1.type(), boutp1.type());
+    EXPECT_NE(inp1.type(), tun.type());
+    EXPECT_NE(inp2.type(), tun.type());
+
     {
         static_assert(canCallSetPort(binp3), "should be able to");
         static_assert(canCallSetPort(binp1), "should be able to");
@@ -1289,39 +1317,39 @@
         EXPECT_TRUE(inp1 == boutp1);
 
         // index
-        EXPECT_EQ(C2Param::Type(inp1.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(inp1.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(inp1.type(), C2NumberPortTuning::input::typeIndex);
+        EXPECT_EQ(C2Param::Type(inp1.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(inp1.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(inp1.type(), C2NumberPortTuning::input::PARAM_TYPE);
         EXPECT_EQ(inp1.stream(), ~0u);
 
-        EXPECT_EQ(C2Param::Type(inp2.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(inp2.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(inp2.type(), C2NumberPortTuning::input::typeIndex);
+        EXPECT_EQ(C2Param::Type(inp2.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(inp2.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(inp2.type(), C2NumberPortTuning::input::PARAM_TYPE);
         EXPECT_EQ(inp2.stream(), ~0u);
 
-        EXPECT_EQ(C2Param::Type(outp1.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(outp1.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(outp1.type(), C2NumberPortTuning::output::typeIndex);
+        EXPECT_EQ(C2Param::Type(outp1.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(outp1.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(outp1.type(), C2NumberPortTuning::output::PARAM_TYPE);
         EXPECT_EQ(outp1.stream(), ~0u);
 
-        EXPECT_EQ(C2Param::Type(outp2.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(outp2.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(outp2.type(), C2NumberPortTuning::output::typeIndex);
+        EXPECT_EQ(C2Param::Type(outp2.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(outp2.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(outp2.type(), C2NumberPortTuning::output::PARAM_TYPE);
         EXPECT_EQ(outp2.stream(), ~0u);
 
-        C2Param::BaseIndex index = C2NumberPortTuning::input::typeIndex;
+        C2Param::CoreIndex index = C2NumberPortTuning::input::PARAM_TYPE;
         EXPECT_FALSE(index.isVendor());
         EXPECT_FALSE(index.isFlexible());
         EXPECT_EQ(index.coreIndex(), kParamIndexNumber);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumber);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumber);
 
-        index = C2NumberPortTuning::output::typeIndex;
+        index = C2NumberPortTuning::output::PARAM_TYPE;
         EXPECT_FALSE(index.isVendor());
         EXPECT_FALSE(index.isFlexible());
         EXPECT_EQ(index.coreIndex(), kParamIndexNumber);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumber);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumber);
 
-        C2Param::Type type = C2NumberPortTuning::input::typeIndex;
+        C2Param::Type type = C2NumberPortTuning::input::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_FALSE(type.isFlexible());
         EXPECT_FALSE(type.isGlobal());
@@ -1330,7 +1358,7 @@
         EXPECT_FALSE(type.forStream());
         EXPECT_TRUE(type.forPort());
 
-        type = C2NumberPortTuning::output::typeIndex;
+        type = C2NumberPortTuning::output::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_FALSE(type.isFlexible());
         EXPECT_FALSE(type.isGlobal());
@@ -1384,6 +1412,24 @@
     const C2NumberStreamTuning::output outs2(1u, 100);
     C2NumberStreamTuning::output bouts2;
 
+    EXPECT_EQ(ins1.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(outs1.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(bins1.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(bouts1.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(ins2.coreIndex(), tun.coreIndex());
+    EXPECT_EQ(outs2.coreIndex(), tun.coreIndex());
+
+    EXPECT_EQ(ins1.type(), ins2.type());
+    EXPECT_EQ(ins1.type(), bins2.type());
+    EXPECT_EQ(outs1.type(), outs2.type());
+    EXPECT_EQ(outs1.type(), bouts2.type());
+    EXPECT_NE(ins1.type(), outs1.type());
+    EXPECT_NE(ins2.type(), outs2.type());
+    EXPECT_NE(ins1.type(), bins1.type());
+    EXPECT_NE(outs1.type(), bouts1.type());
+    EXPECT_NE(ins1.type(), tun.type());
+    EXPECT_NE(ins2.type(), tun.type());
+
     {
         static_assert(canCallSetPort(bins3), "should be able to");
         static_assert(canCallSetPort(bins1), "should be able to");
@@ -1509,35 +1555,35 @@
         EXPECT_TRUE(ins1 == bouts1);
 
         // index
-        EXPECT_EQ(C2Param::Type(ins1.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(ins1.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(ins1.type(), C2NumberStreamTuning::input::typeIndex);
+        EXPECT_EQ(C2Param::Type(ins1.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(ins1.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(ins1.type(), C2NumberStreamTuning::input::PARAM_TYPE);
 
-        EXPECT_EQ(C2Param::Type(ins2.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(ins2.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(ins2.type(), C2NumberStreamTuning::input::typeIndex);
+        EXPECT_EQ(C2Param::Type(ins2.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(ins2.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(ins2.type(), C2NumberStreamTuning::input::PARAM_TYPE);
 
-        EXPECT_EQ(C2Param::Type(outs1.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(outs1.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(outs1.type(), C2NumberStreamTuning::output::typeIndex);
+        EXPECT_EQ(C2Param::Type(outs1.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(outs1.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(outs1.type(), C2NumberStreamTuning::output::PARAM_TYPE);
 
-        EXPECT_EQ(C2Param::Type(outs2.type()).coreIndex(), C2NumberStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(outs2.type()).paramIndex(), kParamIndexNumber);
-        EXPECT_EQ(outs2.type(), C2NumberStreamTuning::output::typeIndex);
+        EXPECT_EQ(C2Param::Type(outs2.type()).coreIndex(), C2NumberStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(outs2.type()).typeIndex(), kParamIndexNumber);
+        EXPECT_EQ(outs2.type(), C2NumberStreamTuning::output::PARAM_TYPE);
 
-        C2Param::BaseIndex index = C2NumberStreamTuning::input::typeIndex;
+        C2Param::CoreIndex index = C2NumberStreamTuning::input::PARAM_TYPE;
         EXPECT_FALSE(index.isVendor());
         EXPECT_FALSE(index.isFlexible());
         EXPECT_EQ(index.coreIndex(), kParamIndexNumber);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumber);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumber);
 
-        index = C2NumberStreamTuning::output::typeIndex;
+        index = C2NumberStreamTuning::output::PARAM_TYPE;
         EXPECT_FALSE(index.isVendor());
         EXPECT_FALSE(index.isFlexible());
         EXPECT_EQ(index.coreIndex(), kParamIndexNumber);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumber);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumber);
 
-        C2Param::Type type = C2NumberStreamTuning::input::typeIndex;
+        C2Param::Type type = C2NumberStreamTuning::input::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_FALSE(type.isFlexible());
         EXPECT_FALSE(type.isGlobal());
@@ -1546,7 +1592,7 @@
         EXPECT_TRUE(type.forStream());
         EXPECT_FALSE(type.forPort());
 
-        type = C2NumberStreamTuning::output::typeIndex;
+        type = C2NumberStreamTuning::output::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_FALSE(type.isFlexible());
         EXPECT_FALSE(type.isGlobal());
@@ -1594,11 +1640,11 @@
     }
 
     {
-        uint32_t videoWidth[] = { 12u, C2NumberStreamTuning::output::typeIndex, 100 };
+        uint32_t videoWidth[] = { 12u, C2NumberStreamTuning::output::PARAM_TYPE, 100 };
         C2Param *p1 = C2Param::From(videoWidth, sizeof(videoWidth));
         EXPECT_NE(p1, nullptr);
         EXPECT_EQ(12u, p1->size());
-        EXPECT_EQ(p1->type(), C2NumberStreamTuning::output::typeIndex);
+        EXPECT_EQ(p1->type(), C2NumberStreamTuning::output::PARAM_TYPE);
 
         p1 = C2Param::From(videoWidth, sizeof(videoWidth) + 2);
         EXPECT_EQ(p1, nullptr);
@@ -1616,9 +1662,9 @@
 
 void StaticTestAddCoreIndex() {
     struct nobase {};
-    struct base { enum : uint32_t { coreIndex = 1 }; };
-    static_assert(C2AddCoreIndex<nobase, 2>::coreIndex == 2, "should be 2");
-    static_assert(C2AddCoreIndex<base, 1>::coreIndex == 1, "should be 1");
+    struct base { enum : uint32_t { CORE_INDEX = 1 }; };
+    static_assert(C2AddCoreIndex<nobase, 2>::CORE_INDEX == 2, "should be 2");
+    static_assert(C2AddCoreIndex<base, 1>::CORE_INDEX == 1, "should be 1");
 }
 
 class TestFlexHelper {
@@ -1640,11 +1686,11 @@
 
 
     static void StaticTest() {
-        static_assert(std::is_same<_C2FlexHelper<char>::flexType, void>::value, "should be void");
-        static_assert(std::is_same<_C2FlexHelper<char[]>::flexType, char>::value, "should be char");
-        static_assert(std::is_same<_C2FlexHelper<_Flex>::flexType, char>::value, "should be char");
+        static_assert(std::is_same<_C2FlexHelper<char>::FlexType, void>::value, "should be void");
+        static_assert(std::is_same<_C2FlexHelper<char[]>::FlexType, char>::value, "should be char");
+        static_assert(std::is_same<_C2FlexHelper<_Flex>::FlexType, char>::value, "should be char");
 
-        static_assert(std::is_same<_C2FlexHelper<_BoFlex>::flexType, char>::value, "should be void");
+        static_assert(std::is_same<_C2FlexHelper<_BoFlex>::FlexType, char>::value, "should be void");
 
         static_assert(_C2Flexible<_Flex>::value, "should be flexible");
         static_assert(!_C2Flexible<_NonFlex>::value, "should not be flexible");
@@ -1658,11 +1704,11 @@
 //        EXPECT_EQ(100, str->m.mNumbers[0]);
         (void)&bstr.mNumbers[0];
 
-        C2Param::BaseIndex index = C2NumbersStruct::coreIndex;
+        C2Param::CoreIndex index = C2NumbersStruct::CORE_INDEX;
         EXPECT_FALSE(index.isVendor());
         EXPECT_TRUE(index.isFlexible());
-        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::BaseIndex::_kFlexibleFlag);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumbers);
+        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::CoreIndex::IS_FLEX_FLAG);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumbers);
     }
 
     std::unique_ptr<C2NumbersTuning> tun_ = C2NumbersTuning::alloc_unique(1);
@@ -1698,18 +1744,18 @@
         EXPECT_EQ(*tun, *btun);
 
         // index
-        EXPECT_EQ(C2Param::Type(tun->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(tun->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(tun->type(), C2NumbersTuning::typeIndex);
+        EXPECT_EQ(C2Param::Type(tun->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(tun->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(tun->type(), C2NumbersTuning::PARAM_TYPE);
         EXPECT_EQ(tun->stream(), ~0u);
 
-        C2Param::BaseIndex index = C2NumbersTuning::coreIndex;
+        C2Param::CoreIndex index = C2NumbersTuning::CORE_INDEX;
         EXPECT_FALSE(index.isVendor());
         EXPECT_TRUE(index.isFlexible());
-        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::BaseIndex::_kFlexibleFlag);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumbers);
+        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::CoreIndex::IS_FLEX_FLAG);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumbers);
 
-        C2Param::Type type = C2NumbersTuning::typeIndex;
+        C2Param::Type type = C2NumbersTuning::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_TRUE(type.isFlexible());
         EXPECT_TRUE(type.isGlobal());
@@ -1867,39 +1913,39 @@
         EXPECT_TRUE(*inp1 == *boutp1);
 
         // index
-        EXPECT_EQ(C2Param::Type(inp1->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(inp1->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(inp1->type(), C2NumbersPortTuning::input::typeIndex);
+        EXPECT_EQ(C2Param::Type(inp1->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(inp1->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(inp1->type(), C2NumbersPortTuning::input::PARAM_TYPE);
         EXPECT_EQ(inp1->stream(), ~0u);
 
-        EXPECT_EQ(C2Param::Type(inp2->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(inp2->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(inp2->type(), C2NumbersPortTuning::input::typeIndex);
+        EXPECT_EQ(C2Param::Type(inp2->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(inp2->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(inp2->type(), C2NumbersPortTuning::input::PARAM_TYPE);
         EXPECT_EQ(inp2->stream(), ~0u);
 
-        EXPECT_EQ(C2Param::Type(outp1->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(outp1->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(outp1->type(), C2NumbersPortTuning::output::typeIndex);
+        EXPECT_EQ(C2Param::Type(outp1->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(outp1->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(outp1->type(), C2NumbersPortTuning::output::PARAM_TYPE);
         EXPECT_EQ(outp1->stream(), ~0u);
 
-        EXPECT_EQ(C2Param::Type(outp2->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(outp2->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(outp2->type(), C2NumbersPortTuning::output::typeIndex);
+        EXPECT_EQ(C2Param::Type(outp2->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(outp2->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(outp2->type(), C2NumbersPortTuning::output::PARAM_TYPE);
         EXPECT_EQ(outp2->stream(), ~0u);
 
-        C2Param::BaseIndex index = C2NumbersPortTuning::input::typeIndex;
+        C2Param::CoreIndex index = C2NumbersPortTuning::input::PARAM_TYPE;
         EXPECT_FALSE(index.isVendor());
         EXPECT_TRUE(index.isFlexible());
-        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::BaseIndex::_kFlexibleFlag);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumbers);
+        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::CoreIndex::IS_FLEX_FLAG);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumbers);
 
-        index = C2NumbersPortTuning::output::typeIndex;
+        index = C2NumbersPortTuning::output::PARAM_TYPE;
         EXPECT_FALSE(index.isVendor());
         EXPECT_TRUE(index.isFlexible());
-        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::BaseIndex::_kFlexibleFlag);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumbers);
+        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::CoreIndex::IS_FLEX_FLAG);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumbers);
 
-        C2Param::Type type = C2NumbersPortTuning::input::typeIndex;
+        C2Param::Type type = C2NumbersPortTuning::input::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_TRUE(type.isFlexible());
         EXPECT_FALSE(type.isGlobal());
@@ -1908,7 +1954,7 @@
         EXPECT_FALSE(type.forStream());
         EXPECT_TRUE(type.forPort());
 
-        type = C2NumbersPortTuning::output::typeIndex;
+        type = C2NumbersPortTuning::output::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_TRUE(type.isFlexible());
         EXPECT_FALSE(type.isGlobal());
@@ -2104,35 +2150,35 @@
         EXPECT_TRUE(*ins1 == *bouts1);
 
         // index
-        EXPECT_EQ(C2Param::Type(ins1->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(ins1->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(ins1->type(), C2NumbersStreamTuning::input::typeIndex);
+        EXPECT_EQ(C2Param::Type(ins1->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(ins1->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(ins1->type(), C2NumbersStreamTuning::input::PARAM_TYPE);
 
-        EXPECT_EQ(C2Param::Type(ins2->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(ins2->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(ins2->type(), C2NumbersStreamTuning::input::typeIndex);
+        EXPECT_EQ(C2Param::Type(ins2->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(ins2->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(ins2->type(), C2NumbersStreamTuning::input::PARAM_TYPE);
 
-        EXPECT_EQ(C2Param::Type(outs1->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(outs1->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(outs1->type(), C2NumbersStreamTuning::output::typeIndex);
+        EXPECT_EQ(C2Param::Type(outs1->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(outs1->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(outs1->type(), C2NumbersStreamTuning::output::PARAM_TYPE);
 
-        EXPECT_EQ(C2Param::Type(outs2->type()).coreIndex(), C2NumbersStruct::coreIndex);
-        EXPECT_EQ(C2Param::Type(outs2->type()).paramIndex(), kParamIndexNumbers);
-        EXPECT_EQ(outs2->type(), C2NumbersStreamTuning::output::typeIndex);
+        EXPECT_EQ(C2Param::Type(outs2->type()).coreIndex(), C2NumbersStruct::CORE_INDEX);
+        EXPECT_EQ(C2Param::Type(outs2->type()).typeIndex(), kParamIndexNumbers);
+        EXPECT_EQ(outs2->type(), C2NumbersStreamTuning::output::PARAM_TYPE);
 
-        C2Param::BaseIndex index = C2NumbersStreamTuning::input::typeIndex;
+        C2Param::CoreIndex index = C2NumbersStreamTuning::input::PARAM_TYPE;
         EXPECT_FALSE(index.isVendor());
         EXPECT_TRUE(index.isFlexible());
-        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::BaseIndex::_kFlexibleFlag);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumbers);
+        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::CoreIndex::IS_FLEX_FLAG);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumbers);
 
-        index = C2NumbersStreamTuning::output::typeIndex;
+        index = C2NumbersStreamTuning::output::PARAM_TYPE;
         EXPECT_FALSE(index.isVendor());
         EXPECT_TRUE(index.isFlexible());
-        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::BaseIndex::_kFlexibleFlag);
-        EXPECT_EQ(index.paramIndex(), kParamIndexNumbers);
+        EXPECT_EQ(index.coreIndex(), kParamIndexNumbers | C2Param::CoreIndex::IS_FLEX_FLAG);
+        EXPECT_EQ(index.typeIndex(), kParamIndexNumbers);
 
-        C2Param::Type type = C2NumbersStreamTuning::input::typeIndex;
+        C2Param::Type type = C2NumbersStreamTuning::input::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_TRUE(type.isFlexible());
         EXPECT_FALSE(type.isGlobal());
@@ -2141,7 +2187,7 @@
         EXPECT_TRUE(type.forStream());
         EXPECT_FALSE(type.forPort());
 
-        type = C2NumbersStreamTuning::output::typeIndex;
+        type = C2NumbersStreamTuning::output::PARAM_TYPE;
         EXPECT_FALSE(type.isVendor());
         EXPECT_TRUE(type.isFlexible());
         EXPECT_FALSE(type.isGlobal());
@@ -2190,9 +2236,9 @@
 
     {
         C2Int32Value int32Value(INT32_MIN);
-        static_assert(std::is_same<decltype(int32Value.mValue), int32_t>::value, "should be int32_t");
-        EXPECT_EQ(INT32_MIN, int32Value.mValue);
-        std::list<const C2FieldDescriptor> fields = int32Value.fieldList;
+        static_assert(std::is_same<decltype(int32Value.value), int32_t>::value, "should be int32_t");
+        EXPECT_EQ(INT32_MIN, int32Value.value);
+        std::list<const C2FieldDescriptor> fields = int32Value.FIELD_LIST;
         EXPECT_EQ(1u, fields.size());
         EXPECT_EQ(FD::INT32, fields.cbegin()->type());
         EXPECT_EQ(1u, fields.cbegin()->length());
@@ -2201,9 +2247,9 @@
 
     {
         C2Uint32Value uint32Value(UINT32_MAX);
-        static_assert(std::is_same<decltype(uint32Value.mValue), uint32_t>::value, "should be uint32_t");
-        EXPECT_EQ(UINT32_MAX, uint32Value.mValue);
-        std::list<const C2FieldDescriptor> fields = uint32Value.fieldList;
+        static_assert(std::is_same<decltype(uint32Value.value), uint32_t>::value, "should be uint32_t");
+        EXPECT_EQ(UINT32_MAX, uint32Value.value);
+        std::list<const C2FieldDescriptor> fields = uint32Value.FIELD_LIST;
         EXPECT_EQ(1u, fields.size());
         EXPECT_EQ(FD::UINT32, fields.cbegin()->type());
         EXPECT_EQ(1u, fields.cbegin()->length());
@@ -2212,9 +2258,9 @@
 
     {
         C2Int64Value int64Value(INT64_MIN);
-        static_assert(std::is_same<decltype(int64Value.mValue), int64_t>::value, "should be int64_t");
-        EXPECT_EQ(INT64_MIN, int64Value.mValue);
-        std::list<const C2FieldDescriptor> fields = int64Value.fieldList;
+        static_assert(std::is_same<decltype(int64Value.value), int64_t>::value, "should be int64_t");
+        EXPECT_EQ(INT64_MIN, int64Value.value);
+        std::list<const C2FieldDescriptor> fields = int64Value.FIELD_LIST;
         EXPECT_EQ(1u, fields.size());
         EXPECT_EQ(FD::INT64, fields.cbegin()->type());
         EXPECT_EQ(1u, fields.cbegin()->length());
@@ -2223,9 +2269,9 @@
 
     {
         C2Uint64Value uint64Value(UINT64_MAX);
-        static_assert(std::is_same<decltype(uint64Value.mValue), uint64_t>::value, "should be uint64_t");
-        EXPECT_EQ(UINT64_MAX, uint64Value.mValue);
-        std::list<const C2FieldDescriptor> fields = uint64Value.fieldList;
+        static_assert(std::is_same<decltype(uint64Value.value), uint64_t>::value, "should be uint64_t");
+        EXPECT_EQ(UINT64_MAX, uint64Value.value);
+        std::list<const C2FieldDescriptor> fields = uint64Value.FIELD_LIST;
         EXPECT_EQ(1u, fields.size());
         EXPECT_EQ(FD::UINT64, fields.cbegin()->type());
         EXPECT_EQ(1u, fields.cbegin()->length());
@@ -2234,9 +2280,9 @@
 
     {
         C2FloatValue floatValue(123.4f);
-        static_assert(std::is_same<decltype(floatValue.mValue), float>::value, "should be float");
-        EXPECT_EQ(123.4f, floatValue.mValue);
-        std::list<const C2FieldDescriptor> fields = floatValue.fieldList;
+        static_assert(std::is_same<decltype(floatValue.value), float>::value, "should be float");
+        EXPECT_EQ(123.4f, floatValue.value);
+        std::list<const C2FieldDescriptor> fields = floatValue.FIELD_LIST;
         EXPECT_EQ(1u, fields.size());
         EXPECT_EQ(FD::FLOAT, fields.cbegin()->type());
         EXPECT_EQ(1u, fields.cbegin()->length());
@@ -2247,17 +2293,17 @@
         uint8_t initValue[] = "ABCD";
         typedef C2GlobalParam<C2Setting, C2BlobValue, 0> BlobSetting;
         std::unique_ptr<BlobSetting> blobValue = BlobSetting::alloc_unique(6, C2ConstMemoryBlock<uint8_t>(initValue));
-        static_assert(std::is_same<decltype(blobValue->m.mValue), uint8_t[]>::value, "should be uint8_t[]");
-        EXPECT_EQ(0, memcmp(blobValue->m.mValue, "ABCD\0", 6));
+        static_assert(std::is_same<decltype(blobValue->m.value), uint8_t[]>::value, "should be uint8_t[]");
+        EXPECT_EQ(0, memcmp(blobValue->m.value, "ABCD\0", 6));
         EXPECT_EQ(6u, blobValue->flexCount());
-        std::list<const C2FieldDescriptor> fields = blobValue->fieldList;
+        std::list<const C2FieldDescriptor> fields = blobValue->FIELD_LIST;
         EXPECT_EQ(1u, fields.size());
         EXPECT_EQ(FD::BLOB, fields.cbegin()->type());
         EXPECT_EQ(0u, fields.cbegin()->length());
         EXPECT_EQ(C2String("value"), fields.cbegin()->name());
 
         blobValue = BlobSetting::alloc_unique(3, C2ConstMemoryBlock<uint8_t>(initValue));
-        EXPECT_EQ(0, memcmp(blobValue->m.mValue, "ABC", 3));
+        EXPECT_EQ(0, memcmp(blobValue->m.value, "ABC", 3));
         EXPECT_EQ(3u, blobValue->flexCount());
     }
 
@@ -2266,38 +2312,38 @@
         typedef C2GlobalParam<C2Setting, C2StringValue, 0> StringSetting;
         std::unique_ptr<StringSetting> stringValue = StringSetting::alloc_unique(6, C2ConstMemoryBlock<char>(initValue));
         stringValue = StringSetting::alloc_unique(6, initValue);
-        static_assert(std::is_same<decltype(stringValue->m.mValue), char[]>::value, "should be char[]");
-        EXPECT_EQ(0, memcmp(stringValue->m.mValue, "ABCD\0", 6));
+        static_assert(std::is_same<decltype(stringValue->m.value), char[]>::value, "should be char[]");
+        EXPECT_EQ(0, memcmp(stringValue->m.value, "ABCD\0", 6));
         EXPECT_EQ(6u, stringValue->flexCount());
-        std::list<const C2FieldDescriptor> fields = stringValue->fieldList;
+        std::list<const C2FieldDescriptor> fields = stringValue->FIELD_LIST;
         EXPECT_EQ(1u, fields.size());
         EXPECT_EQ(FD::STRING, fields.cbegin()->type());
         EXPECT_EQ(0u, fields.cbegin()->length());
         EXPECT_EQ(C2String("value"), fields.cbegin()->name());
 
         stringValue = StringSetting::alloc_unique(3, C2ConstMemoryBlock<char>(initValue));
-        EXPECT_EQ(0, memcmp(stringValue->m.mValue, "AB", 3));
+        EXPECT_EQ(0, memcmp(stringValue->m.value, "AB", 3));
         EXPECT_EQ(3u, stringValue->flexCount());
 
         stringValue = StringSetting::alloc_unique(11, "initValue");
-        EXPECT_EQ(0, memcmp(stringValue->m.mValue, "initValue\0", 11));
+        EXPECT_EQ(0, memcmp(stringValue->m.value, "initValue\0", 11));
         EXPECT_EQ(11u, stringValue->flexCount());
 
         stringValue = StringSetting::alloc_unique(initValue);
-        EXPECT_EQ(0, memcmp(stringValue->m.mValue, "ABCD", 5));
+        EXPECT_EQ(0, memcmp(stringValue->m.value, "ABCD", 5));
         EXPECT_EQ(5u, stringValue->flexCount());
 
         stringValue = StringSetting::alloc_unique({ 'A', 'B', 'C', 'D' });
-        EXPECT_EQ(0, memcmp(stringValue->m.mValue, "ABC", 4));
+        EXPECT_EQ(0, memcmp(stringValue->m.value, "ABC", 4));
         EXPECT_EQ(4u, stringValue->flexCount());
     }
 
     {
-        uint32_t videoWidth[] = { 12u, C2NumbersStreamTuning::output::typeIndex, 100 };
+        uint32_t videoWidth[] = { 12u, C2NumbersStreamTuning::output::PARAM_TYPE, 100 };
         C2Param *p1 = C2Param::From(videoWidth, sizeof(videoWidth));
         EXPECT_NE(nullptr, p1);
         EXPECT_EQ(12u, p1->size());
-        EXPECT_EQ(C2NumbersStreamTuning::output::typeIndex, p1->type());
+        EXPECT_EQ(p1->type(), C2NumbersStreamTuning::output::PARAM_TYPE);
 
         C2NumbersStreamTuning::output *vst = C2NumbersStreamTuning::output::From(p1);
         EXPECT_NE(nullptr, vst);
@@ -2320,12 +2366,12 @@
     }
 
     {
-        uint32_t videoWidth[] = { 16u, C2NumbersPortTuning::input::typeIndex, 101, 102 };
+        uint32_t videoWidth[] = { 16u, C2NumbersPortTuning::input::PARAM_TYPE, 101, 102 };
 
         C2Param *p1 = C2Param::From(videoWidth, sizeof(videoWidth));
         EXPECT_NE(nullptr, p1);
         EXPECT_EQ(16u, p1->size());
-        EXPECT_EQ(C2NumbersPortTuning::input::typeIndex, p1->type());
+        EXPECT_EQ(p1->type(), C2NumbersPortTuning::input::PARAM_TYPE);
 
         C2NumbersPortTuning::input *vpt = C2NumbersPortTuning::input::From(p1);
         EXPECT_NE(nullptr, vpt);
@@ -2375,18 +2421,18 @@
 };
 
 struct C2VideoConfigStruct {
-    int32_t mWidth;
-    uint32_t mHeight;
-    MetadataType mMetadataType;
-    int32_t mSupportedFormats[];
+    int32_t width;
+    uint32_t height;
+    MetadataType metadataType;
+    int32_t supportedFormats[];
 
     C2VideoConfigStruct() {}
 
-    DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(VideoConfig, mSupportedFormats)
-    C2FIELD(mWidth, "width")
-    C2FIELD(mHeight, "height")
-    C2FIELD(mMetadataType, "metadata-type")
-    C2FIELD(mSupportedFormats, "formats")
+    DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(VideoConfig, supportedFormats)
+    C2FIELD(width, "width")
+    C2FIELD(height, "height")
+    C2FIELD(metadataType, "metadata-type")
+    C2FIELD(supportedFormats, "formats")
 };
 
 typedef C2PortParam<C2Tuning, C2VideoConfigStruct> C2VideoConfigPortTuning;
@@ -2430,11 +2476,11 @@
             }
 
             // note: this does not handle stream params (should use index...)
-            if (!mMyParams.count(param->type())) {
+            if (!mMyParams.count(param->index())) {
                 continue; // not my param
             }
 
-            C2Param & myParam = mMyParams.find(param->type())->second;
+            C2Param & myParam = mMyParams.find(param->index())->second;
             if (myParam.size() != param->size()) { // incorrect size
                 param->invalidate();
                 continue;
@@ -2459,7 +2505,7 @@
     C2ComponentDomainInfo mDomainInfo;
 
     MyComponentInstance() {
-        mMyParams.insert({mDomainInfo.type(), mDomainInfo});
+        mMyParams.insert({mDomainInfo.index(), mDomainInfo});
     }
 
     virtual c2_status_t releaseTunnel_sm(c2_node_id_t targetComponent) override {
@@ -2473,13 +2519,13 @@
     public:
         MyParamReflector(const MyComponentInstance *i) : instance(i) { }
 
-        virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::BaseIndex paramIndex) override {
-            switch (paramIndex.coreIndex()) {
-            case decltype(instance->mDomainInfo)::coreIndex:
+        virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::CoreIndex paramIndex) override {
+            switch (paramIndex.typeIndex()) {
+            case decltype(instance->mDomainInfo)::CORE_INDEX:
             default:
                 return std::unique_ptr<C2StructDescriptor>(new C2StructDescriptor{
                     instance->mDomainInfo.type(),
-                    decltype(instance->mDomainInfo)::fieldList,
+                    decltype(instance->mDomainInfo)::FIELD_LIST,
                 });
             }
             return nullptr;
@@ -2491,10 +2537,10 @@
             c2_blocking_t mayBlock) const override {
         (void)mayBlock;
         for (C2FieldSupportedValuesQuery &query : fields) {
-            if (query.field == C2ParamField(&mDomainInfo, &C2ComponentDomainInfo::mValue)) {
+            if (query.field == C2ParamField(&mDomainInfo, &C2ComponentDomainInfo::value)) {
                 query.values = C2FieldSupportedValues(
                     false /* flag */,
-                    &mDomainInfo.mValue
+                    &mDomainInfo.value
                     //,
                     //{(int32_t)C2DomainVideo}
                 );
@@ -2611,7 +2657,7 @@
         cout << "Flex";
     }
 
-    cout << type.paramIndex();
+    cout << type.typeIndex();
 
     switch (type.kind()) {
     case C2Param::INFO: cout << "Info"; break;
@@ -2622,17 +2668,17 @@
     }
 }
 
-void dumpType(C2Param::BaseIndex type) {
+void dumpType(C2Param::CoreIndex type) {
     using namespace std;
     cout << (type.isVendor() ? "Vendor" : "C2");
     if (type.isFlexible()) {
         cout << "Flex";
     }
 
-    cout << type.paramIndex() << "Struct";
+    cout << type.typeIndex() << "Struct";
 }
 
-void dumpType(FD::Type type) {
+void dumpType(FD::type_t type) {
     using namespace std;
     switch (type) {
     case FD::BLOB: cout << "blob "; break;
@@ -2700,21 +2746,21 @@
     std::shared_ptr<C2ComponentInterface> comp = myComp;
 
     std::unique_ptr<C2StructDescriptor> desc{
-        myComp->getParamReflector()->describe(C2ComponentDomainInfo::indexFlags)};
+        myComp->getParamReflector()->describe(C2ComponentDomainInfo::CORE_INDEX)};
     dumpStruct(*desc);
 
     std::vector<C2FieldSupportedValuesQuery> query = {
-        { C2ParamField(&domainInfo, &C2ComponentDomainInfo::mValue),
+        { C2ParamField(&domainInfo, &C2ComponentDomainInfo::value),
           C2FieldSupportedValuesQuery::CURRENT },
-        C2FieldSupportedValuesQuery(C2ParamField(&domainInfo, &C2ComponentDomainInfo::mValue),
+        C2FieldSupportedValuesQuery(C2ParamField(&domainInfo, &C2ComponentDomainInfo::value),
           C2FieldSupportedValuesQuery::CURRENT),
-        C2FieldSupportedValuesQuery::Current(C2ParamField(&domainInfo, &C2ComponentDomainInfo::mValue)),
+        C2FieldSupportedValuesQuery::Current(C2ParamField(&domainInfo, &C2ComponentDomainInfo::value)),
     };
 
     EXPECT_EQ(C2_OK, comp->querySupportedValues_vb(query, C2_DONT_BLOCK));
 
     for (const C2FieldSupportedValuesQuery &q : query) {
-        dumpFSV(q.values, &domainInfo.mValue);
+        dumpFSV(q.values, &domainInfo.value);
     }
 }
 
@@ -2730,7 +2776,7 @@
     values.push_back(C2FieldSupportedValues(false, v));  // flags, std::vector
 
     for (const C2FieldSupportedValues &sv : values) {
-        dumpFSV(sv, &t.mValue);
+        dumpFSV(sv, &t.value);
     }
 }
 
diff --git a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
index 1bcf070..f6e6478 100644
--- a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
+++ b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
@@ -40,7 +40,7 @@
     void allocateLinear(size_t capacity) {
         c2_status_t err = mLinearAllocator->newLinearAllocation(
                 capacity,
-                { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+                { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
                 &mLinearAllocation);
         if (err != C2_OK) {
             mLinearAllocation.reset();
@@ -53,7 +53,7 @@
         c2_status_t err = mLinearAllocation->map(
                 offset,
                 size,
-                { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+                { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
                 // TODO: fence
                 nullptr,
                 &mAddr);
@@ -86,7 +86,7 @@
                 width,
                 height,
                 HAL_PIXEL_FORMAT_YCBCR_420_888,
-                { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+                { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
                 &mGraphicAllocation);
         if (err != C2_OK) {
             mGraphicAllocation.reset();
@@ -94,19 +94,19 @@
         }
     }
 
-    void mapGraphic(C2Rect rect, C2PlaneLayout *layout, uint8_t **addr) {
+    void mapGraphic(C2Rect rect, C2PlanarLayout *layout, uint8_t **addr) {
         ASSERT_TRUE(mGraphicAllocation);
         c2_status_t err = mGraphicAllocation->map(
                 rect,
-                { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+                { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
                 // TODO: fence
                 nullptr,
                 layout,
                 addr);
         if (err != C2_OK) {
-            addr[C2PlaneLayout::Y] = nullptr;
-            addr[C2PlaneLayout::U] = nullptr;
-            addr[C2PlaneLayout::V] = nullptr;
+            addr[C2PlanarLayout::PLANE_Y] = nullptr;
+            addr[C2PlanarLayout::PLANE_U] = nullptr;
+            addr[C2PlanarLayout::PLANE_V] = nullptr;
             FAIL() << "C2GraphicAllocation::map() failed: " << err;
         }
     }
@@ -163,7 +163,7 @@
     std::shared_ptr<C2LinearBlock> block;
     ASSERT_EQ(C2_OK, blockPool->fetchLinearBlock(
             kCapacity,
-            { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+            { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
             &block));
     ASSERT_TRUE(block);
 
@@ -210,20 +210,20 @@
 }
 
 void fillPlane(const C2Rect rect, const C2PlaneInfo info, uint8_t *addr, uint8_t value) {
-    for (uint32_t row = 0; row < rect.mHeight / info.mVertSubsampling; ++row) {
-        int32_t rowOffset = (row + rect.mTop / info.mVertSubsampling) * info.mRowInc;
-        for (uint32_t col = 0; col < rect.mWidth / info.mHorizSubsampling; ++col) {
-            int32_t colOffset = (col + rect.mLeft / info.mHorizSubsampling) * info.mColInc;
+    for (uint32_t row = 0; row < rect.height / info.rowSampling; ++row) {
+        int32_t rowOffset = (row + rect.top / info.rowSampling) * info.rowInc;
+        for (uint32_t col = 0; col < rect.width / info.colSampling; ++col) {
+            int32_t colOffset = (col + rect.left / info.colSampling) * info.colInc;
             addr[rowOffset + colOffset] = value;
         }
     }
 }
 
 bool verifyPlane(const C2Rect rect, const C2PlaneInfo info, const uint8_t *addr, uint8_t value) {
-    for (uint32_t row = 0; row < rect.mHeight / info.mVertSubsampling; ++row) {
-        int32_t rowOffset = (row + rect.mTop / info.mVertSubsampling) * info.mRowInc;
-        for (uint32_t col = 0; col < rect.mWidth / info.mHorizSubsampling; ++col) {
-            int32_t colOffset = (col + rect.mLeft / info.mHorizSubsampling) * info.mColInc;
+    for (uint32_t row = 0; row < rect.height / info.rowSampling; ++row) {
+        int32_t rowOffset = (row + rect.top / info.rowSampling) * info.rowInc;
+        for (uint32_t col = 0; col < rect.width / info.colSampling; ++col) {
+            int32_t colOffset = (col + rect.left / info.colSampling) * info.colInc;
             if (addr[rowOffset + colOffset] != value) {
                 return false;
             }
@@ -238,20 +238,20 @@
 
     allocateGraphic(kWidth, kHeight);
 
-    uint8_t *addr[C2PlaneLayout::MAX_NUM_PLANES];
+    uint8_t *addr[C2PlanarLayout::MAX_NUM_PLANES];
     C2Rect rect{ 0, 0, kWidth, kHeight };
-    C2PlaneLayout layout;
+    C2PlanarLayout layout;
     mapGraphic(rect, &layout, addr);
-    ASSERT_NE(nullptr, addr[C2PlaneLayout::Y]);
-    ASSERT_NE(nullptr, addr[C2PlaneLayout::U]);
-    ASSERT_NE(nullptr, addr[C2PlaneLayout::V]);
+    ASSERT_NE(nullptr, addr[C2PlanarLayout::PLANE_Y]);
+    ASSERT_NE(nullptr, addr[C2PlanarLayout::PLANE_U]);
+    ASSERT_NE(nullptr, addr[C2PlanarLayout::PLANE_V]);
 
-    uint8_t *y = addr[C2PlaneLayout::Y];
-    C2PlaneInfo yInfo = layout.mPlanes[C2PlaneLayout::Y];
-    uint8_t *u = addr[C2PlaneLayout::U];
-    C2PlaneInfo uInfo = layout.mPlanes[C2PlaneLayout::U];
-    uint8_t *v = addr[C2PlaneLayout::V];
-    C2PlaneInfo vInfo = layout.mPlanes[C2PlaneLayout::V];
+    uint8_t *y = addr[C2PlanarLayout::PLANE_Y];
+    C2PlaneInfo yInfo = layout.planes[C2PlanarLayout::PLANE_Y];
+    uint8_t *u = addr[C2PlanarLayout::PLANE_U];
+    C2PlaneInfo uInfo = layout.planes[C2PlanarLayout::PLANE_U];
+    uint8_t *v = addr[C2PlanarLayout::PLANE_V];
+    C2PlaneInfo vInfo = layout.planes[C2PlanarLayout::PLANE_V];
 
     fillPlane(rect, yInfo, y, 0);
     fillPlane(rect, uInfo, u, 0);
@@ -263,16 +263,16 @@
     unmapGraphic();
 
     mapGraphic(rect, &layout, addr);
-    ASSERT_NE(nullptr, addr[C2PlaneLayout::Y]);
-    ASSERT_NE(nullptr, addr[C2PlaneLayout::U]);
-    ASSERT_NE(nullptr, addr[C2PlaneLayout::V]);
+    ASSERT_NE(nullptr, addr[C2PlanarLayout::PLANE_Y]);
+    ASSERT_NE(nullptr, addr[C2PlanarLayout::PLANE_U]);
+    ASSERT_NE(nullptr, addr[C2PlanarLayout::PLANE_V]);
 
-    y = addr[C2PlaneLayout::Y];
-    yInfo = layout.mPlanes[C2PlaneLayout::Y];
-    u = addr[C2PlaneLayout::U];
-    uInfo = layout.mPlanes[C2PlaneLayout::U];
-    v = addr[C2PlaneLayout::V];
-    vInfo = layout.mPlanes[C2PlaneLayout::V];
+    y = addr[C2PlanarLayout::PLANE_Y];
+    yInfo = layout.planes[C2PlanarLayout::PLANE_Y];
+    u = addr[C2PlanarLayout::PLANE_U];
+    uInfo = layout.planes[C2PlanarLayout::PLANE_U];
+    v = addr[C2PlanarLayout::PLANE_V];
+    vInfo = layout.planes[C2PlanarLayout::PLANE_V];
 
     ASSERT_TRUE(verifyPlane({ kWidth / 4, kHeight / 4, kWidth / 2, kHeight / 2 }, yInfo, y, 0x12));
     ASSERT_TRUE(verifyPlane({ kWidth / 4, kHeight / 4, kWidth / 2, kHeight / 2 }, uInfo, u, 0x34));
@@ -296,7 +296,7 @@
             kWidth,
             kHeight,
             HAL_PIXEL_FORMAT_YCBCR_420_888,
-            { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+            { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
             &block));
     ASSERT_TRUE(block);
 
@@ -307,15 +307,15 @@
     ASSERT_EQ(kHeight, graphicView.height());
 
     uint8_t *const *data = graphicView.data();
-    C2PlaneLayout layout = graphicView.layout();
+    C2PlanarLayout layout = graphicView.layout();
     ASSERT_NE(nullptr, data);
 
-    uint8_t *y = data[C2PlaneLayout::Y];
-    C2PlaneInfo yInfo = layout.mPlanes[C2PlaneLayout::Y];
-    uint8_t *u = data[C2PlaneLayout::U];
-    C2PlaneInfo uInfo = layout.mPlanes[C2PlaneLayout::U];
-    uint8_t *v = data[C2PlaneLayout::V];
-    C2PlaneInfo vInfo = layout.mPlanes[C2PlaneLayout::V];
+    uint8_t *y = data[C2PlanarLayout::PLANE_Y];
+    C2PlaneInfo yInfo = layout.planes[C2PlanarLayout::PLANE_Y];
+    uint8_t *u = data[C2PlanarLayout::PLANE_U];
+    C2PlaneInfo uInfo = layout.planes[C2PlanarLayout::PLANE_U];
+    uint8_t *v = data[C2PlanarLayout::PLANE_V];
+    C2PlaneInfo vInfo = layout.planes[C2PlanarLayout::PLANE_V];
 
     fillPlane({ 0, 0, kWidth, kHeight }, yInfo, y, 0);
     fillPlane({ 0, 0, kWidth, kHeight }, uInfo, u, 0);
@@ -339,12 +339,12 @@
     layout = graphicView.layout();
     ASSERT_NE(nullptr, constData);
 
-    const uint8_t *cy = constData[C2PlaneLayout::Y];
-    yInfo = layout.mPlanes[C2PlaneLayout::Y];
-    const uint8_t *cu = constData[C2PlaneLayout::U];
-    uInfo = layout.mPlanes[C2PlaneLayout::U];
-    const uint8_t *cv = constData[C2PlaneLayout::V];
-    vInfo = layout.mPlanes[C2PlaneLayout::V];
+    const uint8_t *cy = constData[C2PlanarLayout::PLANE_Y];
+    yInfo = layout.planes[C2PlanarLayout::PLANE_Y];
+    const uint8_t *cu = constData[C2PlanarLayout::PLANE_U];
+    uInfo = layout.planes[C2PlanarLayout::PLANE_U];
+    const uint8_t *cv = constData[C2PlanarLayout::PLANE_V];
+    vInfo = layout.planes[C2PlanarLayout::PLANE_V];
 
     ASSERT_TRUE(verifyPlane({ kWidth / 4, kHeight / 4, kWidth / 2, kHeight / 2 }, yInfo, cy, 0x12));
     ASSERT_TRUE(verifyPlane({ kWidth / 4, kHeight / 4, kWidth / 2, kHeight / 2 }, uInfo, cu, 0x34));
@@ -386,11 +386,11 @@
     std::shared_ptr<C2LinearBlock> linearBlock2;
     ASSERT_EQ(C2_OK, linearBlockPool->fetchLinearBlock(
             kCapacity1,
-            { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+            { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
             &linearBlock1));
     ASSERT_EQ(C2_OK, linearBlockPool->fetchLinearBlock(
             kCapacity2,
-            { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+            { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
             &linearBlock2));
     std::shared_ptr<C2GraphicBlock> graphicBlock1;
     std::shared_ptr<C2GraphicBlock> graphicBlock2;
@@ -398,13 +398,13 @@
             kWidth1,
             kHeight1,
             HAL_PIXEL_FORMAT_YCBCR_420_888,
-            { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+            { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
             &graphicBlock1));
     ASSERT_EQ(C2_OK, graphicBlockPool->fetchGraphicBlock(
             kWidth2,
             kHeight2,
             HAL_PIXEL_FORMAT_YCBCR_420_888,
-            { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+            { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
             &graphicBlock2));
 
     std::shared_ptr<C2BufferData> data(new BufferData({ linearBlock1->share(0, kCapacity1, C2Fence()) }));
@@ -460,7 +460,7 @@
 
     ASSERT_EQ(C2_OK, alloc->fetchLinearBlock(
             kCapacity,
-            { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+            { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
             &block));
 
     std::atomic_bool destroyed(false);
diff --git a/media/libstagefright/codec2/vndk/Android.bp b/media/libstagefright/codec2/vndk/Android.bp
index fb469d7..cc79dc0 100644
--- a/media/libstagefright/codec2/vndk/Android.bp
+++ b/media/libstagefright/codec2/vndk/Android.bp
@@ -1,4 +1,4 @@
-cc_library_static {
+cc_library {
     name: "libstagefright_codec2_vndk",
 
     srcs: [
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
index da8372c..18db3e9 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
@@ -20,6 +20,7 @@
 
 #include <android/hardware/graphics/allocator/2.0/IAllocator.h>
 #include <android/hardware/graphics/mapper/2.0/IMapper.h>
+#include <cutils/native_handle.h>
 #include <hardware/gralloc.h>
 
 #include <C2AllocatorGralloc.h>
@@ -50,16 +51,121 @@
     return C2_CORRUPTED;
 }
 
+static
+bool native_handle_is_invalid(const native_handle_t *const handle) {
+    // perform basic validation of a native handle
+    if (handle == nullptr) {
+        // null handle is considered valid
+        return false;
+    }
+    return ((size_t)handle->version != sizeof(native_handle_t) ||
+            handle->numFds < 0 ||
+            handle->numInts < 0 ||
+            // for sanity assume handles must occupy less memory than INT_MAX bytes
+            handle->numFds > int((INT_MAX - handle->version) / sizeof(int)) - handle->numInts);
+}
+
+class C2HandleGralloc : public C2Handle {
+private:
+    struct ExtraData {
+        uint32_t width;
+        uint32_t height;
+        uint32_t format;
+        uint32_t usage_lo;
+        uint32_t usage_hi;
+        uint32_t magic;
+    };
+
+    enum {
+        NUM_INTS = sizeof(ExtraData) / sizeof(int),
+    };
+    const static uint32_t MAGIC = '\xc2gr\x00';
+
+    static
+    const ExtraData* getExtraData(const C2Handle *const handle) {
+        if (handle == nullptr
+                || native_handle_is_invalid(handle)
+                || handle->numInts < NUM_INTS) {
+            return nullptr;
+        }
+        return reinterpret_cast<const ExtraData*>(
+                &handle->data[handle->numFds + handle->numInts - NUM_INTS]);
+    }
+
+    static
+    ExtraData *getExtraData(C2Handle *const handle) {
+        return const_cast<ExtraData *>(getExtraData(const_cast<const C2Handle *const>(handle)));
+    }
+
+public:
+    static bool isValid(const C2Handle *const o) {
+        if (o == nullptr) { // null handle is always valid
+            return true;
+        }
+        const ExtraData *xd = getExtraData(o);
+        // we cannot validate width/height/format/usage without accessing gralloc driver
+        return xd != nullptr && xd->magic == MAGIC;
+    }
+
+    static C2HandleGralloc* WrapNativeHandle(
+            const native_handle_t *const handle,
+            uint32_t width, uint32_t height, uint32_t format, uint64_t usage) {
+        //CHECK(handle != nullptr);
+        if (native_handle_is_invalid(handle) ||
+            handle->numInts > int((INT_MAX - handle->version) / sizeof(int)) - NUM_INTS - handle->numFds) {
+            return nullptr;
+        }
+        ExtraData xd = { width, height, format, uint32_t(usage & 0xFFFFFFFF), uint32_t(usage >> 32), MAGIC };
+        native_handle_t *res = native_handle_create(handle->numFds, handle->numInts + NUM_INTS);
+        if (res != nullptr) {
+            memcpy(&res->data, &handle->data, sizeof(int) * (handle->numFds + handle->numInts));
+            *getExtraData(res) = xd;
+        }
+        return reinterpret_cast<C2HandleGralloc *>(res);
+    }
+
+    static native_handle_t* UnwrapNativeHandle(const C2Handle *const handle) {
+        const ExtraData *xd = getExtraData(handle);
+        if (xd == nullptr || xd->magic != MAGIC) {
+            return nullptr;
+        }
+        native_handle_t *res = native_handle_create(handle->numFds, handle->numInts - NUM_INTS);
+        if (res != nullptr) {
+            memcpy(&res->data, &handle->data, sizeof(int) * (res->numFds + res->numInts));
+        }
+        return res;
+    }
+
+    static const C2HandleGralloc* Import(
+            const C2Handle *const handle,
+            uint32_t *width, uint32_t *height, uint32_t *format, uint64_t *usage) {
+        const ExtraData *xd = getExtraData(handle);
+        if (xd == nullptr) {
+            return nullptr;
+        }
+        *width = xd->width;
+        *height = xd->height;
+        *format = xd->format;
+        *usage = xd->usage_lo | (uint64_t(xd->usage_hi) << 32);
+
+        return reinterpret_cast<const C2HandleGralloc *>(handle);
+    }
+};
+
+native_handle_t* UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle) {
+    return C2HandleGralloc::UnwrapNativeHandle(handle);
+}
+
 class C2AllocationGralloc : public C2GraphicAllocation {
 public:
     virtual ~C2AllocationGralloc() override;
 
     virtual c2_status_t map(
             C2Rect rect, C2MemoryUsage usage, int *fenceFd,
-            C2PlaneLayout *layout /* nonnull */, uint8_t **addr /* nonnull */) override;
+            C2PlanarLayout *layout /* nonnull */, uint8_t **addr /* nonnull */) override;
     virtual c2_status_t unmap(C2Fence *fenceFd /* nullable */) override;
     virtual bool isValid() const override { return true; }
-    virtual const C2Handle *handle() const override { return mHandle; }
+    virtual const C2Handle *handle() const override { return mLockedHandle ? : mHandle; }
     virtual bool equals(const std::shared_ptr<const C2GraphicAllocation> &other) const override;
 
     // internal methods
@@ -67,26 +173,31 @@
     C2AllocationGralloc(
               const IMapper::BufferDescriptorInfo &info,
               const sp<IMapper> &mapper,
-              hidl_handle &handle);
+              hidl_handle &hidlHandle,
+              const C2HandleGralloc *const handle);
     int dup() const;
     c2_status_t status() const;
 
 private:
     const IMapper::BufferDescriptorInfo mInfo;
     const sp<IMapper> mMapper;
-    const hidl_handle mHandle;
+    const hidl_handle mHidlHandle;
+    const C2HandleGralloc *mHandle;
     buffer_handle_t mBuffer;
+    const C2HandleGralloc *mLockedHandle;
     bool mLocked;
 };
 
 C2AllocationGralloc::C2AllocationGralloc(
           const IMapper::BufferDescriptorInfo &info,
           const sp<IMapper> &mapper,
-          hidl_handle &handle)
+          hidl_handle &hidlHandle,
+          const C2HandleGralloc *const handle)
     : C2GraphicAllocation(info.width, info.height),
       mInfo(info),
       mMapper(mapper),
-      mHandle(std::move(handle)),
+      mHidlHandle(std::move(hidlHandle)),
+      mHandle(handle),
       mBuffer(nullptr),
       mLocked(false) {}
 
@@ -102,7 +213,7 @@
 
 c2_status_t C2AllocationGralloc::map(
         C2Rect rect, C2MemoryUsage usage, int *fenceFd,
-        C2PlaneLayout *layout /* nonnull */, uint8_t **addr /* nonnull */) {
+        C2PlanarLayout *layout /* nonnull */, uint8_t **addr /* nonnull */) {
     // TODO
     (void) fenceFd;
     (void) usage;
@@ -117,7 +228,7 @@
     c2_status_t err = C2_OK;
     if (!mBuffer) {
         mMapper->importBuffer(
-                mHandle, [&err, this](const auto &maperr, const auto &buffer) {
+                mHidlHandle, [&err, this](const auto &maperr, const auto &buffer) {
                     err = maperr2error(maperr);
                     if (err == C2_OK) {
                         mBuffer = static_cast<buffer_handle_t>(buffer);
@@ -126,6 +237,11 @@
         if (err != C2_OK) {
             return err;
         }
+        if (mBuffer == nullptr) {
+            return C2_CORRUPTED;
+        }
+        mLockedHandle = C2HandleGralloc::WrapNativeHandle(
+                mBuffer, mInfo.width, mInfo.height, (uint32_t)mInfo.format, mInfo.usage);
     }
 
     if (mInfo.format == PixelFormat::YCBCR_420_888 || mInfo.format == PixelFormat::YV12) {
@@ -133,7 +249,7 @@
         mMapper->lockYCbCr(
                 const_cast<native_handle_t *>(mBuffer),
                 BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
-                { (int32_t)rect.mLeft, (int32_t)rect.mTop, (int32_t)rect.mWidth, (int32_t)rect.mHeight },
+                { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
                 // TODO: fence
                 hidl_handle(),
                 [&err, &ycbcrLayout](const auto &maperr, const auto &mapLayout) {
@@ -145,44 +261,50 @@
         if (err != C2_OK) {
             return err;
         }
-        addr[C2PlaneLayout::Y] = (uint8_t *)ycbcrLayout.y;
-        addr[C2PlaneLayout::U] = (uint8_t *)ycbcrLayout.cb;
-        addr[C2PlaneLayout::V] = (uint8_t *)ycbcrLayout.cr;
-        layout->mType = C2PlaneLayout::MEDIA_IMAGE_TYPE_YUV;
-        layout->mNumPlanes = 3;
-        layout->mPlanes[C2PlaneLayout::Y] = {
-            C2PlaneInfo::Y,                 // mChannel
-            1,                              // mColInc
-            (int32_t)ycbcrLayout.yStride,   // mRowInc
-            1,                              // mHorizSubsampling
-            1,                              // mVertSubsampling
-            8,                              // mBitDepth
-            8,                              // mAllocatedDepth
+        addr[C2PlanarLayout::PLANE_Y] = (uint8_t *)ycbcrLayout.y;
+        addr[C2PlanarLayout::PLANE_U] = (uint8_t *)ycbcrLayout.cb;
+        addr[C2PlanarLayout::PLANE_V] = (uint8_t *)ycbcrLayout.cr;
+        layout->type = C2PlanarLayout::TYPE_YUV;
+        layout->numPlanes = 3;
+        layout->planes[C2PlanarLayout::PLANE_Y] = {
+            C2PlaneInfo::CHANNEL_Y,         // channel
+            1,                              // colInc
+            (int32_t)ycbcrLayout.yStride,   // rowInc
+            1,                              // mColSampling
+            1,                              // mRowSampling
+            8,                              // allocatedDepth
+            8,                              // bitDepth
+            0,                              // rightShift
+            C2PlaneInfo::NATIVE,            // endianness
         };
-        layout->mPlanes[C2PlaneLayout::U] = {
-            C2PlaneInfo::Cb,                  // mChannel
-            (int32_t)ycbcrLayout.chromaStep,  // mColInc
-            (int32_t)ycbcrLayout.cStride,     // mRowInc
-            2,                                // mHorizSubsampling
-            2,                                // mVertSubsampling
-            8,                                // mBitDepth
-            8,                                // mAllocatedDepth
+        layout->planes[C2PlanarLayout::PLANE_U] = {
+            C2PlaneInfo::CHANNEL_CB,          // channel
+            (int32_t)ycbcrLayout.chromaStep,  // colInc
+            (int32_t)ycbcrLayout.cStride,     // rowInc
+            2,                                // mColSampling
+            2,                                // mRowSampling
+            8,                                // allocatedDepth
+            8,                                // bitDepth
+            0,                                // rightShift
+            C2PlaneInfo::NATIVE,              // endianness
         };
-        layout->mPlanes[C2PlaneLayout::V] = {
-            C2PlaneInfo::Cr,                  // mChannel
-            (int32_t)ycbcrLayout.chromaStep,  // mColInc
-            (int32_t)ycbcrLayout.cStride,     // mRowInc
-            2,                                // mHorizSubsampling
-            2,                                // mVertSubsampling
-            8,                                // mBitDepth
-            8,                                // mAllocatedDepth
+        layout->planes[C2PlanarLayout::PLANE_V] = {
+            C2PlaneInfo::CHANNEL_CR,          // channel
+            (int32_t)ycbcrLayout.chromaStep,  // colInc
+            (int32_t)ycbcrLayout.cStride,     // rowInc
+            2,                                // mColSampling
+            2,                                // mRowSampling
+            8,                                // allocatedDepth
+            8,                                // bitDepth
+            0,                                // rightShift
+            C2PlaneInfo::NATIVE,              // endianness
         };
     } else {
         void *pointer = nullptr;
         mMapper->lock(
                 const_cast<native_handle_t *>(mBuffer),
                 BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
-                { (int32_t)rect.mLeft, (int32_t)rect.mTop, (int32_t)rect.mWidth, (int32_t)rect.mHeight },
+                { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
                 // TODO: fence
                 hidl_handle(),
                 [&err, &pointer](const auto &maperr, const auto &mapPointer) {
@@ -315,17 +437,30 @@
         return err;
     }
 
-    allocation->reset(new C2AllocationGralloc(info, mMapper, buffer));
+
+    allocation->reset(new C2AllocationGralloc(
+            info, mMapper, buffer,
+            C2HandleGralloc::WrapNativeHandle(
+                    buffer.getNativeHandle(),
+                    info.width, info.height, (uint32_t)info.format, info.usage)));
     return C2_OK;
 }
 
 c2_status_t C2AllocatorGralloc::Impl::priorGraphicAllocation(
         const C2Handle *handle,
         std::shared_ptr<C2GraphicAllocation> *allocation) {
-    (void) handle;
+    IMapper::BufferDescriptorInfo info;
+    info.layerCount = 1u;
+    const C2HandleGralloc *grallocHandle = C2HandleGralloc::Import(
+            handle,
+            &info.width, &info.height, (uint32_t *)&info.format, (uint64_t *)&info.usage);
+    if (grallocHandle == nullptr) {
+        return C2_BAD_VALUE;
+    }
 
-    // TODO: need to figure out BufferDescriptorInfo from the handle.
-    allocation->reset();
+    hidl_handle hidlHandle = C2HandleGralloc::UnwrapNativeHandle(grallocHandle);
+
+    allocation->reset(new C2AllocationGralloc(info, mMapper, hidlHandle, grallocHandle));
     return C2_OMITTED;
 }
 
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp b/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
index acd69af..34c68bb 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
@@ -27,35 +27,67 @@
 
 namespace android {
 
+/* size_t <=> int(lo), int(hi) conversions */
+constexpr inline int size2intLo(size_t s) {
+    return int(s & 0xFFFFFFFF);
+}
+
+constexpr inline int size2intHi(size_t s) {
+    // cast to uint64_t as size_t may be 32 bits wide
+    return int((uint64_t(s) >> 32) & 0xFFFFFFFF);
+}
+
+constexpr inline size_t ints2size(int intLo, int intHi) {
+    // convert in 2 stages to 64 bits as intHi may be negative
+    return size_t(unsigned(intLo)) | size_t(uint64_t(unsigned(intHi)) << 32);
+}
+
 /* ========================================= ION HANDLE ======================================== */
+/**
+ * ION handle
+ *
+ * There can be only a sole ion client per process, this is captured in the ion fd that is passed
+ * to the constructor, but this should be managed by the ion buffer allocator/mapper.
+ *
+ * ion uses ion_user_handle_t for buffers. We don't store this in the native handle as
+ * it requires an ion_free to decref. Instead, we share the buffer to get an fd that also holds
+ * a refcount.
+ *
+ * This handle will not capture mapped fd-s as updating that would require a global mutex.
+ */
+
 struct C2HandleIon : public C2Handle {
-    C2HandleIon(int ionFd, ion_user_handle_t buffer) : C2Handle(cHeader),
-          mFds{ ionFd, buffer },
-          mInts{ kMagic } { }
+    // ion handle owns ionFd(!) and bufferFd
+    C2HandleIon(int bufferFd, size_t size)
+        : C2Handle(cHeader),
+          mFds{ bufferFd },
+          mInts{ int(size & 0xFFFFFFFF), int((uint64_t(size) >> 32) & 0xFFFFFFFF), kMagic } { }
 
     static bool isValid(const C2Handle * const o);
 
-    int ionFd() const { return mFds.mIon; }
-    ion_user_handle_t buffer() const { return mFds.mBuffer; }
-
-    void setBuffer(ion_user_handle_t bufferFd) { mFds.mBuffer = bufferFd; }
+    int bufferFd() const { return mFds.mBuffer; }
+    size_t size() const {
+        return size_t(unsigned(mInts.mSizeLo))
+                | size_t(uint64_t(unsigned(mInts.mSizeHi)) << 32);
+    }
 
 protected:
     struct {
-        int mIon;
-        int mBuffer; // ion_user_handle_t
+        int mBuffer; // shared ion buffer
     } mFds;
     struct {
+        int mSizeLo; // low 32-bits of size
+        int mSizeHi; // high 32-bits of size
         int mMagic;
     } mInts;
 
 private:
     typedef C2HandleIon _type;
     enum {
-        kMagic = 'ion1',
+        kMagic = '\xc2io\x00',
         numFds = sizeof(mFds) / sizeof(int),
         numInts = sizeof(mInts) / sizeof(int),
-        version = sizeof(C2Handle) + sizeof(mFds) + sizeof(mInts)
+        version = sizeof(C2Handle)
     };
     //constexpr static C2Handle cHeader = { version, numFds, numInts, {} };
     const static C2Handle cHeader;
@@ -82,6 +114,7 @@
 /* ======================================= ION ALLOCATION ====================================== */
 class C2AllocationIon : public C2LinearAllocation {
 public:
+    /* Interface methods */
     virtual c2_status_t map(
         size_t offset, size_t size, C2MemoryUsage usage, int *fence,
         void **addr /* nonnull */) override;
@@ -94,63 +127,114 @@
     // internal methods
     C2AllocationIon(int ionFd, size_t size, size_t align, unsigned heapMask, unsigned flags);
     C2AllocationIon(int ionFd, size_t size, int shareFd);
-    int dup() const;
+
     c2_status_t status() const;
 
 protected:
     class Impl;
     Impl *mImpl;
+
+    // TODO: we could make this encapsulate shared_ptr and copiable
+    C2_DO_NOT_COPY(C2AllocationIon);
 };
 
 class C2AllocationIon::Impl {
-public:
-    // NOTE: using constructor here instead of a factory method as we will need the
-    // error value and this simplifies the error handling by the wrapper.
-    Impl(int ionFd, size_t capacity, size_t align, unsigned heapMask, unsigned flags)
-        : mInit(C2_OK),
-          mHandle(ionFd, -1),
+private:
+    /**
+     * Constructs an ion allocation.
+     *
+     * \note We always create an ion allocation, even if the allocation or import fails
+     * so that we can capture the error.
+     *
+     * \param ionFd     ion client (ownership transferred to created object)
+     * \param capacity  size of allocation
+     * \param bufferFd  buffer handle (ownership transferred to created object). Must be
+     *                  invalid if err is not 0.
+     * \param buffer    ion buffer user handle (ownership transferred to created object). Must be
+     *                  invalid if err is not 0.
+     * \param err       errno during buffer allocation or import
+     */
+    Impl(int ionFd, size_t capacity, int bufferFd, ion_user_handle_t buffer, int err)
+        : mIonFd(ionFd),
+          mHandle(bufferFd, capacity),
+          mBuffer(buffer),
+          mInit(c2_map_errno<ENOMEM, EACCES, EINVAL>(err)),
           mMapFd(-1),
-          mCapacity(capacity) {
-        ion_user_handle_t buffer = -1;
-        int ret = ion_alloc(mHandle.ionFd(), mCapacity, align, heapMask, flags, &buffer);
-        if (ret == 0) {
-            mHandle.setBuffer(buffer);
-        } else {
-            mInit = c2_map_errno<ENOMEM, EACCES, EINVAL>(-ret);
+          mMapSize(0) {
+        if (mInit != C2_OK) {
+            // close ionFd now on error
+            if (mIonFd >= 0) {
+                close(mIonFd);
+                mIonFd = -1;
+            }
+            // C2_CHECK(bufferFd < 0);
+            // C2_CHECK(buffer < 0);
         }
     }
 
-    Impl(int ionFd, size_t capacity, int shareFd)
-        : mInit(C2_OK),
-          mHandle(ionFd, -1),
-          mMapFd(-1),
-          mCapacity(capacity) {
-        ion_user_handle_t buffer;
-        int ret = ion_import(mHandle.ionFd(), shareFd, &buffer);
-        switch (-ret) {
-        case 0:
-            mHandle.setBuffer(buffer);
-            break;
-        case EBADF: // bad ion handle - should not happen
-        case ENOTTY: // bad ion driver
-            mInit = C2_CORRUPTED;
-            break;
-        default:
-            mInit = c2_map_errno<ENOMEM, EACCES, EINVAL>(-ret);
-            break;
+public:
+    /**
+     * Constructs an ion allocation by importing a shared buffer fd.
+     *
+     * \param ionFd     ion client (ownership transferred to created object)
+     * \param capacity  size of allocation
+     * \param bufferFd  buffer handle (ownership transferred to created object)
+     *
+     * \return created ion allocation (implementation) which may be invalid if the
+     * import failed.
+     */
+    static Impl *Import(int ionFd, size_t capacity, int bufferFd) {
+        ion_user_handle_t buffer = -1;
+        int ret = ion_import(ionFd, bufferFd, &buffer);
+        return new Impl(ionFd, capacity, bufferFd, buffer, ret);
+    }
+
+    /**
+     * Constructs an ion allocation by allocating an ion buffer.
+     *
+     * \param ionFd     ion client (ownership transferred to created object)
+     * \param size      size of allocation
+     * \param align     desired alignment of allocation
+     * \param heapMask  mask of heaps considered
+     * \param flags     ion allocation flags
+     *
+     * \return created ion allocation (implementation) which may be invalid if the
+     * allocation failed.
+     */
+    static Impl *Alloc(int ionFd, size_t size, size_t align, unsigned heapMask, unsigned flags) {
+        int bufferFd = -1;
+        ion_user_handle_t buffer = -1;
+        int ret = ion_alloc(ionFd, size, align, heapMask, flags, &buffer);
+        if (ret == 0) {
+            // get buffer fd for native handle constructor
+            ret = ion_share(ionFd, buffer, &bufferFd);
+            if (ret != 0) {
+                ion_free(ionFd, buffer);
+                buffer = -1;
+            }
         }
-        (void)mCapacity; // TODO
+        return new Impl(ionFd, size, bufferFd, buffer, ret);
     }
 
     c2_status_t map(size_t offset, size_t size, C2MemoryUsage usage, int *fenceFd, void **addr) {
         (void)fenceFd; // TODO: wait for fence
         *addr = nullptr;
+        if (mMapSize > 0) {
+            // TODO: technically we should return DUPLICATE here, but our block views don't
+            // actually unmap, so we end up remapping an ion buffer multiple times.
+            //
+            // return C2_DUPLICATE;
+        }
+        if (size == 0) {
+            return C2_BAD_VALUE;
+        }
+
         int prot = PROT_NONE;
         int flags = MAP_PRIVATE;
-        if (usage.mConsumer & GRALLOC_USAGE_SW_READ_MASK) {
+        if (usage.consumer & C2MemoryUsage::CPU_READ) {
             prot |= PROT_READ;
         }
-        if (usage.mProducer & GRALLOC_USAGE_SW_WRITE_MASK) {
+        if (usage.producer & C2MemoryUsage::CPU_WRITE) {
             prot |= PROT_WRITE;
             flags = MAP_SHARED;
         }
@@ -161,7 +245,7 @@
 
         c2_status_t err = C2_OK;
         if (mMapFd == -1) {
-            int ret = ion_map(mHandle.ionFd(), mHandle.buffer(), mapSize, prot,
+            int ret = ion_map(mIonFd, mBuffer, mapSize, prot,
                               flags, mapOffset, (unsigned char**)&mMapAddr, &mMapFd);
             if (ret) {
                 mMapFd = -1;
@@ -187,6 +271,9 @@
     }
 
     c2_status_t unmap(void *addr, size_t size, int *fenceFd) {
+        if (mMapFd < 0 || mMapSize == 0) {
+            return C2_NOT_FOUND;
+        }
         if (addr != (uint8_t *)mMapAddr + mMapAlignmentBytes ||
                 size + mMapAlignmentBytes != mMapSize) {
             return C2_BAD_VALUE;
@@ -196,44 +283,43 @@
             return c2_map_errno<EINVAL>(errno);
         }
         if (fenceFd) {
-            *fenceFd = -1;
+            *fenceFd = -1; // not using fences
         }
+        mMapSize = 0;
         return C2_OK;
     }
 
     ~Impl() {
-        if (mMapFd != -1) {
+        if (mMapFd >= 0) {
             close(mMapFd);
             mMapFd = -1;
         }
-
-        (void)ion_free(mHandle.ionFd(), mHandle.buffer());
+        if (mInit == C2_OK) {
+            (void)ion_free(mIonFd, mBuffer);
+        }
+        if (mIonFd >= 0) {
+            close(mIonFd);
+        }
+        native_handle_close(&mHandle);
     }
 
     c2_status_t status() const {
         return mInit;
     }
 
-    const C2Handle * handle() const {
+    const C2Handle *handle() const {
         return &mHandle;
     }
 
-    int dup() const {
-        int fd = -1;
-        if (mInit != 0 || ion_share(mHandle.ionFd(), mHandle.buffer(), &fd) != 0) {
-            fd = -1;
-        }
-        return fd;
-    }
-
 private:
-    c2_status_t mInit;
+    int mIonFd;
     C2HandleIon mHandle;
+    ion_user_handle_t mBuffer;
+    c2_status_t mInit;
     int mMapFd; // only one for now
     void *mMapAddr;
     size_t mMapAlignmentBytes;
     size_t mMapSize;
-    size_t mCapacity;
 };
 
 c2_status_t C2AllocationIon::map(
@@ -268,15 +354,11 @@
 
 C2AllocationIon::C2AllocationIon(int ionFd, size_t size, size_t align, unsigned heapMask, unsigned flags)
     : C2LinearAllocation(size),
-      mImpl(new Impl(ionFd, size, align, heapMask, flags)) { }
+      mImpl(Impl::Alloc(ionFd, size, align, heapMask, flags)) { }
 
 C2AllocationIon::C2AllocationIon(int ionFd, size_t size, int shareFd)
     : C2LinearAllocation(size),
-      mImpl(new Impl(ionFd, size, shareFd)) { }
-
-int C2AllocationIon::dup() const {
-    return mImpl->dup();
-}
+      mImpl(Impl::Import(ionFd, size, shareFd)) { }
 
 /* ======================================= ION ALLOCATOR ====================================== */
 C2AllocatorIon::C2AllocatorIon() : mInit(C2_OK), mIonFd(ion_open()) {
@@ -328,7 +410,7 @@
 #endif
 
     std::shared_ptr<C2AllocationIon> alloc
-        = std::make_shared<C2AllocationIon>(mIonFd, capacity, align, heapMask, flags);
+        = std::make_shared<C2AllocationIon>(dup(mIonFd), capacity, align, heapMask, flags);
     c2_status_t ret = alloc->status();
     if (ret == C2_OK) {
         *allocation = alloc;
@@ -350,7 +432,7 @@
     // TODO: get capacity and validate it
     const C2HandleIon *h = static_cast<const C2HandleIon*>(handle);
     std::shared_ptr<C2AllocationIon> alloc
-        = std::make_shared<C2AllocationIon>(mIonFd, 0 /* capacity */, h->buffer());
+        = std::make_shared<C2AllocationIon>(dup(mIonFd), h->size(), h->bufferFd());
     c2_status_t ret = alloc->status();
     if (ret == C2_OK) {
         *allocation = alloc;
diff --git a/media/libstagefright/codec2/vndk/C2Buffer.cpp b/media/libstagefright/codec2/vndk/C2Buffer.cpp
index d9bde7a..65a271e 100644
--- a/media/libstagefright/codec2/vndk/C2Buffer.cpp
+++ b/media/libstagefright/codec2/vndk/C2Buffer.cpp
@@ -227,7 +227,7 @@
         if (mBase == nullptr) {
             void *base = nullptr;
             mError = mAllocation->map(
-                    offset, size, { C2MemoryUsage::kSoftwareRead, 0 }, nullptr, &base);
+                    offset, size, { C2MemoryUsage::CPU_READ, 0 }, nullptr, &base);
             // TODO: fence
             if (mError == C2_OK) {
                 mBase = (uint8_t *)base;
@@ -291,7 +291,7 @@
             mError = mAllocation->map(
                     0u,
                     capacity,
-                    { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+                    { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
                     nullptr,
                     &base);
             if (mError == C2_OK) {
@@ -390,24 +390,24 @@
 
 class C2GraphicView::Impl {
 public:
-    Impl(uint8_t *const *data, const C2PlaneLayout &layout)
+    Impl(uint8_t *const *data, const C2PlanarLayout &layout)
         : mData(data), mLayout(layout), mError(C2_OK) {}
     explicit Impl(c2_status_t error) : mData(nullptr), mError(error) {}
 
     uint8_t *const *data() const { return mData; }
-    const C2PlaneLayout &layout() const { return mLayout; }
+    const C2PlanarLayout &layout() const { return mLayout; }
     c2_status_t error() const { return mError; }
 
 private:
     uint8_t *const *mData;
-    C2PlaneLayout mLayout;
+    C2PlanarLayout mLayout;
     c2_status_t mError;
 };
 
 C2GraphicView::C2GraphicView(
         const _C2PlanarCapacityAspect *parent,
         uint8_t *const *data,
-        const C2PlaneLayout& layout)
+        const C2PlanarLayout& layout)
     : _C2PlanarSection(parent), mImpl(new Impl(data, layout)) {}
 
 C2GraphicView::C2GraphicView(c2_status_t error)
@@ -421,7 +421,7 @@
     return mImpl->data();
 }
 
-const C2PlaneLayout C2GraphicView::layout() const {
+const C2PlanarLayout C2GraphicView::layout() const {
     return mImpl->layout();
 }
 
@@ -460,7 +460,7 @@
         }
         c2_status_t err = mAllocation->map(
                 rect,
-                { C2MemoryUsage::kSoftwareRead, 0 },
+                { C2MemoryUsage::CPU_READ, 0 },
                 nullptr,
                 &mLayout,
                 mData);
@@ -480,12 +480,12 @@
         return mData[0] == nullptr ? nullptr : &mData[0];
     }
 
-    const C2PlaneLayout &layout() const { return mLayout; }
+    const C2PlanarLayout &layout() const { return mLayout; }
 
 private:
     std::shared_ptr<C2GraphicAllocation> mAllocation;
-    C2PlaneLayout mLayout;
-    uint8_t *mData[C2PlaneLayout::MAX_NUM_PLANES];
+    C2PlanarLayout mLayout;
+    uint8_t *mData[C2PlanarLayout::MAX_NUM_PLANES];
 };
 
 C2ConstGraphicBlock::C2ConstGraphicBlock(
@@ -523,10 +523,10 @@
             // Already mapped.
             return C2_OK;
         }
-        uint8_t *data[C2PlaneLayout::MAX_NUM_PLANES];
+        uint8_t *data[C2PlanarLayout::MAX_NUM_PLANES];
         c2_status_t err = mAllocation->map(
                 rect,
-                { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+                { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
                 nullptr,
                 &mLayout,
                 data);
@@ -548,12 +548,12 @@
         return mData[0] == nullptr ? nullptr : mData;
     }
 
-    const C2PlaneLayout &layout() const { return mLayout; }
+    const C2PlanarLayout &layout() const { return mLayout; }
 
 private:
     std::shared_ptr<C2GraphicAllocation> mAllocation;
-    C2PlaneLayout mLayout;
-    uint8_t *mData[C2PlaneLayout::MAX_NUM_PLANES];
+    C2PlanarLayout mLayout;
+    uint8_t *mData[C2PlanarLayout::MAX_NUM_PLANES];
 };
 
 C2GraphicBlock::C2GraphicBlock(const std::shared_ptr<C2GraphicAllocation> &alloc)
@@ -708,7 +708,7 @@
 private:
     C2Buffer * const mThis;
     C2DefaultBufferData mData;
-    std::map<uint32_t, std::shared_ptr<C2Info>> mInfos;
+    std::map<C2Param::Type, std::shared_ptr<C2Info>> mInfos;
     std::list<std::pair<OnDestroyNotify, void *>> mNotify;
 };
 
diff --git a/media/libstagefright/codec2/vndk/C2Store.cpp b/media/libstagefright/codec2/vndk/C2Store.cpp
index 204f895..eb72d17 100644
--- a/media/libstagefright/codec2/vndk/C2Store.cpp
+++ b/media/libstagefright/codec2/vndk/C2Store.cpp
@@ -404,6 +404,7 @@
 C2PlatformComponentStore::C2PlatformComponentStore() {
     // TODO: move this also into a .so so it can be updated
     mComponents.emplace("c2.google.avc.decoder", "libstagefright_soft_c2avcdec.so");
+    mComponents.emplace("c2.google.aac.decoder", "libstagefright_soft_c2aacdec.so");
 }
 
 c2_status_t C2PlatformComponentStore::copyBuffer(
diff --git a/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h b/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
index 374b0ed..5311747 100644
--- a/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
+++ b/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
@@ -24,12 +24,18 @@
 
 namespace android {
 
+/**
+ * Unwrap the native handle from a Codec2 handle allocated by C2AllocatorGralloc.
+ *
+ * @param handle a handle allocated by C2AllocatorGralloc. This includes handles returned for a
+ * graphic block allocation handle returned.
+ *
+ * @return a new NON-OWNING native handle that must be deleted using native_handle_delete.
+ */
+native_handle_t*UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle);
+
 class C2AllocatorGralloc : public C2Allocator {
 public:
-    // (usage, capacity) => (align, heapMask, flags)
-    typedef std::function<int (C2MemoryUsage, size_t,
-                      /* => */ size_t*, unsigned*, unsigned*)> usage_mapper_fn;
-
     virtual id_t getId() const override;
 
     virtual C2String getName() const override;
diff --git a/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h b/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
index 5ce6071..81c5495 100644
--- a/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
+++ b/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
@@ -278,9 +278,9 @@
 C2_HIDE
 void addC2Params(std::list<const C2FieldDescriptor> &fields, _C2Tuple<T, Params...> *)
 {
-    //C2Param::index_t index = T::coreIndex;
+    //C2Param::CodeIndex index = T::CORE_INDEX;
     //(void)index;
-    fields.insert(fields.end(), T::fieldList);
+    fields.insert(fields.end(), T::FIELD_LIST);
     addC2Params(fields, (_C2Tuple<Params...> *)nullptr);
 }
 
diff --git a/media/libstagefright/codecs/aacdec/Android.bp b/media/libstagefright/codecs/aacdec/Android.bp
index 21c00a1..f1ff11b 100644
--- a/media/libstagefright/codecs/aacdec/Android.bp
+++ b/media/libstagefright/codecs/aacdec/Android.bp
@@ -1,4 +1,45 @@
 cc_library_shared {
+    name: "libstagefright_soft_c2aacdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: [
+        "C2SoftAac.cpp",
+        "DrcPresModeWrap.cpp",
+    ],
+
+    cflags: ["-Werror"],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    static_libs: [
+        "libFraunhoferAAC",
+        "libstagefright_codec2_vndk"
+    ],
+
+    shared_libs: [
+        "libcutils",
+        "libion",
+        "liblog",
+        "libstagefright_codec2",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+        "libutils",
+    ],
+}
+
+cc_library_shared {
     name: "libstagefright_soft_aacdec",
     vendor_available: true,
     vndk: {
@@ -17,6 +58,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
new file mode 100644
index 0000000..390f36c
--- /dev/null
+++ b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
@@ -0,0 +1,710 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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 "C2SoftAac"
+#include <utils/Log.h>
+
+#include "C2SoftAac.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <cutils/properties.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaErrors.h>
+#include <utils/misc.h>
+
+#include <inttypes.h>
+#include <math.h>
+#include <numeric>
+
+#define FILEREAD_MAX_LAYERS 2
+
+#define DRC_DEFAULT_MOBILE_REF_LEVEL 64  /* 64*-0.25dB = -16 dB below full scale for mobile conf */
+#define DRC_DEFAULT_MOBILE_DRC_CUT   127 /* maximum compression of dynamic range for mobile conf */
+#define DRC_DEFAULT_MOBILE_DRC_BOOST 127 /* maximum compression of dynamic range for mobile conf */
+#define DRC_DEFAULT_MOBILE_DRC_HEAVY 1   /* switch for heavy compression for mobile conf */
+#define DRC_DEFAULT_MOBILE_ENC_LEVEL (-1) /* encoder target level; -1 => the value is unknown, otherwise dB step value (e.g. 64 for -16 dB) */
+#define MAX_CHANNEL_COUNT            8  /* maximum number of audio channels that can be decoded */
+// names of properties that can be used to override the default DRC settings
+#define PROP_DRC_OVERRIDE_REF_LEVEL  "aac_drc_reference_level"
+#define PROP_DRC_OVERRIDE_CUT        "aac_drc_cut"
+#define PROP_DRC_OVERRIDE_BOOST      "aac_drc_boost"
+#define PROP_DRC_OVERRIDE_HEAVY      "aac_drc_heavy"
+#define PROP_DRC_OVERRIDE_ENC_LEVEL "aac_drc_enc_target_level"
+
+namespace android {
+
+C2SoftAac::C2SoftAac(const char *name, c2_node_id_t id)
+    : SimpleC2Component(
+            SimpleC2Interface::Builder(name, id)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatAudio)
+            .build()),
+      mAACDecoder(NULL),
+      mStreamInfo(NULL),
+      mIsADTS(false),
+      mSignalledError(false),
+      mOutputDelayRingBuffer(NULL) {
+}
+
+C2SoftAac::~C2SoftAac() {
+    onRelease();
+}
+
+c2_status_t C2SoftAac::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftAac::onStop() {
+    drainDecoder();
+    // reset the "configured" state
+    mOutputDelayCompensated = 0;
+    mOutputDelayRingBufferWritePos = 0;
+    mOutputDelayRingBufferReadPos = 0;
+    mOutputDelayRingBufferFilled = 0;
+    mBuffersInfo.clear();
+
+    // To make the codec behave the same before and after a reset, we need to invalidate the
+    // streaminfo struct. This does that:
+    mStreamInfo->sampleRate = 0; // TODO: mStreamInfo is read only
+
+    mSignalledError = false;
+
+    return C2_OK;
+}
+
+void C2SoftAac::onReset() {
+    (void)onStop();
+}
+
+void C2SoftAac::onRelease() {
+    if (mAACDecoder) {
+        aacDecoder_Close(mAACDecoder);
+        mAACDecoder = NULL;
+    }
+    if (mOutputDelayRingBuffer) {
+        delete[] mOutputDelayRingBuffer;
+        mOutputDelayRingBuffer = NULL;
+    }
+}
+
+status_t C2SoftAac::initDecoder() {
+    ALOGV("initDecoder()");
+    status_t status = UNKNOWN_ERROR;
+    mAACDecoder = aacDecoder_Open(TT_MP4_ADIF, /* num layers */ 1);
+    if (mAACDecoder != NULL) {
+        mStreamInfo = aacDecoder_GetStreamInfo(mAACDecoder);
+        if (mStreamInfo != NULL) {
+            status = OK;
+        }
+    }
+
+    mOutputDelayCompensated = 0;
+    mOutputDelayRingBufferSize = 2048 * MAX_CHANNEL_COUNT * kNumDelayBlocksMax;
+    mOutputDelayRingBuffer = new short[mOutputDelayRingBufferSize];
+    mOutputDelayRingBufferWritePos = 0;
+    mOutputDelayRingBufferReadPos = 0;
+    mOutputDelayRingBufferFilled = 0;
+
+    if (mAACDecoder == NULL) {
+        ALOGE("AAC decoder is null. TODO: Can not call aacDecoder_SetParam in the following code");
+    }
+
+    //aacDecoder_SetParam(mAACDecoder, AAC_PCM_LIMITER_ENABLE, 0);
+
+    //init DRC wrapper
+    mDrcWrap.setDecoderHandle(mAACDecoder);
+    mDrcWrap.submitStreamData(mStreamInfo);
+
+    // for streams that contain metadata, use the mobile profile DRC settings unless overridden by platform properties
+    // TODO: change the DRC settings depending on audio output device type (HDMI, loadspeaker, headphone)
+    char value[PROPERTY_VALUE_MAX];
+    //  DRC_PRES_MODE_WRAP_DESIRED_TARGET
+    if (property_get(PROP_DRC_OVERRIDE_REF_LEVEL, value, NULL)) {
+        unsigned refLevel = atoi(value);
+        ALOGV("AAC decoder using desired DRC target reference level of %d instead of %d", refLevel,
+                DRC_DEFAULT_MOBILE_REF_LEVEL);
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_TARGET, refLevel);
+    } else {
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_TARGET, DRC_DEFAULT_MOBILE_REF_LEVEL);
+    }
+    //  DRC_PRES_MODE_WRAP_DESIRED_ATT_FACTOR
+    if (property_get(PROP_DRC_OVERRIDE_CUT, value, NULL)) {
+        unsigned cut = atoi(value);
+        ALOGV("AAC decoder using desired DRC attenuation factor of %d instead of %d", cut,
+                DRC_DEFAULT_MOBILE_DRC_CUT);
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_ATT_FACTOR, cut);
+    } else {
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_ATT_FACTOR, DRC_DEFAULT_MOBILE_DRC_CUT);
+    }
+    //  DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR
+    if (property_get(PROP_DRC_OVERRIDE_BOOST, value, NULL)) {
+        unsigned boost = atoi(value);
+        ALOGV("AAC decoder using desired DRC boost factor of %d instead of %d", boost,
+                DRC_DEFAULT_MOBILE_DRC_BOOST);
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR, boost);
+    } else {
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR, DRC_DEFAULT_MOBILE_DRC_BOOST);
+    }
+    //  DRC_PRES_MODE_WRAP_DESIRED_HEAVY
+    if (property_get(PROP_DRC_OVERRIDE_HEAVY, value, NULL)) {
+        unsigned heavy = atoi(value);
+        ALOGV("AAC decoder using desried DRC heavy compression switch of %d instead of %d", heavy,
+                DRC_DEFAULT_MOBILE_DRC_HEAVY);
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_HEAVY, heavy);
+    } else {
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_HEAVY, DRC_DEFAULT_MOBILE_DRC_HEAVY);
+    }
+    // DRC_PRES_MODE_WRAP_ENCODER_TARGET
+    if (property_get(PROP_DRC_OVERRIDE_ENC_LEVEL, value, NULL)) {
+        unsigned encoderRefLevel = atoi(value);
+        ALOGV("AAC decoder using encoder-side DRC reference level of %d instead of %d",
+                encoderRefLevel, DRC_DEFAULT_MOBILE_ENC_LEVEL);
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_ENCODER_TARGET, encoderRefLevel);
+    } else {
+        mDrcWrap.setParam(DRC_PRES_MODE_WRAP_ENCODER_TARGET, DRC_DEFAULT_MOBILE_ENC_LEVEL);
+    }
+
+    // By default, the decoder creates a 5.1 channel downmix signal.
+    // For seven and eight channel input streams, enable 6.1 and 7.1 channel output
+    aacDecoder_SetParam(mAACDecoder, AAC_PCM_MAX_OUTPUT_CHANNELS, -1);
+
+    return status;
+}
+
+bool C2SoftAac::outputDelayRingBufferPutSamples(INT_PCM *samples, int32_t numSamples) {
+    if (numSamples == 0) {
+        return true;
+    }
+    if (outputDelayRingBufferSpaceLeft() < numSamples) {
+        ALOGE("RING BUFFER WOULD OVERFLOW");
+        return false;
+    }
+    if (mOutputDelayRingBufferWritePos + numSamples <= mOutputDelayRingBufferSize
+            && (mOutputDelayRingBufferReadPos <= mOutputDelayRingBufferWritePos
+                    || mOutputDelayRingBufferReadPos > mOutputDelayRingBufferWritePos + numSamples)) {
+        // faster memcopy loop without checks, if the preconditions allow this
+        for (int32_t i = 0; i < numSamples; i++) {
+            mOutputDelayRingBuffer[mOutputDelayRingBufferWritePos++] = samples[i];
+        }
+
+        if (mOutputDelayRingBufferWritePos >= mOutputDelayRingBufferSize) {
+            mOutputDelayRingBufferWritePos -= mOutputDelayRingBufferSize;
+        }
+    } else {
+        ALOGV("slow C2SoftAac::outputDelayRingBufferPutSamples()");
+
+        for (int32_t i = 0; i < numSamples; i++) {
+            mOutputDelayRingBuffer[mOutputDelayRingBufferWritePos] = samples[i];
+            mOutputDelayRingBufferWritePos++;
+            if (mOutputDelayRingBufferWritePos >= mOutputDelayRingBufferSize) {
+                mOutputDelayRingBufferWritePos -= mOutputDelayRingBufferSize;
+            }
+        }
+    }
+    mOutputDelayRingBufferFilled += numSamples;
+    return true;
+}
+
+int32_t C2SoftAac::outputDelayRingBufferGetSamples(INT_PCM *samples, int32_t numSamples) {
+
+    if (numSamples > mOutputDelayRingBufferFilled) {
+        ALOGE("RING BUFFER WOULD UNDERRUN");
+        return -1;
+    }
+
+    if (mOutputDelayRingBufferReadPos + numSamples <= mOutputDelayRingBufferSize
+            && (mOutputDelayRingBufferWritePos < mOutputDelayRingBufferReadPos
+                    || mOutputDelayRingBufferWritePos >= mOutputDelayRingBufferReadPos + numSamples)) {
+        // faster memcopy loop without checks, if the preconditions allow this
+        if (samples != 0) {
+            for (int32_t i = 0; i < numSamples; i++) {
+                samples[i] = mOutputDelayRingBuffer[mOutputDelayRingBufferReadPos++];
+            }
+        } else {
+            mOutputDelayRingBufferReadPos += numSamples;
+        }
+        if (mOutputDelayRingBufferReadPos >= mOutputDelayRingBufferSize) {
+            mOutputDelayRingBufferReadPos -= mOutputDelayRingBufferSize;
+        }
+    } else {
+        ALOGV("slow C2SoftAac::outputDelayRingBufferGetSamples()");
+
+        for (int32_t i = 0; i < numSamples; i++) {
+            if (samples != 0) {
+                samples[i] = mOutputDelayRingBuffer[mOutputDelayRingBufferReadPos];
+            }
+            mOutputDelayRingBufferReadPos++;
+            if (mOutputDelayRingBufferReadPos >= mOutputDelayRingBufferSize) {
+                mOutputDelayRingBufferReadPos -= mOutputDelayRingBufferSize;
+            }
+        }
+    }
+    mOutputDelayRingBufferFilled -= numSamples;
+    return numSamples;
+}
+
+int32_t C2SoftAac::outputDelayRingBufferSamplesAvailable() {
+    return mOutputDelayRingBufferFilled;
+}
+
+int32_t C2SoftAac::outputDelayRingBufferSpaceLeft() {
+    return mOutputDelayRingBufferSize - outputDelayRingBufferSamplesAvailable();
+}
+
+void C2SoftAac::drainRingBuffer(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool,
+        bool eos) {
+    while (!mBuffersInfo.empty() && outputDelayRingBufferSamplesAvailable()
+            >= mStreamInfo->frameSize * mStreamInfo->numChannels) {
+        Info &outInfo = mBuffersInfo.front();
+        ALOGV("outInfo.frameIndex = %" PRIu64, outInfo.frameIndex);
+        int samplesize = mStreamInfo->numChannels * sizeof(int16_t);
+
+        int available = outputDelayRingBufferSamplesAvailable();
+        int numFrames = outInfo.decodedSizes.size();
+        int numSamples = numFrames * (mStreamInfo->frameSize * mStreamInfo->numChannels);
+        if (available < numSamples) {
+            if (eos) {
+                numSamples = available;
+            } else {
+                break;
+            }
+        }
+        ALOGV("%d samples available (%d), or %d frames",
+                numSamples, available, numFrames);
+        ALOGV("getting %d from ringbuffer", numSamples);
+
+        std::shared_ptr<C2LinearBlock> block;
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        // TODO: error handling, proper usage, etc.
+        c2_status_t err = pool->fetchLinearBlock(numSamples * sizeof(int16_t), usage, &block);
+        if (err != C2_OK) {
+            ALOGE("err = %d", err);
+        }
+
+        C2WriteView wView = block->map().get();
+        // TODO
+        INT_PCM *outBuffer = reinterpret_cast<INT_PCM *>(wView.data());
+        int32_t ns = outputDelayRingBufferGetSamples(outBuffer, numSamples);
+        if (ns != numSamples) {
+            ALOGE("not a complete frame of samples available");
+            mSignalledError = true;
+            // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
+            return;
+        }
+        auto fillWork = [buffer = createLinearBuffer(block)](const std::unique_ptr<C2Work> &work) {
+            work->worklets.front()->output.flags = work->input.flags;
+            work->worklets.front()->output.buffers.clear();
+            work->worklets.front()->output.buffers.push_back(buffer);
+            work->worklets.front()->output.ordinal = work->input.ordinal;
+            work->worklets_processed = 1u;
+        };
+        if (work && work->input.ordinal.frame_index == outInfo.frameIndex) {
+            fillWork(work);
+        } else {
+            finish(outInfo.frameIndex, fillWork);
+        }
+
+        ALOGV("out timestamp %" PRIu64 " / %u", outInfo.timestamp, block->capacity());
+        mBuffersInfo.pop_front();
+    }
+}
+
+void C2SoftAac::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->worklets_processed = 0u;
+    if (mSignalledError) {
+        return;
+    }
+
+    UCHAR* inBuffer[FILEREAD_MAX_LAYERS];
+    UINT inBufferLength[FILEREAD_MAX_LAYERS] = {0};
+    UINT bytesValid[FILEREAD_MAX_LAYERS] = {0};
+
+    INT_PCM tmpOutBuffer[2048 * MAX_CHANNEL_COUNT];
+    C2ReadView view = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    size_t offset = 0u;
+    size_t size = view.capacity();
+
+    bool eos = (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) != 0;
+    bool codecConfig = (work->input.flags & C2BufferPack::FLAG_CODEC_CONFIG) != 0;
+
+    //TODO
+#if 0
+    if (mInputBufferCount == 0 && !codecConfig) {
+        ALOGW("first buffer should have FLAG_CODEC_CONFIG set");
+        codecConfig = true;
+    }
+#endif
+    if (codecConfig) {
+        // const_cast because of libAACdec method signature.
+        inBuffer[0] = const_cast<UCHAR *>(view.data() + offset);
+        inBufferLength[0] = size;
+
+        AAC_DECODER_ERROR decoderErr =
+            aacDecoder_ConfigRaw(mAACDecoder,
+                                 inBuffer,
+                                 inBufferLength);
+
+        if (decoderErr != AAC_DEC_OK) {
+            ALOGE("aacDecoder_ConfigRaw decoderErr = 0x%4.4x", decoderErr);
+            mSignalledError = true;
+            // TODO: error
+            return;
+        }
+
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.buffers.push_back(nullptr);
+
+        return;
+    }
+
+    Info inInfo;
+    inInfo.frameIndex = work->input.ordinal.frame_index;
+    inInfo.timestamp = work->input.ordinal.timestamp;
+    inInfo.bufferSize = size;
+    inInfo.decodedSizes.clear();
+    while (size > 0u) {
+        ALOGV("size = %zu", size);
+        if (mIsADTS) {
+            size_t adtsHeaderSize = 0;
+            // skip 30 bits, aac_frame_length follows.
+            // ssssssss ssssiiip ppffffPc ccohCCll llllllll lll?????
+
+            const uint8_t *adtsHeader = view.data() + offset;
+
+            bool signalError = false;
+            if (size < 7) {
+                ALOGE("Audio data too short to contain even the ADTS header. "
+                        "Got %zu bytes.", size);
+                hexdump(adtsHeader, size);
+                signalError = true;
+            } else {
+                bool protectionAbsent = (adtsHeader[1] & 1);
+
+                unsigned aac_frame_length =
+                    ((adtsHeader[3] & 3) << 11)
+                    | (adtsHeader[4] << 3)
+                    | (adtsHeader[5] >> 5);
+
+                if (size < aac_frame_length) {
+                    ALOGE("Not enough audio data for the complete frame. "
+                            "Got %zu bytes, frame size according to the ADTS "
+                            "header is %u bytes.",
+                            size, aac_frame_length);
+                    hexdump(adtsHeader, size);
+                    signalError = true;
+                } else {
+                    adtsHeaderSize = (protectionAbsent ? 7 : 9);
+                    if (aac_frame_length < adtsHeaderSize) {
+                        signalError = true;
+                    } else {
+                        // const_cast because of libAACdec method signature.
+                        inBuffer[0] = const_cast<UCHAR *>(adtsHeader + adtsHeaderSize);
+                        inBufferLength[0] = aac_frame_length - adtsHeaderSize;
+
+                        offset += adtsHeaderSize;
+                        size -= adtsHeaderSize;
+                    }
+                }
+            }
+
+            if (signalError) {
+                mSignalledError = true;
+                // TODO: notify(OMX_EventError, OMX_ErrorStreamCorrupt, ERROR_MALFORMED, NULL);
+                return;
+            }
+        } else {
+            // const_cast because of libAACdec method signature.
+            inBuffer[0] = const_cast<UCHAR *>(view.data() + offset);
+            inBufferLength[0] = size;
+        }
+
+        // Fill and decode
+        bytesValid[0] = inBufferLength[0];
+
+        INT prevSampleRate = mStreamInfo->sampleRate;
+        INT prevNumChannels = mStreamInfo->numChannels;
+
+        aacDecoder_Fill(mAACDecoder,
+                        inBuffer,
+                        inBufferLength,
+                        bytesValid);
+
+        // run DRC check
+        mDrcWrap.submitStreamData(mStreamInfo);
+        mDrcWrap.update();
+
+        UINT inBufferUsedLength = inBufferLength[0] - bytesValid[0];
+        size -= inBufferUsedLength;
+        offset += inBufferUsedLength;
+
+        AAC_DECODER_ERROR decoderErr;
+        do {
+            if (outputDelayRingBufferSpaceLeft() <
+                    (mStreamInfo->frameSize * mStreamInfo->numChannels)) {
+                ALOGV("skipping decode: not enough space left in ringbuffer");
+                break;
+            }
+
+            int numConsumed = mStreamInfo->numTotalBytes;
+            decoderErr = aacDecoder_DecodeFrame(mAACDecoder,
+                                       tmpOutBuffer,
+                                       2048 * MAX_CHANNEL_COUNT,
+                                       0 /* flags */);
+
+            numConsumed = mStreamInfo->numTotalBytes - numConsumed;
+
+            if (decoderErr == AAC_DEC_NOT_ENOUGH_BITS) {
+                break;
+            }
+            inInfo.decodedSizes.push_back(numConsumed);
+
+            if (decoderErr != AAC_DEC_OK) {
+                ALOGW("aacDecoder_DecodeFrame decoderErr = 0x%4.4x", decoderErr);
+            }
+
+            if (bytesValid[0] != 0) {
+                ALOGE("bytesValid[0] != 0 should never happen");
+                mSignalledError = true;
+                // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
+                return;
+            }
+
+            size_t numOutBytes =
+                mStreamInfo->frameSize * sizeof(int16_t) * mStreamInfo->numChannels;
+
+            if (decoderErr == AAC_DEC_OK) {
+                if (!outputDelayRingBufferPutSamples(tmpOutBuffer,
+                        mStreamInfo->frameSize * mStreamInfo->numChannels)) {
+                    mSignalledError = true;
+                    // TODO: notify(OMX_EventError, OMX_ErrorUndefined, decoderErr, NULL);
+                    return;
+                }
+            } else {
+                ALOGW("AAC decoder returned error 0x%4.4x, substituting silence", decoderErr);
+
+                memset(tmpOutBuffer, 0, numOutBytes); // TODO: check for overflow
+
+                if (!outputDelayRingBufferPutSamples(tmpOutBuffer,
+                        mStreamInfo->frameSize * mStreamInfo->numChannels)) {
+                    mSignalledError = true;
+                    // TODO: notify(OMX_EventError, OMX_ErrorUndefined, decoderErr, NULL);
+                    return;
+                }
+
+                // Discard input buffer.
+                size = 0;
+
+                aacDecoder_SetParam(mAACDecoder, AAC_TPDEC_CLEAR_BUFFER, 1);
+
+                // After an error, replace bufferSize with the sum of the
+                // decodedSizes to resynchronize the in/out lists.
+                inInfo.decodedSizes.pop_back();
+                inInfo.bufferSize = std::accumulate(
+                        inInfo.decodedSizes.begin(), inInfo.decodedSizes.end(), 0);
+
+                // fall through
+            }
+
+            /*
+             * AAC+/eAAC+ streams can be signalled in two ways: either explicitly
+             * or implicitly, according to MPEG4 spec. AAC+/eAAC+ is a dual
+             * rate system and the sampling rate in the final output is actually
+             * doubled compared with the core AAC decoder sampling rate.
+             *
+             * Explicit signalling is done by explicitly defining SBR audio object
+             * type in the bitstream. Implicit signalling is done by embedding
+             * SBR content in AAC extension payload specific to SBR, and hence
+             * requires an AAC decoder to perform pre-checks on actual audio frames.
+             *
+             * Thus, we could not say for sure whether a stream is
+             * AAC+/eAAC+ until the first data frame is decoded.
+             */
+            if (!mStreamInfo->sampleRate || !mStreamInfo->numChannels) {
+                // TODO:
+#if 0
+                if ((mInputBufferCount > 2) && (mOutputBufferCount <= 1)) {
+                    ALOGW("Invalid AAC stream");
+                    mSignalledError = true;
+                    // TODO: notify(OMX_EventError, OMX_ErrorUndefined, decoderErr, NULL);
+                    return false;
+                }
+#endif
+            }
+            ALOGV("size = %zu", size);
+        } while (decoderErr == AAC_DEC_OK);
+    }
+
+    int32_t outputDelay = mStreamInfo->outputDelay * mStreamInfo->numChannels;
+
+    mBuffersInfo.push_back(std::move(inInfo));
+
+    if (!eos && mOutputDelayCompensated < outputDelay) {
+        // discard outputDelay at the beginning
+        int32_t toCompensate = outputDelay - mOutputDelayCompensated;
+        int32_t discard = outputDelayRingBufferSamplesAvailable();
+        if (discard > toCompensate) {
+            discard = toCompensate;
+        }
+        int32_t discarded = outputDelayRingBufferGetSamples(0, discard);
+        mOutputDelayCompensated += discarded;
+        return;
+    }
+
+    if (eos) {
+        drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
+    } else {
+        drainRingBuffer(work, pool, false /* not EOS */);
+    }
+}
+
+c2_status_t C2SoftAac::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;
+    }
+
+    bool eos = (drainMode == DRAIN_COMPONENT_WITH_EOS);
+
+    drainDecoder();
+    drainRingBuffer(work, pool, eos);
+
+    if (eos) {
+        auto 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.buffers.emplace_back(nullptr);
+            work->worklets.front()->output.ordinal = work->input.ordinal;
+            work->worklets_processed = 1u;
+        };
+        while (mBuffersInfo.size() > 1u) {
+            finish(mBuffersInfo.front().frameIndex, fillEmptyWork);
+            mBuffersInfo.pop_front();
+        }
+        if (work->worklets_processed == 0u) {
+            fillEmptyWork(work);
+        }
+        mBuffersInfo.clear();
+    }
+
+    return C2_OK;
+}
+
+c2_status_t C2SoftAac::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    return drainInternal(drainMode, pool, nullptr);
+}
+
+c2_status_t C2SoftAac::onFlush_sm() {
+    drainDecoder();
+    mBuffersInfo.clear();
+
+    int avail;
+    while ((avail = outputDelayRingBufferSamplesAvailable()) > 0) {
+        if (avail > mStreamInfo->frameSize * mStreamInfo->numChannels) {
+            avail = mStreamInfo->frameSize * mStreamInfo->numChannels;
+        }
+        int32_t ns = outputDelayRingBufferGetSamples(0, avail);
+        if (ns != avail) {
+            ALOGW("not a complete frame of samples available");
+            break;
+        }
+    }
+    mOutputDelayRingBufferReadPos = mOutputDelayRingBufferWritePos;
+
+    return C2_OK;
+}
+
+void C2SoftAac::drainDecoder() {
+    // flush decoder until outputDelay is compensated
+    while (mOutputDelayCompensated > 0) {
+        // a buffer big enough for MAX_CHANNEL_COUNT channels of decoded HE-AAC
+        INT_PCM tmpOutBuffer[2048 * MAX_CHANNEL_COUNT];
+
+        // run DRC check
+        mDrcWrap.submitStreamData(mStreamInfo);
+        mDrcWrap.update();
+
+        AAC_DECODER_ERROR decoderErr =
+            aacDecoder_DecodeFrame(mAACDecoder,
+                                   tmpOutBuffer,
+                                   2048 * MAX_CHANNEL_COUNT,
+                                   AACDEC_FLUSH);
+        if (decoderErr != AAC_DEC_OK) {
+            ALOGW("aacDecoder_DecodeFrame decoderErr = 0x%4.4x", decoderErr);
+        }
+
+        int32_t tmpOutBufferSamples = mStreamInfo->frameSize * mStreamInfo->numChannels;
+        if (tmpOutBufferSamples > mOutputDelayCompensated) {
+            tmpOutBufferSamples = mOutputDelayCompensated;
+        }
+        outputDelayRingBufferPutSamples(tmpOutBuffer, tmpOutBufferSamples);
+
+        mOutputDelayCompensated -= tmpOutBufferSamples;
+    }
+}
+
+class C2SoftAacDecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id, std::shared_ptr<C2Component>* const component,
+            std::function<void(::android::C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftAac("aac", id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(::android::C2ComponentInterface*)> deleter) override {
+        *interface =
+                SimpleC2Interface::Builder("aac", id, deleter)
+                .inputFormat(C2FormatCompressed)
+                .outputFormat(C2FormatVideo)
+                .build();
+        return C2_OK;
+    }
+
+    virtual ~C2SoftAacDecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::android::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftAacDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::android::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/aacdec/C2SoftAac.h b/media/libstagefright/codecs/aacdec/C2SoftAac.h
new file mode 100644
index 0000000..b877635
--- /dev/null
+++ b/media/libstagefright/codecs/aacdec/C2SoftAac.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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_AAC_H_
+#define C2_SOFT_AAC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+#include "aacdecoder_lib.h"
+#include "DrcPresModeWrap.h"
+
+namespace android {
+
+struct C2SoftAac : public SimpleC2Component {
+    C2SoftAac(const char *name, c2_node_id_t id);
+    virtual ~C2SoftAac();
+
+    // 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 {
+        kNumDelayBlocksMax      = 8,
+    };
+
+    HANDLE_AACDECODER mAACDecoder;
+    CStreamInfo *mStreamInfo;
+    bool mIsADTS;
+    bool mIsFirst;
+    size_t mInputBufferCount;
+    size_t mOutputBufferCount;
+    bool mSignalledError;
+    struct Info {
+        uint64_t frameIndex;
+        size_t bufferSize;
+        uint64_t timestamp;
+        std::vector<int32_t> decodedSizes;
+    };
+    std::list<Info> mBuffersInfo;
+
+    CDrcPresModeWrapper mDrcWrap;
+
+    enum {
+        NONE,
+        AWAITING_DISABLED,
+        AWAITING_ENABLED
+    } mOutputPortSettingsChange;
+
+    void initPorts();
+    status_t initDecoder();
+    bool isConfigured() const;
+    void drainDecoder();
+
+    void drainRingBuffer(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool,
+            bool eos);
+    c2_status_t drainInternal(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool,
+            const std::unique_ptr<C2Work> &work);
+
+//      delay compensation
+    bool mEndOfInput;
+    bool mEndOfOutput;
+    int32_t mOutputDelayCompensated;
+    int32_t mOutputDelayRingBufferSize;
+    short *mOutputDelayRingBuffer;
+    int32_t mOutputDelayRingBufferWritePos;
+    int32_t mOutputDelayRingBufferReadPos;
+    int32_t mOutputDelayRingBufferFilled;
+    bool outputDelayRingBufferPutSamples(INT_PCM *samples, int numSamples);
+    int32_t outputDelayRingBufferGetSamples(INT_PCM *samples, int numSamples);
+    int32_t outputDelayRingBufferSamplesAvailable();
+    int32_t outputDelayRingBufferSpaceLeft();
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftAac);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_AAC_H_
diff --git a/media/libstagefright/codecs/aacdec/exports.lds b/media/libstagefright/codecs/aacdec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/aacdec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/aacenc/Android.bp b/media/libstagefright/codecs/aacenc/Android.bp
index d734b9c..9342351 100644
--- a/media/libstagefright/codecs/aacenc/Android.bp
+++ b/media/libstagefright/codecs/aacenc/Android.bp
@@ -14,6 +14,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/aacenc/exports.lds b/media/libstagefright/codecs/aacenc/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/aacenc/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/amrnb/dec/Android.bp b/media/libstagefright/codecs/amrnb/dec/Android.bp
index b493e21..e4a2607 100644
--- a/media/libstagefright/codecs/amrnb/dec/Android.bp
+++ b/media/libstagefright/codecs/amrnb/dec/Android.bp
@@ -50,6 +50,8 @@
         "-Werror",
     ],
 
+    version_script: "exports.lds",
+
     //sanitize: {
     //    misc_undefined: [
     //        "signed-integer-overflow",
@@ -85,6 +87,8 @@
         "-Werror",
     ],
 
+    version_script: "exports.lds",
+
     //sanitize: {
     //    misc_undefined: [
     //        "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/amrnb/dec/exports.lds b/media/libstagefright/codecs/amrnb/dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/amrnb/enc/Android.bp b/media/libstagefright/codecs/amrnb/enc/Android.bp
index 1e8fd31..f844459 100644
--- a/media/libstagefright/codecs/amrnb/enc/Android.bp
+++ b/media/libstagefright/codecs/amrnb/enc/Android.bp
@@ -70,6 +70,8 @@
         "-Werror",
     ],
 
+    version_script: "exports.lds",
+
     //addressing b/25409744
     //sanitize: {
     //    misc_undefined: [
diff --git a/media/libstagefright/codecs/amrnb/enc/exports.lds b/media/libstagefright/codecs/amrnb/enc/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/enc/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/amrwbenc/Android.bp b/media/libstagefright/codecs/amrwbenc/Android.bp
index b6f637f..ebe08c6 100644
--- a/media/libstagefright/codecs/amrwbenc/Android.bp
+++ b/media/libstagefright/codecs/amrwbenc/Android.bp
@@ -159,6 +159,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/amrwbenc/exports.lds b/media/libstagefright/codecs/amrwbenc/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/amrwbenc/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/avcdec/Android.bp b/media/libstagefright/codecs/avcdec/Android.bp
index 259fb25..d789096 100644
--- a/media/libstagefright/codecs/avcdec/Android.bp
+++ b/media/libstagefright/codecs/avcdec/Android.bp
@@ -13,6 +13,8 @@
         "-Werror",
     ],
 
+    version_script: "exports.lds",
+
     include_dirs: [
         "external/libavc/decoder",
         "external/libavc/common",
@@ -46,7 +48,6 @@
 
     static_libs: [
         "libavcdec",
-        "libstagefright_codec2_vndk",
     ],
     srcs: ["C2SoftAvcDec.cpp"],
 
@@ -58,21 +59,16 @@
     include_dirs: [
         "external/libavc/decoder",
         "external/libavc/common",
-        "frameworks/av/media/libstagefright/codec2/include",
-        "frameworks/av/media/libstagefright/codec2/vndk/include",
     ],
 
     shared_libs: [
-        "android.hardware.graphics.allocator@2.0",
-        "android.hardware.graphics.mapper@2.0",
-        "libhidlbase",
-        "libion",
         "liblog",
+        "libutils",
         "libmedia",
         "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
         "libstagefright_foundation",
         "libstagefright_simple_c2component",
-        "libutils",
     ],
 
     sanitize: {
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
index ac8e1de..ffe6332 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-//#define LOG_NDEBUG 0
+#define LOG_NDEBUG 0
 #define LOG_TAG "C2SoftAvcDec"
 #include <utils/Log.h>
 
@@ -29,6 +29,7 @@
 #include "C2SoftAvcDec.h"
 
 #include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
 
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/MediaDefs.h>
@@ -68,16 +69,11 @@
 
 #define IVDEXT_CMD_CTL_SET_NUM_CORES    \
         (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_SET_NUM_CORES
-
 namespace {
 
+#if 0
 using SupportedValuesWithFields = C2SoftAvcDecIntf::SupportedValuesWithFields;
 
-uint32_t restoreIndex(const C2Param *param) {
-    return (param->forStream() ? (0x02000000 | ((param->stream() << 17) & 0x01FE0000)) : 0)
-            | param->type();
-}
-
 struct ValidateParam {
     explicit ValidateParam(
             const std::map<C2ParamField, SupportedValuesWithFields> &supportedValues)
@@ -156,9 +152,9 @@
 
     std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
         T* param = (T*)c2param;
-        C2ParamField field(param, &T::mValue);
+        C2ParamField field(param, &T::value);
         const C2FieldSupportedValues &supportedValues = mSupportedValues.at(field).supported;
-        if (!validateField(supportedValues, param->mValue)) {
+        if (!validateField(supportedValues, param->value)) {
             return std::unique_ptr<C2SettingResult>(
                     new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
         }
@@ -174,15 +170,15 @@
 
     std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
         T* param = (T*)c2param;
-        C2ParamField field(param, &T::mWidth);
+        C2ParamField field(param, &T::width);
         const C2FieldSupportedValues &supportedWidth = mSupportedValues.at(field).supported;
-        if (!validateField(supportedWidth, param->mWidth)) {
+        if (!validateField(supportedWidth, param->width)) {
             return std::unique_ptr<C2SettingResult>(
                     new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
         }
-        field = C2ParamField(param, &T::mHeight);
+        field = C2ParamField(param, &T::height);
         const C2FieldSupportedValues &supportedHeight = mSupportedValues.at(field).supported;
-        if (!validateField(supportedHeight, param->mHeight)) {
+        if (!validateField(supportedHeight, param->height)) {
             return std::unique_ptr<C2SettingResult>(
                     new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
         }
@@ -196,7 +192,7 @@
 
     std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
         T* param = (T*)c2param;
-        if (strncmp(param->m.mValue, mExpected, param->flexCount()) != 0) {
+        if (strncmp(param->m.value, mExpected, param->flexCount()) != 0) {
             return std::unique_ptr<C2SettingResult>(
                     new C2SettingResult {C2SettingResult::BAD_VALUE, {C2ParamField(param, &T::m), nullptr}, {}});
         }
@@ -206,23 +202,31 @@
 private:
     const char *mExpected;
 };
+#endif
 
-class GraphicBuffer : public C2Buffer {
-public:
-    explicit GraphicBuffer(const std::shared_ptr<C2GraphicBlock> &block)
-        : C2Buffer({ block->share(C2Rect(block->width(), block->height()), ::android::C2Fence()) }) {}
-};
+void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    uint32_t flags = 0;
+    if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
+        flags |= C2BufferPack::FLAG_END_OF_STREAM;
+    }
+    work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.buffers.emplace_back(nullptr);
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->worklets_processed = 1u;
+}
 
 }  // namespace
 
+#if 0
 #define CASE(member) \
-    case decltype(component->member)::coreIndex: \
+    case decltype(component->member)::CORE_INDEX: \
         return std::unique_ptr<C2StructDescriptor>(new C2StructDescriptor( \
                 static_cast<decltype(component->member) *>(nullptr)))
 
 class C2SoftAvcDecIntf::ParamReflector : public C2ParamReflector {
 public:
-    virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::BaseIndex coreIndex) override {
+    virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::CoreIndex coreIndex) override {
         constexpr C2SoftAvcDecIntf *component = nullptr;
         switch (coreIndex.coreIndex()) {
         CASE(mDomainInfo);
@@ -233,7 +237,7 @@
         CASE(mMaxVideoSizeHint);
 
         // port mime configs are stored as unique_ptr.
-        case C2PortMimeConfig::coreIndex:
+        case C2PortMimeConfig::CORE_INDEX:
             return std::unique_ptr<C2StructDescriptor>(new C2StructDescriptor(
                     static_cast<C2PortMimeConfig *>(nullptr)));
         }
@@ -264,104 +268,104 @@
       mParamReflector(new ParamReflector) {
     ALOGV("in %s", __func__);
     mInputPortMime = C2PortMimeConfig::input::alloc_unique(strlen(CODEC_MIME_TYPE) + 1);
-    strcpy(mInputPortMime->m.mValue, CODEC_MIME_TYPE);
+    strcpy(mInputPortMime->m.value, CODEC_MIME_TYPE);
     mOutputPortMime = C2PortMimeConfig::output::alloc_unique(strlen(MEDIA_MIMETYPE_VIDEO_RAW) + 1);
-    strcpy(mOutputPortMime->m.mValue, MEDIA_MIMETYPE_VIDEO_RAW);
+    strcpy(mOutputPortMime->m.value, MEDIA_MIMETYPE_VIDEO_RAW);
 
-    mVideoSize.mWidth = 320;
-    mVideoSize.mHeight = 240;
-    mBlockSize.mWidth = 16;
-    mBlockSize.mHeight = 16;
-    mAlignment.mWidth = 2;
-    mAlignment.mHeight = 2;
+    mVideoSize.width = 320;
+    mVideoSize.height = 240;
+    mBlockSize.width = 16;
+    mBlockSize.height = 16;
+    mAlignment.width = 2;
+    mAlignment.height = 2;
 
-    mMaxVideoSizeHint.mWidth = H264_MAX_FRAME_WIDTH;
-    mMaxVideoSizeHint.mHeight = H264_MAX_FRAME_HEIGHT;
+    mMaxVideoSizeHint.width = H264_MAX_FRAME_WIDTH;
+    mMaxVideoSizeHint.height = H264_MAX_FRAME_HEIGHT;
 
     mOutputBlockPools = C2PortBlockPoolsTuning::output::alloc_unique({});
 
     auto insertParam = [&params = mParams] (C2Param *param) {
-        params[restoreIndex(param)] = param;
+        params[param->index()] = param;
     };
 
     auto markReadOnly = [&supported = mSupportedValues] (auto *param) {
         supported.emplace(
-                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::mValue),
+                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::value),
                 C2FieldSupportedValues(false /* flags */, {}));
     };
 
     auto markReadOnlyVideoSize = [&supported = mSupportedValues] (auto *param) {
         supported.emplace(
-                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::mWidth),
+                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::width),
                 C2FieldSupportedValues(false /* flags */, {}));
         supported.emplace(
-                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::mHeight),
+                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::height),
                 C2FieldSupportedValues(false /* flags */, {}));
     };
 
     insertParam(&mDomainInfo);
     markReadOnly(&mDomainInfo);
-    mFieldVerifiers[restoreIndex(&mDomainInfo)] =
+    mFieldVerifiers[mDomainInfo.index()] =
             ValidateSimpleParam<decltype(mDomainInfo)>(mSupportedValues);
 
     insertParam(mInputPortMime.get());
-    mFieldVerifiers[restoreIndex(mInputPortMime.get())] =
+    mFieldVerifiers[mInputPortMime->index()] =
             ValidateCString<std::remove_reference<decltype(*mInputPortMime)>::type>(CODEC_MIME_TYPE);
 
     insertParam(&mInputStreamCount);
     markReadOnly(&mInputStreamCount);
-    mFieldVerifiers[restoreIndex(&mInputStreamCount)] =
+    mFieldVerifiers[mInputStreamCount.index()] =
             ValidateSimpleParam<decltype(mInputStreamCount)>(mSupportedValues);
 
     insertParam(mOutputPortMime.get());
-    mFieldVerifiers[restoreIndex(mOutputPortMime.get())] =
+    mFieldVerifiers[mOutputPortMime->index()] =
             ValidateCString<std::remove_reference<decltype(*mOutputPortMime)>::type>(MEDIA_MIMETYPE_VIDEO_RAW);
 
     insertParam(&mOutputStreamCount);
     markReadOnly(&mOutputStreamCount);
-    mFieldVerifiers[restoreIndex(&mOutputStreamCount)] =
+    mFieldVerifiers[mOutputStreamCount.index()] =
             ValidateSimpleParam<decltype(mOutputStreamCount)>(mSupportedValues);
 
     insertParam(&mInputStreamFormat);
     markReadOnly(&mInputStreamFormat);
-    mFieldVerifiers[restoreIndex(&mInputStreamFormat)] =
+    mFieldVerifiers[mInputStreamFormat.index()] =
             ValidateSimpleParam<decltype(mInputStreamFormat)>(mSupportedValues);
 
     insertParam(&mOutputStreamFormat);
     markReadOnly(&mOutputStreamFormat);
-    mFieldVerifiers[restoreIndex(&mOutputStreamFormat)] =
+    mFieldVerifiers[mOutputStreamFormat.index()] =
             ValidateSimpleParam<decltype(mOutputStreamFormat)>(mSupportedValues);
 
     insertParam(&mVideoSize);
     markReadOnlyVideoSize(&mVideoSize);
-    mFieldVerifiers[restoreIndex(&mVideoSize)] =
+    mFieldVerifiers[mVideoSize.index()] =
             ValidateVideoSize<decltype(mVideoSize)>(mSupportedValues);
 
     insertParam(&mMaxVideoSizeHint);
     mSupportedValues.emplace(
-            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::mWidth),
-            C2FieldSupportedValues(H264_MIN_FRAME_WIDTH, H264_MAX_FRAME_WIDTH, mAlignment.mWidth));
+            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::width),
+            C2FieldSupportedValues(H264_MIN_FRAME_WIDTH, H264_MAX_FRAME_WIDTH, mAlignment.width));
     mSupportedValues.emplace(
-            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::mHeight),
-            C2FieldSupportedValues(H264_MIN_FRAME_HEIGHT, H264_MAX_FRAME_HEIGHT, mAlignment.mHeight));
-    mFieldVerifiers[restoreIndex(&mMaxVideoSizeHint)] =
+            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::height),
+            C2FieldSupportedValues(H264_MIN_FRAME_HEIGHT, H264_MAX_FRAME_HEIGHT, mAlignment.height));
+    mFieldVerifiers[mMaxVideoSizeHint.index()] =
             ValidateVideoSize<decltype(mMaxVideoSizeHint)>(mSupportedValues);
 
     insertParam(&mProfile);
     mSupportedValues.emplace(
-            C2ParamField(&mProfile, &C2AvcProfileInfo::mValue),
+            C2ParamField(&mProfile, &C2AvcProfileInfo::value),
             C2FieldSupportedValues(false /* flags */, {
                 kAvcProfileUnknown,
                 kAvcProfileBaseline,
                 kAvcProfileMain,
                 kAvcProfileHigh,
             }));
-    mFieldVerifiers[restoreIndex(&mProfile)] =
+    mFieldVerifiers[mProfile.index()] =
             ValidateSimpleParam<decltype(mProfile)>(mSupportedValues);
 
     insertParam(&mLevel);
     mSupportedValues.emplace(
-            C2ParamField(&mLevel, &C2AvcLevelInfo::mValue),
+            C2ParamField(&mLevel, &C2AvcLevelInfo::value),
             C2FieldSupportedValues(false /* flags */, {
                 kAvcLevelUnknown,
                 kAvcLevel10,
@@ -382,31 +386,31 @@
                 kAvcLevel51,
                 kAvcLevel52,
             }));
-    mFieldVerifiers[restoreIndex(&mLevel)] =
+    mFieldVerifiers[mLevel.index()] =
             ValidateSimpleParam<decltype(mLevel)>(mSupportedValues);
 
     insertParam(&mBlockSize);
     markReadOnlyVideoSize(&mBlockSize);
-    mFieldVerifiers[restoreIndex(&mBlockSize)] =
+    mFieldVerifiers[mBlockSize.index()] =
             ValidateVideoSize<decltype(mBlockSize)>(mSupportedValues);
 
     insertParam(&mAlignment);
     markReadOnlyVideoSize(&mAlignment);
-    mFieldVerifiers[restoreIndex(&mAlignment)] =
+    mFieldVerifiers[mAlignment.index()] =
             ValidateVideoSize<decltype(mAlignment)>(mSupportedValues);
 
     insertParam(&mFrameRate);
     mSupportedValues.emplace(
-            C2ParamField(&mFrameRate, &C2FrameRateInfo::mValue),
+            C2ParamField(&mFrameRate, &C2FrameRateInfo::value),
             C2FieldSupportedValues(0, 240));
-    mFieldVerifiers[restoreIndex(&mFrameRate)] =
+    mFieldVerifiers[mFrameRate.index()] =
             ValidateSimpleParam<decltype(mFrameRate)>(mSupportedValues);
 
     insertParam(&mBlocksPerSecond);
     mSupportedValues.emplace(
-            C2ParamField(&mFrameRate, &C2BlocksPerSecondInfo::mValue),
+            C2ParamField(&mFrameRate, &C2BlocksPerSecondInfo::value),
             C2FieldSupportedValues(0, 244800));
-    mFieldVerifiers[restoreIndex(&mBlocksPerSecond)] =
+    mFieldVerifiers[mBlocksPerSecond.index()] =
             ValidateSimpleParam<decltype(mBlocksPerSecond)>(mSupportedValues);
 
     mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
@@ -454,7 +458,7 @@
             continue;
         }
 
-        uint32_t index = restoreIndex(param);
+        uint32_t index = param->index();
         if (!mParams.count(index)) {
             // TODO: add support for output-block-pools (this will be done when we move all
             // config to shared ptr)
@@ -487,7 +491,7 @@
     (void)mayBlock;
     c2_status_t err = C2_OK;
     for (C2Param *param : params) {
-        uint32_t index = restoreIndex(param);
+        uint32_t index = param->index();
         if (param->index() == mOutputBlockPools.get()->index()) {
             // setting output block pools
             mOutputBlockPools.reset(
@@ -557,7 +561,7 @@
     // cf: Rec. ITU-T H.264 A.3
     int maxFrameRate = 172;
     std::vector<C2ParamField> fields;
-    if (mLevel.mValue != kAvcLevelUnknown) {
+    if (mLevel.value != kAvcLevelUnknown) {
         // cf: Rec. ITU-T H.264 Table A-1
         constexpr int MaxFS[] = {
         //  0       1       2       3       4       5       6       7       8       9
@@ -579,38 +583,43 @@
         };
 
         // cf: Rec. ITU-T H.264 A.3.1
-        maxWidth = std::min(maxWidth, floor32(std::sqrt(MaxFS[mLevel.mValue] * 8)) * MB_SIZE);
-        maxHeight = std::min(maxHeight, floor32(std::sqrt(MaxFS[mLevel.mValue] * 8)) * MB_SIZE);
-        int32_t MBs = ((mVideoSize.mWidth + 15) / 16) * ((mVideoSize.mHeight + 15) / 16);
-        maxFrameRate = std::min(maxFrameRate, MaxMBPS[mLevel.mValue] / MBs);
-        fields.push_back(C2ParamField(&mLevel, &C2AvcLevelInfo::mValue));
+        maxWidth = std::min(maxWidth, floor32(std::sqrt(MaxFS[mLevel.value] * 8)) * MB_SIZE);
+        maxHeight = std::min(maxHeight, floor32(std::sqrt(MaxFS[mLevel.value] * 8)) * MB_SIZE);
+        int32_t MBs = ((mVideoSize.width + 15) / 16) * ((mVideoSize.height + 15) / 16);
+        maxFrameRate = std::min(maxFrameRate, MaxMBPS[mLevel.value] / MBs);
+        fields.push_back(C2ParamField(&mLevel, &C2AvcLevelInfo::value));
     }
 
     SupportedValuesWithFields &maxWidthVals = mSupportedValues.at(
-            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::mWidth));
+            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::width));
     maxWidthVals.supported.range.max = maxWidth;
     maxWidthVals.restrictingFields.clear();
     maxWidthVals.restrictingFields.insert(fields.begin(), fields.end());
 
     SupportedValuesWithFields &maxHeightVals = mSupportedValues.at(
-            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::mHeight));
+            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::height));
     maxHeightVals.supported.range.max = maxHeight;
     maxHeightVals.restrictingFields.clear();
     maxHeightVals.restrictingFields.insert(fields.begin(), fields.end());
 
     SupportedValuesWithFields &frameRate = mSupportedValues.at(
-            C2ParamField(&mFrameRate, &C2FrameRateInfo::mValue));
+            C2ParamField(&mFrameRate, &C2FrameRateInfo::value));
     frameRate.supported.range.max = maxFrameRate;
     frameRate.restrictingFields.clear();
     frameRate.restrictingFields.insert(fields.begin(), fields.end());
 }
+#endif
 
 ///////////////////////////////////////////////////////////////////////////////
 
 C2SoftAvcDec::C2SoftAvcDec(
         const char *name,
         c2_node_id_t id)
-    : SimpleC2Component(std::make_shared<C2SoftAvcDecIntf>(name, id)),
+    : SimpleC2Component(
+          SimpleC2Interface::Builder(name, id)
+          .inputFormat(C2FormatCompressed)
+          .outputFormat(C2FormatVideo)
+          .build()),
       mCodecCtx(NULL),
       mFlushOutBuffer(NULL),
       mIvColorFormat(IV_YUV_420P),
@@ -689,11 +698,6 @@
     return C2_OK;
 }
 
-c2_status_t C2SoftAvcDec::onDrain_nb() {
-    // TODO
-    return C2_OK;
-}
-
 static void *ivd_aligned_malloc(void *ctxt, WORD32 alignment, WORD32 size) {
     (void) ctxt;
     return memalign(alignment, size);
@@ -880,7 +884,6 @@
             ALOGE("Error in create: 0x%x",
                     s_create_op.s_ivd_create_op_t.u4_error_code);
             deInitDecoder();
-            mCodecCtx = NULL;
             return UNKNOWN_ERROR;
         }
     }
@@ -919,6 +922,7 @@
                     s_delete_op.s_ivd_delete_op_t.u4_error_code);
             return UNKNOWN_ERROR;
         }
+        mCodecCtx = NULL;
     }
 
     mChangingResolution = false;
@@ -1023,78 +1027,89 @@
     return true;
 }
 
-bool C2SoftAvcDec::process(const std::unique_ptr<C2Work> &work, std::shared_ptr<C2BlockPool> pool) {
-    bool isInFlush = false;
-    bool eos = false;
+c2_status_t C2SoftAvcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) {
+    if (NULL == mCodecCtx) {
+        if (OK != initDecoder()) {
+            ALOGE("Failed to initialize decoder");
+            // TODO: notify(OMX_EventError, OMX_ErrorUnsupportedSetting, 0, NULL);
+            mSignalledError = true;
+            return C2_CORRUPTED;
+        }
+    }
+    if (mWidth != mStride) {
+        /* Set the run-time (dynamic) parameters */
+        mStride = mWidth;
+        setParams(mStride);
+    }
 
-    bool done = false;
-    work->result = C2_OK;
+    if (!mAllocatedBlock) {
+        // TODO: error handling
+        // TODO: format & usage
+        uint32_t format = HAL_PIXEL_FORMAT_YV12;
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        ALOGV("using allocator %u", pool->getAllocatorId());
 
-    const C2ConstLinearBlock &buffer =
-            work->input.buffers[0]->data().linearBlocks().front();
-    auto fillEmptyWork = [](const std::unique_ptr<C2Work> &work) {
+        (void)pool->fetchGraphicBlock(
+                mWidth, mHeight, format, usage, &mAllocatedBlock);
+        ALOGV("provided (%dx%d) required (%dx%d)",
+                mAllocatedBlock->width(), mAllocatedBlock->height(), mWidth, mHeight);
+    }
+    return C2_OK;
+}
+
+void C2SoftAvcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) {
+    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mAllocatedBlock));
+    auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) {
         uint32_t flags = 0;
-        if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
+        if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
             flags |= C2BufferPack::FLAG_END_OF_STREAM;
+            ALOGV("EOS");
         }
         work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
         work->worklets.front()->output.buffers.clear();
-        work->worklets.front()->output.buffers.emplace_back(nullptr);
+        work->worklets.front()->output.buffers.push_back(buffer);
         work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->worklets_processed = 1u;
     };
-    if (buffer.capacity() == 0) {
-        ALOGV("empty input: %" PRIu64, work->input.ordinal.frame_index);
+    if (work && index == work->input.ordinal.frame_index) {
+        fillWork(work);
+    } else {
+        finish(index, fillWork);
+    }
+}
 
+void C2SoftAvcDec::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    bool eos = false;
+
+    work->result = C2_OK;
+    work->worklets_processed = 0u;
+
+    const C2ConstLinearBlock &buffer =
+        work->input.buffers[0]->data().linearBlocks().front();
+    if (buffer.capacity() == 0) {
+        ALOGV("empty input: %llu", (long long)work->input.ordinal.frame_index);
         // TODO: result?
         fillEmptyWork(work);
         if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
             eos = true;
         }
-        done = true;
+        return;
     } else if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
-        ALOGV("input EOS: %" PRIu64, work->input.ordinal.frame_index);
+        ALOGV("input EOS: %llu", (long long)work->input.ordinal.frame_index);
         eos = true;
     }
 
-    std::unique_ptr<C2ReadView> deferred;
-    std::unique_ptr<C2ReadView> input(new C2ReadView(
-            work->input.buffers[0]->data().linearBlocks().front().map().get()));
+    C2ReadView input = work->input.buffers[0]->data().linearBlocks().front().map().get();
     uint32_t workIndex = work->input.ordinal.frame_index & 0xFFFFFFFF;
     size_t inOffset = 0u;
 
-    while (input || isInFlush) {
+    while (inOffset < input.capacity()) {
         if (mSignalledError) {
-            return done;
+            break;
         }
-        if (NULL == mCodecCtx) {
-            if (OK != initDecoder()) {
-                ALOGE("Failed to initialize decoder");
-                // TODO: notify(OMX_EventError, OMX_ErrorUnsupportedSetting, 0, NULL);
-                mSignalledError = true;
-                return done;
-            }
-        }
-        if (mWidth != mStride) {
-            /* Set the run-time (dynamic) parameters */
-            mStride = mWidth;
-            setParams(mStride);
-        }
-
-        if (isInFlush) {
-            ALOGV("flushing");
-        }
-
-        if (!mAllocatedBlock) {
-            // TODO: error handling
-            // TODO: format & usage
-            uint32_t format = HAL_PIXEL_FORMAT_YV12;
-            C2MemoryUsage usage = { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite };
-            ALOGV("using allocator %u", pool->getAllocatorId());
-
-            (void)pool->fetchGraphicBlock(
-                    mWidth, mHeight, format, usage, &mAllocatedBlock);
-            ALOGV("provided (%dx%d) required (%dx%d)", mAllocatedBlock->width(), mAllocatedBlock->height(), mWidth, mHeight);
-        }
+        (void)ensureDecoderState(pool);
         C2GraphicView output = mAllocatedBlock->map().get();
         if (output.error() != OK) {
             ALOGE("mapped err = %d", output.error());
@@ -1105,11 +1120,11 @@
         WORD32 timeDelay, timeTaken;
         //size_t sizeY, sizeUV;
 
-        if (!setDecodeArgs(&s_dec_ip, &s_dec_op, input.get(), &output, workIndex, inOffset)) {
+        if (!setDecodeArgs(&s_dec_ip, &s_dec_op, &input, &output, workIndex, inOffset)) {
             ALOGE("Decoder arg setup failed");
             // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
             mSignalledError = true;
-            return done;
+            break;
         }
         ALOGV("Decoder arg setup succeeded");
         // If input dump is enabled, then write to file
@@ -1122,6 +1137,7 @@
 
         IV_API_CALL_STATUS_T status;
         status = ivdec_api_function(mCodecCtx, (void *)&s_dec_ip, (void *)&s_dec_op);
+        ALOGV("status = %d, error_code = %d", status, (s_dec_op.u4_error_code & 0xFF));
 
         bool unsupportedResolution =
             (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_dec_op.u4_error_code & 0xFF));
@@ -1131,7 +1147,7 @@
             ALOGE("Unsupported resolution : %dx%d", mWidth, mHeight);
             // TODO: notify(OMX_EventError, OMX_ErrorUnsupportedSetting, 0, NULL);
             mSignalledError = true;
-            return done;
+            break;
         }
 
         bool allocationFailed = (IVD_MEM_ALLOC_FAILED == (s_dec_op.u4_error_code & 0xFF));
@@ -1139,7 +1155,7 @@
             ALOGE("Allocation failure in decoder");
             // TODO: notify(OMX_EventError, OMX_ErrorUnsupportedSetting, 0, NULL);
             mSignalledError = true;
-            return done;
+            break;
         }
 
         bool resChanged = (IVD_RES_CHANGED == (s_dec_op.u4_error_code & 0xFF));
@@ -1152,26 +1168,23 @@
 
         PRINT_TIME("timeTaken=%6d delay=%6d numBytes=%6d", timeTaken, timeDelay,
                s_dec_op.u4_num_bytes_consumed);
-        if (input) {
-            ALOGV("bytes total=%u", input->capacity());
-        }
+        ALOGV("bytes total=%u", input.capacity());
         if (s_dec_op.u4_frame_decoded_flag && !mFlushNeeded) {
             mFlushNeeded = true;
         }
 
-        if (1 != s_dec_op.u4_frame_decoded_flag && input) {
+        if (1 != s_dec_op.u4_frame_decoded_flag) {
             /* If the input did not contain picture data, return work without
              * buffer */
             ALOGV("no picture data: %u", workIndex);
             fillEmptyWork(work);
-            done = true;
         }
 
-        // If the decoder is in the changing resolution mode and there is no output present,
-        // that means the switching is done and it's ready to reset the decoder and the plugin.
-        if (mChangingResolution && !s_dec_op.u4_output_present) {
-            ALOGV("changing resolution");
-            mChangingResolution = false;
+        if (resChanged) {
+            ALOGV("res changed");
+            if (mFlushNeeded) {
+                drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work);
+            }
             resetDecoder();
             resetPlugin();
             mStride = mWidth;
@@ -1179,19 +1192,6 @@
             continue;
         }
 
-        if (resChanged) {
-            ALOGV("res changed");
-            mChangingResolution = true;
-            if (mFlushNeeded) {
-                setFlushMode();
-                isInFlush = true;
-                deferred = std::move(input);
-            }
-            continue;
-        }
-
-        // Combine the resolution change and coloraspects change in one PortSettingChange event
-        // if necessary.
         if ((0 < s_dec_op.u4_pic_wd) && (0 < s_dec_op.u4_pic_ht)) {
             uint32_t width = s_dec_op.u4_pic_wd;
             uint32_t height = s_dec_op.u4_pic_ht;
@@ -1201,73 +1201,76 @@
                 mWidth = width;
                 mHeight = height;
             }
-        } else if (mUpdateColorAspects) {
+            // TODO: continue?
+        }
+
+        if (mUpdateColorAspects) {
             //notify(OMX_EventPortSettingsChanged, kOutputPortIndex,
             //    kDescribeColorAspectsIndex, NULL);
             ALOGV("update color aspect");
             mUpdateColorAspects = false;
-            continue;
         }
 
         if (s_dec_op.u4_output_present) {
             ALOGV("output_present: %d", s_dec_op.u4_ts);
-            auto fillWork = [this](const std::unique_ptr<C2Work> &work) {
-                uint32_t flags = 0;
-                if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
-                    flags |= C2BufferPack::FLAG_END_OF_STREAM;
-                    ALOGV("EOS");
-                }
-                work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
-                work->worklets.front()->output.buffers.clear();
-                work->worklets.front()->output.buffers.emplace_back(
-                        std::make_shared<GraphicBuffer>(std::move(mAllocatedBlock)));
-                work->worklets.front()->output.ordinal = work->input.ordinal;
-            };
-            if (s_dec_op.u4_ts != workIndex) {
-                finish(s_dec_op.u4_ts, fillWork);
-            } else {
-                fillWork(work);
-                done = true;
-            }
-        } else if (isInFlush) {
-            ALOGV("flush complete");
-            /* If in flush mode and no output is returned by the codec,
-             * then come out of flush mode */
-            isInFlush = false;
-
-            /* If EOS was recieved on input port and there is no output
-             * from the codec, then signal EOS on output port */
-            if (eos) {
-                // TODO: It's an error if not done.
-
-                resetPlugin();
-                return done;
-            }
-
-            input = std::move(deferred);
+            finishWork(s_dec_op.u4_ts, work);
         }
 
-        if (input) {
-            inOffset += s_dec_op.u4_num_bytes_consumed;
-            if (inOffset >= input->capacity()) {
-                /* If input EOS is seen and decoder is not in flush mode,
-                 * set the decoder in flush mode.
-                 * There can be a case where EOS is sent along with last picture data
-                 * In that case, only after decoding that input data, decoder has to be
-                 * put in flush. This case is handled here  */
-                if (eos && !isInFlush) {
-                    setFlushMode();
-                    isInFlush = true;
-                }
-                if (isInFlush) {
-                    input.reset();
-                } else {
-                    break;
-                }
-            }
+        inOffset += s_dec_op.u4_num_bytes_consumed;
+    }
+    if (inOffset >= input.capacity()) {
+        /* If input EOS is seen, drain the decoder.
+         * There can be a case where EOS is sent along with last picture data
+         * In that case, only after decoding that input data, decoder has to be
+         * put in flush. This case is handled here  */
+        if (eos) {
+            drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
         }
     }
-    return done;
+}
+
+c2_status_t C2SoftAvcDec::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;
+    }
+    setFlushMode();
+
+    while (true) {
+        (void)ensureDecoderState(pool);
+        C2GraphicView output = mAllocatedBlock->map().get();
+        if (output.error() != OK) {
+            ALOGE("mapped err = %d", output.error());
+        }
+
+        ivd_video_decode_ip_t s_dec_ip;
+        ivd_video_decode_op_t s_dec_op;
+
+        setDecodeArgs(&s_dec_ip, &s_dec_op, NULL, &output, 0, 0u);
+
+        (void)ivdec_api_function(mCodecCtx, (void *)&s_dec_ip, (void *)&s_dec_op);
+
+        if (s_dec_op.u4_output_present) {
+            ALOGV("output_present: %d", s_dec_op.u4_ts);
+            finishWork(s_dec_op.u4_ts, work);
+        } else {
+            break;
+        }
+    }
+
+    if (drainMode == DRAIN_COMPONENT_WITH_EOS
+            && work && work->worklets_processed == 0u) {
+        fillEmptyWork(work);
+    }
+
+    return C2_OK;
 }
 
 bool C2SoftAvcDec::colorAspectsDiffer(
@@ -1281,6 +1284,12 @@
     return false;
 }
 
+c2_status_t C2SoftAvcDec::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    return drainInternal(drainMode, pool, nullptr);
+}
+
 void C2SoftAvcDec::updateFinalColorAspects(
         const ColorAspects &otherAspects, const ColorAspects &preferredAspects) {
     Mutex::Autolock autoLock(mColorAspectsLock);
@@ -1329,7 +1338,11 @@
             c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface,
             std::function<void(::android::C2ComponentInterface*)> deleter) override {
         *interface =
-            std::shared_ptr<C2ComponentInterface>(new C2SoftAvcDecIntf("avc", id), deleter);
+              SimpleC2Interface::Builder("avc", id, deleter)
+              .inputFormat(C2FormatCompressed)
+              .outputFormat(C2FormatVideo)
+              .build();
+//            std::shared_ptr<C2ComponentInterface>(new C2SoftAvcDecIntf("avc", id), deleter);
         return C2_OK;
     }
 
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
index aa22e23..0e8cf77 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
@@ -67,7 +67,7 @@
     diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \
             ((end).tv_usec - (start).tv_usec);
 
-
+#if 0
 class C2SoftAvcDecIntf : public C2ComponentInterface {
 public:
     struct SupportedValuesWithFields {
@@ -139,6 +139,7 @@
     void updateSupportedValues();
     friend class C2SoftAvcDec;
 };
+#endif
 
 class C2SoftAvcDec : public SimpleC2Component {
 public:
@@ -146,15 +147,17 @@
     virtual ~C2SoftAvcDec();
 
     // From SimpleC2Component
-    virtual c2_status_t onInit() override;
-    virtual c2_status_t onStop() override;
-    virtual void onReset() override;
-    virtual void onRelease() override;
-    virtual c2_status_t onFlush_sm() override;
-    virtual c2_status_t onDrain_nb() override;
-    virtual bool process(
+    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,
-            std::shared_ptr<C2BlockPool> pool) override;
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
 
 private:
     Mutex mColorAspectsLock;
@@ -216,6 +219,13 @@
     status_t resetDecoder();
     status_t resetPlugin();
 
+    c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool);
+    void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work);
+    c2_status_t drainInternal(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool,
+            const std::unique_ptr<C2Work> &work);
+
     bool setDecodeArgs(
             ivd_video_decode_ip_t *ps_dec_ip,
             ivd_video_decode_op_t *ps_dec_op,
diff --git a/media/libstagefright/codecs/avcdec/exports.lds b/media/libstagefright/codecs/avcdec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/avcdec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/avcenc/Android.bp b/media/libstagefright/codecs/avcenc/Android.bp
index 4a0411e..cefe77c 100644
--- a/media/libstagefright/codecs/avcenc/Android.bp
+++ b/media/libstagefright/codecs/avcenc/Android.bp
@@ -39,5 +39,8 @@
         "-Wno-unused-variable",
     ],
     ldflags: ["-Wl,-Bsymbolic"],
+
+    version_script: "exports.lds",
+
     compile_multilib: "32",
 }
diff --git a/media/libstagefright/codecs/avcenc/exports.lds b/media/libstagefright/codecs/avcenc/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/avcenc/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/cmds/Android.bp b/media/libstagefright/codecs/cmds/Android.bp
index ad0bd2d..40f1a3d 100644
--- a/media/libstagefright/codecs/cmds/Android.bp
+++ b/media/libstagefright/codecs/cmds/Android.bp
@@ -6,30 +6,21 @@
     ],
 
     include_dirs: [
-        "frameworks/av/media/libstagefright/codec2/include",
-        "frameworks/av/media/libstagefright/codec2/vndk/include",
     ],
 
     shared_libs: [
-        "android.hardware.graphics.allocator@2.0",
-        "android.hardware.graphics.mapper@2.0",
         "libbinder",
         "libcutils",
         "libgui",
-        "libhidlbase",
-        "libion",
         "liblog",
         "libstagefright",
         "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
         "libstagefright_foundation",
         "libui",
         "libutils",
     ],
 
-    static_libs: [
-        "libstagefright_codec2_vndk",
-    ],
-
     cflags: [
         "-Werror",
         "-Wall",
diff --git a/media/libstagefright/codecs/cmds/codec2.cpp b/media/libstagefright/codecs/cmds/codec2.cpp
index ad58871..78fb527 100644
--- a/media/libstagefright/codecs/cmds/codec2.cpp
+++ b/media/libstagefright/codecs/cmds/codec2.cpp
@@ -52,6 +52,7 @@
 #include <gui/SurfaceComposerClient.h>
 
 #include <util/C2ParamUtils.h>
+#include <C2AllocatorGralloc.h>
 #include <C2Buffer.h>
 #include <C2BufferPriv.h>
 #include <C2Component.h>
@@ -174,6 +175,7 @@
 
 void SimplePlayer::onWorkDone(
         std::weak_ptr<C2Component> component, std::vector<std::unique_ptr<C2Work>> workItems) {
+    ALOGV("SimplePlayer::onWorkDone");
     (void) component;
     ULock l(mProcessedLock);
     for (auto & item : workItems) {
@@ -245,31 +247,36 @@
             }
             int slot;
             sp<Fence> fence;
+            ALOGV("Render: Frame #%" PRId64, work->worklets.front()->output.ordinal.frame_index);
             const std::shared_ptr<C2Buffer> &output = work->worklets.front()->output.buffers[0];
-            const C2ConstGraphicBlock &block = output->data().graphicBlocks().front();
-            sp<GraphicBuffer> buffer(new GraphicBuffer(
-                    block.handle(),
-                    GraphicBuffer::CLONE_HANDLE,
-                    block.width(),
-                    block.height(),
-                    HAL_PIXEL_FORMAT_YV12,
-                    1,
-                    (uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
-                    block.width()));
+            if (output) {
+                const C2ConstGraphicBlock &block = output->data().graphicBlocks().front();
+                native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(block.handle());
+                sp<GraphicBuffer> buffer(new GraphicBuffer(
+                        grallocHandle,
+                        GraphicBuffer::CLONE_HANDLE,
+                        block.width(),
+                        block.height(),
+                        HAL_PIXEL_FORMAT_YV12,
+                        1,
+                        (uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                        block.width()));
+                native_handle_delete(grallocHandle);
 
-            status_t err = igbp->attachBuffer(&slot, buffer);
+                status_t err = igbp->attachBuffer(&slot, buffer);
 
-            IGraphicBufferProducer::QueueBufferInput qbi(
-                    work->worklets.front()->output.ordinal.timestamp * 1000ll,
-                    false,
-                    HAL_DATASPACE_UNKNOWN,
-                    Rect(block.width(), block.height()),
-                    NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,
-                    0,
-                    Fence::NO_FENCE,
-                    0);
-            IGraphicBufferProducer::QueueBufferOutput qbo;
-            err = igbp->queueBuffer(slot, qbi, &qbo);
+                IGraphicBufferProducer::QueueBufferInput qbi(
+                        work->worklets.front()->output.ordinal.timestamp * 1000ll,
+                        false,
+                        HAL_DATASPACE_UNKNOWN,
+                        Rect(block.width(), block.height()),
+                        NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,
+                        0,
+                        Fence::NO_FENCE,
+                        0);
+                IGraphicBufferProducer::QueueBufferOutput qbo;
+                err = igbp->queueBuffer(slot, qbi, &qbo);
+            }
 
             work->input.buffers.clear();
             work->worklets.clear();
@@ -278,6 +285,7 @@
             mWorkQueue.push_back(std::move(work));
             mQueueCondition.notify_all();
         }
+        ALOGV("render loop finished");
     });
 
     long numFrames = 0;
@@ -337,7 +345,7 @@
         std::shared_ptr<C2LinearBlock> block;
         mLinearPool->fetchLinearBlock(
                 size,
-                { C2MemoryUsage::kSoftwareRead, C2MemoryUsage::kSoftwareWrite },
+                { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
                 &block);
         C2WriteView view = block->map().get();
         if (view.error() != C2_OK) {
@@ -365,11 +373,12 @@
 
         ++numFrames;
     }
+    ALOGV("main loop finished");
     source->stop();
-    component->release();
-
     running.store(false);
     surfaceThread.join();
+
+    component->release();
     printf("\n");
 }
 
diff --git a/media/libstagefright/codecs/flac/dec/Android.bp b/media/libstagefright/codecs/flac/dec/Android.bp
index 595cfdb..9af086b 100644
--- a/media/libstagefright/codecs/flac/dec/Android.bp
+++ b/media/libstagefright/codecs/flac/dec/Android.bp
@@ -18,6 +18,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/flac/dec/exports.lds b/media/libstagefright/codecs/flac/dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/flac/dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/flac/enc/Android.bp b/media/libstagefright/codecs/flac/enc/Android.bp
index 854f7ce..46b974d 100644
--- a/media/libstagefright/codecs/flac/enc/Android.bp
+++ b/media/libstagefright/codecs/flac/enc/Android.bp
@@ -10,6 +10,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/flac/enc/exports.lds b/media/libstagefright/codecs/flac/enc/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/flac/enc/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/g711/dec/Android.bp b/media/libstagefright/codecs/g711/dec/Android.bp
index 07e5052..3d97d8c 100644
--- a/media/libstagefright/codecs/g711/dec/Android.bp
+++ b/media/libstagefright/codecs/g711/dec/Android.bp
@@ -21,6 +21,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/g711/dec/exports.lds b/media/libstagefright/codecs/g711/dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/g711/dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/gsm/dec/Android.bp b/media/libstagefright/codecs/gsm/dec/Android.bp
index 0739ad4..1c3208b 100644
--- a/media/libstagefright/codecs/gsm/dec/Android.bp
+++ b/media/libstagefright/codecs/gsm/dec/Android.bp
@@ -15,6 +15,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/gsm/dec/exports.lds b/media/libstagefright/codecs/gsm/dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/gsm/dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/hevcdec/Android.bp b/media/libstagefright/codecs/hevcdec/Android.bp
index f19ba00..45920e6 100644
--- a/media/libstagefright/codecs/hevcdec/Android.bp
+++ b/media/libstagefright/codecs/hevcdec/Android.bp
@@ -14,6 +14,8 @@
         "-Wno-unused-variable",
     ],
 
+    version_script: "exports.lds",
+
     include_dirs: [
         "external/libhevc/decoder",
         "external/libhevc/common",
diff --git a/media/libstagefright/codecs/hevcdec/exports.lds b/media/libstagefright/codecs/hevcdec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/hevcdec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/m4v_h263/dec/Android.bp b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
index e57bb78..ca70cc2 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
@@ -53,6 +53,8 @@
         "-Werror",
     ],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
index 39b67ab..fda7028 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
@@ -353,7 +353,8 @@
     bool portWillReset = false;
     const bool fakeStride = true;
     SoftVideoDecoderOMXComponent::handlePortSettingsChange(
-            &portWillReset, buf_width, buf_height, cropSettingsMode, fakeStride);
+            &portWillReset, buf_width, buf_height,
+            OMX_COLOR_FormatYUV420Planar, cropSettingsMode, fakeStride);
     if (portWillReset) {
         if (mMode == MODE_H263) {
             PVCleanUpVideoDecoder(mHandle);
diff --git a/media/libstagefright/codecs/m4v_h263/dec/exports.lds b/media/libstagefright/codecs/m4v_h263/dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/m4v_h263/enc/Android.bp b/media/libstagefright/codecs/m4v_h263/enc/Android.bp
index 8a3fe34..6be4036 100644
--- a/media/libstagefright/codecs/m4v_h263/enc/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/enc/Android.bp
@@ -30,6 +30,8 @@
         "-Werror",
     ],
 
+    version_script: "exports.lds",
+
     include_dirs: [
         "frameworks/av/media/libstagefright/include",
         "frameworks/native/include/media/openmax",
diff --git a/media/libstagefright/codecs/m4v_h263/enc/exports.lds b/media/libstagefright/codecs/m4v_h263/enc/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/enc/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/mp3dec/Android.bp b/media/libstagefright/codecs/mp3dec/Android.bp
index 5a0e282..e6eb32b 100644
--- a/media/libstagefright/codecs/mp3dec/Android.bp
+++ b/media/libstagefright/codecs/mp3dec/Android.bp
@@ -96,6 +96,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/mp3dec/exports.lds b/media/libstagefright/codecs/mp3dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/mp3dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/mpeg2dec/Android.bp b/media/libstagefright/codecs/mpeg2dec/Android.bp
index 9b8a188..fb0db8f 100644
--- a/media/libstagefright/codecs/mpeg2dec/Android.bp
+++ b/media/libstagefright/codecs/mpeg2dec/Android.bp
@@ -14,6 +14,8 @@
         "-Wno-unused-variable",
     ],
 
+    version_script: "exports.lds",
+
     include_dirs: [
         "external/libmpeg2/decoder",
         "external/libmpeg2/common",
diff --git a/media/libstagefright/codecs/mpeg2dec/exports.lds b/media/libstagefright/codecs/mpeg2dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/mpeg2dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/on2/dec/Android.bp b/media/libstagefright/codecs/on2/dec/Android.bp
index a4eed8c..8a9399a 100644
--- a/media/libstagefright/codecs/on2/dec/Android.bp
+++ b/media/libstagefright/codecs/on2/dec/Android.bp
@@ -23,6 +23,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/on2/dec/SoftVPX.cpp b/media/libstagefright/codecs/on2/dec/SoftVPX.cpp
index 3490008..8d5f3e7 100644
--- a/media/libstagefright/codecs/on2/dec/SoftVPX.cpp
+++ b/media/libstagefright/codecs/on2/dec/SoftVPX.cpp
@@ -30,7 +30,9 @@
 
 // Only need to declare the highest supported profile and level here.
 static const CodecProfileLevel kVP9ProfileLevels[] = {
-    { OMX_VIDEO_VP9Profile0, OMX_VIDEO_VP9Level5  },
+    { OMX_VIDEO_VP9Profile0, OMX_VIDEO_VP9Level5 },
+    { OMX_VIDEO_VP9Profile2, OMX_VIDEO_VP9Level5 },
+    { OMX_VIDEO_VP9Profile2HDR, OMX_VIDEO_VP9Level5 },
 };
 
 SoftVPX::SoftVPX(
@@ -78,6 +80,10 @@
     return cpuCoreCount;
 }
 
+bool SoftVPX::supportDescribeHdrStaticInfo() {
+    return true;
+}
+
 status_t SoftVPX::initDecoder() {
     mCtx = new vpx_codec_ctx_t;
     vpx_codec_err_t vpx_err;
@@ -146,15 +152,21 @@
         uint32_t height = mImg->d_h;
         outInfo = *outQueue.begin();
         outHeader = outInfo->mHeader;
-        CHECK_EQ(mImg->fmt, VPX_IMG_FMT_I420);
-        handlePortSettingsChange(portWillReset, width, height);
+        CHECK(mImg->fmt == VPX_IMG_FMT_I420 || mImg->fmt == VPX_IMG_FMT_I42016);
+        OMX_COLOR_FORMATTYPE outputColorFormat = OMX_COLOR_FormatYUV420Planar;
+        int32_t bpp = 1;
+        if (mImg->fmt == VPX_IMG_FMT_I42016) {
+            outputColorFormat = OMX_COLOR_FormatYUV420Planar16;
+            bpp = 2;
+        }
+        handlePortSettingsChange(portWillReset, width, height, outputColorFormat);
         if (*portWillReset) {
             return true;
         }
 
         outHeader->nOffset = 0;
         outHeader->nFlags = 0;
-        outHeader->nFilledLen = (outputBufferWidth() * outputBufferHeight() * 3) / 2;
+        outHeader->nFilledLen = (outputBufferWidth() * outputBufferHeight() * bpp * 3) / 2;
         outHeader->nTimeStamp = *(OMX_TICKS *)mImg->user_priv;
         if (outputBufferSafe(outHeader)) {
             uint8_t *dst = outHeader->pBuffer;
diff --git a/media/libstagefright/codecs/on2/dec/SoftVPX.h b/media/libstagefright/codecs/on2/dec/SoftVPX.h
index d6bb902..a01a4f3 100644
--- a/media/libstagefright/codecs/on2/dec/SoftVPX.h
+++ b/media/libstagefright/codecs/on2/dec/SoftVPX.h
@@ -40,6 +40,7 @@
     virtual void onQueueFilled(OMX_U32 portIndex);
     virtual void onPortFlushCompleted(OMX_U32 portIndex);
     virtual void onReset();
+    virtual bool supportDescribeHdrStaticInfo();
 
 private:
     enum {
diff --git a/media/libstagefright/codecs/on2/dec/exports.lds b/media/libstagefright/codecs/on2/dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/on2/dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/on2/enc/Android.bp b/media/libstagefright/codecs/on2/enc/Android.bp
index b21ffa1..3d9feeb 100644
--- a/media/libstagefright/codecs/on2/enc/Android.bp
+++ b/media/libstagefright/codecs/on2/enc/Android.bp
@@ -13,6 +13,8 @@
 
     cflags: ["-Wall", "-Werror"],
 
+    version_script: "exports.lds",
+
     include_dirs: [
         "frameworks/av/media/libstagefright/include",
         "frameworks/native/include/media/openmax",
diff --git a/media/libstagefright/codecs/on2/enc/SoftVP9Encoder.cpp b/media/libstagefright/codecs/on2/enc/SoftVP9Encoder.cpp
index 4c7290d..1ea1c85 100644
--- a/media/libstagefright/codecs/on2/enc/SoftVP9Encoder.cpp
+++ b/media/libstagefright/codecs/on2/enc/SoftVP9Encoder.cpp
@@ -69,6 +69,13 @@
               codecReturn);
         return codecReturn;
     }
+    codecReturn = vpx_codec_control(mCodecContext, VP9E_SET_ROW_MT, 1);
+    if (codecReturn != VPX_CODEC_OK) {
+        ALOGE("Error setting VP9E_SET_ROW_MT to 1. vpx_codec_control() "
+              "returned %d", codecReturn);
+        return codecReturn;
+    }
+
     // For VP9, we always set CPU_USED to 8 (because the realtime default is 0
     // which is too slow).
     codecReturn = vpx_codec_control(mCodecContext, VP8E_SET_CPUUSED, 8);
diff --git a/media/libstagefright/codecs/on2/enc/exports.lds b/media/libstagefright/codecs/on2/enc/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/on2/enc/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/opus/dec/Android.bp b/media/libstagefright/codecs/opus/dec/Android.bp
index 32a4f32..43318f2 100644
--- a/media/libstagefright/codecs/opus/dec/Android.bp
+++ b/media/libstagefright/codecs/opus/dec/Android.bp
@@ -22,6 +22,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/opus/dec/exports.lds b/media/libstagefright/codecs/opus/dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/opus/dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/raw/Android.bp b/media/libstagefright/codecs/raw/Android.bp
index f21d46f..c8d7d00 100644
--- a/media/libstagefright/codecs/raw/Android.bp
+++ b/media/libstagefright/codecs/raw/Android.bp
@@ -14,6 +14,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/raw/exports.lds b/media/libstagefright/codecs/raw/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/raw/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/codecs/tests/Android.mk b/media/libstagefright/codecs/tests/Android.mk
deleted file mode 100644
index ea188ea..0000000
--- a/media/libstagefright/codecs/tests/Android.mk
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# 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_SRC_FILES := \
-	C2SoftAvcDec_test.cpp \
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := c2_google_component_test
-
-LOCAL_SHARED_LIBRARIES := \
-	libcutils \
-	libstagefright_codec2 \
-	libstagefright_foundation \
-	libstagefright_soft_c2avcdec \
-	liblog \
-
-LOCAL_C_INCLUDES := \
-	frameworks/av/media/libstagefright/codec2/include \
-	frameworks/av/media/libstagefright/codec2/vndk/include \
-	frameworks/av/media/libstagefright/codecs/avcdec \
-
-LOCAL_CFLAGS += -Werror -Wall -std=c++14
-LOCAL_CLANG := true
-
-include $(BUILD_NATIVE_TEST)
diff --git a/media/libstagefright/codecs/tests/C2SoftAvcDec_test.cpp b/media/libstagefright/codecs/tests/C2SoftAvcDec_test.cpp
deleted file mode 100644
index 78e82b3..0000000
--- a/media/libstagefright/codecs/tests/C2SoftAvcDec_test.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 "C2SoftAvcDec_test"
-#include <utils/Log.h>
-
-#include <gtest/gtest.h>
-
-#include <media/stagefright/foundation/MediaDefs.h>
-
-#include "C2SoftAvcDec.h"
-
-namespace android {
-
-namespace {
-
-template <class T>
-std::unique_ptr<T> alloc_unique_cstr(const char *cstr) {
-    std::unique_ptr<T> ptr = T::alloc_unique(strlen(cstr) + 1);
-    strcpy(ptr->m.mValue, cstr);
-    return ptr;
-}
-
-}  // namespace
-
-
-class C2SoftAvcDecTest : public ::testing::Test {
-public:
-    C2SoftAvcDecTest() : mIntf(new C2SoftAvcDecIntf("dummy", 0u)) {}
-    ~C2SoftAvcDecTest() = default;
-
-    template <typename T>
-    void testReadOnlyParam(const T *expected, const T *invalid);
-
-    template <typename T>
-    void testReadOnlyParamOnStack(const T *expected, const T *invalid);
-
-    template <typename T>
-    void testReadOnlyParamOnHeap(const T *expected, const T *invalid);
-
-    template <typename T>
-    void testReadOnlyFlexParam(
-            const std::unique_ptr<T> &expected, const std::unique_ptr<T> &invalid);
-
-protected:
-    std::shared_ptr<C2SoftAvcDecIntf> mIntf;
-};
-
-template <typename T>
-void C2SoftAvcDecTest::testReadOnlyParam(const T *expected, const T *invalid) {
-    testReadOnlyParamOnStack(expected, invalid);
-    testReadOnlyParamOnHeap(expected, invalid);
-}
-
-template <typename T>
-void C2SoftAvcDecTest::testReadOnlyParamOnStack(const T *expected, const T *invalid) {
-    T param;
-    ASSERT_EQ(C2_OK, mIntf->query_vb({&param}, {}, C2_DONT_BLOCK, nullptr));
-    ASSERT_EQ(*expected, param);
-
-    std::vector<C2Param * const> params{ (C2Param * const)invalid };
-    std::vector<std::unique_ptr<C2SettingResult>> failures;
-    ASSERT_EQ(C2_BAD_VALUE, mIntf->config_vb(params, C2_DONT_BLOCK, &failures));
-
-    // The param must not change after failed config.
-    ASSERT_EQ(C2_OK, mIntf->query_vb({&param}, {}, C2_DONT_BLOCK, nullptr));
-    ASSERT_EQ(*expected, param);
-}
-
-template <typename T>
-void C2SoftAvcDecTest::testReadOnlyParamOnHeap(const T *expected, const T *invalid) {
-    std::vector<std::unique_ptr<C2Param>> heapParams;
-
-    uint32_t index = expected->type();
-    if (expected->forStream()) {
-        index |= ((expected->stream() << 17) & 0x01FE0000) | 0x02000000;
-    }
-
-    ASSERT_EQ(C2_OK, mIntf->query_vb({}, {index}, C2_DONT_BLOCK, &heapParams));
-    ASSERT_EQ(1u, heapParams.size());
-    ASSERT_EQ(*expected, *heapParams[0]);
-
-    std::vector<C2Param * const> params{ (C2Param * const)invalid };
-    std::vector<std::unique_ptr<C2SettingResult>> failures;
-    ASSERT_EQ(C2_BAD_VALUE, mIntf->config_vb(params, C2_DONT_BLOCK, &failures));
-
-    // The param must not change after failed config.
-    heapParams.clear();
-    ASSERT_EQ(C2_OK, mIntf->query_vb({}, {index}, C2_DONT_BLOCK, &heapParams));
-    ASSERT_EQ(1u, heapParams.size());
-    ASSERT_EQ(*expected, *heapParams[0]);
-}
-
-template <typename T>
-void C2SoftAvcDecTest::testReadOnlyFlexParam(
-        const std::unique_ptr<T> &expected, const std::unique_ptr<T> &invalid) {
-    std::vector<std::unique_ptr<C2Param>> heapParams;
-
-    uint32_t index = expected->type();
-    if (expected->forStream()) {
-        index |= ((expected->stream() << 17) & 0x01FE0000) | 0x02000000;
-    }
-
-    ASSERT_EQ(C2_OK, mIntf->query_vb({}, {index}, C2_DONT_BLOCK, &heapParams));
-    ASSERT_EQ(1u, heapParams.size());
-    ASSERT_EQ(*expected, *heapParams[0]);
-
-    std::vector<C2Param * const> params{ invalid.get() };
-    std::vector<std::unique_ptr<C2SettingResult>> failures;
-    ASSERT_EQ(C2_BAD_VALUE, mIntf->config_vb(params, C2_DONT_BLOCK, &failures));
-
-    // The param must not change after failed config.
-    heapParams.clear();
-    ASSERT_EQ(C2_OK, mIntf->query_vb({}, {index}, C2_DONT_BLOCK, &heapParams));
-    ASSERT_EQ(1u, heapParams.size());
-    ASSERT_EQ(*expected, *heapParams[0]);
-}
-
-
-TEST_F(C2SoftAvcDecTest, TestNameAndId) {
-    EXPECT_STREQ("dummy", mIntf->getName().c_str());
-    EXPECT_EQ(0u, mIntf->getId());
-}
-
-TEST_F(C2SoftAvcDecTest, TestDomainInfo) {
-    C2ComponentDomainInfo expected(C2DomainVideo);
-    C2ComponentDomainInfo invalid(C2DomainAudio);
-    testReadOnlyParam(&expected, &invalid);
-}
-
-TEST_F(C2SoftAvcDecTest, TestInputStreamCount) {
-    C2PortStreamCountConfig::input expected(1);
-    C2PortStreamCountConfig::input invalid(100);
-    testReadOnlyParam(&expected, &invalid);
-}
-
-TEST_F(C2SoftAvcDecTest, TestOutputStreamCount) {
-    C2PortStreamCountConfig::output expected(1);
-    C2PortStreamCountConfig::output invalid(100);
-    testReadOnlyParam(&expected, &invalid);
-}
-
-TEST_F(C2SoftAvcDecTest, TestInputPortMime) {
-    std::unique_ptr<C2PortMimeConfig::input> expected(
-            alloc_unique_cstr<C2PortMimeConfig::input>(MEDIA_MIMETYPE_VIDEO_AVC));
-    std::unique_ptr<C2PortMimeConfig::input> invalid(
-            alloc_unique_cstr<C2PortMimeConfig::input>(MEDIA_MIMETYPE_VIDEO_RAW));
-    testReadOnlyFlexParam(expected, invalid);
-}
-
-TEST_F(C2SoftAvcDecTest, TestOutputPortMime) {
-    std::unique_ptr<C2PortMimeConfig::output> expected(
-            alloc_unique_cstr<C2PortMimeConfig::output>(MEDIA_MIMETYPE_VIDEO_RAW));
-    std::unique_ptr<C2PortMimeConfig::output> invalid(
-            alloc_unique_cstr<C2PortMimeConfig::output>(MEDIA_MIMETYPE_VIDEO_AVC));
-    testReadOnlyFlexParam(expected, invalid);
-}
-
-TEST_F(C2SoftAvcDecTest, TestInputStreamFormat) {
-    C2StreamFormatConfig::input expected(0u, C2FormatCompressed);
-    C2StreamFormatConfig::input invalid(0u, C2FormatVideo);
-    testReadOnlyParam(&expected, &invalid);
-}
-
-TEST_F(C2SoftAvcDecTest, TestOutputStreamFormat) {
-    C2StreamFormatConfig::output expected(0u, C2FormatVideo);
-    C2StreamFormatConfig::output invalid(0u, C2FormatCompressed);
-    testReadOnlyParam(&expected, &invalid);
-}
-
-} // namespace android
diff --git a/media/libstagefright/codecs/vorbis/dec/Android.bp b/media/libstagefright/codecs/vorbis/dec/Android.bp
index b7a6c1c..a9265cb 100644
--- a/media/libstagefright/codecs/vorbis/dec/Android.bp
+++ b/media/libstagefright/codecs/vorbis/dec/Android.bp
@@ -22,6 +22,8 @@
 
     cflags: ["-Werror"],
 
+    version_script: "exports.lds",
+
     sanitize: {
         misc_undefined: [
             "signed-integer-overflow",
diff --git a/media/libstagefright/codecs/vorbis/dec/exports.lds b/media/libstagefright/codecs/vorbis/dec/exports.lds
new file mode 100644
index 0000000..e24f3fa
--- /dev/null
+++ b/media/libstagefright/codecs/vorbis/dec/exports.lds
@@ -0,0 +1,5 @@
+{
+    global:
+        _Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPEPvPP17OMX_COMPONENTTYPE;
+    local: *;
+};
diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp
index cbb38fd..c7f9001 100644
--- a/media/libstagefright/colorconversion/ColorConverter.cpp
+++ b/media/libstagefright/colorconversion/ColorConverter.cpp
@@ -19,13 +19,28 @@
 #include <utils/Log.h>
 
 #include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
 #include <media/stagefright/ColorConverter.h>
 #include <media/stagefright/MediaErrors.h>
 
 #include "libyuv/convert_from.h"
 #include "libyuv/video_common.h"
 
+#include <sys/time.h>
+
 #define USE_LIBYUV
+#define PERF_PROFILING 0
+
+
+#if defined(__aarch64__) || defined(__ARM_NEON__)
+#define USE_NEON_Y410 1
+#else
+#define USE_NEON_Y410 0
+#endif
+
+#if USE_NEON_Y410
+#include <arm_neon.h>
+#endif
 
 namespace android {
 
@@ -48,6 +63,9 @@
                     || mDstFormat == OMX_COLOR_Format32BitRGBA8888
                     || mDstFormat == OMX_COLOR_Format32bitBGRA8888;
 
+        case OMX_COLOR_FormatYUV420Planar16:
+            return mDstFormat == OMX_COLOR_Format32BitRGBA1010102;
+
         case OMX_COLOR_FormatCbYCrY:
         case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
         case OMX_COLOR_FormatYUV420SemiPlanar:
@@ -81,10 +99,16 @@
 
     case OMX_COLOR_Format32bitBGRA8888:
     case OMX_COLOR_Format32BitRGBA8888:
+    case OMX_COLOR_Format32BitRGBA1010102:
         mBpp = 4;
         mStride = 4 * mWidth;
         break;
 
+    case OMX_COLOR_FormatYUV420Planar16:
+        mBpp = 2;
+        mStride = 2 * mWidth;
+        break;
+
     case OMX_COLOR_FormatYUV420Planar:
     case OMX_COLOR_FormatCbYCrY:
     case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
@@ -146,6 +170,19 @@
 #endif
             break;
 
+        case OMX_COLOR_FormatYUV420Planar16:
+        {
+#if PERF_PROFILING
+            int64_t startTimeUs = ALooper::GetNowUs();
+#endif
+            err = convertYUV420Planar16(src, dst);
+#if PERF_PROFILING
+            int64_t endTimeUs = ALooper::GetNowUs();
+            ALOGD("convertYUV420Planar16 took %lld us", (long long) (endTimeUs - startTimeUs));
+#endif
+            break;
+        }
+
         case OMX_COLOR_FormatCbYCrY:
             err = convertCbYCrY(src, dst);
             break;
@@ -406,6 +443,234 @@
     return OK;
 }
 
+/*
+ * Pack 10-bit YUV into RGBA_1010102.
+ *
+ * Media sends 10-bit YUV in a RGBA_1010102 format buffer. SF will handle
+ * the conversion to RGB using RenderEngine fallback.
+ *
+ * We do not perform a YUV->RGB conversion here, however the conversion with
+ * BT2020 to Full range is below for reference:
+ *
+ *   B = 1.168  *(Y - 64) + 2.148  *(U - 512)
+ *   G = 1.168  *(Y - 64) - 0.652  *(V - 512) - 0.188  *(U - 512)
+ *   R = 1.168  *(Y - 64) + 1.683  *(V - 512)
+ *
+ *   B = 1196/1024  *(Y - 64) + 2200/1024  *(U - 512)
+ *   G = .................... -  668/1024  *(V - 512) - 192/1024  *(U - 512)
+ *   R = .................... + 1723/1024  *(V - 512)
+ *
+ *   min_B = (1196  *(- 64) + 2200  *(- 512)) / 1024 = -1175
+ *   min_G = (1196  *(- 64) - 668  *(1023 - 512) - 192  *(1023 - 512)) / 1024 = -504
+ *   min_R = (1196  *(- 64) + 1723  *(- 512)) / 1024 = -937
+ *
+ *   max_B = (1196  *(1023 - 64) + 2200  *(1023 - 512)) / 1024 = 2218
+ *   max_G = (1196  *(1023 - 64) - 668  *(- 512) - 192  *(- 512)) / 1024 = 1551
+ *   max_R = (1196  *(1023 - 64) + 1723  *(1023 - 512)) / 1024 = 1980
+ *
+ *   clip range -1175 .. 2218
+ *
+ */
+
+#if !USE_NEON_Y410
+
+status_t ColorConverter::convertYUV420Planar16(
+        const BitmapParams &src, const BitmapParams &dst) {
+    uint8_t *dst_ptr = (uint8_t *)dst.mBits
+        + dst.mCropTop * dst.mStride + dst.mCropLeft * dst.mBpp;
+
+    const uint8_t *src_y =
+        (const uint8_t *)src.mBits + src.mCropTop * src.mStride + src.mCropLeft * src.mBpp;
+
+    const uint8_t *src_u =
+        (const uint8_t *)src.mBits + src.mStride * src.mHeight
+        + (src.mCropTop / 2) * (src.mStride / 2) + (src.mCropLeft / 2) * src.mBpp;
+
+    const uint8_t *src_v =
+        src_u + (src.mStride / 2) * (src.mHeight / 2);
+
+    // Converting two lines at a time, slightly faster
+    for (size_t y = 0; y < src.cropHeight(); y += 2) {
+        uint32_t *dst_top = (uint32_t *) dst_ptr;
+        uint32_t *dst_bot = (uint32_t *) (dst_ptr + dst.mStride);
+        uint16_t *ptr_ytop = (uint16_t*) src_y;
+        uint16_t *ptr_ybot = (uint16_t*) (src_y + src.mStride);
+        uint16_t *ptr_u = (uint16_t*) src_u;
+        uint16_t *ptr_v = (uint16_t*) src_v;
+
+        uint32_t u01, v01, y01, y23, y45, y67, uv0, uv1;
+        size_t x = 0;
+        for (; x < src.cropWidth() - 3; x += 4) {
+            u01 = *((uint32_t*)ptr_u); ptr_u += 2;
+            v01 = *((uint32_t*)ptr_v); ptr_v += 2;
+
+            y01 = *((uint32_t*)ptr_ytop); ptr_ytop += 2;
+            y23 = *((uint32_t*)ptr_ytop); ptr_ytop += 2;
+            y45 = *((uint32_t*)ptr_ybot); ptr_ybot += 2;
+            y67 = *((uint32_t*)ptr_ybot); ptr_ybot += 2;
+
+            uv0 = (u01 & 0x3FF) | ((v01 & 0x3FF) << 20);
+            uv1 = (u01 >> 16) | ((v01 >> 16) << 20);
+
+            *dst_top++ = ((y01 & 0x3FF) << 10) | uv0;
+            *dst_top++ = ((y01 >> 16) << 10) | uv0;
+            *dst_top++ = ((y23 & 0x3FF) << 10) | uv1;
+            *dst_top++ = ((y23 >> 16) << 10) | uv1;
+
+            *dst_bot++ = ((y45 & 0x3FF) << 10) | uv0;
+            *dst_bot++ = ((y45 >> 16) << 10) | uv0;
+            *dst_bot++ = ((y67 & 0x3FF) << 10) | uv1;
+            *dst_bot++ = ((y67 >> 16) << 10) | uv1;
+        }
+
+        // There should be at most 2 more pixels to process. Note that we don't
+        // need to consider odd case as the buffer is always aligned to even.
+        if (x < src.cropWidth()) {
+            u01 = *ptr_u;
+            v01 = *ptr_v;
+            y01 = *((uint32_t*)ptr_ytop);
+            y45 = *((uint32_t*)ptr_ybot);
+            uv0 = (u01 & 0x3FF) | ((v01 & 0x3FF) << 20);
+            *dst_top++ = ((y01 & 0x3FF) << 10) | uv0;
+            *dst_top++ = ((y01 >> 16) << 10) | uv0;
+            *dst_bot++ = ((y45 & 0x3FF) << 10) | uv0;
+            *dst_bot++ = ((y45 >> 16) << 10) | uv0;
+        }
+
+        src_y += src.mStride * 2;
+        src_u += src.mStride / 2;
+        src_v += src.mStride / 2;
+        dst_ptr += dst.mStride * 2;
+    }
+
+    return OK;
+}
+
+#else
+
+status_t ColorConverter::convertYUV420Planar16(
+        const BitmapParams &src, const BitmapParams &dst) {
+    uint8_t *out = (uint8_t *)dst.mBits
+        + dst.mCropTop * dst.mStride + dst.mCropLeft * dst.mBpp;
+
+    const uint8_t *src_y =
+        (const uint8_t *)src.mBits + src.mCropTop * src.mStride + src.mCropLeft * src.mBpp;
+
+    const uint8_t *src_u =
+        (const uint8_t *)src.mBits + src.mStride * src.mHeight
+        + (src.mCropTop / 2) * (src.mStride / 2) + (src.mCropLeft / 2) * src.mBpp;
+
+    const uint8_t *src_v =
+        src_u + (src.mStride / 2) * (src.mHeight / 2);
+
+    for (size_t y = 0; y < src.cropHeight(); y++) {
+        uint16_t *ptr_y = (uint16_t*) src_y;
+        uint16_t *ptr_u = (uint16_t*) src_u;
+        uint16_t *ptr_v = (uint16_t*) src_v;
+        uint32_t *ptr_out = (uint32_t *) out;
+
+        // Process 16-pixel at a time.
+        uint32_t *ptr_limit = ptr_out + (src.cropWidth() & ~15);
+        while (ptr_out < ptr_limit) {
+            uint16x4_t u0123 = vld1_u16(ptr_u); ptr_u += 4;
+            uint16x4_t u4567 = vld1_u16(ptr_u); ptr_u += 4;
+            uint16x4_t v0123 = vld1_u16(ptr_v); ptr_v += 4;
+            uint16x4_t v4567 = vld1_u16(ptr_v); ptr_v += 4;
+            uint16x4_t y0123 = vld1_u16(ptr_y); ptr_y += 4;
+            uint16x4_t y4567 = vld1_u16(ptr_y); ptr_y += 4;
+            uint16x4_t y89ab = vld1_u16(ptr_y); ptr_y += 4;
+            uint16x4_t ycdef = vld1_u16(ptr_y); ptr_y += 4;
+
+            uint32x2_t uvtempl;
+            uint32x4_t uvtempq;
+
+            uvtempq = vaddw_u16(vshll_n_u16(v0123, 20), u0123);
+
+            uvtempl = vget_low_u32(uvtempq);
+            uint32x4_t uv0011 = vreinterpretq_u32_u64(
+                    vaddw_u32(vshll_n_u32(uvtempl, 32), uvtempl));
+
+            uvtempl = vget_high_u32(uvtempq);
+            uint32x4_t uv2233 = vreinterpretq_u32_u64(
+                    vaddw_u32(vshll_n_u32(uvtempl, 32), uvtempl));
+
+            uvtempq = vaddw_u16(vshll_n_u16(v4567, 20), u4567);
+
+            uvtempl = vget_low_u32(uvtempq);
+            uint32x4_t uv4455 = vreinterpretq_u32_u64(
+                    vaddw_u32(vshll_n_u32(uvtempl, 32), uvtempl));
+
+            uvtempl = vget_high_u32(uvtempq);
+            uint32x4_t uv6677 = vreinterpretq_u32_u64(
+                    vaddw_u32(vshll_n_u32(uvtempl, 32), uvtempl));
+
+            uint32x4_t dsttemp;
+
+            dsttemp = vorrq_u32(uv0011, vshll_n_u16(y0123, 10));
+            vst1q_u32(ptr_out, dsttemp); ptr_out += 4;
+
+            dsttemp = vorrq_u32(uv2233, vshll_n_u16(y4567, 10));
+            vst1q_u32(ptr_out, dsttemp); ptr_out += 4;
+
+            dsttemp = vorrq_u32(uv4455, vshll_n_u16(y89ab, 10));
+            vst1q_u32(ptr_out, dsttemp); ptr_out += 4;
+
+            dsttemp = vorrq_u32(uv6677, vshll_n_u16(ycdef, 10));
+            vst1q_u32(ptr_out, dsttemp); ptr_out += 4;
+        }
+
+        src_y += src.mStride;
+        if (y & 1) {
+            src_u += src.mStride / 2;
+            src_v += src.mStride / 2;
+        }
+        out += dst.mStride;
+    }
+
+    // Process the left-overs out-of-loop, 2-pixel at a time. Note that we don't
+    // need to consider odd case as the buffer is always aligned to even.
+    if (src.cropWidth() & 15) {
+        size_t xstart = (src.cropWidth() & ~15);
+
+        uint8_t *out = (uint8_t *)dst.mBits + dst.mCropTop * dst.mStride
+                + (dst.mCropLeft + xstart) * dst.mBpp;
+
+        const uint8_t *src_y = (const uint8_t *)src.mBits + src.mCropTop * src.mStride
+                + (src.mCropLeft + xstart) * src.mBpp;
+
+        const uint8_t *src_u = (const uint8_t *)src.mBits + src.mStride * src.mHeight
+            + (src.mCropTop / 2) * (src.mStride / 2)
+            + ((src.mCropLeft + xstart) / 2) * src.mBpp;
+
+        const uint8_t *src_v = src_u + (src.mStride / 2) * (src.mHeight / 2);
+
+        for (size_t y = 0; y < src.cropHeight(); y++) {
+            uint16_t *ptr_y = (uint16_t*) src_y;
+            uint16_t *ptr_u = (uint16_t*) src_u;
+            uint16_t *ptr_v = (uint16_t*) src_v;
+            uint32_t *ptr_out = (uint32_t *) out;
+            for (size_t x = xstart; x < src.cropWidth(); x += 2) {
+                uint16_t u = *ptr_u++;
+                uint16_t v = *ptr_v++;
+                uint32_t y01 = *((uint32_t*)ptr_y); ptr_y += 2;
+                uint32_t uv = u | (((uint32_t)v) << 20);
+                *ptr_out++ = ((y01 & 0x3FF) << 10) | uv;
+                *ptr_out++ = ((y01 >> 16) << 10) | uv;
+            }
+            src_y += src.mStride;
+            if (y & 1) {
+                src_u += src.mStride / 2;
+                src_v += src.mStride / 2;
+            }
+            out += dst.mStride;
+        }
+    }
+
+    return OK;
+}
+
+#endif // USE_NEON_Y410
+
 status_t ColorConverter::convertQCOMYUV420SemiPlanar(
         const BitmapParams &src, const BitmapParams &dst) {
     uint8_t *kAdjustedClip = initClip();
diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
index a07787a..fca9c09 100644
--- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp
+++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
@@ -18,10 +18,11 @@
 #include <utils/Log.h>
 
 #include "../include/SoftwareRenderer.h"
-
 #include <cutils/properties.h> // for property_get
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ColorUtils.h>
+#include <media/stagefright/SurfaceUtils.h>
 #include <system/window.h>
 #include <ui/Fence.h>
 #include <ui/GraphicBufferMapper.h>
@@ -30,7 +31,6 @@
 
 namespace android {
 
-
 static int ALIGN(int x, int y) {
     // y must be a power of 2.
     return (x + y - 1) & ~(y - 1);
@@ -50,7 +50,9 @@
       mCropBottom(0),
       mCropWidth(0),
       mCropHeight(0),
-      mRotationDegrees(rotation) {
+      mRotationDegrees(rotation),
+      mDataSpace(HAL_DATASPACE_UNKNOWN) {
+    memset(&mHDRStaticInfo, 0, sizeof(mHDRStaticInfo));
 }
 
 SoftwareRenderer::~SoftwareRenderer() {
@@ -130,6 +132,13 @@
                 bufHeight = (mCropHeight + 1) & ~1;
                 break;
             }
+            case OMX_COLOR_FormatYUV420Planar16:
+            {
+                halFormat = HAL_PIXEL_FORMAT_RGBA_1010102;
+                bufWidth = (mCropWidth + 1) & ~1;
+                bufHeight = (mCropHeight + 1) & ~1;
+                break;
+            }
             default:
             {
                 break;
@@ -141,6 +150,10 @@
         mConverter = new ColorConverter(
                 mColorFormat, OMX_COLOR_Format16bitRGB565);
         CHECK(mConverter->isValid());
+    } else if (mColorFormat == OMX_COLOR_FormatYUV420Planar16) {
+        mConverter = new ColorConverter(
+                mColorFormat, OMX_COLOR_Format32BitRGBA1010102);
+        CHECK(mConverter->isValid());
     }
 
     CHECK(mNativeWindow != NULL);
@@ -365,12 +378,29 @@
     // color conversion to RGB. For now, just mark dataspace for YUV rendering.
     android_dataspace dataSpace;
     if (format->findInt32("android._dataspace", (int32_t *)&dataSpace) && dataSpace != mDataSpace) {
+        mDataSpace = dataSpace;
+
+        if (mConverter != NULL) {
+            // graphics only supports full range RGB. ColorConverter should have
+            // converted any YUV to full range.
+            dataSpace = (android_dataspace)
+                    ((dataSpace & ~HAL_DATASPACE_RANGE_MASK) | HAL_DATASPACE_RANGE_FULL);
+        }
+
         ALOGD("setting dataspace on output surface to #%x", dataSpace);
         if ((err = native_window_set_buffers_data_space(mNativeWindow.get(), dataSpace))) {
             ALOGW("failed to set dataspace on surface (%d)", err);
         }
-        mDataSpace = dataSpace;
     }
+    if (format->contains("hdr-static-info")) {
+        HDRStaticInfo info;
+        if (ColorUtils::getHDRStaticInfoFromFormat(format, &info)
+            && memcmp(&mHDRStaticInfo, &info, sizeof(info))) {
+            setNativeWindowHdrMetadata(mNativeWindow.get(), &info);
+            mHDRStaticInfo = info;
+        }
+    }
+
     if ((err = mNativeWindow->queueBuffer(mNativeWindow.get(), buf, -1)) != 0) {
         ALOGW("Surface::queueBuffer returned error %d", err);
     } else {
diff --git a/media/libstagefright/exports.lds b/media/libstagefright/exports.lds
new file mode 100644
index 0000000..59dfc49
--- /dev/null
+++ b/media/libstagefright/exports.lds
@@ -0,0 +1,508 @@
+{
+    global:
+        *;
+    local:
+        _ZN7android4ESDS*;
+        _ZNK7android4ESDS*;
+        _ZN7android14ColorConverter*;
+        _ZNK7android14ColorConverter*;
+        _ZN7android16SoftwareRenderer*;
+        ABGRToARGB;
+        ABGRToI420;
+        ABGRToUVRow_Any_NEON;
+        ABGRToUVRow_C;
+        ABGRToUVRow_NEON;
+        ABGRToYRow_Any_NEON;
+        ABGRToYRow_C;
+        ABGRToYRow_NEON;
+        Android420ToI420;
+        ARGB1555ToARGB;
+        ARGB1555ToARGBRow_Any_NEON;
+        ARGB1555ToARGBRow_C;
+        ARGB1555ToARGBRow_NEON;
+        ARGB1555ToI420;
+        ARGB1555ToUVRow_Any_NEON;
+        ARGB1555ToUVRow_C;
+        ARGB1555ToUVRow_NEON;
+        ARGB1555ToYRow_Any_NEON;
+        ARGB1555ToYRow_C;
+        ARGB1555ToYRow_NEON;
+        ARGB4444ToARGB;
+        ARGB4444ToARGBRow_Any_NEON;
+        ARGB4444ToARGBRow_C;
+        ARGB4444ToARGBRow_NEON;
+        ARGB4444ToI420;
+        ARGB4444ToUVRow_Any_NEON;
+        ARGB4444ToUVRow_C;
+        ARGB4444ToUVRow_NEON;
+        ARGB4444ToYRow_Any_NEON;
+        ARGB4444ToYRow_C;
+        ARGB4444ToYRow_NEON;
+        ARGBAdd;
+        ARGBAddRow_Any_NEON;
+        ARGBAddRow_C;
+        ARGBAddRow_NEON;
+        ARGBAffineRow_C;
+        ARGBAttenuate;
+        ARGBAttenuateRow_Any_NEON;
+        ARGBAttenuateRow_C;
+        ARGBAttenuateRow_NEON;
+        ARGBBlend;
+        ARGBBlendRow_C;
+        ARGBBlendRow_NEON;
+        ARGBBlur;
+        ARGBColorMatrix;
+        ARGBColorMatrixRow_C;
+        ARGBColorMatrixRow_NEON;
+        ARGBColorTable;
+        ARGBColorTableRow_C;
+        ARGBComputeCumulativeSum;
+        ARGBCopy;
+        ARGBCopyAlpha;
+        ARGBCopyAlphaRow_C;
+        ARGBCopyYToAlpha;
+        ARGBCopyYToAlphaRow_C;
+        ARGBExtractAlpha;
+        ARGBExtractAlphaRow_Any_NEON;
+        ARGBExtractAlphaRow_C;
+        ARGBExtractAlphaRow_NEON;
+        ARGBGray;
+        ARGBGrayRow_C;
+        ARGBGrayRow_NEON;
+        ARGBGrayTo;
+        ARGBInterpolate;
+        ARGBLumaColorTable;
+        ARGBLumaColorTableRow_C;
+        ARGBMirror;
+        ARGBMirrorRow_Any_NEON;
+        ARGBMirrorRow_C;
+        ARGBMirrorRow_NEON;
+        ARGBMultiply;
+        ARGBMultiplyRow_Any_NEON;
+        ARGBMultiplyRow_C;
+        ARGBMultiplyRow_NEON;
+        ARGBPolynomial;
+        ARGBPolynomialRow_C;
+        ARGBQuantize;
+        ARGBQuantizeRow_C;
+        ARGBQuantizeRow_NEON;
+        ARGBRect;
+        ARGBSepia;
+        ARGBSepiaRow_C;
+        ARGBSepiaRow_NEON;
+        ARGBSetRow_Any_NEON;
+        ARGBSetRow_C;
+        ARGBSetRow_NEON;
+        ARGBShade;
+        ARGBShadeRow_C;
+        ARGBShadeRow_NEON;
+        ARGBShuffle;
+        ARGBShuffleRow_Any_NEON;
+        ARGBShuffleRow_C;
+        ARGBShuffleRow_NEON;
+        ARGBSobel;
+        ARGBSobelToPlane;
+        ARGBSobelXY;
+        ARGBSubtract;
+        ARGBSubtractRow_Any_NEON;
+        ARGBSubtractRow_C;
+        ARGBSubtractRow_NEON;
+        ARGBToABGR;
+        ARGBToARGB1555Row_Any_NEON;
+        ARGBToARGB1555Row_C;
+        ARGBToARGB1555Row_NEON;
+        ARGBToARGB4444Row_Any_NEON;
+        ARGBToARGB4444Row_C;
+        ARGBToARGB4444Row_NEON;
+        ARGBToBGRA;
+        ARGBToI420;
+        ARGBToRAWRow_Any_NEON;
+        ARGBToRAWRow_C;
+        ARGBToRAWRow_NEON;
+        ARGBToRGB24Row_Any_NEON;
+        ARGBToRGB24Row_C;
+        ARGBToRGB24Row_NEON;
+        ARGBToRGB565DitherRow_Any_NEON;
+        ARGBToRGB565DitherRow_C;
+        ARGBToRGB565DitherRow_NEON;
+        ARGBToRGB565Row_Any_NEON;
+        ARGBToRGB565Row_C;
+        ARGBToRGB565Row_NEON;
+        ARGBToUV444Row_Any_NEON;
+        ARGBToUV444Row_C;
+        ARGBToUV444Row_NEON;
+        ARGBToUVJRow_Any_NEON;
+        ARGBToUVJRow_C;
+        ARGBToUVJRow_NEON;
+        ARGBToUVRow_Any_NEON;
+        ARGBToUVRow_C;
+        ARGBToUVRow_NEON;
+        ARGBToYJRow_Any_NEON;
+        ARGBToYJRow_C;
+        ARGBToYJRow_NEON;
+        ARGBToYRow_Any_NEON;
+        ARGBToYRow_C;
+        ARGBToYRow_NEON;
+        ARGBUnattenuate;
+        ARGBUnattenuateRow_C;
+        ArmCpuCaps;
+        BGRAToARGB;
+        BGRAToI420;
+        BGRAToUVRow_Any_NEON;
+        BGRAToUVRow_C;
+        BGRAToUVRow_NEON;
+        BGRAToYRow_Any_NEON;
+        BGRAToYRow_C;
+        BGRAToYRow_NEON;
+        BlendPlane;
+        BlendPlaneRow_C;
+        CanonicalFourCC;
+        ComputeCumulativeSumRow_C;
+        ConvertFromI420;
+        CopyPlane;
+        CopyPlane_16;
+        CopyRow_16_C;
+        CopyRow_Any_NEON;
+        CopyRow_C;
+        CopyRow_NEON;
+        CpuId;
+        cpu_info_;
+        CumulativeSumToAverageRow_C;
+        FixedDiv1_C;
+        FixedDiv_C;
+        fixed_invtbl8;
+        GetARGBBlend;
+        H420ToABGR;
+        H420ToARGB;
+        H422ToABGR;
+        H422ToARGB;
+        HalfFloat1Row_Any_NEON;
+        HalfFloat1Row_NEON;
+        HalfFloatPlane;
+        HalfFloatRow_Any_NEON;
+        HalfFloatRow_C;
+        HalfFloatRow_NEON;
+        I400Copy;
+        I400Mirror;
+        I400ToARGB;
+        I400ToARGBRow_Any_NEON;
+        I400ToARGBRow_C;
+        I400ToARGBRow_NEON;
+        I400ToI400;
+        I400ToI420;
+        I420AlphaToABGR;
+        I420AlphaToARGB;
+        I420Blend;
+        I420Copy;
+        I420Interpolate;
+        I420Mirror;
+        I420Rect;
+        I420Scale;
+        I420Scale_16;
+        I420ToABGR;
+        I420ToARGB;
+        I420ToARGB1555;
+        I420ToARGB4444;
+        I420ToBGRA;
+        I420ToI400;
+        I420ToI422;
+        I420ToI444;
+        I420ToNV12;
+        I420ToNV21;
+        I420ToRAW;
+        I420ToRGB24;
+        I420ToRGB565;
+        I420ToRGB565Dither;
+        I420ToRGBA;
+        I420ToUYVY;
+        I420ToYUY2;
+        I422AlphaToARGBRow_Any_NEON;
+        I422AlphaToARGBRow_C;
+        I422AlphaToARGBRow_NEON;
+        I422Copy;
+        I422ToABGR;
+        I422ToARGB;
+        I422ToARGB1555Row_Any_NEON;
+        I422ToARGB1555Row_C;
+        I422ToARGB1555Row_NEON;
+        I422ToARGB4444Row_Any_NEON;
+        I422ToARGB4444Row_C;
+        I422ToARGB4444Row_NEON;
+        I422ToARGBRow_Any_NEON;
+        I422ToARGBRow_C;
+        I422ToARGBRow_NEON;
+        I422ToBGRA;
+        I422ToI420;
+        I422ToRGB24Row_Any_NEON;
+        I422ToRGB24Row_C;
+        I422ToRGB24Row_NEON;
+        I422ToRGB565;
+        I422ToRGB565Row_Any_NEON;
+        I422ToRGB565Row_C;
+        I422ToRGB565Row_NEON;
+        I422ToRGBA;
+        I422ToRGBARow_Any_NEON;
+        I422ToRGBARow_C;
+        I422ToRGBARow_NEON;
+        I422ToUYVY;
+        I422ToUYVYRow_Any_NEON;
+        I422ToUYVYRow_C;
+        I422ToUYVYRow_NEON;
+        I422ToYUY2;
+        I422ToYUY2Row_Any_NEON;
+        I422ToYUY2Row_C;
+        I422ToYUY2Row_NEON;
+        I444Copy;
+        I444ToABGR;
+        I444ToARGB;
+        I444ToARGBRow_Any_NEON;
+        I444ToARGBRow_C;
+        I444ToARGBRow_NEON;
+        I444ToI420;
+        InitCpuFlags;
+        InterpolatePlane;
+        InterpolateRow_16_C;
+        InterpolateRow_Any_NEON;
+        InterpolateRow_C;
+        InterpolateRow_NEON;
+        J400ToARGB;
+        J400ToARGBRow_Any_NEON;
+        J400ToARGBRow_C;
+        J400ToARGBRow_NEON;
+        J420ToABGR;
+        J420ToARGB;
+        J422ToABGR;
+        J422ToARGB;
+        J444ToARGB;
+        kYuvH709Constants;
+        kYuvI601Constants;
+        kYuvJPEGConstants;
+        kYvuH709Constants;
+        kYvuI601Constants;
+        kYvuJPEGConstants;
+        M420ToARGB;
+        M420ToI420;
+        MaskCpuFlags;
+        MergeUVPlane;
+        MergeUVRow_Any_NEON;
+        MergeUVRow_C;
+        MergeUVRow_NEON;
+        MipsCpuCaps;
+        MirrorPlane;
+        MirrorRow_Any_NEON;
+        MirrorRow_C;
+        MirrorRow_NEON;
+        MirrorUVRow_C;
+        MirrorUVRow_NEON;
+        NV12ToARGBRow_Any_NEON;
+        NV12ToARGBRow_C;
+        NV12ToARGBRow_NEON;
+        NV12ToI420;
+        NV12ToRGB565;
+        NV12ToRGB565Row_Any_NEON;
+        NV12ToRGB565Row_C;
+        NV12ToRGB565Row_NEON;
+        NV21ToARGB;
+        NV21ToARGBRow_Any_NEON;
+        NV21ToARGBRow_C;
+        NV21ToARGBRow_NEON;
+        NV21ToI420;
+        RAWToARGB;
+        RAWToARGBRow_Any_NEON;
+        RAWToARGBRow_C;
+        RAWToARGBRow_NEON;
+        RAWToI420;
+        RAWToRGB24;
+        RAWToRGB24Row_Any_NEON;
+        RAWToRGB24Row_C;
+        RAWToRGB24Row_NEON;
+        RAWToUVRow_Any_NEON;
+        RAWToUVRow_C;
+        RAWToUVRow_NEON;
+        RAWToYRow_Any_NEON;
+        RAWToYRow_C;
+        RAWToYRow_NEON;
+        RGB24ToARGB;
+        RGB24ToARGBRow_Any_NEON;
+        RGB24ToARGBRow_C;
+        RGB24ToARGBRow_NEON;
+        RGB24ToI420;
+        RGB24ToUVRow_Any_NEON;
+        RGB24ToUVRow_C;
+        RGB24ToUVRow_NEON;
+        RGB24ToYRow_Any_NEON;
+        RGB24ToYRow_C;
+        RGB24ToYRow_NEON;
+        RGB565ToARGB;
+        RGB565ToARGBRow_Any_NEON;
+        RGB565ToARGBRow_C;
+        RGB565ToARGBRow_NEON;
+        RGB565ToI420;
+        RGB565ToUVRow_Any_NEON;
+        RGB565ToUVRow_C;
+        RGB565ToUVRow_NEON;
+        RGB565ToYRow_Any_NEON;
+        RGB565ToYRow_C;
+        RGB565ToYRow_NEON;
+        RGBAToARGB;
+        RGBAToI420;
+        RGBAToUVRow_Any_NEON;
+        RGBAToUVRow_C;
+        RGBAToUVRow_NEON;
+        RGBAToYRow_Any_NEON;
+        RGBAToYRow_C;
+        RGBAToYRow_NEON;
+        RGBColorMatrix;
+        RGBColorTable;
+        RGBColorTableRow_C;
+        Scale;
+        ScaleAddRow_16_C;
+        ScaleAddRow_C;
+        ScaleAddRows_NEON;
+        ScaleARGBCols64_C;
+        ScaleARGBCols_Any_NEON;
+        ScaleARGBCols_C;
+        ScaleARGBCols_NEON;
+        ScaleARGBColsUp2_C;
+        ScaleARGBFilterCols64_C;
+        ScaleARGBFilterCols_Any_NEON;
+        ScaleARGBFilterCols_C;
+        ScaleARGBFilterCols_NEON;
+        ScaleARGBRowDown2_Any_NEON;
+        ScaleARGBRowDown2Box_Any_NEON;
+        ScaleARGBRowDown2Box_C;
+        ScaleARGBRowDown2Box_NEON;
+        ScaleARGBRowDown2_C;
+        ScaleARGBRowDown2Linear_Any_NEON;
+        ScaleARGBRowDown2Linear_C;
+        ScaleARGBRowDown2Linear_NEON;
+        ScaleARGBRowDown2_NEON;
+        ScaleARGBRowDownEven_Any_NEON;
+        ScaleARGBRowDownEvenBox_Any_NEON;
+        ScaleARGBRowDownEvenBox_C;
+        ScaleARGBRowDownEvenBox_NEON;
+        ScaleARGBRowDownEven_C;
+        ScaleARGBRowDownEven_NEON;
+        ScaleCols_16_C;
+        ScaleCols_C;
+        ScaleColsUp2_16_C;
+        ScaleColsUp2_C;
+        ScaleFilterCols_16_C;
+        ScaleFilterCols64_16_C;
+        ScaleFilterCols64_C;
+        ScaleFilterCols_Any_NEON;
+        ScaleFilterCols_C;
+        ScaleFilterCols_NEON;
+        ScaleFilterReduce;
+        ScaleFilterRows_NEON;
+        ScaleOffset;
+        ScalePlane;
+        ScalePlane_16;
+        ScalePlaneBilinearDown;
+        ScalePlaneBilinearDown_16;
+        ScalePlaneBilinearUp;
+        ScalePlaneBilinearUp_16;
+        ScalePlaneVertical;
+        ScalePlaneVertical_16;
+        ScaleRowDown2_16_C;
+        ScaleRowDown2_Any_NEON;
+        ScaleRowDown2Box_16_C;
+        ScaleRowDown2Box_Any_NEON;
+        ScaleRowDown2Box_C;
+        ScaleRowDown2Box_NEON;
+        ScaleRowDown2Box_Odd_C;
+        ScaleRowDown2Box_Odd_NEON;
+        ScaleRowDown2_C;
+        ScaleRowDown2Linear_16_C;
+        ScaleRowDown2Linear_Any_NEON;
+        ScaleRowDown2Linear_C;
+        ScaleRowDown2Linear_NEON;
+        ScaleRowDown2_NEON;
+        ScaleRowDown34_0_Box_16_C;
+        ScaleRowDown34_0_Box_Any_NEON;
+        ScaleRowDown34_0_Box_C;
+        ScaleRowDown34_0_Box_NEON;
+        ScaleRowDown34_16_C;
+        ScaleRowDown34_1_Box_16_C;
+        ScaleRowDown34_1_Box_Any_NEON;
+        ScaleRowDown34_1_Box_C;
+        ScaleRowDown34_1_Box_NEON;
+        ScaleRowDown34_Any_NEON;
+        ScaleRowDown34_C;
+        ScaleRowDown34_NEON;
+        ScaleRowDown38_16_C;
+        ScaleRowDown38_2_Box_16_C;
+        ScaleRowDown38_2_Box_Any_NEON;
+        ScaleRowDown38_2_Box_C;
+        ScaleRowDown38_2_Box_NEON;
+        ScaleRowDown38_3_Box_16_C;
+        ScaleRowDown38_3_Box_Any_NEON;
+        ScaleRowDown38_3_Box_C;
+        ScaleRowDown38_3_Box_NEON;
+        ScaleRowDown38_Any_NEON;
+        ScaleRowDown38_C;
+        ScaleRowDown38_NEON;
+        ScaleRowDown4_16_C;
+        ScaleRowDown4_Any_NEON;
+        ScaleRowDown4Box_16_C;
+        ScaleRowDown4Box_Any_NEON;
+        ScaleRowDown4Box_C;
+        ScaleRowDown4Box_NEON;
+        ScaleRowDown4_C;
+        ScaleRowDown4_NEON;
+        ScaleSlope;
+        SetPlane;
+        SetRow_Any_NEON;
+        SetRow_C;
+        SetRow_NEON;
+        SobelRow_Any_NEON;
+        SobelRow_C;
+        SobelRow_NEON;
+        SobelToPlaneRow_Any_NEON;
+        SobelToPlaneRow_C;
+        SobelToPlaneRow_NEON;
+        SobelXRow_C;
+        SobelXRow_NEON;
+        SobelXYRow_Any_NEON;
+        SobelXYRow_C;
+        SobelXYRow_NEON;
+        SobelYRow_C;
+        SobelYRow_NEON;
+        SplitUVPlane;
+        SplitUVRow_Any_NEON;
+        SplitUVRow_C;
+        SplitUVRow_NEON;
+        UYVYToARGB;
+        UYVYToARGBRow_Any_NEON;
+        UYVYToARGBRow_C;
+        UYVYToARGBRow_NEON;
+        UYVYToI420;
+        UYVYToI422;
+        UYVYToNV12;
+        UYVYToUV422Row_Any_NEON;
+        UYVYToUV422Row_C;
+        UYVYToUV422Row_NEON;
+        UYVYToUVRow_Any_NEON;
+        UYVYToUVRow_C;
+        UYVYToUVRow_NEON;
+        UYVYToYRow_Any_NEON;
+        UYVYToYRow_C;
+        UYVYToYRow_NEON;
+        YUY2ToARGB;
+        YUY2ToARGBRow_Any_NEON;
+        YUY2ToARGBRow_C;
+        YUY2ToARGBRow_NEON;
+        YUY2ToI420;
+        YUY2ToI422;
+        YUY2ToNV12;
+        YUY2ToUV422Row_Any_NEON;
+        YUY2ToUV422Row_C;
+        YUY2ToUV422Row_NEON;
+        YUY2ToUVRow_Any_NEON;
+        YUY2ToUVRow_C;
+        YUY2ToUVRow_NEON;
+        YUY2ToY;
+        YUY2ToYRow_Any_NEON;
+        YUY2ToYRow_C;
+        YUY2ToYRow_NEON;
+};
diff --git a/media/libstagefright/foundation/ColorUtils.cpp b/media/libstagefright/foundation/ColorUtils.cpp
index 88a8351..c4eaa27 100644
--- a/media/libstagefright/foundation/ColorUtils.cpp
+++ b/media/libstagefright/foundation/ColorUtils.cpp
@@ -398,6 +398,7 @@
 }
 
 // TODO: move this into a Video HAL
+const static
 ALookup<CU::ColorStandard, std::pair<CA::Primaries, CA::MatrixCoeffs>> sStandardFallbacks {
     {
         { CU::kColorStandardBT601_625, { CA::PrimariesBT709_5, CA::MatrixBT470_6M } },
@@ -420,6 +421,7 @@
     }
 };
 
+const static
 ALookup<CU::ColorStandard, CA::Primaries> sStandardPrimariesFallbacks {
     {
         { CU::kColorStandardFilm,                 CA::PrimariesGenericFilm },
@@ -430,7 +432,8 @@
     }
 };
 
-static ALookup<android_dataspace, android_dataspace> sLegacyDataSpaceToV0 {
+const static
+ALookup<android_dataspace, android_dataspace> sLegacyDataSpaceToV0 {
     {
         { HAL_DATASPACE_SRGB, HAL_DATASPACE_V0_SRGB },
         { HAL_DATASPACE_BT709, HAL_DATASPACE_V0_BT709 },
@@ -441,6 +444,73 @@
     }
 };
 
+#define GET_HAL_ENUM(class, name) HAL_DATASPACE_##class##name
+#define GET_HAL_BITFIELD(class, name) (GET_HAL_ENUM(class, _##name) >> GET_HAL_ENUM(class, _SHIFT))
+
+const static
+ALookup<CU::ColorStandard, uint32_t> sGfxStandards {
+    {
+        { CU::kColorStandardUnspecified,          GET_HAL_BITFIELD(STANDARD, UNSPECIFIED) },
+        { CU::kColorStandardBT709,                GET_HAL_BITFIELD(STANDARD, BT709) },
+        { CU::kColorStandardBT601_625,            GET_HAL_BITFIELD(STANDARD, BT601_625) },
+        { CU::kColorStandardBT601_625_Unadjusted, GET_HAL_BITFIELD(STANDARD, BT601_625_UNADJUSTED) },
+        { CU::kColorStandardBT601_525,            GET_HAL_BITFIELD(STANDARD, BT601_525) },
+        { CU::kColorStandardBT601_525_Unadjusted, GET_HAL_BITFIELD(STANDARD, BT601_525_UNADJUSTED) },
+        { CU::kColorStandardBT2020,               GET_HAL_BITFIELD(STANDARD, BT2020) },
+        { CU::kColorStandardBT2020Constant,       GET_HAL_BITFIELD(STANDARD, BT2020_CONSTANT_LUMINANCE) },
+        { CU::kColorStandardBT470M,               GET_HAL_BITFIELD(STANDARD, BT470M) },
+        { CU::kColorStandardFilm,                 GET_HAL_BITFIELD(STANDARD, FILM) },
+        { CU::kColorStandardDCI_P3,               GET_HAL_BITFIELD(STANDARD, DCI_P3) },
+    }
+};
+
+// verify public values are stable
+static_assert(CU::kColorStandardUnspecified == 0, "SDK mismatch"); // N
+static_assert(CU::kColorStandardBT709 == 1, "SDK mismatch"); // N
+static_assert(CU::kColorStandardBT601_625 == 2, "SDK mismatch"); // N
+static_assert(CU::kColorStandardBT601_525 == 4, "SDK mismatch"); // N
+static_assert(CU::kColorStandardBT2020 == 6, "SDK mismatch"); // N
+
+const static
+ALookup<CU::ColorTransfer, uint32_t> sGfxTransfers {
+    {
+        { CU::kColorTransferUnspecified, GET_HAL_BITFIELD(TRANSFER, UNSPECIFIED) },
+        { CU::kColorTransferLinear,      GET_HAL_BITFIELD(TRANSFER, LINEAR) },
+        { CU::kColorTransferSRGB,        GET_HAL_BITFIELD(TRANSFER, SRGB) },
+        { CU::kColorTransferSMPTE_170M,  GET_HAL_BITFIELD(TRANSFER, SMPTE_170M) },
+        { CU::kColorTransferGamma22,     GET_HAL_BITFIELD(TRANSFER, GAMMA2_2) },
+        { CU::kColorTransferGamma28,     GET_HAL_BITFIELD(TRANSFER, GAMMA2_8) },
+        { CU::kColorTransferST2084,      GET_HAL_BITFIELD(TRANSFER, ST2084) },
+        { CU::kColorTransferHLG,         GET_HAL_BITFIELD(TRANSFER, HLG) },
+    }
+};
+
+// verify public values are stable
+static_assert(CU::kColorTransferUnspecified == 0, "SDK mismatch"); // N
+static_assert(CU::kColorTransferLinear == 1, "SDK mismatch"); // N
+static_assert(CU::kColorTransferSRGB == 2, "SDK mismatch"); // N
+static_assert(CU::kColorTransferSMPTE_170M == 3, "SDK mismatch"); // N
+static_assert(CU::kColorTransferST2084 == 6, "SDK mismatch"); // N
+static_assert(CU::kColorTransferHLG == 7, "SDK mismatch"); // N
+
+const static
+ALookup<CU::ColorRange, uint32_t> sGfxRanges {
+    {
+        { CU::kColorRangeUnspecified, GET_HAL_BITFIELD(RANGE, UNSPECIFIED) },
+        { CU::kColorRangeFull,        GET_HAL_BITFIELD(RANGE, FULL) },
+        { CU::kColorRangeLimited,     GET_HAL_BITFIELD(RANGE, LIMITED) },
+    }
+};
+
+// verify public values are stable
+static_assert(CU::kColorRangeUnspecified == 0, "SDK mismatch"); // N
+static_assert(CU::kColorRangeFull == 1, "SDK mismatch"); // N
+static_assert(CU::kColorRangeLimited == 2, "SDK mismatch"); // N
+
+#undef GET_HAL_BITFIELD
+#undef GET_HAL_ENUM
+
+
 bool ColorUtils::convertDataSpaceToV0(android_dataspace &dataSpace) {
     (void)sLegacyDataSpaceToV0.lookup(dataSpace, &dataSpace);
     return (dataSpace & 0xC000FFFF) == 0;
@@ -507,9 +577,23 @@
         }
     }
 
+    // assume 1-to-1 mapping to HAL values (to deal with potential vendor extensions)
+    uint32_t gfxRange = range;
+    uint32_t gfxStandard = standard;
+    uint32_t gfxTransfer = transfer;
+    // TRICKY: use & to ensure all three mappings are completed
+    if (!(sGfxRanges.map(range, &gfxRange) & sGfxStandards.map(standard, &gfxStandard)
+            & sGfxTransfers.map(transfer, &gfxTransfer))) {
+        ALOGW("could not safely map platform color aspects (R:%u(%s) S:%u(%s) T:%u(%s) to "
+              "graphics dataspace (R:%u S:%u T:%u)",
+              range, asString(range), standard, asString(standard), transfer, asString(transfer),
+              gfxRange, gfxStandard, gfxTransfer);
+    }
+
     android_dataspace dataSpace = (android_dataspace)(
-            (range << HAL_DATASPACE_RANGE_SHIFT) | (standard << HAL_DATASPACE_STANDARD_SHIFT) |
-            (transfer << HAL_DATASPACE_TRANSFER_SHIFT));
+            (gfxRange << HAL_DATASPACE_RANGE_SHIFT) |
+            (gfxStandard << HAL_DATASPACE_STANDARD_SHIFT) |
+            (gfxTransfer << HAL_DATASPACE_TRANSFER_SHIFT));
     (void)sLegacyDataSpaceToV0.rlookup(dataSpace, &dataSpace);
 
     if (!mayExpand) {
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/ColorUtils.h b/media/libstagefright/foundation/include/media/stagefright/foundation/ColorUtils.h
index b889a02..d6c768d 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/ColorUtils.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/ColorUtils.h
@@ -39,26 +39,28 @@
      * vendor-extension section so they won't collide with future platform values.
      */
 
-#define GET_HAL_ENUM(class, name) HAL_DATASPACE_##class##name
-#define GET_HAL_BITFIELD(class, name) (GET_HAL_ENUM(class, _##name) >> GET_HAL_ENUM(class, _SHIFT))
-
+    /**
+     * graphic.h constants changed in Android 8.0 after ColorStandard values were already public
+     * in Android 7.0. We will not deal with the break in graphic.h here, but list the public
+     * Android SDK MediaFormat values here.
+     */
     enum ColorStandard : uint32_t {
-        kColorStandardUnspecified =          GET_HAL_BITFIELD(STANDARD, UNSPECIFIED),
-        kColorStandardBT709 =                GET_HAL_BITFIELD(STANDARD, BT709),
-        kColorStandardBT601_625 =            GET_HAL_BITFIELD(STANDARD, BT601_625),
-        kColorStandardBT601_625_Unadjusted = GET_HAL_BITFIELD(STANDARD, BT601_625_UNADJUSTED),
-        kColorStandardBT601_525 =            GET_HAL_BITFIELD(STANDARD, BT601_525),
-        kColorStandardBT601_525_Unadjusted = GET_HAL_BITFIELD(STANDARD, BT601_525_UNADJUSTED),
-        kColorStandardBT2020 =               GET_HAL_BITFIELD(STANDARD, BT2020),
-        kColorStandardBT2020Constant =       GET_HAL_BITFIELD(STANDARD, BT2020_CONSTANT_LUMINANCE),
-        kColorStandardBT470M =               GET_HAL_BITFIELD(STANDARD, BT470M),
-        kColorStandardFilm =                 GET_HAL_BITFIELD(STANDARD, FILM),
-        kColorStandardMax =                  GET_HAL_BITFIELD(STANDARD, MASK),
+        kColorStandardUnspecified =          0,
+        kColorStandardBT709 =                1,
+        kColorStandardBT601_625 =            2,
+        kColorStandardBT601_625_Unadjusted = 3, // not in SDK
+        kColorStandardBT601_525 =            4,
+        kColorStandardBT601_525_Unadjusted = 5, // not in SDK
+        kColorStandardBT2020 =               6,
+        kColorStandardBT2020Constant =       7, // not in SDK
+        kColorStandardBT470M =               8, // not in SDK
+        kColorStandardFilm =                 9, // not in SDK
+        kColorStandardDCI_P3 =               10, // not in SDK, new in Android 8.0
 
         /* This marks a section of color-standard values that are not supported by graphics HAL,
            but track defined color primaries-matrix coefficient combinations in media.
            These are stable for a given release. */
-        kColorStandardExtendedStart = kColorStandardMax + 1,
+        kColorStandardExtendedStart = 64,
 
         /* This marks a section of color-standard values that are not supported by graphics HAL
            nor using media defined color primaries or matrix coefficients. These may differ per
@@ -67,19 +69,19 @@
     };
 
     enum ColorTransfer : uint32_t  {
-        kColorTransferUnspecified = GET_HAL_BITFIELD(TRANSFER, UNSPECIFIED),
-        kColorTransferLinear =      GET_HAL_BITFIELD(TRANSFER, LINEAR),
-        kColorTransferSRGB =        GET_HAL_BITFIELD(TRANSFER, SRGB),
-        kColorTransferSMPTE_170M =  GET_HAL_BITFIELD(TRANSFER, SMPTE_170M),
-        kColorTransferGamma22 =     GET_HAL_BITFIELD(TRANSFER, GAMMA2_2),
-        kColorTransferGamma28 =     GET_HAL_BITFIELD(TRANSFER, GAMMA2_8),
-        kColorTransferST2084 =      GET_HAL_BITFIELD(TRANSFER, ST2084),
-        kColorTransferHLG =         GET_HAL_BITFIELD(TRANSFER, HLG),
-        kColorTransferMax =         GET_HAL_BITFIELD(TRANSFER, MASK),
+        kColorTransferUnspecified = 0,
+        kColorTransferLinear =      1,
+        kColorTransferSRGB =        2,
+        kColorTransferSMPTE_170M =  3, // not in SDK
+        kColorTransferGamma22 =     4, // not in SDK
+        kColorTransferGamma28 =     5, // not in SDK
+        kColorTransferST2084 =      6,
+        kColorTransferHLG =         7,
+        kColorTransferGamma26 =     8, // not in SDK, new in Android 8.0
 
         /* This marks a section of color-transfer values that are not supported by graphics HAL,
            but track media-defined color-transfer. These are stable for a given release. */
-        kColorTransferExtendedStart = kColorTransferMax + 1,
+        kColorTransferExtendedStart = 32,
 
         /* This marks a section of color-transfer values that are not supported by graphics HAL
            nor defined by media. These may differ per device. */
@@ -87,23 +89,19 @@
     };
 
     enum ColorRange : uint32_t  {
-        kColorRangeUnspecified = GET_HAL_BITFIELD(RANGE, UNSPECIFIED),
-        kColorRangeFull =        GET_HAL_BITFIELD(RANGE, FULL),
-        kColorRangeLimited =     GET_HAL_BITFIELD(RANGE, LIMITED),
-        kColorRangeMax =         GET_HAL_BITFIELD(RANGE, MASK),
+        kColorRangeUnspecified = 0,
+        kColorRangeFull =        1,
+        kColorRangeLimited =     2,
 
         /* This marks a section of color-transfer values that are not supported by graphics HAL,
            but track media-defined color-transfer. These are stable for a given release. */
-        kColorRangeExtendedStart = kColorRangeMax + 1,
+        kColorRangeExtendedStart = 8,
 
         /* This marks a section of color-transfer values that are not supported by graphics HAL
            nor defined by media. These may differ per device. */
         kColorRangeVendorStart = 0x10000,
     };
 
-#undef GET_HAL_BITFIELD
-#undef GET_HAL_ENUM
-
     /*
      * Static utilities for codec support
      */
@@ -197,7 +195,8 @@
         case ColorUtils::kColorStandardBT2020Constant:       return "BT2020Constant";
         case ColorUtils::kColorStandardBT470M:               return "BT470M";
         case ColorUtils::kColorStandardFilm:                 return "Film";
-        default:                                            return def;
+        case ColorUtils::kColorStandardDCI_P3:               return "DCI_P3";
+        default:                                             return def;
     }
 }
 
@@ -212,7 +211,8 @@
         case ColorUtils::kColorTransferGamma28:     return "Gamma28";
         case ColorUtils::kColorTransferST2084:      return "ST2084";
         case ColorUtils::kColorTransferHLG:         return "HLG";
-        default:                                   return def;
+        case ColorUtils::kColorTransferGamma26:     return "Gamma26";
+        default:                                    return def;
     }
 }
 
@@ -222,7 +222,7 @@
         case ColorUtils::kColorRangeUnspecified: return "Unspecified";
         case ColorUtils::kColorRangeFull:        return "Full";
         case ColorUtils::kColorRangeLimited:     return "Limited";
-        default:                                return def;
+        default:                                 return def;
     }
 }
 
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 1e2e684..7eff8eb 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -1014,7 +1014,8 @@
         mFetcherLooper = new ALooper();
 
         mFetcherLooper->setName("Fetcher");
-        mFetcherLooper->start(false, false);
+        mFetcherLooper->start(false, /* runOnCallingThread */
+                              true  /* canCallJava */);
     }
 
     // create fetcher to fetch the master playlist
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index 354cee2..c5062d6 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -33,24 +33,36 @@
 namespace android {
 
 /**
- * BufferChannelBase implementation for ACodec.
+ * BufferChannelBase implementation for CCodec.
  */
 class CCodecBufferChannel : public BufferChannelBase {
 public:
+    /**
+     * Base class for representation of buffers at one port.
+     */
     class Buffers {
     public:
         Buffers() = default;
         virtual ~Buffers() = default;
 
-        inline void setAlloc(const std::shared_ptr<C2BlockPool> &alloc) { mAlloc = alloc; }
+        /**
+         * Set format for MediaCodec-facing buffers.
+         */
         inline void setFormat(const sp<AMessage> &format) { mFormat = format; }
-        inline const std::shared_ptr<C2BlockPool> &getAlloc() { return mAlloc; }
+
+        /**
+         * Returns true if the buffers are operating under array mode.
+         */
+        virtual bool isArrayMode() { return false; }
+
+        /**
+         * Fills the vector with MediaCodecBuffer's if in array mode; otherwise,
+         * no-op.
+         */
+        virtual void getArray(Vector<sp<MediaCodecBuffer>> *) {}
 
     protected:
-        // Input: this object uses it to allocate input buffers with which the
-        // client fills.
-        // Output: this object passes it to the component.
-        std::shared_ptr<C2BlockPool> mAlloc;
+        // Format to be used for creating MediaCodec-facing buffers.
         sp<AMessage> mFormat;
 
     private:
@@ -62,10 +74,41 @@
         using Buffers::Buffers;
         virtual ~InputBuffers() = default;
 
+        /**
+         * Set a block pool to obtain input memory blocks.
+         */
+        inline void setPool(const std::shared_ptr<C2BlockPool> &pool) { mPool = pool; }
+
+        /**
+         * Get a new MediaCodecBuffer for input and its corresponding index.
+         * Returns false if no new buffer can be obtained at the moment.
+         */
         virtual bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) = 0;
+
+        /**
+         * Release the buffer obtained from requestNewBuffer() and get the
+         * associated C2Buffer object back. Returns empty shared_ptr if the
+         * buffer is not on file.
+         */
         virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
+
+        /**
+         * Flush internal state. After this call, no index or buffer previously
+         * returned from requestNewBuffer() is valid.
+         */
         virtual void flush() = 0;
 
+        /**
+         * Return array-backed version of input buffers. The returned object
+         * shall retain the internal state so that it will honor index and
+         * buffer from previous calls of requestNewBuffer().
+         */
+        virtual std::unique_ptr<InputBuffers> toArrayMode() = 0;
+
+    protected:
+        // Pool to obtain blocks for input buffers.
+        std::shared_ptr<C2BlockPool> mPool;
+
     private:
         DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
     };
@@ -75,12 +118,46 @@
         using Buffers::Buffers;
         virtual ~OutputBuffers() = default;
 
+        /**
+         * Register output C2Buffer from the component and obtain corresponding
+         * index and MediaCodecBuffer object. Returns false if registration
+         * fails.
+         */
         virtual bool registerBuffer(
                 const std::shared_ptr<C2Buffer> &buffer,
                 size_t *index,
                 sp<MediaCodecBuffer> *codecBuffer) = 0;
+
+        /**
+         * Register codec specific data as a buffer to be consistent with
+         * MediaCodec behavior.
+         */
+        virtual bool registerCsd(
+                const C2StreamCsdInfo::output * /* csd */,
+                size_t * /* index */,
+                sp<MediaCodecBuffer> * /* codecBuffer */) {
+            return false;
+        }
+
+        /**
+         * Release the buffer obtained from registerBuffer() and get the
+         * associated C2Buffer object back. Returns empty shared_ptr if the
+         * buffer is not on file.
+         */
         virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
-        virtual void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork);
+
+        /**
+         * Flush internal state. After this call, no index or buffer previously
+         * returned from registerBuffer() is valid.
+         */
+        virtual void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) = 0;
+
+        /**
+         * Return array-backed version of output buffers. The returned object
+         * shall retain the internal state so that it will honor index and
+         * buffer from previous calls of registerBuffer().
+         */
+        virtual std::unique_ptr<OutputBuffers> toArrayMode() = 0;
 
     private:
         DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
@@ -151,12 +228,34 @@
 private:
     class QueueGuard;
 
+    /**
+     * Special mutex-like object with the following properties:
+     *
+     * - At STOPPED state (initial, or after stop())
+     *   - QueueGuard object gets created at STOPPED state, and the client is
+     *     supposed to return immediately.
+     * - At RUNNING state (after start())
+     *   - Each QueueGuard object
+     */
     class QueueSync {
     public:
+        /**
+         * At construction the sync object is in STOPPED state.
+         */
         inline QueueSync() : mCount(-1) {}
         ~QueueSync() = default;
 
+        /**
+         * Transition to RUNNING state when stopped. No-op if already in RUNNING
+         * state.
+         */
         void start();
+
+        /**
+         * At RUNNING state, wait until all QueueGuard object created during
+         * RUNNING state are destroyed, and then transition to STOPPED state.
+         * No-op if already in STOPPED state.
+         */
         void stop();
 
     private:
@@ -186,6 +285,7 @@
     std::function<void(status_t, enum ActionCode)> mOnError;
     std::shared_ptr<C2BlockPool> mInputAllocator;
     QueueSync mQueueSync;
+
     Mutexed<std::unique_ptr<InputBuffers>> mInputBuffers;
     Mutexed<std::unique_ptr<OutputBuffers>> mOutputBuffers;
 
diff --git a/media/libstagefright/include/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h
index 258511a..e04b59f 100644
--- a/media/libstagefright/include/SoftwareRenderer.h
+++ b/media/libstagefright/include/SoftwareRenderer.h
@@ -22,6 +22,7 @@
 #include <media/stagefright/FrameRenderTracker.h>
 #include <utils/RefBase.h>
 #include <system/window.h>
+#include <media/hardware/VideoAPI.h>
 
 #include <list>
 
@@ -55,6 +56,7 @@
     int32_t mCropWidth, mCropHeight;
     int32_t mRotationDegrees;
     android_dataspace mDataSpace;
+    HDRStaticInfo mHDRStaticInfo;
     FrameRenderTracker mRenderTracker;
 
     SoftwareRenderer(const SoftwareRenderer &);
diff --git a/media/libstagefright/include/media/stagefright/ACodec.h b/media/libstagefright/include/media/stagefright/ACodec.h
index d1a9d25..3196b10 100644
--- a/media/libstagefright/include/media/stagefright/ACodec.h
+++ b/media/libstagefright/include/media/stagefright/ACodec.h
@@ -235,6 +235,7 @@
     int mNativeWindowUsageBits;
     android_native_rect_t mLastNativeWindowCrop;
     int32_t mLastNativeWindowDataSpace;
+    HDRStaticInfo mLastHDRStaticInfo;
     sp<AMessage> mConfigFormat;
     sp<AMessage> mInputFormat;
     sp<AMessage> mOutputFormat;
diff --git a/media/libstagefright/include/media/stagefright/ColorConverter.h b/media/libstagefright/include/media/stagefright/ColorConverter.h
index 7ac9b37..f6bd353 100644
--- a/media/libstagefright/include/media/stagefright/ColorConverter.h
+++ b/media/libstagefright/include/media/stagefright/ColorConverter.h
@@ -73,6 +73,9 @@
     status_t convertYUV420Planar(
             const BitmapParams &src, const BitmapParams &dst);
 
+    status_t convertYUV420Planar16(
+            const BitmapParams &src, const BitmapParams &dst);
+
     status_t convertYUV420PlanarUseLibYUV(
             const BitmapParams &src, const BitmapParams &dst);
 
diff --git a/media/libstagefright/include/media/stagefright/InterfaceUtils.h b/media/libstagefright/include/media/stagefright/InterfaceUtils.h
index 783f109..224c1f1 100644
--- a/media/libstagefright/include/media/stagefright/InterfaceUtils.h
+++ b/media/libstagefright/include/media/stagefright/InterfaceUtils.h
@@ -31,13 +31,15 @@
 sp<IDataSource> CreateIDataSourceFromDataSource(const sp<DataSource> &source);
 
 // Creates an IMediaExtractor wrapper to the given MediaExtractor.
-sp<IMediaExtractor> CreateIMediaExtractorFromMediaExtractor(const sp<MediaExtractor> &extractor);
+sp<IMediaExtractor> CreateIMediaExtractorFromMediaExtractor(
+        const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin);
 
 // Creates a MediaSource which wraps the given IMediaSource object.
 sp<MediaSource> CreateMediaSourceFromIMediaSource(const sp<IMediaSource> &source);
 
 // Creates an IMediaSource wrapper to the given MediaSource.
-sp<IMediaSource> CreateIMediaSourceFromMediaSource(const sp<MediaSource> &source);
+sp<IMediaSource> CreateIMediaSourceFromMediaSource(
+        const sp<MediaSource> &source, const sp<RefBase> &plugin);
 
 }  // namespace android
 
diff --git a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
index f216ff8..55654f1 100644
--- a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
+++ b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
@@ -26,6 +26,7 @@
 namespace android {
 
 class DataSource;
+struct ExtractorPlugin;
 
 class MediaExtractorFactory {
 public:
@@ -37,20 +38,26 @@
     // will be alsp returned with |out|.
     static sp<IMediaExtractor> CreateFromFd(
             int fd, int64_t offset, int64_t length, const char *mime, sp<DataSource> *out);
-    static sp<MediaExtractor> CreateFromService(
+    static sp<IMediaExtractor> CreateFromService(
             const sp<DataSource> &source, const char *mime = NULL);
+    static void LoadPlugins(const ::std::string& apkPath);
+    static status_t dump(int fd, const Vector<String16>& args);
 
 private:
-    static Mutex gSnifferMutex;
-    static List<MediaExtractor::ExtractorDef> gSniffers;
-    static bool gSniffersRegistered;
+    static Mutex gPluginMutex;
+    static std::shared_ptr<List<sp<ExtractorPlugin>>> gPlugins;
+    static bool gPluginsRegistered;
 
-    static void RegisterSniffer_l(const MediaExtractor::ExtractorDef &def);
+    static void RegisterExtractors(
+            const char *apkPath, List<sp<ExtractorPlugin>> &pluginList);
+    static void RegisterExtractor(
+            const sp<ExtractorPlugin> &plugin, List<sp<ExtractorPlugin>> &pluginList);
 
     static MediaExtractor::CreatorFunc sniff(const sp<DataSource> &source,
-            String8 *mimeType, float *confidence, sp<AMessage> *meta);
+            String8 *mimeType, float *confidence, sp<AMessage> *meta,
+            sp<ExtractorPlugin> &plugin);
 
-    static void RegisterDefaultSniffers();
+    static void UpdateExtractors(const char *newUpdateApkPath);
 };
 
 }  // namespace android
diff --git a/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h b/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
index b5a4b34..2bd71ee 100644
--- a/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
@@ -22,10 +22,12 @@
 
 namespace android {
 
+class MediaAnalyticsItem;
+
 // IMediaExtractor wrapper to the MediaExtractor.
 class RemoteMediaExtractor : public BnMediaExtractor {
 public:
-    static sp<IMediaExtractor> wrap(const sp<MediaExtractor> &extractor);
+    static sp<IMediaExtractor> wrap(const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin);
 
     virtual ~RemoteMediaExtractor();
     virtual size_t countTracks();
@@ -42,8 +44,11 @@
 
 private:
     sp<MediaExtractor> mExtractor;
+    sp<RefBase> mExtractorPlugin;
 
-    explicit RemoteMediaExtractor(const sp<MediaExtractor> &extractor);
+    MediaAnalyticsItem *mAnalyticsItem;
+
+    explicit RemoteMediaExtractor(const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin);
 
     DISALLOW_EVIL_CONSTRUCTORS(RemoteMediaExtractor);
 };
diff --git a/media/libstagefright/include/media/stagefright/RemoteMediaSource.h b/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
index 0a446a5..cb222cc 100644
--- a/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
+++ b/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
@@ -26,7 +26,7 @@
 // IMediaSrouce wrapper to the MediaSource.
 class RemoteMediaSource : public BnMediaSource {
 public:
-    static sp<IMediaSource> wrap(const sp<MediaSource> &source);
+    static sp<IMediaSource> wrap(const sp<MediaSource> &source, const sp<RefBase> &plugin);
     virtual ~RemoteMediaSource();
     virtual status_t start(MetaData *params = NULL);
     virtual status_t stop();
@@ -39,8 +39,9 @@
 
 private:
     sp<MediaSource> mSource;
+    sp<RefBase> mExtractorPlugin;
 
-    explicit RemoteMediaSource(const sp<MediaSource> &source);
+    explicit RemoteMediaSource(const sp<MediaSource> &source, const sp<RefBase> &plugin);
 
     DISALLOW_EVIL_CONSTRUCTORS(RemoteMediaSource);
 };
diff --git a/media/libstagefright/include/media/stagefright/SurfaceUtils.h b/media/libstagefright/include/media/stagefright/SurfaceUtils.h
index a7747c7..689e458 100644
--- a/media/libstagefright/include/media/stagefright/SurfaceUtils.h
+++ b/media/libstagefright/include/media/stagefright/SurfaceUtils.h
@@ -24,6 +24,8 @@
 
 namespace android {
 
+struct HDRStaticInfo;
+
 /**
  * Configures |nativeWindow| for given |width|x|height|, pixel |format|, |rotation| and |usage|.
  * If |reconnect| is true, reconnects to the native window before hand.
@@ -32,6 +34,8 @@
 status_t setNativeWindowSizeFormatAndUsage(
         ANativeWindow *nativeWindow /* nonnull */,
         int width, int height, int format, int rotation, int usage, bool reconnect);
+void setNativeWindowHdrMetadata(
+        ANativeWindow *nativeWindow /* nonnull */, HDRStaticInfo *info /* nonnull */);
 status_t pushBlankBuffersToNativeWindow(ANativeWindow *nativeWindow /* nonnull */);
 status_t nativeWindowConnect(ANativeWindow *surface, const char *reason);
 status_t nativeWindowDisconnect(ANativeWindow *surface, const char *reason);
diff --git a/media/libstagefright/omx/1.0/WOmxNode.cpp b/media/libstagefright/omx/1.0/WOmxNode.cpp
index 9f82283..1dc7c7b 100644
--- a/media/libstagefright/omx/1.0/WOmxNode.cpp
+++ b/media/libstagefright/omx/1.0/WOmxNode.cpp
@@ -154,7 +154,8 @@
                     hidl_handle const& outNativeHandle) {
                 fnStatus = toStatusT(status);
                 *buffer = outBuffer;
-                *native_handle = NativeHandle::create(
+                *native_handle = outNativeHandle.getNativeHandle() == nullptr ?
+                        nullptr : NativeHandle::create(
                         native_handle_clone(outNativeHandle), true);
             }));
     return transStatus == NO_ERROR ? fnStatus : transStatus;
diff --git a/media/libstagefright/omx/OMXUtils.cpp b/media/libstagefright/omx/OMXUtils.cpp
index e032985..f597e02 100644
--- a/media/libstagefright/omx/OMXUtils.cpp
+++ b/media/libstagefright/omx/OMXUtils.cpp
@@ -215,6 +215,9 @@
         fmt != OMX_COLOR_FormatYUV420PackedSemiPlanar &&
         fmt != (OMX_COLOR_FORMATTYPE)HAL_PIXEL_FORMAT_YV12) {
         ALOGW("do not know color format 0x%x = %d", fmt, fmt);
+        if (fmt == OMX_COLOR_FormatYUV420Planar16) {
+            ALOGW("Cannot describe color format OMX_COLOR_FormatYUV420Planar16");
+        }
         return false;
     }
 
diff --git a/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp b/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp
index 8e92539..8ef7620 100644
--- a/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp
+++ b/media/libstagefright/omx/SoftVideoDecoderOMXComponent.cpp
@@ -61,6 +61,7 @@
         mCropTop(0),
         mCropWidth(width),
         mCropHeight(height),
+        mOutputFormat(OMX_COLOR_FormatYUV420Planar),
         mOutputPortSettingsChange(NONE),
         mUpdateColorAspects(false),
         mMinInputBufferSize(384), // arbitrary, using one uncompressed macroblock
@@ -74,6 +75,7 @@
     memset(&mDefaultColorAspects, 0, sizeof(ColorAspects));
     memset(&mBitstreamColorAspects, 0, sizeof(ColorAspects));
     memset(&mFinalColorAspects, 0, sizeof(ColorAspects));
+    memset(&mHdrStaticInfo, 0, sizeof(HDRStaticInfo));
 }
 
 void SoftVideoDecoderOMXComponent::initPorts(
@@ -140,7 +142,6 @@
     def.format.video.xFramerate = 0;
     def.format.video.bFlagErrorConcealment = OMX_FALSE;
     def.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
-    def.format.video.eColorFormat = OMX_COLOR_FormatYUV420Planar;
     def.format.video.pNativeWindow = NULL;
 
     addPort(def);
@@ -152,11 +153,13 @@
     OMX_PARAM_PORTDEFINITIONTYPE *outDef = &editPortInfo(kOutputPortIndex)->mDef;
     outDef->format.video.nFrameWidth = outputBufferWidth();
     outDef->format.video.nFrameHeight = outputBufferHeight();
+    outDef->format.video.eColorFormat = mOutputFormat;
     outDef->format.video.nStride = outDef->format.video.nFrameWidth;
     outDef->format.video.nSliceHeight = outDef->format.video.nFrameHeight;
 
+    int32_t bpp = (mOutputFormat == OMX_COLOR_FormatYUV420Planar16) ? 2 : 1;
     outDef->nBufferSize =
-        (outDef->format.video.nStride * outDef->format.video.nSliceHeight * 3) / 2;
+        (outDef->format.video.nStride * outDef->format.video.nSliceHeight * bpp * 3) / 2;
 
     OMX_PARAM_PORTDEFINITIONTYPE *inDef = &editPortInfo(kInputPortIndex)->mDef;
     inDef->format.video.nFrameWidth = mWidth;
@@ -191,9 +194,11 @@
 
 void SoftVideoDecoderOMXComponent::handlePortSettingsChange(
         bool *portWillReset, uint32_t width, uint32_t height,
+        OMX_COLOR_FORMATTYPE outputFormat,
         CropSettingsMode cropSettingsMode, bool fakeStride) {
     *portWillReset = false;
     bool sizeChanged = (width != mWidth || height != mHeight);
+    bool formatChanged = (outputFormat != mOutputFormat);
     bool updateCrop = (cropSettingsMode == kCropUnSet);
     bool cropChanged = (cropSettingsMode == kCropChanged);
     bool strideChanged = false;
@@ -205,13 +210,18 @@
         }
     }
 
-    if (sizeChanged || cropChanged || strideChanged) {
+    if (formatChanged || sizeChanged || cropChanged || strideChanged) {
+        if (formatChanged) {
+            ALOGD("formatChanged: 0x%08x -> 0x%08x", mOutputFormat, outputFormat);
+        }
+        mOutputFormat = outputFormat;
         mWidth = width;
         mHeight = height;
 
         if ((sizeChanged && !mIsAdaptive)
             || width > mAdaptiveMaxWidth
-            || height > mAdaptiveMaxHeight) {
+            || height > mAdaptiveMaxHeight
+            || formatChanged) {
             if (mIsAdaptive) {
                 if (width > mAdaptiveMaxWidth) {
                     mAdaptiveMaxWidth = width;
@@ -305,27 +315,30 @@
 void SoftVideoDecoderOMXComponent::copyYV12FrameToOutputBuffer(
         uint8_t *dst, const uint8_t *srcY, const uint8_t *srcU, const uint8_t *srcV,
         size_t srcYStride, size_t srcUStride, size_t srcVStride) {
-    size_t dstYStride = outputBufferWidth();
+    OMX_PARAM_PORTDEFINITIONTYPE *outDef = &editPortInfo(kOutputPortIndex)->mDef;
+    int32_t bpp = (outDef->format.video.eColorFormat == OMX_COLOR_FormatYUV420Planar16) ? 2 : 1;
+
+    size_t dstYStride = outputBufferWidth() * bpp;
     size_t dstUVStride = dstYStride / 2;
     size_t dstHeight = outputBufferHeight();
     uint8_t *dstStart = dst;
 
     for (size_t i = 0; i < mHeight; ++i) {
-         memcpy(dst, srcY, mWidth);
+         memcpy(dst, srcY, mWidth * bpp);
          srcY += srcYStride;
          dst += dstYStride;
     }
 
     dst = dstStart + dstYStride * dstHeight;
     for (size_t i = 0; i < mHeight / 2; ++i) {
-         memcpy(dst, srcU, mWidth / 2);
+         memcpy(dst, srcU, mWidth / 2 * bpp);
          srcU += srcUStride;
          dst += dstUVStride;
     }
 
     dst = dstStart + (5 * dstYStride * dstHeight) / 4;
     for (size_t i = 0; i < mHeight / 2; ++i) {
-         memcpy(dst, srcV, mWidth / 2);
+         memcpy(dst, srcV, mWidth / 2 * bpp);
          srcV += srcVStride;
          dst += dstUVStride;
     }
@@ -562,6 +575,24 @@
             return OMX_ErrorNone;
         }
 
+        case kDescribeHdrStaticInfoIndex:
+        {
+            if (!supportDescribeHdrStaticInfo()) {
+                return OMX_ErrorUnsupportedIndex;
+            }
+
+            DescribeHDRStaticInfoParams* hdrStaticInfoParams =
+                    (DescribeHDRStaticInfoParams *)params;
+
+            if (hdrStaticInfoParams->nPortIndex != kOutputPortIndex) {
+                return OMX_ErrorBadPortIndex;
+            }
+
+            hdrStaticInfoParams->sInfo = mHdrStaticInfo;
+
+            return OMX_ErrorNone;
+        }
+
         default:
             return OMX_ErrorUnsupportedIndex;
     }
@@ -595,6 +626,28 @@
             return OMX_ErrorNone;
         }
 
+        case kDescribeHdrStaticInfoIndex:
+        {
+            if (!supportDescribeHdrStaticInfo()) {
+                return OMX_ErrorUnsupportedIndex;
+            }
+
+            const DescribeHDRStaticInfoParams* hdrStaticInfoParams =
+                    (DescribeHDRStaticInfoParams *)params;
+
+            if (hdrStaticInfoParams->nPortIndex != kOutputPortIndex) {
+                return OMX_ErrorBadPortIndex;
+            }
+
+            if (hdrStaticInfoParams != NULL) {
+                mOutputFormat = OMX_COLOR_FormatYUV420Planar16;
+                mHdrStaticInfo = hdrStaticInfoParams->sInfo;
+                updatePortDefinitions(false);
+            }
+
+            return OMX_ErrorNone;
+        }
+
         default:
             return OMX_ErrorUnsupportedIndex;
     }
@@ -610,6 +663,10 @@
                 && supportsDescribeColorAspects()) {
         *(int32_t*)index = kDescribeColorAspectsIndex;
         return OMX_ErrorNone;
+    } else if (!strcmp(name, "OMX.google.android.index.describeHDRStaticInfo")
+            && supportDescribeHdrStaticInfo()) {
+        *(int32_t*)index = kDescribeHdrStaticInfoIndex;
+        return OMX_ErrorNone;
     }
 
     return SimpleSoftOMXComponent::getExtensionIndex(name, index);
@@ -623,6 +680,10 @@
     return kNotSupported;
 }
 
+bool SoftVideoDecoderOMXComponent::supportDescribeHdrStaticInfo() {
+    return false;
+}
+
 void SoftVideoDecoderOMXComponent::onReset() {
     mOutputPortSettingsChange = NONE;
 }
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/1.0/Conversion.h b/media/libstagefright/omx/include/media/stagefright/omx/1.0/Conversion.h
index 8d8a2d9..a79d403 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/1.0/Conversion.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/1.0/Conversion.h
@@ -1862,7 +1862,8 @@
 inline size_t getFlattenedSize(HGraphicBufferProducer::QueueBufferInput const& t) {
     return minFlattenedSize(t) +
             getFenceFlattenedSize(t.fence) +
-            getFlattenedSize(t.surfaceDamage);
+            getFlattenedSize(t.surfaceDamage) +
+            sizeof(HdrMetadata::validTypes);
 }
 
 /**
@@ -1916,7 +1917,12 @@
     if (status != NO_ERROR) {
         return status;
     }
-    return flatten(t.surfaceDamage, buffer, size);
+    status = flatten(t.surfaceDamage, buffer, size);
+    if (status != NO_ERROR) {
+        return status;
+    }
+    FlattenableUtils::write(buffer, size, decltype(HdrMetadata::validTypes)(0));
+    return NO_ERROR;
 }
 
 /**
@@ -1968,6 +1974,7 @@
     if (status != NO_ERROR) {
         return status;
     }
+    // HdrMetadata ignored
     return unflatten(&(t->surfaceDamage), buffer, size);
 }
 
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/SoftVideoDecoderOMXComponent.h b/media/libstagefright/omx/include/media/stagefright/omx/SoftVideoDecoderOMXComponent.h
index c9fd745..56fc691 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/SoftVideoDecoderOMXComponent.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/SoftVideoDecoderOMXComponent.h
@@ -23,6 +23,7 @@
 #include <media/stagefright/foundation/AHandlerReflector.h>
 #include <media/stagefright/foundation/ColorUtils.h>
 #include <media/IOMX.h>
+#include <media/hardware/HardwareAPI.h>
 
 #include <utils/RefBase.h>
 #include <utils/threads.h>
@@ -46,6 +47,7 @@
 protected:
     enum {
         kDescribeColorAspectsIndex = kPrepareForAdaptivePlaybackIndex + 1,
+        kDescribeHdrStaticInfoIndex = kPrepareForAdaptivePlaybackIndex + 2,
     };
 
     enum {
@@ -76,6 +78,8 @@
 
     virtual int getColorAspectPreference();
 
+    virtual bool supportDescribeHdrStaticInfo();
+
     // This function sets both minimum buffer count and actual buffer count of
     // input port to be |numInputBuffers|. It will also set both minimum buffer
     // count and actual buffer count of output port to be |numOutputBuffers|.
@@ -113,7 +117,9 @@
     // It will trigger OMX_EventPortSettingsChanged event if necessary.
     void handlePortSettingsChange(
             bool *portWillReset, uint32_t width, uint32_t height,
-            CropSettingsMode cropSettingsMode = kCropUnSet, bool fakeStride = false);
+            OMX_COLOR_FORMATTYPE outputFormat = OMX_COLOR_FormatYUV420Planar,
+            CropSettingsMode cropSettingsMode = kCropUnSet,
+            bool fakeStride = false);
 
     void copyYV12FrameToOutputBuffer(
             uint8_t *dst, const uint8_t *srcY, const uint8_t *srcU, const uint8_t *srcV,
@@ -129,7 +135,8 @@
     uint32_t mAdaptiveMaxWidth, mAdaptiveMaxHeight;
     uint32_t mWidth, mHeight;
     uint32_t mCropLeft, mCropTop, mCropWidth, mCropHeight;
-
+    OMX_COLOR_FORMATTYPE mOutputFormat;
+    HDRStaticInfo mHdrStaticInfo;
     enum {
         NONE,
         AWAITING_DISABLED,
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
new file mode 100644
index 0000000..bbd2afa
--- /dev/null
+++ b/packages/MediaComponents/Android.mk
@@ -0,0 +1,61 @@
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# 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_PACKAGE_NAME := MediaComponents
+LOCAL_MODULE_OWNER := google
+
+# TODO: create a separate key for this package.
+LOCAL_CERTIFICATE := platform
+
+# TODO: Use System SDK once public APIs are approved
+# LOCAL_SDK_VERSION := system_current
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
+
+LOCAL_MULTILIB := first
+
+LOCAL_JAVA_LIBRARIES += android-support-annotations
+
+# Embed native libraries in package, rather than installing to /system/lib*.
+# TODO: Find a right way to include libs in the apk. b/72066556
+LOCAL_MODULE_TAGS := samples
+
+# To embed native libraries in package, uncomment the lines below.
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libaacextractor \
+    libamrextractor \
+    libflacextractor \
+    libmidiextractor \
+    libmkvextractor \
+    libmp3extractor \
+    libmp4extractor \
+    libmpeg2extractor \
+    liboggextractor \
+    libwavextractor \
+
+# TODO: Remove dependency with other support libraries.
+LOCAL_STATIC_ANDROID_LIBRARIES += \
+    android-support-v4 \
+    android-support-v7-appcompat \
+    android-support-v7-palette
+LOCAL_USE_AAPT2 := true
+
+include $(BUILD_PACKAGE)
diff --git a/packages/MediaUpdate/AndroidManifest.xml b/packages/MediaComponents/AndroidManifest.xml
similarity index 100%
rename from packages/MediaUpdate/AndroidManifest.xml
rename to packages/MediaComponents/AndroidManifest.xml
diff --git a/packages/MediaComponents/proguard.cfg b/packages/MediaComponents/proguard.cfg
new file mode 100644
index 0000000..43f2e63
--- /dev/null
+++ b/packages/MediaComponents/proguard.cfg
@@ -0,0 +1,20 @@
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# 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.
+#
+
+# 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);
+}
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_dark.png
new file mode 100644
index 0000000..17fd51f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..d7c8252
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_dark.png
new file mode 100644
index 0000000..928ddea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_light.png
new file mode 100644
index 0000000..1a9cd75
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
new file mode 100644
index 0000000..0354f61
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
new file mode 100644
index 0000000..5f8febe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_dark.png
new file mode 100644
index 0000000..7192ad4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_light.png
new file mode 100644
index 0000000..bb707ea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_play_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_play_dark.png
new file mode 100644
index 0000000..0c32d00
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_play_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_play_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_play_light.png
new file mode 100644
index 0000000..5345ee3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_play_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_dark.png
new file mode 100644
index 0000000..801d341
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_light.png
new file mode 100644
index 0000000..9d6b65d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_dark.png
new file mode 100644
index 0000000..8ad305d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_light.png
new file mode 100644
index 0000000..887fde4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_dark.png
new file mode 100644
index 0000000..5739df7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_light.png
new file mode 100644
index 0000000..58c344a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..1a03420
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_dark.png
new file mode 100755
index 0000000..723e455
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_dark.png
new file mode 100755
index 0000000..40c25a3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_light.png
new file mode 100755
index 0000000..afdb9c1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_light.png
new file mode 100755
index 0000000..846c109
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_dark.png
new file mode 100755
index 0000000..33bf484
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_light.png
new file mode 100755
index 0000000..c911b5c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_dark.png
new file mode 100644
index 0000000..e94ed50
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..2cf7e0c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_dark.png
new file mode 100644
index 0000000..66558a8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_light.png
new file mode 100644
index 0000000..40a1a84
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
new file mode 100644
index 0000000..0354f61
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
new file mode 100644
index 0000000..5f8febe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_dark.png
new file mode 100644
index 0000000..f49aed7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_light.png
new file mode 100644
index 0000000..74068ea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_play_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_play_dark.png
new file mode 100644
index 0000000..9cc777c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_play_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_play_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_play_light.png
new file mode 100644
index 0000000..f208795
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_play_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_dark.png
new file mode 100644
index 0000000..3ad2c9c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_light.png
new file mode 100644
index 0000000..b002ab7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_dark.png
new file mode 100644
index 0000000..4446ea4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_light.png
new file mode 100644
index 0000000..4d790c6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_dark.png
new file mode 100644
index 0000000..c401dc0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_light.png
new file mode 100644
index 0000000..e24d586
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..ccbb772
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_dark.png
new file mode 100755
index 0000000..7cc9845
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_dark.png
new file mode 100755
index 0000000..22617e1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_light.png
new file mode 100755
index 0000000..cefef3c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_light.png
new file mode 100755
index 0000000..9a0047c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_dark.png
new file mode 100755
index 0000000..ca5d6a2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_light.png
new file mode 100755
index 0000000..8134310
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_dark.png
new file mode 100644
index 0000000..b5c899f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..4778e00
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_dark.png
new file mode 100644
index 0000000..f992fc5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_light.png
new file mode 100644
index 0000000..d3884e6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_dark.png
new file mode 100644
index 0000000..660ac65
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_light.png
new file mode 100644
index 0000000..792104f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_dark.png
new file mode 100644
index 0000000..be5c062
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_light.png
new file mode 100644
index 0000000..d12d495
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_dark.png
new file mode 100644
index 0000000..5239336
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_light.png
new file mode 100644
index 0000000..5bc5a6c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_dark.png
new file mode 100644
index 0000000..f6dd214
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_light.png
new file mode 100644
index 0000000..6b7bdcd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_dark.png
new file mode 100644
index 0000000..c7fe576
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_light.png
new file mode 100644
index 0000000..0a5d6aa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_dark.png
new file mode 100644
index 0000000..0aadfa3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_light.png
new file mode 100644
index 0000000..125fe0b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_dark.png
new file mode 100644
index 0000000..05c48a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_light.png
new file mode 100644
index 0000000..741e911
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_dark.png
new file mode 100644
index 0000000..ae4218a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_light.png
new file mode 100644
index 0000000..8b30fab
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_dark.png
new file mode 100644
index 0000000..d7aa903
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_light.png
new file mode 100644
index 0000000..f7e2f29
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_dark.png
new file mode 100644
index 0000000..e7871e2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_light.png
new file mode 100644
index 0000000..8c57f63
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_dark.png
new file mode 100644
index 0000000..0041b01
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_light.png
new file mode 100644
index 0000000..6dbb694
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_dark.png
new file mode 100644
index 0000000..08e1013
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_light.png
new file mode 100644
index 0000000..5c352c3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_dark.png
new file mode 100644
index 0000000..70532e9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_light.png
new file mode 100644
index 0000000..9c6ba30
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_dark.png
new file mode 100644
index 0000000..9ba3b5f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_light.png
new file mode 100644
index 0000000..bd4bb22
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_dark.png
new file mode 100644
index 0000000..2156127
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_light.png
new file mode 100644
index 0000000..b417a9f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_dark.png
new file mode 100644
index 0000000..9bf633e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_light.png
new file mode 100644
index 0000000..ba51811
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_dark.png
new file mode 100644
index 0000000..756a53c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_light.png
new file mode 100644
index 0000000..4705dca
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_dark.png
new file mode 100644
index 0000000..50e4ea3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_light.png
new file mode 100644
index 0000000..bc6724f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_dark.png
new file mode 100644
index 0000000..9e3b410
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_light.png
new file mode 100644
index 0000000..2f18abd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_dark.png
new file mode 100644
index 0000000..de81133
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_light.png
new file mode 100644
index 0000000..b80b191
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_dark.png
new file mode 100644
index 0000000..48aba3d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_light.png
new file mode 100644
index 0000000..ca34d5b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_dark.png
new file mode 100644
index 0000000..e9957b3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_light.png
new file mode 100644
index 0000000..a5d384f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_dark.png
new file mode 100644
index 0000000..ddc6297
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_light.png
new file mode 100644
index 0000000..28ab684
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_dark.png
new file mode 100644
index 0000000..51e7f75
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_light.png
new file mode 100644
index 0000000..4aa3ca3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_dark.png
new file mode 100644
index 0000000..9caecde
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_light.png
new file mode 100644
index 0000000..1b8d0b6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_dark.png
new file mode 100644
index 0000000..400be3c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_light.png
new file mode 100644
index 0000000..c14f1bf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_dark.png
new file mode 100644
index 0000000..4e18b46
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_light.png
new file mode 100644
index 0000000..c4c2c00
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_dark.png
new file mode 100644
index 0000000..98fae44
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_light.png
new file mode 100644
index 0000000..d64c289
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_dark.png
new file mode 100644
index 0000000..91f9327
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_light.png
new file mode 100644
index 0000000..f5e1f69
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_dark.png
new file mode 100644
index 0000000..3e6fafd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_light.png
new file mode 100644
index 0000000..ae2bd87
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_dark.png
new file mode 100644
index 0000000..f73a1f8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_light.png
new file mode 100644
index 0000000..78c1069
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_dark.png
new file mode 100644
index 0000000..562b803
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_light.png
new file mode 100644
index 0000000..ddfba02
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_dark.png
new file mode 100644
index 0000000..257f2d2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_light.png
new file mode 100644
index 0000000..38f5478
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_dark.png
new file mode 100644
index 0000000..f995af0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_light.png
new file mode 100644
index 0000000..c50b7f0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_dark.png
new file mode 100644
index 0000000..f6dd214
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_light.png
new file mode 100644
index 0000000..6b7bdcd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_dark.png
new file mode 100644
index 0000000..c7fe576
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_light.png
new file mode 100644
index 0000000..0a5d6aa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_dark.png
new file mode 100644
index 0000000..0aadfa3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_light.png
new file mode 100644
index 0000000..125fe0b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_dark.png
new file mode 100644
index 0000000..05c48a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_light.png
new file mode 100644
index 0000000..741e911
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_dark.png
new file mode 100644
index 0000000..ae4218a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_light.png
new file mode 100644
index 0000000..8b30fab
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_dark.png
new file mode 100644
index 0000000..d7aa903
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_light.png
new file mode 100644
index 0000000..f7e2f29
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_dark.png
new file mode 100644
index 0000000..e7871e2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_light.png
new file mode 100644
index 0000000..8c57f63
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_dark.png
new file mode 100644
index 0000000..0041b01
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_light.png
new file mode 100644
index 0000000..6dbb694
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_dark.png
new file mode 100644
index 0000000..08e1013
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_light.png
new file mode 100644
index 0000000..5c352c3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_dark.png
new file mode 100644
index 0000000..70532e9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_light.png
new file mode 100644
index 0000000..9c6ba30
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_dark.png
new file mode 100644
index 0000000..9ba3b5f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_light.png
new file mode 100644
index 0000000..bd4bb22
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_dark.png
new file mode 100644
index 0000000..f3570f4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_light.png
new file mode 100644
index 0000000..65a403e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_dark.png
new file mode 100644
index 0000000..f644bfd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_light.png
new file mode 100644
index 0000000..c7d6048
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_dark.png
new file mode 100644
index 0000000..6e0d558
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_light.png
new file mode 100644
index 0000000..f3bc48d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_dark.png
new file mode 100644
index 0000000..14d8f8e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_light.png
new file mode 100644
index 0000000..98b90e5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_dark.png
new file mode 100644
index 0000000..83234a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_light.png
new file mode 100644
index 0000000..47d452f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_dark.png
new file mode 100644
index 0000000..b81cf5a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_light.png
new file mode 100644
index 0000000..20d08b4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_dark.png
new file mode 100644
index 0000000..6feb3f1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_light.png
new file mode 100644
index 0000000..e6ae8b3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_dark.png
new file mode 100644
index 0000000..0b0fc08
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_light.png
new file mode 100644
index 0000000..c2a16ac
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_dark.png
new file mode 100644
index 0000000..a3598cc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_light.png
new file mode 100644
index 0000000..846d16d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_dark.png
new file mode 100644
index 0000000..2070455
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_light.png
new file mode 100644
index 0000000..ae6db13
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_dark.png
new file mode 100644
index 0000000..7f3828a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_light.png
new file mode 100644
index 0000000..aaccc73
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_dark.png
new file mode 100644
index 0000000..5c8ced9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_light.png
new file mode 100644
index 0000000..ad01b9e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_dark.png
new file mode 100644
index 0000000..ce31dd3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_light.png
new file mode 100644
index 0000000..9ef78e4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_dark.png
new file mode 100644
index 0000000..a7c2cdb
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_light.png
new file mode 100644
index 0000000..e7c5bea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_dark.png
new file mode 100644
index 0000000..ecad0d4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_light.png
new file mode 100644
index 0000000..5fa5923
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_dark.png
new file mode 100644
index 0000000..f687e25
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_light.png
new file mode 100644
index 0000000..9c06db8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_dark.png
new file mode 100644
index 0000000..90225ba
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_light.png
new file mode 100644
index 0000000..19697de
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_dark.png
new file mode 100644
index 0000000..d37ec21
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_light.png
new file mode 100644
index 0000000..21840bf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_dark.png
new file mode 100644
index 0000000..5445e3a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_light.png
new file mode 100644
index 0000000..2337c65
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_dark.png
new file mode 100644
index 0000000..f6dd214
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_light.png
new file mode 100644
index 0000000..6b7bdcd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_dark.png
new file mode 100644
index 0000000..c4dc132
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_light.png
new file mode 100644
index 0000000..b14617c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_dark.png
new file mode 100644
index 0000000..bb30773
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_light.png
new file mode 100644
index 0000000..a05d7d7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..2238d58
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_dark.png
new file mode 100755
index 0000000..e40349d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_dark.png
new file mode 100755
index 0000000..f67c463
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_light.png
new file mode 100755
index 0000000..7fcebf5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_light.png
new file mode 100755
index 0000000..ea32a7a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_dark.png
new file mode 100755
index 0000000..d62ca37
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_light.png
new file mode 100755
index 0000000..3131256
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_dark.png
new file mode 100644
index 0000000..f131e1b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..e5946a2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_dark.png
new file mode 100644
index 0000000..b85e87f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_light.png
new file mode 100644
index 0000000..51b4401
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_dark.png
new file mode 100644
index 0000000..3ea7e03
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_light.png
new file mode 100644
index 0000000..dc63538
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_dark.png
new file mode 100644
index 0000000..2745c3a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_light.png
new file mode 100644
index 0000000..eda3ba5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_dark.png
new file mode 100644
index 0000000..035ca18
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_light.png
new file mode 100644
index 0000000..eac183d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_dark.png
new file mode 100644
index 0000000..0db679e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_light.png
new file mode 100644
index 0000000..51c6051
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_dark.png
new file mode 100644
index 0000000..c083914
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_light.png
new file mode 100644
index 0000000..c3c3caf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_dark.png
new file mode 100644
index 0000000..fc444cf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_light.png
new file mode 100644
index 0000000..abd6377
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_dark.png
new file mode 100644
index 0000000..6dbd1da
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_light.png
new file mode 100644
index 0000000..d2e7108
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_dark.png
new file mode 100644
index 0000000..d9f596b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_light.png
new file mode 100644
index 0000000..4f32e1a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_dark.png
new file mode 100644
index 0000000..c568e04
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_light.png
new file mode 100644
index 0000000..ed20dd9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_dark.png
new file mode 100644
index 0000000..bbe39e7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_light.png
new file mode 100644
index 0000000..1edc15f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_dark.png
new file mode 100644
index 0000000..78aebaf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_light.png
new file mode 100644
index 0000000..b5a6a4f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_dark.png
new file mode 100644
index 0000000..44b91ce
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_light.png
new file mode 100644
index 0000000..85f66f9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_dark.png
new file mode 100644
index 0000000..51ea34b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_light.png
new file mode 100644
index 0000000..952de04
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_dark.png
new file mode 100644
index 0000000..8b1aa21
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_light.png
new file mode 100644
index 0000000..534bcc0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_dark.png
new file mode 100644
index 0000000..f666b35
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_light.png
new file mode 100644
index 0000000..145a8fb
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_dark.png
new file mode 100644
index 0000000..edeb132
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_light.png
new file mode 100644
index 0000000..9da2b60
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_dark.png
new file mode 100644
index 0000000..ab80aa9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_light.png
new file mode 100644
index 0000000..115efe4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_dark.png
new file mode 100644
index 0000000..8c0cc31
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_light.png
new file mode 100644
index 0000000..e6ae6fc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_dark.png
new file mode 100644
index 0000000..b8816c9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_light.png
new file mode 100644
index 0000000..bd42931
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_dark.png
new file mode 100644
index 0000000..10d5b7f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_light.png
new file mode 100644
index 0000000..303a0fe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_dark.png
new file mode 100644
index 0000000..3c2a655
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_light.png
new file mode 100644
index 0000000..90debc2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_dark.png
new file mode 100644
index 0000000..d3e78a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_light.png
new file mode 100644
index 0000000..3a3f991
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_dark.png
new file mode 100644
index 0000000..63fad9e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_light.png
new file mode 100644
index 0000000..d6dd8d4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_dark.png
new file mode 100644
index 0000000..890fd5f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_light.png
new file mode 100644
index 0000000..6b0b5c1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_dark.png
new file mode 100644
index 0000000..9ce1ef1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_light.png
new file mode 100644
index 0000000..81710d4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_dark.png
new file mode 100644
index 0000000..861c080
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_light.png
new file mode 100644
index 0000000..1c4aa21
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_dark.png
new file mode 100644
index 0000000..59a6b30
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_light.png
new file mode 100644
index 0000000..c6e8fe0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_dark.png
new file mode 100644
index 0000000..57b840e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_light.png
new file mode 100644
index 0000000..bf24050
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_dark.png
new file mode 100644
index 0000000..01c18c1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_light.png
new file mode 100644
index 0000000..be9753e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_dark.png
new file mode 100644
index 0000000..3f291b1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_light.png
new file mode 100644
index 0000000..dc1c619
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_dark.png
new file mode 100644
index 0000000..6504a70
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_light.png
new file mode 100644
index 0000000..a7e0a60
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_dark.png
new file mode 100644
index 0000000..57b1f3e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_light.png
new file mode 100644
index 0000000..5c551ec
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_dark.png
new file mode 100644
index 0000000..238667e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_light.png
new file mode 100644
index 0000000..ffb8183
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_dark.png
new file mode 100644
index 0000000..4893f18
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_light.png
new file mode 100644
index 0000000..ac5e156
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_dark.png
new file mode 100644
index 0000000..0db679e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_light.png
new file mode 100644
index 0000000..51c6051
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_dark.png
new file mode 100644
index 0000000..c083914
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_light.png
new file mode 100644
index 0000000..c3c3caf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_dark.png
new file mode 100644
index 0000000..fc444cf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_light.png
new file mode 100644
index 0000000..abd6377
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_dark.png
new file mode 100644
index 0000000..6dbd1da
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_light.png
new file mode 100644
index 0000000..d2e7108
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_dark.png
new file mode 100644
index 0000000..d9f596b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_light.png
new file mode 100644
index 0000000..4f32e1a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_dark.png
new file mode 100644
index 0000000..c568e04
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_light.png
new file mode 100644
index 0000000..ed20dd9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_dark.png
new file mode 100644
index 0000000..bbe39e7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_light.png
new file mode 100644
index 0000000..1edc15f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_dark.png
new file mode 100644
index 0000000..78aebaf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_light.png
new file mode 100644
index 0000000..b5a6a4f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_dark.png
new file mode 100644
index 0000000..44b91ce
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_light.png
new file mode 100644
index 0000000..85f66f9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_dark.png
new file mode 100644
index 0000000..51ea34b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_light.png
new file mode 100644
index 0000000..952de04
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_dark.png
new file mode 100644
index 0000000..8b1aa21
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_light.png
new file mode 100644
index 0000000..534bcc0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_dark.png
new file mode 100644
index 0000000..1fffa01
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_light.png
new file mode 100644
index 0000000..0ff7e57
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_dark.png
new file mode 100644
index 0000000..06ac4dc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_light.png
new file mode 100644
index 0000000..42a86f5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_dark.png
new file mode 100644
index 0000000..0301090
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_light.png
new file mode 100644
index 0000000..4396f0e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_dark.png
new file mode 100644
index 0000000..e19001b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_light.png
new file mode 100644
index 0000000..2271581
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_dark.png
new file mode 100644
index 0000000..5e96208
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_light.png
new file mode 100644
index 0000000..0f69500
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_dark.png
new file mode 100644
index 0000000..07e1bd6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_light.png
new file mode 100644
index 0000000..cde8f19
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_dark.png
new file mode 100644
index 0000000..b632e95
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_light.png
new file mode 100644
index 0000000..11d5d2e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_dark.png
new file mode 100644
index 0000000..660d527
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_light.png
new file mode 100644
index 0000000..2761ae1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_dark.png
new file mode 100644
index 0000000..0aa3f84
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_light.png
new file mode 100644
index 0000000..27d166f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_dark.png
new file mode 100644
index 0000000..ebe527e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_light.png
new file mode 100644
index 0000000..aeb2a8e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_dark.png
new file mode 100644
index 0000000..7337af5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_light.png
new file mode 100644
index 0000000..f3f31ef
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_dark.png
new file mode 100644
index 0000000..20d9f57
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_light.png
new file mode 100644
index 0000000..bf8eb77
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_dark.png
new file mode 100644
index 0000000..56a0e14
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_light.png
new file mode 100644
index 0000000..67425e1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_dark.png
new file mode 100644
index 0000000..7c76e19
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_light.png
new file mode 100644
index 0000000..e02f1ed
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_dark.png
new file mode 100644
index 0000000..f5fdcdd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_light.png
new file mode 100644
index 0000000..8ce9b819
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_dark.png
new file mode 100644
index 0000000..a29e443
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_light.png
new file mode 100644
index 0000000..349ca89
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_dark.png
new file mode 100644
index 0000000..0fc75d5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_light.png
new file mode 100644
index 0000000..5cbd27c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_dark.png
new file mode 100644
index 0000000..0ebb0ac
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_light.png
new file mode 100644
index 0000000..5b514aa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_dark.png
new file mode 100644
index 0000000..8e7fe5c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_light.png
new file mode 100644
index 0000000..efb2c10
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_dark.png
new file mode 100644
index 0000000..0db679e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_light.png
new file mode 100644
index 0000000..51c6051
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_dark.png
new file mode 100644
index 0000000..fdb2121
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_light.png
new file mode 100644
index 0000000..9ce7e3a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_dark.png
new file mode 100644
index 0000000..e8601ce
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_light.png
new file mode 100644
index 0000000..34928d7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..792fd77
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_dark.png
new file mode 100755
index 0000000..f171a8c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_dark.png
new file mode 100755
index 0000000..c8cb6ca
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_light.png
new file mode 100755
index 0000000..9c8863d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_light.png
new file mode 100755
index 0000000..9335038
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_dark.png
new file mode 100755
index 0000000..a6a4858
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_light.png
new file mode 100755
index 0000000..4ca6787
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_00.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_00.png
new file mode 100644
index 0000000..b2305d2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_00.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_01.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_01.png
new file mode 100644
index 0000000..59395d4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_01.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_02.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_02.png
new file mode 100644
index 0000000..70a7282
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_02.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_03.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_03.png
new file mode 100644
index 0000000..b3f0f53
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_03.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_04.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_04.png
new file mode 100644
index 0000000..66a80d9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_04.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_05.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_05.png
new file mode 100644
index 0000000..8ec3939
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_05.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_06.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_06.png
new file mode 100644
index 0000000..0f02536
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_06.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_07.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_07.png
new file mode 100644
index 0000000..ba228f4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_07.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_08.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_08.png
new file mode 100644
index 0000000..304277e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_08.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_09.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_09.png
new file mode 100644
index 0000000..f865bfb
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_09.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_10.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_10.png
new file mode 100644
index 0000000..17c5d6b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_10.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_11.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_11.png
new file mode 100644
index 0000000..a2f4ad5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_11.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_12.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_12.png
new file mode 100644
index 0000000..c230648
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_12.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_13.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_13.png
new file mode 100644
index 0000000..b99324e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_13.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_14.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_14.png
new file mode 100644
index 0000000..c8618f0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_14.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_15.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_15.png
new file mode 100644
index 0000000..4a0d770
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_15.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_00.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_00.png
new file mode 100644
index 0000000..4a0d770
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_00.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_01.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_01.png
new file mode 100644
index 0000000..4db4e50
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_01.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_02.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_02.png
new file mode 100644
index 0000000..82b5f03
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_02.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_03.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_03.png
new file mode 100644
index 0000000..b05c758
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_03.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_04.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_04.png
new file mode 100644
index 0000000..fa5c7fa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_04.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_05.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_05.png
new file mode 100644
index 0000000..2c287e4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_05.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_06.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_06.png
new file mode 100644
index 0000000..eb7d0cf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_06.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_07.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_07.png
new file mode 100644
index 0000000..95fa72b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_07.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_08.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_08.png
new file mode 100644
index 0000000..5650eea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_08.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_09.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_09.png
new file mode 100644
index 0000000..6f44355
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_09.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_10.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_10.png
new file mode 100644
index 0000000..4e877c3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_10.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_11.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_11.png
new file mode 100644
index 0000000..7927f0a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_11.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_12.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_12.png
new file mode 100644
index 0000000..71b19bb
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_12.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_13.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_13.png
new file mode 100644
index 0000000..bf5921e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_13.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_14.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_14.png
new file mode 100644
index 0000000..14b76b1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_14.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_15.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_15.png
new file mode 100644
index 0000000..b2305d2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_15.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..04a9525
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable/ic_arrow_back.xml b/packages/MediaComponents/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..5aba8c6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_cast.xml b/packages/MediaComponents/res/drawable/ic_cast.xml
new file mode 100644
index 0000000..ac22a4b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_cast.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_chevron_left.xml b/packages/MediaComponents/res/drawable/ic_chevron_left.xml
new file mode 100644
index 0000000..8336d17
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_chevron_left.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_chevron_right.xml b/packages/MediaComponents/res/drawable/ic_chevron_right.xml
new file mode 100644
index 0000000..fb2ce09
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_chevron_right.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_forward_30.xml b/packages/MediaComponents/res/drawable/ic_forward_30.xml
new file mode 100644
index 0000000..7efdf16
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_forward_30.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <group>
+        <clip-path android:pathData="M24,24H0V0h24v24z M 0,0" />
+    <path
+        android:pathData="M9.6 13.5h.4c.2 0 .4,-.1.5,-.2s.2,-.2.2,-.4v-.2s-.1,-.1,-.1,-.2,-.1,-.1,-.2,-.1h-.5s-.1.1,-.2.1,-.1.1,-.1.2v.2h-1c0,-.2 0,-.3.1,-.5s.2,-.3.3,-.4.3,-.2.4,-.2.4,-.1.5,-.1c.2 0 .4 0 .6.1s.3.1.5.2.2.2.3.4.1.3.1.5v.3s-.1.2,-.1.3,-.1.2,-.2.2,-.2.1,-.3.2c.2.1.4.2.5.4s.2.4.2.6c0 .2 0 .4,-.1.5s-.2.3,-.3.4,-.3.2,-.5.2,-.4.1,-.6.1c-.2 0,-.4 0,-.5,-.1s-.3,-.1,-.5,-.2,-.2,-.2,-.3,-.4,-.1,-.4,-.1,-.6h.8v.2s.1.1.1.2.1.1.2.1h.5s.1,-.1.2,-.1.1,-.1.1,-.2v-.5s-.1,-.1,-.1,-.2,-.1,-.1,-.2,-.1h-.6v-.7zm5.7.7c0 .3 0 .6,-.1.8l-.3.6s-.3.3,-.5.3,-.4.1,-.6.1,-.4 0,-.6,-.1,-.3,-.2,-.5,-.3,-.2,-.3,-.3,-.6,-.1,-.5,-.1,-.8v-.7c0,-.3 0,-.6.1,-.8l.3,-.6s.3,-.3.5,-.3.4,-.1.6,-.1.4 0 .6.1.3.2.5.3.2.3.3.6.1.5.1.8v.7zm-.9,-.8v-.5s-.1,-.2,-.1,-.3,-.1,-.1,-.2,-.2,-.2,-.1,-.3,-.1,-.2 0,-.3.1l-.2.2s-.1.2,-.1.3v2s.1.2.1.3.1.1.2.2.2.1.3.1.2 0 .3,-.1l.2,-.2s.1,-.2.1,-.3v-1.5zM4 13c0 4.4 3.6 8 8 8s8,-3.6 8,-8h-2c0 3.3,-2.7 6,-6 6s-6,-2.7,-6,-6 2.7,-6 6,-6v4l5,-5,-5,-5v4c-4.4 0,-8 3.6,-8 8z"
+        android:fillColor="#FFFFFF"/>
+    </group>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_fullscreen.xml b/packages/MediaComponents/res/drawable/ic_fullscreen.xml
new file mode 100644
index 0000000..4b4f6bc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_fullscreen.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml b/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml
new file mode 100644
index 0000000..bc204e2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml
new file mode 100644
index 0000000..73be228
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,16L9,16L9,8h2v8zM15,16h-2L13,8h2v8z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml
new file mode 100644
index 0000000..9d39def
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_rewind_10.xml b/packages/MediaComponents/res/drawable/ic_rewind_10.xml
new file mode 100644
index 0000000..ae586b4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_rewind_10.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <group>
+        <clip-path android:pathData="M0,0h24v24H0V0z M 0,0" />
+    <path
+        android:pathData="M12 5V1L7 6l5 5V7c3.3 0 6 2.7 6 6s-2.7 6,-6 6,-6,-2.7,-6,-6H4c0 4.4 3.6 8 8 8s8,-3.6 8,-8,-3.6,-8,-8,-8zm-1.1 11H10v-3.3L9 13v-.7l1.8,-.6h.1V16zm4.3,-1.8c0 .3 0 .6,-.1.8l-.3.6s-.3.3,-.5.3,-.4.1,-.6.1,-.4 0,-.6,-.1,-.3,-.2,-.5,-.3,-.2,-.3,-.3,-.6,-.1,-.5,-.1,-.8v-.7c0,-.3 0,-.6.1,-.8l.3,-.6s.3,-.3.5,-.3.4,-.1.6,-.1.4 0 .6.1c.2.1.3.2.5.3s.2.3.3.6.1.5.1.8v.7zm-.9,-.8v-.5s-.1,-.2,-.1,-.3,-.1,-.1,-.2,-.2,-.2,-.1,-.3,-.1,-.2 0,-.3.1l-.2.2s-.1.2,-.1.3v2s.1.2.1.3.1.1.2.2.2.1.3.1.2 0 .3,-.1l.2,-.2s.1,-.2.1,-.3v-1.5z"
+        android:fillColor="#FFFFFF"/>
+    </group>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_skip_next.xml b/packages/MediaComponents/res/drawable/ic_skip_next.xml
new file mode 100644
index 0000000..b1f2812
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_skip_next.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_skip_previous.xml b/packages/MediaComponents/res/drawable/ic_skip_previous.xml
new file mode 100644
index 0000000..81da314
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_skip_previous.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/mr_button_connected_dark.xml b/packages/MediaComponents/res/drawable/mr_button_connected_dark.xml
new file mode 100644
index 0000000..110ff13
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_connected_dark.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="true">
+    <item android:drawable="@drawable/ic_mr_button_connected_00_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_01_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_02_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_03_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_04_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_05_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_06_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_07_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_08_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_09_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_10_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_11_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_12_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_13_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_14_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_15_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_16_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_17_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_18_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_19_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_20_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_21_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_22_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_23_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_24_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_25_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_26_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_27_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_28_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_29_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_30_dark" android:duration="42" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_button_connected_light.xml b/packages/MediaComponents/res/drawable/mr_button_connected_light.xml
new file mode 100644
index 0000000..bcfc7fe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_connected_light.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="true">
+    <item android:drawable="@drawable/ic_mr_button_connected_00_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_01_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_02_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_03_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_04_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_05_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_06_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_07_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_08_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_09_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_10_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_11_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_12_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_13_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_14_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_15_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_16_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_17_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_18_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_19_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_20_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_21_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_22_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_23_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_24_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_25_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_26_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_27_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_28_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_29_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connected_30_light" android:duration="42" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_button_connecting_dark.xml b/packages/MediaComponents/res/drawable/mr_button_connecting_dark.xml
new file mode 100644
index 0000000..55af7b3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_connecting_dark.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/ic_mr_button_connecting_00_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_01_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_02_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_03_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_04_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_05_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_06_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_07_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_08_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_09_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_10_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_11_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_12_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_13_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_14_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_15_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_16_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_17_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_18_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_19_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_20_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_21_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_22_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_23_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_24_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_25_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_26_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_27_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_28_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_29_dark" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_30_dark" android:duration="42" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_button_connecting_light.xml b/packages/MediaComponents/res/drawable/mr_button_connecting_light.xml
new file mode 100644
index 0000000..93b4170
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_connecting_light.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<animation-list
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="false">
+    <item android:drawable="@drawable/ic_mr_button_connecting_00_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_01_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_02_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_03_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_04_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_05_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_06_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_07_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_08_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_09_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_10_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_11_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_12_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_13_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_14_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_15_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_16_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_17_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_18_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_19_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_20_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_21_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_22_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_23_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_24_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_25_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_26_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_27_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_28_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_29_light" android:duration="42" />
+    <item android:drawable="@drawable/ic_mr_button_connecting_30_light" android:duration="42" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_button_dark.xml b/packages/MediaComponents/res/drawable/mr_button_dark.xml
new file mode 100644
index 0000000..8f1dfaa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_dark.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:state_enabled="true"
+            android:drawable="@drawable/mr_button_connected_dark" />
+    <item android:state_checkable="true" android:state_enabled="true"
+            android:drawable="@drawable/mr_button_connecting_dark" />
+    <item android:state_enabled="true"
+            android:drawable="@drawable/ic_mr_button_disconnected_dark" />
+    <item android:drawable="@drawable/ic_mr_button_disabled_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_button_light.xml b/packages/MediaComponents/res/drawable/mr_button_light.xml
new file mode 100644
index 0000000..1d3d84e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_light.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:state_enabled="true"
+            android:drawable="@drawable/mr_button_connected_light" />
+    <item android:state_checkable="true" android:state_enabled="true"
+            android:drawable="@drawable/mr_button_connecting_light" />
+    <item android:state_enabled="true"
+            android:drawable="@drawable/ic_mr_button_disconnected_light" />
+    <item android:drawable="@drawable/ic_mr_button_disabled_light" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_dialog_close_dark.xml b/packages/MediaComponents/res/drawable/mr_dialog_close_dark.xml
new file mode 100644
index 0000000..288c8c7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_dialog_close_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_dialog_close_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_dialog_close_light.xml b/packages/MediaComponents/res/drawable/mr_dialog_close_light.xml
new file mode 100644
index 0000000..cd50e0f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_dialog_close_light.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <bitmap
+            android:src="@drawable/ic_dialog_close_light"
+            android:alpha="0.87" />
+    </item>
+
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_dialog_material_background_dark.xml b/packages/MediaComponents/res/drawable/mr_dialog_material_background_dark.xml
new file mode 100644
index 0000000..ebc7eca
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_dialog_material_background_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<!-- This is the copy of @drawable/abc_dialog_material_background_dark except for inset
+     which includes unnecessary padding. -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+    <corners android:radius="2dp" />
+    <solid android:color="@color/background_floating_material_dark" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/mr_dialog_material_background_light.xml b/packages/MediaComponents/res/drawable/mr_dialog_material_background_light.xml
new file mode 100644
index 0000000..c1b235a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_dialog_material_background_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<!-- This is the copy of @drawable/abc_dialog_material_background_light except for inset
+     which includes unnecessary padding. -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+    <corners android:radius="2dp" />
+    <solid android:color="@color/background_floating_material_light" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/mr_group_collapse.xml b/packages/MediaComponents/res/drawable/mr_group_collapse.xml
new file mode 100644
index 0000000..8f72bc8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_group_collapse.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="true">
+    <item android:drawable="@drawable/ic_group_collapse_00" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_01" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_02" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_03" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_04" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_05" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_06" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_07" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_08" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_09" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_10" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_11" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_12" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_13" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_14" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_collapse_15" android:duration="13" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_group_expand.xml b/packages/MediaComponents/res/drawable/mr_group_expand.xml
new file mode 100644
index 0000000..6b3fdb6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_group_expand.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+        android:oneshot="true">
+    <item android:drawable="@drawable/ic_group_expand_00" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_01" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_02" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_03" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_04" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_05" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_06" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_07" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_08" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_09" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_10" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_11" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_12" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_13" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_14" android:duration="13" />
+    <item android:drawable="@drawable/ic_group_expand_15" android:duration="13" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_media_pause_dark.xml b/packages/MediaComponents/res/drawable/mr_media_pause_dark.xml
new file mode 100644
index 0000000..86218a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_pause_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_media_pause_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_pause_light.xml b/packages/MediaComponents/res/drawable/mr_media_pause_light.xml
new file mode 100644
index 0000000..2dd1f02
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_pause_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <bitmap
+            android:src="@drawable/ic_media_pause_light"
+            android:alpha="0.87" />
+    </item>
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_play_dark.xml b/packages/MediaComponents/res/drawable/mr_media_play_dark.xml
new file mode 100644
index 0000000..9d45a33
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_play_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_media_play_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_play_light.xml b/packages/MediaComponents/res/drawable/mr_media_play_light.xml
new file mode 100644
index 0000000..f1fb7a6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_play_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <bitmap
+            android:src="@drawable/ic_media_play_light"
+            android:alpha="0.87" />
+    </item>
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_stop_dark.xml b/packages/MediaComponents/res/drawable/mr_media_stop_dark.xml
new file mode 100644
index 0000000..3e108a9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_stop_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_media_stop_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_stop_light.xml b/packages/MediaComponents/res/drawable/mr_media_stop_light.xml
new file mode 100644
index 0000000..b2c6ce8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_stop_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <bitmap
+            android:src="@drawable/ic_media_stop_light"
+            android:alpha="0.87" />
+    </item>
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_dark.xml b/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_dark.xml
new file mode 100644
index 0000000..44f4fd6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ic_audiotrack_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_light.xml b/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_light.xml
new file mode 100644
index 0000000..5c9dbc0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <bitmap
+            android:src="@drawable/ic_audiotrack_light"
+            android:alpha="0.87" />
+    </item>
+</selector>
diff --git a/packages/MediaComponents/res/interpolator/mr_fast_out_slow_in.xml b/packages/MediaComponents/res/interpolator/mr_fast_out_slow_in.xml
new file mode 100644
index 0000000..6b6a171
--- /dev/null
+++ b/packages/MediaComponents/res/interpolator/mr_fast_out_slow_in.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
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.4"
+    android:controlY1="0"
+    android:controlX2="0.2"
+    android:controlY2="1"/>
diff --git a/packages/MediaComponents/res/interpolator/mr_linear_out_slow_in.xml b/packages/MediaComponents/res/interpolator/mr_linear_out_slow_in.xml
new file mode 100644
index 0000000..20bf298
--- /dev/null
+++ b/packages/MediaComponents/res/interpolator/mr_linear_out_slow_in.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
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0"
+    android:controlY1="0"
+    android:controlX2="0.2"
+    android:controlY2="1"/>
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
new file mode 100644
index 0000000..ff4f12a
--- /dev/null
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     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:background="#55000000"
+    android:orientation="vertical"
+    android:layoutDirection="ltr">
+
+    <RelativeLayout
+        android:id="@+id/title_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <RadioButton
+            android:id="@+id/back"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:checked="true"
+            android:visibility="gone"/>
+
+        <TextView
+            android:id="@+id/title_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@id/back"
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="15dp"
+            android:paddingTop="4dp"
+            android:paddingStart="4dp"
+            android:paddingEnd="4dp"
+            android:textSize="20sp"
+            android:text="North by Northwest"
+            android:textColor="#FFFFFFFF" />
+
+        <ImageButton
+            android:id="@+id/cast"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true"
+            style="@style/TitleBarButton.MediaRouteButton"/>
+
+    </RelativeLayout>
+
+    <LinearLayout
+        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" />
+
+    </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"/>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="15dp"
+        android:paddingRight="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" />
+
+        <ImageButton
+            android:id="@+id/overflow"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true"
+            style="@style/BottomBarButton.Overflow"/>
+
+        <ImageButton
+            android:id="@+id/fullscreen"
+            android:layout_toLeftOf="@id/overflow"
+            android:layout_centerVertical="true"
+            style="@style/BottomBarButton.FullScreen"/>
+
+        <ImageButton
+            android:id="@+id/cc"
+            android:scaleType="fitCenter"
+            android:layout_toLeftOf="@id/fullscreen"
+            android:layout_centerVertical="true"
+            style="@style/BottomBarButton.CC" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/mr_chooser_dialog.xml b/packages/MediaComponents/res/layout/mr_chooser_dialog.xml
new file mode 100644
index 0000000..ee89e16
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_chooser_dialog.xml
@@ -0,0 +1,55 @@
+<?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="fill_parent"
+             android:layout_height="wrap_content"
+             android:orientation="vertical">
+    <TextView android:id="@+id/mr_chooser_title"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:paddingLeft="24dp"
+              android:paddingRight="24dp"
+              android:paddingTop="24dp"
+              android:text="@string/mr_chooser_title"
+              android:singleLine="true"
+              android:ellipsize="end"
+              android:textAppearance="@style/TextAppearance.MediaRouter.Title" />
+    <ListView android:id="@+id/mr_chooser_list"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:divider="@android:color/transparent"
+              android:dividerHeight="0dp" />
+    <LinearLayout android:id="@android:id/empty"
+              android:layout_width="fill_parent"
+              android:layout_height="240dp"
+              android:orientation="vertical"
+              android:paddingTop="90dp"
+              android:paddingLeft="16dp"
+              android:paddingRight="16dp"
+              android:visibility="gone">
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="center"
+                  android:text="@string/mr_chooser_searching"
+                  android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText" />
+        <ProgressBar android:layout_width="150dp"
+                     android:layout_height="wrap_content"
+                     android:layout_gravity="center"
+                     android:indeterminate="true"
+                     style="?android:attr/progressBarStyleHorizontal" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/mr_chooser_list_item.xml b/packages/MediaComponents/res/layout/mr_chooser_list_item.xml
new file mode 100644
index 0000000..958879b
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_chooser_list_item.xml
@@ -0,0 +1,51 @@
+<?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="fill_parent"
+              android:layout_height="wrap_content"
+              android:minHeight="56dp"
+              android:paddingLeft="24dp"
+              android:paddingRight="24dp"
+              android:orientation="horizontal"
+              android:gravity="center_vertical" >
+
+    <ImageView android:id="@+id/mr_chooser_route_icon"
+               android:layout_width="24dp"
+               android:layout_height="24dp"
+               android:layout_marginRight="24dp" />
+
+    <LinearLayout android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_marginBottom="1dp"
+                  android:orientation="vertical" >
+
+        <TextView android:id="@+id/mr_chooser_route_name"
+                  android:layout_width="fill_parent"
+                  android:layout_height="32dp"
+                  android:singleLine="true"
+                  android:ellipsize="marquee"
+                  android:textAppearance="@style/TextAppearance.MediaRouter.PrimaryText" />
+
+        <TextView android:id="@+id/mr_chooser_route_desc"
+                  android:layout_width="fill_parent"
+                  android:layout_height="24dp"
+                  android:singleLine="true"
+                  android:ellipsize="marquee"
+                  android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml b/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml
new file mode 100644
index 0000000..b409e6b
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml
@@ -0,0 +1,101 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@+id/mr_expandable_area"
+          android:layout_width="fill_parent"
+          android:layout_height="fill_parent">
+    <LinearLayout android:id="@+id/mr_dialog_area"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="center"
+                  android:orientation="vertical"
+                  android:background="?attr/colorBackgroundFloating">
+        <LinearLayout android:id="@+id/mr_title_bar"
+                      android:layout_width="fill_parent"
+                      android:layout_height="wrap_content"
+                      android:paddingLeft="24dp"
+                      android:paddingRight="12dp"
+                      android:orientation="horizontal" >
+            <TextView android:id="@+id/mr_name"
+                      android:layout_width="0dp"
+                      android:layout_height="72dp"
+                      android:layout_weight="1"
+                      android:gravity="center_vertical"
+                      android:singleLine="true"
+                      android:ellipsize="end"
+                      android:textAppearance="@style/TextAppearance.MediaRouter.Title" />
+            <ImageButton android:id="@+id/mr_close"
+                         android:layout_width="48dp"
+                         android:layout_height="48dp"
+                         android:layout_gravity="center_vertical"
+                         android:contentDescription="@string/mr_controller_close_description"
+                         android:src="?attr/mediaRouteCloseDrawable"
+                         android:background="?attr/selectableItemBackgroundBorderless" />
+        </LinearLayout>
+        <FrameLayout android:id="@+id/mr_custom_control"
+                     android:layout_width="fill_parent"
+                     android:layout_height="wrap_content"
+                     android:visibility="gone" />
+        <FrameLayout android:id="@+id/mr_default_control"
+                     android:layout_width="fill_parent"
+                     android:layout_height="wrap_content">
+            <ImageView android:id="@+id/mr_art"
+                       android:layout_width="fill_parent"
+                       android:layout_height="wrap_content"
+                       android:adjustViewBounds="true"
+                       android:scaleType="fitXY"
+                       android:background="?attr/colorPrimary"
+                       android:layout_gravity="top"
+                       android:contentDescription="@string/mr_controller_album_art"
+                       android:visibility="gone" />
+            <LinearLayout android:layout_width="fill_parent"
+                          android:layout_height="wrap_content"
+                          android:orientation="vertical"
+                          android:layout_gravity="bottom"
+                          android:splitMotionEvents="false">
+                <LinearLayout android:id="@+id/mr_media_main_control"
+                              android:layout_width="fill_parent"
+                              android:layout_height="wrap_content"
+                              android:orientation="vertical"
+                              android:paddingTop="16dp"
+                              android:paddingBottom="16dp"
+                              android:layout_gravity="bottom"
+                              android:theme="?attr/mediaRouteControlPanelThemeOverlay">
+                    <include android:id="@+id/mr_playback_control"
+                             layout="@layout/mr_playback_control" />
+                    <View android:id="@+id/mr_control_divider"
+                          android:layout_width="fill_parent"
+                          android:layout_height="8dp"
+                          android:visibility="gone" />
+                    <include android:id="@+id/mr_volume_control"
+                             layout="@layout/mr_volume_control" />
+                </LinearLayout>
+                <android.support.v7.app.OverlayListView
+                        android:id="@+id/mr_volume_group_list"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:paddingTop="@dimen/mr_controller_volume_group_list_padding_top"
+                        android:scrollbarStyle="outsideOverlay"
+                        android:clipToPadding="false"
+                        android:visibility="gone"
+                        android:splitMotionEvents="false"
+                        android:theme="?attr/mediaRouteControlPanelThemeOverlay" />
+            </LinearLayout>
+        </FrameLayout>
+        <include layout="@layout/abc_alert_dialog_button_bar_material" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/MediaComponents/res/layout/mr_controller_volume_item.xml b/packages/MediaComponents/res/layout/mr_controller_volume_item.xml
new file mode 100644
index 0000000..a89058b
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_controller_volume_item.xml
@@ -0,0 +1,52 @@
+<?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="fill_parent"
+              android:layout_height="wrap_content">
+    <LinearLayout android:id="@+id/volume_item_container"
+                  android:layout_width="fill_parent"
+                  android:layout_height="@dimen/mr_controller_volume_group_list_item_height"
+                  android:paddingLeft="24dp"
+                  android:paddingRight="60dp"
+                  android:paddingBottom="8dp"
+                  android:orientation="vertical" >
+        <TextView android:id="@+id/mr_name"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText"
+                  android:singleLine="true" />
+        <LinearLayout android:layout_width="fill_parent"
+                      android:layout_height="wrap_content"
+                      android:orientation="horizontal"
+                      android:gravity="center_vertical">
+            <ImageView android:id="@+id/mr_volume_item_icon"
+                       android:layout_width="@dimen/mr_controller_volume_group_list_item_icon_size"
+                       android:layout_height="@dimen/mr_controller_volume_group_list_item_icon_size"
+                       android:layout_marginTop="8dp"
+                       android:layout_marginBottom="8dp"
+                       android:scaleType="fitCenter"
+                       android:src="?attr/mediaRouteAudioTrackDrawable" />
+            <android.support.v7.app.MediaRouteVolumeSlider
+                android:id="@+id/mr_volume_slider"
+                android:layout_width="fill_parent"
+                android:layout_height="40dp"
+                android:minHeight="40dp"
+                android:maxHeight="40dp"
+                android:contentDescription="@string/mr_controller_volume_slider" />
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/mr_playback_control.xml b/packages/MediaComponents/res/layout/mr_playback_control.xml
new file mode 100644
index 0000000..870dd50
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_playback_control.xml
@@ -0,0 +1,50 @@
+<?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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:paddingLeft="24dp"
+                android:paddingRight="12dp" >
+    <ImageButton android:id="@+id/mr_control_playback_ctrl"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_marginLeft="12dp"
+                 android:layout_alignParentRight="true"
+                 android:layout_centerVertical="true"
+                 android:contentDescription="@string/mr_controller_play"
+                 android:background="?attr/selectableItemBackgroundBorderless"
+                 android:visibility="gone" />
+    <LinearLayout android:id="@+id/mr_control_title_container"
+                  android:orientation="vertical"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_toLeftOf="@id/mr_control_playback_ctrl"
+                  android:layout_alignParentLeft="true"
+                  android:layout_centerVertical="true">
+        <TextView android:id="@+id/mr_control_title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAppearance="@style/TextAppearance.MediaRouter.PrimaryText"
+                  android:singleLine="true" />
+        <TextView android:id="@+id/mr_control_subtitle"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText"
+                  android:singleLine="true" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/packages/MediaComponents/res/layout/mr_volume_control.xml b/packages/MediaComponents/res/layout/mr_volume_control.xml
new file mode 100644
index 0000000..5212532
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_volume_control.xml
@@ -0,0 +1,50 @@
+<?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="fill_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp"
+        android:gravity="center_vertical"
+        android:paddingLeft="24dp"
+        android:paddingRight="12dp"
+        android:splitMotionEvents="false">
+    <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:src="?attr/mediaRouteAudioTrackDrawable"
+            android:gravity="center"
+            android:scaleType="center"/>
+    <!-- Since dialog's top layout mr_expandable_area is clickable, it propagates pressed state
+         to its non-clickable children. Specify android:clickable="true" to prevent volume slider
+         from having false pressed state. -->
+    <android.support.v7.app.MediaRouteVolumeSlider
+            android:id="@+id/mr_volume_slider"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:minHeight="48dp"
+            android:maxHeight="48dp"
+            android:layout_weight="1"
+            android:clickable="true"
+            android:contentDescription="@string/mr_controller_volume_slider" />
+    <android.support.v7.app.MediaRouteExpandCollapseButton
+            android:id="@+id/mr_group_expand_collapse"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="12dp"
+            android:background="?attr/selectableItemBackgroundBorderless"
+            android:visibility="gone"/>
+</LinearLayout>
diff --git a/packages/MediaComponents/res/values-af/strings.xml b/packages/MediaComponents/res/values-af/strings.xml
new file mode 100644
index 0000000..47230ad
--- /dev/null
+++ b/packages/MediaComponents/res/values-af/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Stelsel"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Toestelle"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-knoppie"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Uitsaai-knoppie. Ontkoppel"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Uitsaai-knoppie. Koppel tans"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Uitsaai-knoppie. Gekoppel"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Saai uit na"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Vind tans toestelle"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ontkoppel"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Hou op uitsaai"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Maak toe"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Speel"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Laat wag"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Vou uit"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Vou in"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumkunswerk"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volumeglyer"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Geen media is gekies nie"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Geen inligting beskikbaar nie"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Saai tans skerm uit"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-am/strings.xml b/packages/MediaComponents/res/values-am/strings.xml
new file mode 100644
index 0000000..39a1903
--- /dev/null
+++ b/packages/MediaComponents/res/values-am/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ስርዓት"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"መሣሪያዎች"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"የCast አዝራር"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast አዝራር። ግንኙነት ተቋርጧል"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast አዝራር በማገናኘት ላይ"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast አዝራር። ተገናኝቷል"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Cast አድርግ ወደ"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"መሣሪያዎችን በማግኘት ላይ"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"ግንኙነት አቋርጥ"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Cast ማድረግ አቁም"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"ዝጋ"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"አጫውት"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"ለአፍታ አቁም"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"አቁም"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"አስፋ"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ሰብስብ"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"የአልበም ስነ-ጥበብ"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ተንሸራታች የድምፅ መቆጣጠሪያ"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ምንም ማህደረመረጃ አልተመረጠም"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ምንም መረጃ አይገኝም"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"ማያ ገጽን በመውሰድ ላይ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ar/strings.xml b/packages/MediaComponents/res/values-ar/strings.xml
new file mode 100644
index 0000000..f8fb97d
--- /dev/null
+++ b/packages/MediaComponents/res/values-ar/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"النظام"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"الأجهزة"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"زر الإرسال"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"زر الإرسال. تم قطع الاتصال"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"زر الإرسال. جارٍ الاتصال"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"زر الإرسال. تم الاتصال"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"إرسال إلى"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"جارٍ البحث عن أجهزة"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"قطع الاتصال"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"إيقاف الإرسال"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"إغلاق"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"تشغيل"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"إيقاف مؤقت"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"إيقاف"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"توسيع"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"تصغير"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"صورة الألبوم"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"شريط تمرير مستوى الصوت"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"لم يتم اختيار أي وسائط"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"لا تتوفر أي معلومات"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"جارٍ إرسال الشاشة"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-az/strings.xml b/packages/MediaComponents/res/values-az/strings.xml
new file mode 100644
index 0000000..a3c60ab
--- /dev/null
+++ b/packages/MediaComponents/res/values-az/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Cihazlar"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Yayım düyməsi"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Yayım düyməsi. Bağlantı kəsildi"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Yayım düyməsi. Qoşulur"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Yayım düyməsi. Qoşuldu"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Bura yayımlayın"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Cihazlar axtarılır"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Bağlantını kəsin"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Yayımı dayandırın"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Qapadın"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Oynadın"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Durdurun"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Dayandırın"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Genişləndirin"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Yığcamlaşdırın"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albom incəsənəti"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Səs hərmi diyircəyi"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Heç bir media seçilməyib"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Əlçatan məlumat yoxdur"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekran yayımlanır"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-b+sr+Latn/strings.xml b/packages/MediaComponents/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..e25bd6e
--- /dev/null
+++ b/packages/MediaComponents/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Dugme Prebaci"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Dugme Prebaci. Veza je prekinuta"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Dugme Prebaci. Povezuje se"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Dugme Prebaci. Povezan je"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Prebacuj na"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Pronalaženje uređaja"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zaustavi prebacivanje"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvori"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Pusti"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziraj"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Zaustavi"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširi"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skupi"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Omot albuma"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Klizač za jačinu zvuka"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nema izabranih medija"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nisu dostupne nikakve informacije"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prebacuje se ekran"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-be/strings.xml b/packages/MediaComponents/res/values-be/strings.xml
new file mode 100644
index 0000000..ac391c1
--- /dev/null
+++ b/packages/MediaComponents/res/values-be/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Сістэма"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Прылады"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Кнопка трансляцыі"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Кнопка трансляцыі. Адключана"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Кнопка трансляцыі. Ідзе падключэнне"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Кнопка трансляцыі. Падключана"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Трансліраваць на"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Пошук прылад"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Адлучыць"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Спыніць трансляцыю"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Закрыць"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Прайграць"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Прыпыніць"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Спыніць"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Разгарнуць"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Згарнуць"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Вокладка альбома"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Паўзунок гучнасці"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медыяфайл не выбраны"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Інфармацыя адсутнічае"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Экран трансляцыі"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-bg/strings.xml b/packages/MediaComponents/res/values-bg/strings.xml
new file mode 100644
index 0000000..76712d4
--- /dev/null
+++ b/packages/MediaComponents/res/values-bg/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Устройства"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Бутон за предаване"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Бутон за предаване. Връзката е прекратена"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Бутон за предаване. Свързва се"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Бутон за предаване. Установена е връзка"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Предаване към"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Търсят се устройства"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Прекратяване на връзката"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Спиране на предаването"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Затваряне"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Пускане"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Поставяне на пауза"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Спиране"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Разгъване"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Свиване"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Обложка на албума"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Плъзгач за силата на звука"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Няма избрана мултимедия"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Няма налична информация"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Екранът се предава"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-bn/strings.xml b/packages/MediaComponents/res/values-bn/strings.xml
new file mode 100644
index 0000000..1bf5932
--- /dev/null
+++ b/packages/MediaComponents/res/values-bn/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"সিস্টেম"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ডিভাইসগুলি"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"কাস্ট করার বোতাম"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"কাস্ট করার বোতাম৷ সংযোগ বিচ্ছিন্ন হয়েছে"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"কাস্ট করার বোতাম৷ সংযোগ করা হচ্ছে"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"কাস্ট করার বোতাম৷ সংযুক্ত হয়েছে"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"এতে কাস্ট করুন"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"ডিভাইসগুলিকে খোঁজা হচ্ছে"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"সংযোগ বিচ্ছিন্ন করুন"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"কাস্ট করা বন্ধ করুন"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"বন্ধ করুন"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"চালান"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"বিরাম দিন"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"থামান"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"বড় করুন"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"সঙ্কুচিত করুন"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"অ্যালবাম শৈলি"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ভলিউম স্লাইডার"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"কোনো মিডিয়া নির্বাচন করা হয়নি"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"কোনো তথ্য উপলব্ধ নেই"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"স্ক্রীন কাস্ট করা হচ্ছে"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-bs/strings.xml b/packages/MediaComponents/res/values-bs/strings.xml
new file mode 100644
index 0000000..711c742
--- /dev/null
+++ b/packages/MediaComponents/res/values-bs/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Dugme za emitiranje"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Dugme za emitiranje. Veza je prekinuta"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Dugme za emitiranje. Povezivanje"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Dugme za emitiranje. Povezano"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Emitiranje na"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Traženje uređaja"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zaustavi prebacivanje"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvori"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reproduciraj"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziraj"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Zaustavi"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširi"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skupi"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Omot albuma"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Klizač za jačinu zvuka"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nijedan medij nije odabran"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nema dostupnih informacija"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prebacuje se ekran"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ca/strings.xml b/packages/MediaComponents/res/values-ca/strings.xml
new file mode 100644
index 0000000..bf85acf
--- /dev/null
+++ b/packages/MediaComponents/res/values-ca/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositius"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Botó d\'emetre"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botó Emet. Desconnectat."</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botó Emet. S\'està connectant."</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botó Emet. Connectat."</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Emet a"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"S\'estan cercant dispositius"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconnecta"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Atura l\'emissió"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Tanca"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reprodueix"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Posa en pausa"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Atura"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Desplega"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Replega"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Imatge de l\'àlbum"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control lliscant de volum"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No s\'ha seleccionat cap fitxer multimèdia"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No hi ha informació disponible"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emissió de pantalla"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-cs/strings.xml b/packages/MediaComponents/res/values-cs/strings.xml
new file mode 100644
index 0000000..09a8920
--- /dev/null
+++ b/packages/MediaComponents/res/values-cs/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Systém"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Zařízení"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Tlačítko odesílání"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tlačítko odesílání. Odpojeno"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tlačítko odesílání. Připojování"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tlačítko odesílání. Připojeno"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Odesílat do"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Hledání zařízení"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Odpojit"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zastavit odesílání"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zavřít"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Přehrát"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pozastavit"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Zastavit"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozbalit"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sbalit"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Obal alba"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Posuvník hlasitosti"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nebyla vybrána žádná média"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nejsou k dispozici žádné informace"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Odesílání obsahu obrazovky"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-da/strings.xml b/packages/MediaComponents/res/values-da/strings.xml
new file mode 100644
index 0000000..8e7a790
--- /dev/null
+++ b/packages/MediaComponents/res/values-da/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheder"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-knap"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-knap. Forbindelsen er afbrudt"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-knap. Opretter forbindelse"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-knap. Tilsluttet"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Cast til"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Finder enheder"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Afbryd"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop med at caste"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Luk"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Afspil"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Sæt på pause"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Udvid"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skjul"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumgrafik"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Lydstyrkeskyder"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ingen medier er markeret"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Der er ingen tilgængelige oplysninger"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skærmen castes"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-de/strings.xml b/packages/MediaComponents/res/values-de/strings.xml
new file mode 100644
index 0000000..26bf57c
--- /dev/null
+++ b/packages/MediaComponents/res/values-de/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Geräte"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-Symbol"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Streaming-Schaltfläche. Nicht verbunden"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Streaming-Schaltfläche. Verbindung wird hergestellt"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Streaming-Schaltfläche. Verbunden"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Streamen auf"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Geräte werden gesucht."</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Verbindung trennen"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Streaming beenden"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Schließen"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Wiedergeben"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausieren"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Beenden"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Maximieren"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Minimieren"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumcover"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Schieberegler für die Lautstärke"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Keine Medien ausgewählt"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Keine Informationen verfügbar"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Bildschirm wird gestreamt."</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-el/strings.xml b/packages/MediaComponents/res/values-el/strings.xml
new file mode 100644
index 0000000..d82f69b
--- /dev/null
+++ b/packages/MediaComponents/res/values-el/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Σύστημα"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Συσκευές"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Κουμπί Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Κουμπί μετάδοσης. Αποσυνδέθηκε"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Κουμπί μετάδοση. Σύνδεση σε εξέλιξη"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Κουμπί μετάδοσης. Συνδέθηκε"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Μετάδοση σε"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Εύρεση συσκευών"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Αποσύνδεση"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Διακοπή μετάδοσης"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Κλείσιμο"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Αναπαραγωγή"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Παύση"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Διακοπή"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Ανάπτυξη"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Σύμπτυξη"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Εξώφυλλο άλμπουμ"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Ρυθμιστικό έντασης ήχου"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Δεν έχουν επιλεγεί μέσα"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Δεν υπάρχουν διαθέσιμες πληροφορίες"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Μετάδοση οθόνης"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rAU/strings.xml b/packages/MediaComponents/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..dd3f219
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rAU/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rCA/strings.xml b/packages/MediaComponents/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..dd3f219
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rCA/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rGB/strings.xml b/packages/MediaComponents/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..dd3f219
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rGB/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rIN/strings.xml b/packages/MediaComponents/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..dd3f219
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rIN/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rXC/strings.xml b/packages/MediaComponents/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..a87007e
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rXC/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎System‎‏‎‎‏‎"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‎Devices‎‏‎‎‏‎"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎Cast button‎‏‎‎‏‎"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‎‎Cast button. Disconnected‎‏‎‎‏‎"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎Cast button. Connecting‎‏‎‎‏‎"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎Cast button. Connected‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎Cast to‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎Finding devices‎‏‎‎‏‎"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎Disconnect‎‏‎‎‏‎"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎Stop casting‎‏‎‎‏‎"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎Close‎‏‎‎‏‎"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎Play‎‏‎‎‏‎"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎Pause‎‏‎‎‏‎"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎Stop‎‏‎‎‏‎"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎Expand‎‏‎‎‏‎"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‏‏‎‎‎‏‏‏‏‎‎Collapse‎‏‎‎‏‎"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎Album art‎‏‎‎‏‎"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎Volume slider‎‏‎‎‏‎"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‎‏‏‎‏‎No media selected‎‏‎‎‏‎"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‎‎‎No info available‎‏‎‎‏‎"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‏‏‎Casting screen‎‏‎‎‏‎"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-es-rUS/strings.xml b/packages/MediaComponents/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..441ead1
--- /dev/null
+++ b/packages/MediaComponents/res/values-es-rUS/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Botón para transmitir"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botón para transmitir (desconectado)"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botón para transmitir (conectando)"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botón para transmitir (conectado)"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Transmitir a"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Buscando dispositivos"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Detener la transmisión"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Cerrar"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reproducir"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Detener"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Mostrar"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ocultar"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Imagen del álbum"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control deslizante del volumen"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No se seleccionó ningún contenido multimedia"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Sin información disponible"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitiendo pantalla"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-es/strings.xml b/packages/MediaComponents/res/values-es/strings.xml
new file mode 100644
index 0000000..ff43008
--- /dev/null
+++ b/packages/MediaComponents/res/values-es/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Botón de enviar"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botón de enviar. Desconectado"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botón de enviar. Conectando"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botón de enviar. Conectado"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Enviar a"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Buscando dispositivos"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Detener envío de contenido"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Cerrar"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reproducir"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Detener"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Mostrar"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ocultar"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Portada del álbum"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control deslizante de volumen"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No se ha seleccionado ningún medio"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No hay información disponible"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Enviando pantalla"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-et/strings.xml b/packages/MediaComponents/res/values-et/strings.xml
new file mode 100644
index 0000000..453235b
--- /dev/null
+++ b/packages/MediaComponents/res/values-et/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Süsteem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Seadmed"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Ülekandenupp"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Ülekandenupp. Ühendus on katkestatud"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Ülekandenupp. Ühendamine"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Ülekandenupp. Ühendatud"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Ülekandmine seadmesse"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Seadmete otsimine"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Katkesta ühendus"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Peata ülekandmine"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Sulgemine"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Esitamine"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Peatamine"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Peata"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Laiendamine"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ahendamine"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumi kujundus"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Helitugevuse liugur"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Meediat pole valitud"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Teave puudub"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekraanikuva ülekandmine"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-eu/strings.xml b/packages/MediaComponents/res/values-eu/strings.xml
new file mode 100644
index 0000000..dba19e4
--- /dev/null
+++ b/packages/MediaComponents/res/values-eu/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Gailuak"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Igorri botoia"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Igortzeko botoia. Deskonektatuta"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Igortzeko botoia. Konektatzen"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Igortzeko botoia. Konektatuta"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Igorri hona:"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Gailuak bilatzen"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Deskonektatu"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Utzi igortzeari"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Itxi"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Erreproduzitu"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausatu"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Gelditu"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Zabaldu"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Tolestu"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumaren azala"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Bolumenaren graduatzailea"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ez da hautatu multimedia-edukirik"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Ez dago informaziorik"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Pantaila igortzen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-fa/strings.xml b/packages/MediaComponents/res/values-fa/strings.xml
new file mode 100644
index 0000000..4c6c779
--- /dev/null
+++ b/packages/MediaComponents/res/values-fa/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"سیستم"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"دستگاه‌ها"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"دکمه ارسال محتوا"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"دکمه فرستادن. ارتباط قطع شد"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"دکمه فرستادن. درحال مرتبط‌سازی"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"دکمه فرستادن. مرتبط شد"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"ارسال محتوا به"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"پیدا کردن دستگاه‌ها"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"قطع ارتباط"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"توقف ارسال محتوا"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"بستن"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"پخش"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"مکث"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"توقف"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"بزرگ کردن"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"کوچک کردن"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"عکس روی جلد آلبوم"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"لغزنده میزان صدا"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"رسانه انتخاب نشده است"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"اطلاعات در دسترس نیست"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"درحال فرستادن صفحه"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-fi/strings.xml b/packages/MediaComponents/res/values-fi/strings.xml
new file mode 100644
index 0000000..d683435
--- /dev/null
+++ b/packages/MediaComponents/res/values-fi/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Järjestelmä"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Laitteet"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-painike"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-painike. Yhteys katkaistu"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-painike. Yhdistetään"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-painike. Yhdistetty"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Suoratoiston kohde"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Etsitään laitteita"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Katkaise yhteys"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Lopeta suoratoisto"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Sulje"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Toista"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Keskeytä"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Pysäytä"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Laajenna"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Tiivistä"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumin kansikuva"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Äänenvoimakkuuden liukusäädin"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ei valittua mediaa."</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Tietoja ei ole saatavilla"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Suoratoistetaan näyttöä"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-fr-rCA/strings.xml b/packages/MediaComponents/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..c4f984b
--- /dev/null
+++ b/packages/MediaComponents/res/values-fr-rCA/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Système"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Appareils"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Bouton Diffuser"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Bouton Diffuser. Déconnecté"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Bouton Diffuser. Connexion en cours…"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Bouton Diffuser. Connecté"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Diffuser sur"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Recherche d\'appareils"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Se déconnecter"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Arrêter la diffusion"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Fermer"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Lire"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Interrompre"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Arrêter"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Développer"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Réduire"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Image de l\'album"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Curseur de réglage du volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Aucun média sélectionné"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Aucune information disponible"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Diffusion de l\'écran en cours"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-fr/strings.xml b/packages/MediaComponents/res/values-fr/strings.xml
new file mode 100644
index 0000000..12c312f
--- /dev/null
+++ b/packages/MediaComponents/res/values-fr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Système"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Appareils"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Icône Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Icône Cast. Déconnecté"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Icône Cast. Connexion…"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Icône Cast. Connecté"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Caster sur"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Recherche d\'appareils…"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Déconnecter"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Arrêter la diffusion"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Fermer"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Lecture"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Arrêter"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Développer"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Réduire"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Image de l\'album"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Curseur de volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Aucun média sélectionné"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Aucune information disponible"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Diffusion de l\'écran en cours…"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-gl/strings.xml b/packages/MediaComponents/res/values-gl/strings.xml
new file mode 100644
index 0000000..1b2c354
--- /dev/null
+++ b/packages/MediaComponents/res/values-gl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Botón de emitir"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botón de emitir. Desconectado"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botón de emitir. Conectando"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botón de emitir. Conectado"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Emitir a"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Buscando dispositivos"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Deter emisión"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Pechar"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reproduce"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Deter"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Ampliar"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Contraer"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Portada do álbum"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control desprazable do volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Non se seleccionaron recursos"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Non hai información dispoñible"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emisión de pantalla"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-gu/strings.xml b/packages/MediaComponents/res/values-gu/strings.xml
new file mode 100644
index 0000000..2cd5f3f
--- /dev/null
+++ b/packages/MediaComponents/res/values-gu/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"સિસ્ટમ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ઉપકરણો"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"કાસ્ટ કરો બટન"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"કાસ્ટ કરો બટન. ડિસ્કનેક્ટ કર્યું"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"કાસ્ટ કરો બટન. કનેક્ટ થઈ રહ્યું છે"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"કાસ્ટ કરો બટન. કનેક્ટ થયું"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"આના પર કાસ્ટ કરો"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"ઉપકરણો શોધી રહ્યાં છીએ"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"ડિસ્કનેક્ટ કરો"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"કાસ્ટ કરવાનું રોકો"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"બંધ કરો"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"ચલાવો"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"થોભાવો"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"રોકો"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"વિસ્તૃત કરો"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"સંકુચિત કરો"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"આલ્બમ કલા"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"વૉલ્યુમ સ્લાઇડર"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"કોઈ મીડિયા પસંદ કરેલ નથી"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"કોઈ માહિતી ઉપલબ્ધ નથી"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"સ્ક્રીનને કાસ્ટ કરી રહ્યાં છે"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-hi/strings.xml b/packages/MediaComponents/res/values-hi/strings.xml
new file mode 100644
index 0000000..9552a59
--- /dev/null
+++ b/packages/MediaComponents/res/values-hi/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"सिस्टम"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिवाइस"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"कास्ट करें बटन"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट करें बटन. डिसकनेक्ट है"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट करें बटन. कनेक्ट हो रहा है"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"कास्ट करें बटन. कनेक्ट है"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"इस पर कास्‍ट करें"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"डिवाइस ढूंढ रहा है"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"डिसकनेक्ट करें"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"कास्ट करना बंद करें"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"बंद करें"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"चलाएं"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"रोकें"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"बंद करें"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तार करें"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"छोटा करें"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"एल्बम आर्ट"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"वॉल्यूम स्लाइडर"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"कोई मीडिया चयनित नहीं है"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"कोई जानकारी मौजूद नहीं है"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्ट हो रही है"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-hr/strings.xml b/packages/MediaComponents/res/values-hr/strings.xml
new file mode 100644
index 0000000..3c43ee7
--- /dev/null
+++ b/packages/MediaComponents/res/values-hr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sustav"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Gumb za emitiranje"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Gumb za emitiranje. Veza prekinuta"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Gumb za emitiranje. Povezivanje"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Gumb za emitiranje. Povezan"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Emitiranje na"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Traženje uređaja"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zaustavi emitiranje"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvaranje"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reprodukcija"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziranje"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Zaustavi"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširivanje"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sažimanje"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Naslovnica albuma"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Klizač za glasnoću"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nije odabran nijedan medij"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Informacije nisu dostupne"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emitiranje zaslona"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-hu/strings.xml b/packages/MediaComponents/res/values-hu/strings.xml
new file mode 100644
index 0000000..a36bdfe
--- /dev/null
+++ b/packages/MediaComponents/res/values-hu/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Rendszer"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Eszközök"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Átküldés gomb"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Átküldés gomb. Kapcsolat bontva"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Átküldés gomb. Csatlakozás"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Átküldés gomb. Csatlakoztatva"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Átküldés ide"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Eszközök keresése"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Leválasztás"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Átküldés leállítása"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Bezárás"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Lejátszás"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Szüneteltetés"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Leállítás"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Kibontás"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Összecsukás"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Lemezborító"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Hangerőszabályzó"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nincs média kiválasztva"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nincs információ"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Képernyőtartalom átküldése"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-hy/strings.xml b/packages/MediaComponents/res/values-hy/strings.xml
new file mode 100644
index 0000000..8ec82b7
--- /dev/null
+++ b/packages/MediaComponents/res/values-hy/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Համակարգ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Սարքեր"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Հեռարձակման կոճակ"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Հեռարձակման կոճակ: Սարքն անջատված է"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Հեռարձակման կոճակ: Սարքը կապակցվում է"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Հեռարձակման կոճակ: Սարքը կապակցված է"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Ընտրեք սարքը"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Սարքերի որոնում"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Անջատել"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Դադարեցնել հեռարձակումը"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Փակել"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Նվագարկել"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Դադար"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Դադարեցնել"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Ընդարձակել"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Կոծկել"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Ալբոմի շապիկ"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Ձայնի ուժգնության կարգավորիչ"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Մեդիա ֆայլեր չեն ընտրվել"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Տեղեկությունները հասանելի չեն"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Էկրանը հեռարձակվում է"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-in/strings.xml b/packages/MediaComponents/res/values-in/strings.xml
new file mode 100644
index 0000000..6b2752e
--- /dev/null
+++ b/packages/MediaComponents/res/values-in/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Perangkat"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Tombol Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tombol Cast. Terputus"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tombol Cast. Menghubungkan"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tombol Cast. Terhubung"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Transmisikan ke"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Mencari perangkat"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Putuskan sambungan"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Hentikan cast"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Tutup"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Putar"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Jeda"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Berhenti"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Luaskan"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ciutkan"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Sampul album"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Bilah geser volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Tidak ada media yang dipilih"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Tidak ada info yang tersedia"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmisi layar"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-is/strings.xml b/packages/MediaComponents/res/values-is/strings.xml
new file mode 100644
index 0000000..6a35ea6
--- /dev/null
+++ b/packages/MediaComponents/res/values-is/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Kerfi"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Tæki"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Útsendingarhnappur"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Útsendingarhnappur. Aftengt"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Útsendingarhnappur. Tengist"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Útsendingarhnappur. Tengt"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Senda út í"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Leitað að tækjum"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Aftengjast"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stöðva útsendingu"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Loka"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Spila"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Hlé"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stöðva"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Stækka"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Minnka"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Plötuumslag"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Hljóðstyrkssleði"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Enginn miðill valinn"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Engar upplýsingar í boði"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skjár sendur út"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-it/strings.xml b/packages/MediaComponents/res/values-it/strings.xml
new file mode 100644
index 0000000..716e3ac
--- /dev/null
+++ b/packages/MediaComponents/res/values-it/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivi"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Pulsante Trasmetti"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Pulsante Trasmetti. Disconnesso"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Pulsante Trasmetti. Connessione in corso"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Pulsante Trasmetti. Connesso"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Trasmetti a"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Ricerca di dispositivi in corso"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Scollega"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Interrompi trasmissione"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Chiudi"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Riproduci"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Interrompi"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Espandi"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Comprimi"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Copertina"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Dispositivo di scorrimento del volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nessun contenuto multimediale selezionato"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nessuna informazione disponibile"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Trasmissione dello schermo in corso"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-iw/strings.xml b/packages/MediaComponents/res/values-iw/strings.xml
new file mode 100644
index 0000000..252b0ce
--- /dev/null
+++ b/packages/MediaComponents/res/values-iw/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"מערכת"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"מכשירים"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"‏לחצן הפעלת Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"‏לחצן הפעלת Cast. מנותק"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"‏לחצן הפעלת Cast. מתחבר"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"‏לחצן הפעלת Cast. מחובר"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"העברה אל"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"מחפש מכשירים"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"נתק"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"הפסק את ההעברה"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"סגור"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"הפעל"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"השהה"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"הפסק"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"הרחב"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"כווץ"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"עטיפת אלבום"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"מחוון עוצמה"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"לא נבחרה מדיה"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"אין מידע זמין"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"העברת מסך מתבצעת"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ja/strings.xml b/packages/MediaComponents/res/values-ja/strings.xml
new file mode 100644
index 0000000..a149727
--- /dev/null
+++ b/packages/MediaComponents/res/values-ja/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"システム"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"端末"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"キャストアイコン"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"キャスト アイコン。接続解除済み"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"キャスト アイコン。接続中"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"キャスト アイコン。接続済み"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"キャストするデバイス"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"端末を検索しています"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"接続を解除"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"キャストを停止"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"閉じる"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"再生"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"一時停止"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"停止"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"折りたたむ"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"アルバムアート"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量スライダー"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"メディアが選択されていません"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"情報がありません"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"画面をキャストしています"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ka/strings.xml b/packages/MediaComponents/res/values-ka/strings.xml
new file mode 100644
index 0000000..3da081a
--- /dev/null
+++ b/packages/MediaComponents/res/values-ka/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"სისტემა"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"მოწყობილობები"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ტრანსლირების ღილაკი"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ტრანსლირების ღილაკი. გათიშული"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ტრანსლირების ღილაკი. მიმდინარეობს დაკავშირება"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"ტრანსლირების ღილაკი. დაკავშირებული"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"ტრანსლირება:"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"მოწყობილობების მოძიება..."</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"კავშირის გაწყვეტა"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ტრანსლირების შეწყვეტა"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"დახურვა"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"დაკვრა"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"პაუზა"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"შეწყვეტა"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"გაშლა"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ჩაკეცვა"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ალბომის გარეკანი"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ხმის სლაიდერი"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"მედია არჩეული არ არის"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ინფორმაცია არ არის ხელმისაწვდომი"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"მიმდინარეობს ეკრანის გადაცემა"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-kk/strings.xml b/packages/MediaComponents/res/values-kk/strings.xml
new file mode 100644
index 0000000..94dcbb3
--- /dev/null
+++ b/packages/MediaComponents/res/values-kk/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Жүйе"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Құрылғылар"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Трансляциялау түймесі"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"\"Трансляциялау\" түймесі. Ажыратулы"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"\"Трансляциялау\" түймесі. Қосылуда"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"\"Трансляциялау\" түймесі. Қосылды"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Келесіге трансляциялау"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Құрылғыларды табу"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ажырату"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Трансляциялауды тоқтату"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Жабу"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Ойнату"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Кідірту"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Тоқтату"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Жаю"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Жию"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Альбом шебері"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Дыбыс деңгейінің жүгірткісі"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ешбір тасушы таңдалмаған"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Қол жетімді ақпарат жоқ"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Экранды трансляциялау"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-km/strings.xml b/packages/MediaComponents/res/values-km/strings.xml
new file mode 100644
index 0000000..e44780e
--- /dev/null
+++ b/packages/MediaComponents/res/values-km/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ប្រព័ន្ធ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ឧបករណ៍"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ប៊ូតុងខាស"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ខាសប៊ូតុង៖ បានកាត់ផ្តាច់"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ខាសប៊ូតុង៖ កំពុងភ្ជាប់"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"ខាសប៊ូតុង៖ បានភ្ជាប់ហើយ"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"បញ្ជូនទៅ"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"កំពុងស្វែងរកឧបករណ៍"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"ផ្ដាច់"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ឈប់ភ្ជាប់"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"បិទ"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"ចាក់"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"ផ្អាក"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"ឈប់"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"ពង្រីក"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"បង្រួម"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ស្នាដៃសិល្បៈអាល់ប៊ុម"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"របារកម្រិតសំឡេង"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"គ្មានការជ្រើសមេឌៀទេ"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"មិនមានព័ត៌មានទេ"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"កំពុងខាសអេក្រង់"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-kn/strings.xml b/packages/MediaComponents/res/values-kn/strings.xml
new file mode 100644
index 0000000..4237fdd
--- /dev/null
+++ b/packages/MediaComponents/res/values-kn/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ಸಿಸ್ಟಂ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ಸಾಧನಗಳು"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ಬಿತ್ತರಿಸು ಬಟನ್‌"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ಬಿತ್ತರಿಸು ಬಟನ್‌. ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ಬಿತ್ತರಿಸು ಬಟನ್‌. ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"ಬಿತ್ತರಿಸು ಬಟನ್‌. ಸಂಪರ್ಕಿತಗೊಂಡಿದೆ"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"ಇದಕ್ಕೆ ಬಿತ್ತರಿಸಿ"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"ಸಾಧನಗಳನ್ನು ಹುಡುಕಲಾಗುತ್ತಿದೆ"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸು"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ಬಿತ್ತರಿಸುವಿಕೆ ನಿಲ್ಲಿಸಿ"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"ಮುಚ್ಚು"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"ಪ್ಲೇ ಮಾಡಿ"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"ವಿರಾಮ"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"ನಿಲ್ಲಿಸಿ"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"ವಿಸ್ತರಿಸು"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ಸಂಕುಚಿಸು"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ಆಲ್ಬಮ್ ಕಲೆ"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ವಾಲ್ಯೂಮ್ ಸ್ಲೈಡರ್"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ಯಾವುದೇ ಮಾಧ್ಯಮ ಆಯ್ಕೆಮಾಡಲಾಗಿಲ್ಲ"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ಯಾವುದೇ ಮಾಹಿತಿ ಲಭ್ಯವಿಲ್ಲ"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"ಪರದೆಯನ್ನು ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ko/strings.xml b/packages/MediaComponents/res/values-ko/strings.xml
new file mode 100644
index 0000000..be893a9
--- /dev/null
+++ b/packages/MediaComponents/res/values-ko/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"시스템"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"기기"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"전송 버튼"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"전송 버튼. 연결 해제됨"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"전송 버튼. 연결 중"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"전송 버튼. 연결됨"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"전송할 기기"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"기기를 찾는 중"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"연결 해제"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"전송 중지"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"닫기"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"재생"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"일시중지"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"중지"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"펼치기"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"접기"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"앨범아트"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"볼륨 슬라이더"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"선택한 미디어 없음"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"정보가 없습니다."</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"화면 전송 중"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ky/strings.xml b/packages/MediaComponents/res/values-ky/strings.xml
new file mode 100644
index 0000000..57813af
--- /dev/null
+++ b/packages/MediaComponents/res/values-ky/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Тутум"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Түзмөктөр"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Тышкы экранга чыгаруу баскычы"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Тышкы экранга чыгаруу баскычы. Түзмөк ажырап турат."</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Тышкы экранга чыгаруу баскычы. Түзмөк туташууда"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Тышкы экранга чыгаруу баскычы. Түзмөк туташып турат"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Төмөнкүгө чыгаруу"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Түзмөктөр изделүүдө"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ажыратуу"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Тышк экранга чыгарну токтотуу"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Жабуу"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Ойнотуу"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Тындыруу"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Токтотуу"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Жайып көрсөтүү"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Жыйыштыруу"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Альбом мукабасы"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Үндү катуулатуучу сыдырма"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Бир да медиа файл тандалган жок"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Эч маалымат жок"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Тышкы экранга чыгарылууда"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-land/dimens.xml b/packages/MediaComponents/res/values-land/dimens.xml
new file mode 100644
index 0000000..29f1e1d
--- /dev/null
+++ b/packages/MediaComponents/res/values-land/dimens.xml
@@ -0,0 +1,26 @@
+<?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>
+    <!-- MediaRouteController's volume group list -->
+    <eat-comment />
+    <!-- Maximum height of volume group list. -->
+    <dimen name="mr_controller_volume_group_list_max_height">132dp</dimen>
+    <!-- Height of volume group item. -->
+    <dimen name="mr_controller_volume_group_list_item_height">61dp</dimen>
+    <!-- Size of an item's icon. -->
+    <dimen name="mr_controller_volume_group_list_item_icon_size">18dp</dimen>
+</resources>
diff --git a/packages/MediaComponents/res/values-lo/strings.xml b/packages/MediaComponents/res/values-lo/strings.xml
new file mode 100644
index 0000000..91737db
--- /dev/null
+++ b/packages/MediaComponents/res/values-lo/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ລະບົບ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ອຸປະກອນ"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ປຸ່ມ​ຄາ​ສ​ທ໌"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ປຸ່ມສົ່ງສັນຍານ. ຕັດການເຊື່ອມຕໍ່ແລ້ວ"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ປຸ່ມສົ່ງສັນຍານ. ກຳລັງເຊື່ອມຕໍ່"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"ປຸ່ມສົ່ງສັນຍານ. ເຊື່ອມຕໍ່ແລ້ວ"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"ສົ່ງສັນຍານຫາ"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"ກຳລັງ​ຊອກ​ຫາ​ອຸ​ປະ​ກອນ"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"ຕັດການເຊື່ອມຕໍ່"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ຢຸດການສົ່ງສັນຍານ"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"ປິດ"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"ຫຼິ້ນ"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"ຢຸດຊົ່ວຄາວ"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"ຢຸດ"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"ຂະຫຍາຍ"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ຫຍໍ້ລົງ"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ໜ້າປົກອະລະບໍ້າ"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ຕົວປັບລະດັບສຽງ"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ບໍ່​ໄດ້​ເລືອກ​ມີ​ເດຍ​ໃດ​ໄວ້"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ບໍ່​ມີ​ຂໍ້​ມູນ"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"ການສົ່ງພາບໜ້າຈໍ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-lt/strings.xml b/packages/MediaComponents/res/values-lt/strings.xml
new file mode 100644
index 0000000..ff036d1
--- /dev/null
+++ b/packages/MediaComponents/res/values-lt/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Įrenginiai"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Perdavimo mygtukas"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Perdavimo mygtukas. Atsijungta"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Perdavimo mygtukas. Prisijungiama"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Perdavimo mygtukas. Prisijungta"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Perduoti į"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Randami įrenginiai"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Atjungti"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Sustabdyti perdavimą"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Uždaryti"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Leisti"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pristabdyti"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Sustabdyti"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Išskleisti"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sutraukti"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumo viršelis"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Garsumo šliaužiklis"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nepasirinkta jokia medija"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Informacija nepasiekiama"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Perduodamas ekranas"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-lv/strings.xml b/packages/MediaComponents/res/values-lv/strings.xml
new file mode 100644
index 0000000..454063e
--- /dev/null
+++ b/packages/MediaComponents/res/values-lv/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistēma"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Ierīces"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Apraides poga"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Apraides poga. Savienojums pārtraukts"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Apraides poga. Notiek savienojuma izveide"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Apraides poga. Savienojums izveidots"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Apraidīšana uz ierīci"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Notiek ierīču meklēšana"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Atvienot"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Apturēt apraidi"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Aizvērt"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Atskaņot"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Apturēt"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Apturēt"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Izvērst"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sakļaut"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albuma vāciņš"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Skaļuma slīdnis"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nav atlasīti multivides faili"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nav pieejama informācija"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Notiek ekrāna apraide"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-mk/strings.xml b/packages/MediaComponents/res/values-mk/strings.xml
new file mode 100644
index 0000000..12dee36
--- /dev/null
+++ b/packages/MediaComponents/res/values-mk/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Уреди"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Копчето за Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Копче за Cast. Исклучено"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Копче за Cast. Се поврзува"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Копче за Cast. Поврзано"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Емитувај на"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Се бараат уреди"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Исклучи"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Сопри го емитувањето"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Затвори"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Репродуцирај"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Паузирај"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Сопри"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Прошири"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Собери"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Корица на албум"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Лизгач за јачина на звук"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Не се избрани медиуми"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Нема достапни информации"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Екранот се емитува"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ml/strings.xml b/packages/MediaComponents/res/values-ml/strings.xml
new file mode 100644
index 0000000..2d914b9
--- /dev/null
+++ b/packages/MediaComponents/res/values-ml/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"സിസ്റ്റം"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ഉപകരണങ്ങൾ"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ടാപ്പുചെയ്യുക"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"കാസ്റ്റ് ബട്ടൺ. വിച്ഛേദിച്ചു"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"കാസ്റ്റ് ബട്ടൺ. കണക്‌റ്റുചെയ്യുന്നു"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"കാസ്റ്റ് ബട്ടൺ. കണക്റ്റുചെയ്തു"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"ഇതിലേക്ക് കാസ്റ്റുചെയ്യുക"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"ഉപകരണങ്ങൾ കണ്ടെത്തുന്നു"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"വിച്ഛേദിക്കുക"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"കാസ്റ്റുചെയ്യൽ നിർത്തുക"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"അവസാനിപ്പിക്കുക"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"പ്ലേ ചെയ്യുക"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"തൽക്കാലം നിർത്തൂ"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"നിര്‍ത്തുക"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"വികസിപ്പിക്കുക"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ചുരുക്കുക"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ആൽബം ആർട്ട്"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"വോളിയം സ്ലൈഡർ"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"മീഡിയയൊന്നും തിരഞ്ഞെടുത്തിട്ടില്ല"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"വിവരങ്ങളൊന്നും ലഭ്യമല്ല"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"സ്‌ക്രീൻ കാസ്റ്റുചെയ്യുന്നു"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-mn/strings.xml b/packages/MediaComponents/res/values-mn/strings.xml
new file mode 100644
index 0000000..ef87c92
--- /dev/null
+++ b/packages/MediaComponents/res/values-mn/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Төхөөрөмжүүд"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Дамжуулах товчлуур"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Дамжуулах товчлуур. Салсан"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Дамжуулах товчлуур. Холбож байна"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Дамжуулах товчлуур. Холбогдсон"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Дамжуулах"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Төхөөрөмж хайж байна"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Салгах"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Дамжуулахыг зогсоох"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Хаах"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Тоглуулах"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Түр зогсоох"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Зогсоох"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Дэлгэх"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Хураах"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Цомгийн зураг"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Дууны түвшин тааруулагч"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ямар ч медиа сонгоогүй"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Мэдээлэл байхгүй байна"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Дэлгэцийг дамжуулж байна"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-mr/strings.xml b/packages/MediaComponents/res/values-mr/strings.xml
new file mode 100644
index 0000000..2ffbebb
--- /dev/null
+++ b/packages/MediaComponents/res/values-mr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"सिस्टम"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिव्हाइसेस"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"कास्ट बटण"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट बटण. डिस्कनेक्ट केले"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट बटण. कनेक्ट करत आहे"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"कास्ट बटण. कनेक्ट केले"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"यावर कास्ट करा"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"डिव्हाइसेस शोधत आहे"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"डिस्‍कनेक्‍ट करा"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"कास्ट करणे थांबवा"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"बंद करा"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"प्ले करा"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"विराम"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"थांबा"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तृत करा"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"संकुचित करा"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"अल्बम कला"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"व्हॉल्यूम स्लायडर"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"मीडिया निवडला नाही"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"कोणतीही माहिती उपलब्ध नाही"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्‍ट करत आहे"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ms/strings.xml b/packages/MediaComponents/res/values-ms/strings.xml
new file mode 100644
index 0000000..085e480
--- /dev/null
+++ b/packages/MediaComponents/res/values-ms/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Peranti"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Butang Hantar"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Butang hantar. Sambungan diputuskan"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Butang hantar. Menyambung"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Butang hantar. Disambungkan"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Hantar ke"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Mencari peranti"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Putuskan sambungan"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Berhenti menghantar"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Tutup"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Main"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Jeda"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Berhenti"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Kembangkan"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Runtuhkan"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Seni album"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Peluncur kelantangan"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Tiada media dipilih"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Maklumat tidak tersedia"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Menghantar skrin"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-my/strings.xml b/packages/MediaComponents/res/values-my/strings.xml
new file mode 100644
index 0000000..083d805
--- /dev/null
+++ b/packages/MediaComponents/res/values-my/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"စနစ်"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"စက်ပစ္စည်းများ"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ကာစ်တ်လုပ်ရန် ခလုတ်"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ကာစ်ခလုတ်။ ချိတ်ဆက်မထားပါ"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ကာစ်ခလုတ်။ ချိတ်ဆက်နေသည်"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"ကာစ်ခလုတ်။ ချိတ်ဆက်ထားသည်"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"ကာစ်လုပ်ရန် စက်"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"စက်ပစ္စည်းများ ရှာဖွေခြင်း"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"ဆက်သွယ်မှု ဖြတ်ရန်"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ကာစ်လုပ်ခြင်း ရပ်ရန်"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"ပိတ်ရန်"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"ဖွင့်ရန်"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"ခဏရပ်ရန်"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"ရပ်ရန်"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"ဖြန့်ချရန်၃"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ခေါက်သိမ်းရန်..."</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"အယ်လ်ဘမ်ပုံ"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"အသံအတိုးအကျယ်ချိန်သည့် ဆလိုက်ဒါ"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"မည်သည့်မီဒီမှ မရွေးချယ်ထားပါ"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"အချက်အလက် မရရှိနိုင်ပါ"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"တည်းဖြတ်ရေး မျက်နှာပြင်"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-nb/strings.xml b/packages/MediaComponents/res/values-nb/strings.xml
new file mode 100644
index 0000000..4f764c9
--- /dev/null
+++ b/packages/MediaComponents/res/values-nb/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheter"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-ikonet"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-knappen. Frakoblet"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-knappen. Kobler til"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-knappen. Tilkoblet"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Cast til"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Søker etter enheter"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Koble fra"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stopp castingen"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Lukk"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Spill av"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Sett på pause"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stopp"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Utvid"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skjul"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumgrafikk"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Glidebryter for volum"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Du har ikke valgt noen medier"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Ingen informasjon er tilgjengelig"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Caster skjermen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ne/strings.xml b/packages/MediaComponents/res/values-ne/strings.xml
new file mode 100644
index 0000000..d6c2e1a
--- /dev/null
+++ b/packages/MediaComponents/res/values-ne/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"प्रणाली"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"उपकरणहरू"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast बटन"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast बटन। जडान विच्छेद भयो"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast बटन। जडान हुँदै"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast बटन। जडान भयो"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"यसमा Cast गर्नुहोस्"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"यन्त्रहरू पत्ता लगाउँदै"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"विच्छेद गर्नुहोस्"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"casting रोक्नुहोस्"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"बन्द गर्नुहोस्"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"बजाउनुहोस्"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"रोक्नुहोस्"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"रोक्नुहोस्"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तार गर्नुहोस्"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"संक्षिप्त पार्नुहोस्"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"एल्बम आर्ट"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"भोल्युमको स्लाइडर"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"कुनै मिडिया चयन भएको छैन"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"जानकारी उपलब्ध छैन"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रिन cast गर्दै"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-nl/strings.xml b/packages/MediaComponents/res/values-nl/strings.xml
new file mode 100644
index 0000000..05df62d
--- /dev/null
+++ b/packages/MediaComponents/res/values-nl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Systeem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Apparaten"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-icoon"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-icoon. Verbinding verbroken"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-icoon. Verbinding maken"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-icoon. Verbonden"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Casten naar"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Apparaten zoeken"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Loskoppelen"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Casten stoppen"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Sluiten"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Afspelen"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Onderbreken"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Stoppen"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Uitvouwen"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Samenvouwen"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumhoes"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volumeschuifregelaar"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Geen media geselecteerd"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Geen informatie beschikbaar"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Scherm casten"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pa/strings.xml b/packages/MediaComponents/res/values-pa/strings.xml
new file mode 100644
index 0000000..1b5df71
--- /dev/null
+++ b/packages/MediaComponents/res/values-pa/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ਸਿਸਟਮ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ਡਿਵਾਈਸਾਂ"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ਕਾਸਟ ਬਟਨ"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ਕਾਸਟ ਬਟਨ। ਡਿਸਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ਕਾਸਟ ਬਟਨ। ਕਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"ਕਾਸਟ ਬਟਨ। ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"ਏਥੇ ਕਾਸਟ ਕਰੋ"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"ਡੀਵਾਈਸਾਂ ਨੂੰ ਲੱਭਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ਕਾਸਟ ਕਰਨਾ ਬੰਦ ਕਰੋ"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"ਬੰਦ ਕਰੋ"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"ਪਲੇ ਕਰੋ"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"ਰੋਕੋ"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"ਬੰਦ ਕਰੋ"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"ਵਿਸਤਾਰ ਕਰੋ"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ਬੰਦ ਕਰੋ"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ਐਲਬਮ ਆਰਟ"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ਵੌਲਯੂਮ ਸਲਾਈਡਰ"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ਕੋਈ ਵੀ ਮੀਡੀਆ ਨਹੀਂ ਚੁਣਿਆ ਗਿਆ"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ਕੋਈ ਜਾਣਕਾਰੀ ਉਪਲਬਧ ਨਹੀਂ"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"ਸਕ੍ਰੀਨ ਜੋੜ ਰਿਹਾ ਹੈ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pl/strings.xml b/packages/MediaComponents/res/values-pl/strings.xml
new file mode 100644
index 0000000..c792a6d
--- /dev/null
+++ b/packages/MediaComponents/res/values-pl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Urządzenia"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Przycisk Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Przycisk Prześlij ekran. Rozłączono"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Przycisk Prześlij ekran. Łączę"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Przycisk Prześlij ekran. Połączono"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Przesyłaj na"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Znajdowanie urządzeń"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Odłącz"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zatrzymaj przesyłanie"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zamknij"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Odtwórz"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Wstrzymaj"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Zatrzymaj"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozwiń"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Zwiń"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Okładka albumu"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Suwak głośności"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nie wybrano multimediów"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Brak informacji"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Przesyłam ekran"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pt-rBR/strings.xml b/packages/MediaComponents/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..43c619d
--- /dev/null
+++ b/packages/MediaComponents/res/values-pt-rBR/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Botão Transmitir"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botão \"Transmitir\". Desconectado"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botão \"Transmitir\". Conectando"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botão \"Transmitir\". Conectado"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Transmitir para"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Localizando dispositivos"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Interromper transmissão"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Fechar"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reproduzir"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Parar"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Recolher"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Arte do álbum"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Controle deslizante de volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhuma mídia selecionada"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitindo a tela"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pt-rPT/strings.xml b/packages/MediaComponents/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..3f0a61d
--- /dev/null
+++ b/packages/MediaComponents/res/values-pt-rPT/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Botão Transmitir"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botão Transmitir. Desligado"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botão Transmitir. A ligar..."</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botão Transmitir. Ligado"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Transmitir para"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"A localizar dispositivos"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desassociar"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Parar a transmissão"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Fechar"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reproduzir"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Interromper"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Parar"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Reduzir"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Imagem do álbum"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Controlo de deslize do volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhum suporte multimédia selecionado"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"A transmitir o ecrã"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pt/strings.xml b/packages/MediaComponents/res/values-pt/strings.xml
new file mode 100644
index 0000000..43c619d
--- /dev/null
+++ b/packages/MediaComponents/res/values-pt/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Botão Transmitir"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botão \"Transmitir\". Desconectado"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botão \"Transmitir\". Conectando"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botão \"Transmitir\". Conectado"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Transmitir para"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Localizando dispositivos"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Interromper transmissão"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Fechar"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reproduzir"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Parar"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Recolher"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Arte do álbum"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Controle deslizante de volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhuma mídia selecionada"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitindo a tela"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ro/strings.xml b/packages/MediaComponents/res/values-ro/strings.xml
new file mode 100644
index 0000000..6ebb2f6
--- /dev/null
+++ b/packages/MediaComponents/res/values-ro/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispozitive"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Butonul de proiecție"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Butonul de proiecție. Deconectat"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Butonul de proiecție. Se conectează"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Butonul de proiecție. Conectat"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Proiectați pe"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Se caută dispozitive"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Deconectați-vă"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Nu mai proiectați"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Închideți"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Redați"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Întrerupeți"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Opriți"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Extindeți"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Restrângeți"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Grafica albumului"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Glisor pentru volum"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Niciun fișier media selectat"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nu sunt disponibile informații"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Se proiectează ecranul"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ru/strings.xml b/packages/MediaComponents/res/values-ru/strings.xml
new file mode 100644
index 0000000..7c462d2
--- /dev/null
+++ b/packages/MediaComponents/res/values-ru/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Устройства"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Кнопка трансляции"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Кнопка трансляции. Устройство отключено."</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Кнопка трансляции. Устройство подключается."</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Кнопка трансляции. Устройство подключено."</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Выберите устройство"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Поиск устройств…"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Отключить"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Прекратить трансляцию"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Закрыть"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Воспроизвести"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Приостановить"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Остановить"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Развернуть"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Свернуть"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Обложка"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Регулятор громкости"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медиафайл не выбран"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Данных нет"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Подключение к удаленному монитору"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-si/strings.xml b/packages/MediaComponents/res/values-si/strings.xml
new file mode 100644
index 0000000..a55ce50
--- /dev/null
+++ b/packages/MediaComponents/res/values-si/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"පද්ධතිය"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"උපාංග"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"විකාශ බොත්තම"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"විකාශ බොත්තම. විසන්ධි කරන ලදී"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"විකාශ බොත්තම සම්බන්ධ කරමින්"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"විකාශ බොත්තම සම්බන්ධ කරන ලදී"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"විකාශය"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"උපාංග සෙවීම"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"විසන්ධි කරන්න"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"විකාශ කිරීම නතර කරන්න"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"වසන්න"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"ධාවනය කරන්න"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"විරාම ගන්වන්න"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"නතර කරන්න"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"දිග හරින්න"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"හකුළන්න"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ඇල්බම කලාව"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"හඬ පරිමා ස්ලයිඩරය"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"මාධ්‍යය තෝරා නැත"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ලබා ගත හැකි තොරතුරු නොමැත"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"විකාශ තිරය"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sk/strings.xml b/packages/MediaComponents/res/values-sk/strings.xml
new file mode 100644
index 0000000..a58aa11
--- /dev/null
+++ b/packages/MediaComponents/res/values-sk/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Systém"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Zariadenia"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Tlačidlo prenosu"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tlačidlo prenosu. Odpojené"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tlačidlo prenosu. Pripája sa"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tlačidlo prenosu. Pripojené"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Prenos do"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Hľadajú sa zariadenia"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Odpojiť"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zastaviť prenášanie"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zavrieť"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Prehrať"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pozastaviť"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Zastaviť"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozbaliť"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Zbaliť"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Obrázok albumu"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Posúvač hlasitosti"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nie sú vybrané žiadne médiá"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nie sú k dispozícii žiadne informácie"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prenáša sa obrazovka"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sl/strings.xml b/packages/MediaComponents/res/values-sl/strings.xml
new file mode 100644
index 0000000..8ca4ce4
--- /dev/null
+++ b/packages/MediaComponents/res/values-sl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Naprave"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Gumb za predvajanje"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Gumb za predvajanje. Povezava je prekinjena."</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Gumb za predvajanje. Vzpostavljanje povezave."</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Gumb za predvajanje. Povezava je vzpostavljena."</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Predvajanje prek:"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Iskanje naprav"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini povezavo"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Ustavi predvajanje"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zapri"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Predvajanje"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Zaustavi"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Ustavi"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Razširi"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Strni"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Naslovnica albuma"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Drsnik za glasnost"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ni izbrane predstavnosti"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Podatki niso na voljo"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Predvajanje zaslona"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sq/strings.xml b/packages/MediaComponents/res/values-sq/strings.xml
new file mode 100644
index 0000000..816e110
--- /dev/null
+++ b/packages/MediaComponents/res/values-sq/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistemi"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Pajisjet"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Butoni i transmetimit"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Butoni i transmetimit. Je i shkëputur"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Butoni i transmetimit. Po lidhet"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Butoni i transmetimit. Je i lidhur"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Transmeto te"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Po kërkon pajisje"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Shkëpute"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Ndalo transmetimin"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Mbyll"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Luaj"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pauzë"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Ndalo"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Zgjeroje"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Palose"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Kopertina e albumit"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Rrëshqitësi i volumit"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nuk u zgjodh asnjë media"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nuk jepet asnjë informacion"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Po transmeton ekranin"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sr/strings.xml b/packages/MediaComponents/res/values-sr/strings.xml
new file mode 100644
index 0000000..caabad5
--- /dev/null
+++ b/packages/MediaComponents/res/values-sr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Уређаји"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Дугме Пребаци"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Дугме Пребаци. Веза је прекинута"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Дугме Пребаци. Повезује се"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Дугме Пребаци. Повезан је"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Пребацуј на"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Проналажење уређаја"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Прекини везу"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Заустави пребацивање"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Затвори"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Пусти"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Паузирај"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Заустави"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Прошири"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Скупи"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Омот албума"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Клизач за јачину звука"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Нема изабраних медија"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Нису доступне никакве информације"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Пребацује се екран"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sv/strings.xml b/packages/MediaComponents/res/values-sv/strings.xml
new file mode 100644
index 0000000..ca7d3e0
--- /dev/null
+++ b/packages/MediaComponents/res/values-sv/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheter"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-knappen"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-knappen. Frånkopplad"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-knappen. Ansluter"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-knappen. Ansluten"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Casta till"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Letar efter enheter"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Koppla från"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Sluta casta"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Stäng"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Spela upp"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Avbryt"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Utöka"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Komprimera"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Skivomslag"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volymreglage"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Inga media har valts"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Det finns ingen information"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skärmen castas"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sw/strings.xml b/packages/MediaComponents/res/values-sw/strings.xml
new file mode 100644
index 0000000..9562cb1
--- /dev/null
+++ b/packages/MediaComponents/res/values-sw/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Mfumo"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Vifaa"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Kitufe cha kutuma"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Kitufe cha kutuma. Kimeondolewa"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Kitufe cha kutuma. Kinaunganisha"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Kitufe cha kutuma. Kimeunganishwa"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Tuma kwenye"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Inatafuta vifaa"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ondoa"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Acha kutuma"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Funga"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Cheza"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Sitisha"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Simamisha"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Panua"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Kunja"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Sanaa ya albamu"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Kitelezi cha sauti"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Hakuna maudhui yaliyochaguliwa"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Hakuna maelezo yaliyopatikana"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Inatuma skrini"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sw600dp/dimens.xml b/packages/MediaComponents/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..4042348
--- /dev/null
+++ b/packages/MediaComponents/res/values-sw600dp/dimens.xml
@@ -0,0 +1,24 @@
+<?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>
+    <!-- The platform's desired fixed width for a dialog along the major axis
+         (the screen is in landscape). This may be either a fraction or a dimension.-->
+    <item type="dimen" name="mr_dialog_fixed_width_major">60%</item>
+    <!-- The platform's desired fixed width for a dialog along the minor axis
+         (the screen is in portrait). This may be either a fraction or a dimension.-->
+    <item type="dimen" name="mr_dialog_fixed_width_minor">90%</item>
+</resources>
diff --git a/packages/MediaComponents/res/values-sw720dp/dimens.xml b/packages/MediaComponents/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..634ab8d
--- /dev/null
+++ b/packages/MediaComponents/res/values-sw720dp/dimens.xml
@@ -0,0 +1,24 @@
+<?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>
+    <!-- The platform's desired fixed width for a dialog along the major axis
+         (the screen is in landscape). This may be either a fraction or a dimension.-->
+    <item type="dimen" name="mr_dialog_fixed_width_major">50%</item>
+    <!-- The platform's desired fixed width for a dialog along the minor axis
+         (the screen is in portrait). This may be either a fraction or a dimension.-->
+    <item type="dimen" name="mr_dialog_fixed_width_minor">70%</item>
+</resources>
diff --git a/packages/MediaComponents/res/values-ta/strings.xml b/packages/MediaComponents/res/values-ta/strings.xml
new file mode 100644
index 0000000..e1978f3
--- /dev/null
+++ b/packages/MediaComponents/res/values-ta/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"சிஸ்டம்"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"சாதனங்கள்"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"திரையிடு பட்டன்"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"அனுப்புதல் பொத்தான். துண்டிக்கப்பட்டது"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"அனுப்புதல் பொத்தான். இணைக்கிறது"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"அனுப்புதல் பொத்தான். இணைக்கப்பட்டது"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"இதற்கு அனுப்பு"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"சாதனங்களைத் தேடுகிறது"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"தொடர்பைத் துண்டி"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"அனுப்புவதை நிறுத்து"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"மூடும்"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"இயக்கும்"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"இடைநிறுத்தும்"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"நிறுத்துவதற்கான பொத்தான்"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"விரிவாக்கு"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"சுருக்கு"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ஆல்பம் ஆர்ட்"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ஒலியளவு ஸ்லைடர்"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"மீடியா எதுவும் தேர்ந்தெடுக்கப்படவில்லை"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"தகவல் எதுவுமில்லை"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"திரையை அனுப்புகிறீர்கள்"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-te/strings.xml b/packages/MediaComponents/res/values-te/strings.xml
new file mode 100644
index 0000000..7d312e3
--- /dev/null
+++ b/packages/MediaComponents/res/values-te/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"సిస్టమ్"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"పరికరాలు"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ప్రసారం చేయి బటన్"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ప్రసార బటన్. డిస్‌కనెక్ట్ చేయబడింది"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ప్రసార బటన్. కనెక్ట్ చేస్తోంది"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"ప్రసార బటన్. కనెక్ట్ చేయబడింది"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"దీనికి ప్రసారం చేయండి"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"పరికరాలను కనుగొంటోంది"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"డిస్‌కనెక్ట్ చేయి"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ప్రసారాన్ని ఆపివేయి"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"మూసివేస్తుంది"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"ప్లే చేస్తుంది"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"పాజ్ చేస్తుంది"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"ఆపివేయి"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"విస్తరింపజేస్తుంది"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"కుదిస్తుంది"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ఆల్బమ్ ఆర్ట్"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"వాల్యూమ్ స్లయిడర్"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"మీడియా ఏదీ ఎంచుకోబడలేదు"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"సమాచారం అందుబాటులో లేదు"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"స్క్రీన్‌ను ప్రసారం చేస్తోంది"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-th/strings.xml b/packages/MediaComponents/res/values-th/strings.xml
new file mode 100644
index 0000000..cfa8ae5
--- /dev/null
+++ b/packages/MediaComponents/res/values-th/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ระบบ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"อุปกรณ์"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"ปุ่ม \"แคสต์\""</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ปุ่ม \"แคสต์\" ยกเลิกการเชื่อมต่อ"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ปุ่ม \"แคสต์\" กำลังเชื่อมต่อ"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"ปุ่ม \"แคสต์\" เชื่อมต่อแล้ว"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"แคสต์ไปยัง"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"กำลังค้นหาอุปกรณ์"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"ยกเลิกการเชื่อมต่อ"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"หยุดแคสต์"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"ปิด"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"เล่น"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"หยุดชั่วคราว"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"หยุด"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"ขยาย"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ยุบ"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"ปกอัลบั้ม"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"แถบเลื่อนปรับระดับเสียง"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ไม่ได้เลือกสื่อไว้"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ไม่มีข้อมูล"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"กำลังแคสต์หน้าจอ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-tl/strings.xml b/packages/MediaComponents/res/values-tl/strings.xml
new file mode 100644
index 0000000..a8be3d0
--- /dev/null
+++ b/packages/MediaComponents/res/values-tl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Mga Device"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Button na I-cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Button na I-cast. Nadiskonekta"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Button na I-cast. Kumokonekta"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Button na I-cast. Nakakonekta"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"I-cast sa"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Naghahanap ng mga device"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Idiskonekta"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Ihinto ang pag-cast"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Isara"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"I-play"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"I-pause"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Ihinto"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Palawakin"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"I-collapse"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Slider ng volume"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Walang piniling media"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Walang available na impormasyon"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Kina-cast ang screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-tr/strings.xml b/packages/MediaComponents/res/values-tr/strings.xml
new file mode 100644
index 0000000..05f6392
--- /dev/null
+++ b/packages/MediaComponents/res/values-tr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Cihazlar"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Yayınla düğmesi"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Yayınla düğmesi. Bağlantı kesildi"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Yayınla düğmesi. Bağlanıyor"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Yayınla düğmesi. Bağlandı"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Şuraya yayınla:"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Cihazlar bulunuyor"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Bağlantıyı kes"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Yayını durdur"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Kapat"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Oynat"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Duraklat"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Durdur"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Genişlet"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Daralt"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albüm kapağı"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Ses düzeyi kaydırma çubuğu"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Medya seçilmedi"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Bilgi yok"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekran yayınlanıyor"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-uk/strings.xml b/packages/MediaComponents/res/values-uk/strings.xml
new file mode 100644
index 0000000..33d365e
--- /dev/null
+++ b/packages/MediaComponents/res/values-uk/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Пристрої"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Кнопка трансляції"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Кнопка трансляції. Від’єднано"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Кнопка трансляції. Під’єднання"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Кнопка трансляції. Під’єднано"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Транслювати на"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Пошук пристроїв"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Відключити"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Припинити трансляцію"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Закрити"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Відтворити"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Призупинити"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Припинити"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Розгорнути"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Згорнути"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Обкладинка альбому"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Повзунок гучності"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медіа-файл не вибрано"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Немає даних"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Трансляція екрана"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ur/strings.xml b/packages/MediaComponents/res/values-ur/strings.xml
new file mode 100644
index 0000000..632c598
--- /dev/null
+++ b/packages/MediaComponents/res/values-ur/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"سسٹم"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"آلات"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"کاسٹ کرنے کا بٹن"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"کاسٹ کرنے کا بٹن۔ غیر منسلک ہے"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"کاسٹ کرنے کا بٹن۔ منسلک ہو رہا ہے"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"کاسٹ کرنے کا بٹن۔ منسلک ہے"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"اس میں کاسٹ کریں"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"آلات تلاش ہو رہے ہیں"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"غیر منسلک کریں"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"کاسٹ کرنا بند کریں"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"بند کریں"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"چلائیں"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"موقوف کریں"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"روکیں"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"پھیلائیں"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"سکیڑیں"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"البم آرٹ"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"والیوم سلائیڈر"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"کوئی میڈیا منتخب نہیں ہے"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"کوئی معلومات دستیاب نہیں"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"سکرین کاسٹ ہو رہی ہے"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-uz/strings.xml b/packages/MediaComponents/res/values-uz/strings.xml
new file mode 100644
index 0000000..10a0817
--- /dev/null
+++ b/packages/MediaComponents/res/values-uz/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Tizim"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Qurilmalar"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Translatsiya tugmasi"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Translatsiya tugmasi. Uzildi"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Translatsiya tugmasi. Ulanmoqda"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Translatsiya tugmasi. Ulandi"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Quyidagiga translatsiya qilish:"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Qurilmalarni topish"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ulanishni uzish"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Translatsiyani to‘xtatish"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Yopish"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Boshlash"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"To‘xtatib turish"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"To‘xtatish"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Yoyish"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Yig‘ish"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Albom muqovasi"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Tovush balandligi slayderi"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Multimedia tanlamagan"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Hech qanday ma’lumot yo‘q"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekranni translatsiya qilish"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-vi/strings.xml b/packages/MediaComponents/res/values-vi/strings.xml
new file mode 100644
index 0000000..7098cca
--- /dev/null
+++ b/packages/MediaComponents/res/values-vi/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Hệ thống"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Thiết bị"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Nút truyền"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Nút truyền. Đã ngắt kết nối"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Nút truyền. Đang kết nối"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Nút truyền. Đã kết nối"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Truyền tới"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Đang tìm thiết bị"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ngắt kết nối"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Dừng truyền"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Đóng"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Phát"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Tạm dừng"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Dừng"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Mở rộng"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Thu gọn"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Ảnh bìa album"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Thanh trượt âm lượng"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Không có phương tiện nào được chọn"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Không có thông tin nào"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Đang truyền màn hình"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-zh-rCN/strings.xml b/packages/MediaComponents/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..1e22d01
--- /dev/null
+++ b/packages/MediaComponents/res/values-zh-rCN/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"系统"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"设备"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"投射按钮"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"投射按钮。已断开连接"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"投射按钮。正在连接"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"投射按钮。已连接"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"投射到"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"正在查找设备"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"断开连接"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"停止投射"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"关闭"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"播放"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"暂停"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"停止"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"展开"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"收起"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"专辑封面"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量滑块"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"未选择任何媒体"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"没有任何相关信息"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投射屏幕"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-zh-rHK/strings.xml b/packages/MediaComponents/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..156e5c2
--- /dev/null
+++ b/packages/MediaComponents/res/values-zh-rHK/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"系統"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"裝置"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"投放按鈕"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"投放按鈕。已解除連接"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"投放按鈕。正在連接"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"投放按鈕。已連接"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"投放至"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"正在尋找裝置"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"中斷連線"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"停止投放"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"關閉"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"播放"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"暫停"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"停止"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"收合"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"專輯封面"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量滑桿"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"尚未選擇媒體"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"沒有詳細資料"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投放螢幕"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-zh-rTW/strings.xml b/packages/MediaComponents/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..6cafde1
--- /dev/null
+++ b/packages/MediaComponents/res/values-zh-rTW/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"系統"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"裝置"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"投放按鈕"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"投放按鈕;已中斷連線"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"投放按鈕;連線中"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"投放按鈕;已連線"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"投放到"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"正在尋找裝置"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"中斷連線"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"停止投放"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"關閉"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"播放"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"暫停"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"停止"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"收合"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"專輯封面"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量滑桿"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"未選取任何媒體"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"沒有可用的資訊"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投放螢幕"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-zu/strings.xml b/packages/MediaComponents/res/values-zu/strings.xml
new file mode 100644
index 0000000..e107c43
--- /dev/null
+++ b/packages/MediaComponents/res/values-zu/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Isistimu"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Amadivayisi"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Inkinobho ye-Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Inkinobho yokusakaza. Kunqanyuliwe"</string>
+    <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Inkinobho yokusakaza. Kuyaxhunywa"</string>
+    <string name="mr_cast_button_connected" msgid="5088427771788648085">"Inkinobho yokusakaza. Kuxhunyiwe"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Sakaza ku-"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Ithola amadivayisi"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Nqamula"</string>
+    <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Misa ukusakaza"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Vala"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Dlala"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Misa isikhashana"</string>
+    <string name="mr_controller_stop" msgid="735874641921425123">"Misa"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Nweba"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Goqa"</string>
+    <string name="mr_controller_album_art" msgid="6422801843540543585">"Ubuciko be-albhamu"</string>
+    <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Isilayida sevolumu"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ayikho imidiya ekhethiwe"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Alukho ulwazi olutholakalayo"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Isikrini sokusakaza"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values/attrs.xml b/packages/MediaComponents/res/values/attrs.xml
new file mode 100644
index 0000000..6175b11
--- /dev/null
+++ b/packages/MediaComponents/res/values/attrs.xml
@@ -0,0 +1,45 @@
+<?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>
+    <declare-styleable name="MediaRouteButton">
+        <!-- This drawable is a state list where the "checked" state
+             indicates active media routing.  Checkable indicates connecting
+             and non-checked / non-checkable indicates
+             that media is playing to the local device only. -->
+        <attr name="externalRouteEnabledDrawable" format="reference" />
+        <!-- Tint to apply to the media route button -->
+        <attr name="mediaRouteButtonTint" format="color" />
+
+        <attr name="android:minWidth" />
+        <attr name="android:minHeight" />
+    </declare-styleable>
+
+    <attr name="mediaRouteButtonStyle" format="reference" />
+    <attr name="mediaRouteCloseDrawable" format="reference" />
+    <attr name="mediaRoutePlayDrawable" format="reference" />
+    <attr name="mediaRoutePauseDrawable" format="reference" />
+    <attr name="mediaRouteStopDrawable" format="reference" />
+    <attr name="mediaRouteAudioTrackDrawable" format="reference" />
+    <attr name="mediaRouteDefaultIconDrawable" format="reference" />
+    <attr name="mediaRouteTvIconDrawable" format="reference" />
+    <attr name="mediaRouteSpeakerIconDrawable" format="reference" />
+    <attr name="mediaRouteSpeakerGroupIconDrawable" format="reference" />
+    <attr name="mediaRouteControlPanelThemeOverlay" format="reference" />
+
+    <attr name="mediaRouteTheme" format="reference" />
+    <attr name="enableControlView" format="boolean" />
+</resources>
diff --git a/packages/MediaComponents/res/values/dimens.xml b/packages/MediaComponents/res/values/dimens.xml
new file mode 100644
index 0000000..91241cd
--- /dev/null
+++ b/packages/MediaComponents/res/values/dimens.xml
@@ -0,0 +1,43 @@
+<?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>
+    <!-- Dialog size -->
+    <eat-comment />
+    <!-- The platform's desired fixed width for a dialog along the major axis
+         (the screen is in landscape). This may be either a fraction or a dimension.-->
+    <dimen name="mr_dialog_fixed_width_major">320dp</dimen>
+    <!-- The platform's desired fixed width for a dialog along the minor axis
+         (the screen is in portrait). This may be either a fraction or a dimension.-->
+    <dimen name="mr_dialog_fixed_width_minor">320dp</dimen>
+
+    <!-- MediaRouteController's volume group list -->
+    <eat-comment />
+    <!-- Maximum height of volume group list. -->
+    <dimen name="mr_controller_volume_group_list_max_height">288dp</dimen>
+    <!-- Height of volume group item. -->
+    <dimen name="mr_controller_volume_group_list_item_height">68dp</dimen>
+    <!-- Size of an item's icon. -->
+    <dimen name="mr_controller_volume_group_list_item_icon_size">24dp</dimen>
+
+    <dimen name="mr_controller_volume_group_list_padding_top">16dp</dimen>
+    <!-- Group list expand/collapse animation duration. -->
+    <integer name="mr_controller_volume_group_list_animation_duration_ms">400</integer>
+    <!-- Group list fade in animation duration. -->
+    <integer name="mr_controller_volume_group_list_fade_in_duration_ms">400</integer>
+    <!-- Group list fade out animation duration. -->
+    <integer name="mr_controller_volume_group_list_fade_out_duration_ms">200</integer>
+</resources>
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
new file mode 100644
index 0000000..e93269c
--- /dev/null
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -0,0 +1,94 @@
+<?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>
+    <!-- Name for the default system route prior to Jellybean. [CHAR LIMIT=30] -->
+    <string name="mr_system_route_name">System</string>
+
+    <!-- Name for the user route category created when publishing routes to the system in Jellybean and above. [CHAR LIMIT=30] -->
+    <string name="mr_user_route_category_name">Devices</string>
+
+    <!-- String to be shown as a tooltip of MediaRouteButton
+        Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=50] -->
+    <string name="mr_button_content_description">Cast button</string>
+
+    <!-- Content description of a MediaRouteButton for accessibility support when no remote device is connected.
+        Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="mr_cast_button_disconnected">Cast button. Disconnected</string>
+
+    <!-- Content description of a MediaRouteButton for accessibility support while connecting to a remote device.
+        Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="mr_cast_button_connecting">Cast button. Connecting</string>
+
+    <!-- Content description of a MediaRouteButton for accessibility support when a remote device is connected.
+        Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="mr_cast_button_connected">Cast button. Connected</string>
+
+    <!-- Title of the media route chooser dialog. [CHAR LIMIT=30] -->
+    <string name="mr_chooser_title">Cast to</string>
+
+    <!-- Placeholder text to show when no devices have been found. [CHAR LIMIT=50] -->
+    <string name="mr_chooser_searching">Finding devices</string>
+
+    <!-- Button to disconnect from a media route.  [CHAR LIMIT=30] -->
+    <string name="mr_controller_disconnect">Disconnect</string>
+
+    <!-- Button to stop playback and disconnect from a media route. [CHAR LIMIT=30] -->
+    <string name="mr_controller_stop_casting">Stop casting</string>
+
+    <!-- Content description for accessibility (not shown on the screen): dialog close button. [CHAR LIMIT=NONE] -->
+    <string name="mr_controller_close_description">Close</string>
+
+    <!-- Content description for accessibility (not shown on the screen): media play button. [CHAR LIMIT=NONE] -->
+    <string name="mr_controller_play">Play</string>
+
+    <!-- Content description for accessibility (not shown on the screen): media pause button. [CHAR LIMIT=NONE] -->
+    <string name="mr_controller_pause">Pause</string>
+
+    <!-- Content description for accessibility (not shown on the screen): media stop button. [CHAR LIMIT=NONE] -->
+    <string name="mr_controller_stop">Stop</string>
+
+    <!-- Content description for accessibility (not shown on the screen): group expand button. Pressing button shows group members of a selected route group. [CHAR LIMIT=NONE] -->
+    <string name="mr_controller_expand_group">Expand</string>
+
+    <!-- Content description for accessibility (not shown on the screen): group collapse button. Pressing button hides group members of a selected route group. [CHAR LIMIT=NONE] -->
+    <string name="mr_controller_collapse_group">Collapse</string>
+
+    <!-- Content description for accessibility (not shown on the screen): album art button. Clicking on the album art takes user to a predefined activity per media app. [CHAR LIMIT=50] -->
+    <string name="mr_controller_album_art">Album art</string>
+
+    <!-- Content description for accessibility (not shown on the screen): volume slider. [CHAR LIMIT=NONE] -->
+    <string name="mr_controller_volume_slider">Volume slider</string>
+
+    <!-- Placeholder text to show when no media have been selected for playback. [CHAR LIMIT=50] -->
+    <string name="mr_controller_no_media_selected">No media selected</string>
+
+    <!-- Placeholder text to show when no title/description have been found for a given song/video. [CHAR LIMIT=50] -->
+    <string name="mr_controller_no_info_available">No info available</string>
+
+    <!-- Placeholder text indicating that the user is currently casting screen. [CHAR LIMIT=50] -->
+    <string name="mr_controller_casting_screen">Casting screen</string>
+
+    <string name="lockscreen_pause_button_content_description">Pause</string>
+    <string name="lockscreen_play_button_content_description">Play</string>
+
+    <!-- Text for error alert when a video container is not valid for progressive download/playback. -->
+    <string name="VideoView2_error_text_invalid_progressive_playback">This video isn\'t valid for streaming to this device.</string>
+    <!-- Text for error alert when a video cannot be played. It can be used by any app. -->
+    <string name="VideoView2_error_text_unknown">Can\'t play this video.</string>
+    <!-- Button to close error alert when a video cannot be played. -->
+    <string name="VideoView2_error_button">OK</string>
+</resources>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
new file mode 100644
index 0000000..d31b41d
--- /dev/null
+++ b/packages/MediaComponents/res/values/style.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="TransportControlsButton">
+        <item name="android:background">@null</item>
+        <item name="android:layout_width">70dp</item>
+        <item name="android:layout_height">40dp</item>
+    </style>
+
+    <style name="TransportControlsButton.Previous">
+        <item name="android:src">@drawable/ic_skip_previous</item>
+    </style>
+
+    <style name="TransportControlsButton.Next">
+        <item name="android:src">@drawable/ic_skip_next</item>
+    </style>
+
+    <style name="TransportControlsButton.Pause">
+        <item name="android:src">@drawable/ic_pause_circle_filled</item>
+    </style>
+
+    <style name="TransportControlsButton.Ffwd">
+        <item name="android:src">@drawable/ic_forward_30</item>
+    </style>
+
+    <style name="TransportControlsButton.Rew">
+        <item name="android:src">@drawable/ic_rewind_10</item>
+    </style>
+
+
+    <style name="TitleBarButton">
+        <item name="android:background">@null</item>
+        <item name="android:layout_width">36dp</item>
+        <item name="android:layout_height">36dp</item>
+        <item name="android:layout_margin">10dp</item>
+    </style>
+
+    <style name="TitleBarButton.MediaRouteButton">
+        <item name="android:src">@drawable/ic_cast</item>
+    </style>
+
+
+    <style name="BottomBarButton">
+        <item name="android:background">@null</item>
+        <item name="android:layout_width">24dp</item>
+        <item name="android:layout_height">24dp</item>
+        <item name="android:layout_margin">10dp</item>
+    </style>
+
+    <style name="BottomBarButton.CC">
+        <item name="android:src">@drawable/ic_media_cc_disabled</item>
+    </style>
+
+    <style name="BottomBarButton.FullScreen">
+        <item name="android:src">@drawable/ic_fullscreen</item>
+    </style>
+
+    <style name="BottomBarButton.Overflow">
+        <item name="android:src">@drawable/ic_chevron_right</item>
+    </style>
+
+</resources>
+
diff --git a/packages/MediaComponents/res/values/styles.xml b/packages/MediaComponents/res/values/styles.xml
new file mode 100644
index 0000000..bde6900
--- /dev/null
+++ b/packages/MediaComponents/res/values/styles.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.
+-->
+
+<resources>
+    <style name="Widget.MediaRouter.MediaRouteButton"
+            parent="Widget.AppCompat.ActionButton">
+        <item name="externalRouteEnabledDrawable">@drawable/mr_button_dark</item>
+    </style>
+
+    <style name="Widget.MediaRouter.Light.MediaRouteButton"
+            parent="Widget.AppCompat.Light.ActionButton">
+        <item name="externalRouteEnabledDrawable">@drawable/mr_button_light</item>
+    </style>
+
+    <style name="TextAppearance.MediaRouter.Title" parent="TextAppearance.AppCompat.Title" />
+
+    <style name="TextAppearance.MediaRouter.PrimaryText" parent="TextAppearance.AppCompat.Subhead" />
+
+    <style name="TextAppearance.MediaRouter.SecondaryText" parent="TextAppearance.AppCompat.Body1" />
+</resources>
diff --git a/packages/MediaComponents/res/values/symbols.xml b/packages/MediaComponents/res/values/symbols.xml
new file mode 100644
index 0000000..ee0e8c6
--- /dev/null
+++ b/packages/MediaComponents/res/values/symbols.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** 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>
+    <!--java-symbol type="id" name="cc" />
+    <java-symbol type="id" name="ffwd" />
+    <java-symbol type="id" name="mediacontroller_progress" />
+    <java-symbol type="id" name="next" />
+    <java-symbol type="id" name="pause" />
+    <java-symbol type="id" name="prev" />
+    <java-symbol type="id" name="rew" />
+    <java-symbol type="id" name="time" />
+    <java-symbol type="id" name="time_current" /-->
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/themes.xml b/packages/MediaComponents/res/values/themes.xml
new file mode 100644
index 0000000..51098e9
--- /dev/null
+++ b/packages/MediaComponents/res/values/themes.xml
@@ -0,0 +1,75 @@
+<?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>
+
+    <style name="Theme.MediaRouter" parent="ThemeOverlay.AppCompat.Dark">
+        <item name="windowNoTitle">true</item>
+        <item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton</item>
+
+        <item name="mediaRouteCloseDrawable">@drawable/mr_dialog_close_dark</item>
+        <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_dark</item>
+        <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_dark</item>
+        <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_dark</item>
+        <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_dark</item>
+        <item name="mediaRouteDefaultIconDrawable">@drawable/ic_mr_button_disconnected_dark</item>
+        <item name="mediaRouteTvIconDrawable">@drawable/ic_vol_type_tv_dark</item>
+        <item name="mediaRouteSpeakerIconDrawable">@drawable/ic_vol_type_speaker_dark</item>
+        <item name="mediaRouteSpeakerGroupIconDrawable">@drawable/ic_vol_type_speaker_group_dark</item>
+
+        <item name="mediaRouteControlPanelThemeOverlay">@null</item>
+    </style>
+
+    <style name="Theme.MediaRouter.LightControlPanel">
+        <item name="mediaRouteControlPanelThemeOverlay">@style/ThemeOverlay.MediaRouter.Light</item>
+    </style>
+
+    <style name="Theme.MediaRouter.Light" parent="ThemeOverlay.AppCompat.Light">
+        <item name="windowNoTitle">true</item>
+        <item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.Light.MediaRouteButton</item>
+
+        <item name="mediaRouteCloseDrawable">@drawable/mr_dialog_close_light</item>
+        <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_light</item>
+        <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_light</item>
+        <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_light</item>
+        <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_light</item>
+        <item name="mediaRouteDefaultIconDrawable">@drawable/ic_mr_button_grey</item>
+        <item name="mediaRouteTvIconDrawable">@drawable/ic_vol_type_tv_light</item>
+        <item name="mediaRouteSpeakerIconDrawable">@drawable/ic_vol_type_speaker_light</item>
+        <item name="mediaRouteSpeakerGroupIconDrawable">@drawable/ic_vol_type_speaker_group_light</item>
+
+        <item name="mediaRouteControlPanelThemeOverlay">@null</item>
+    </style>
+
+    <style name="Theme.MediaRouter.Light.DarkControlPanel">
+        <item name="mediaRouteControlPanelThemeOverlay">@style/ThemeOverlay.MediaRouter.Dark</item>
+    </style>
+
+    <style name="ThemeOverlay.MediaRouter.Dark" parent="ThemeOverlay.AppCompat.Dark">
+        <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_dark</item>
+        <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_dark</item>
+        <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_dark</item>
+        <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_dark</item>
+
+    </style>
+    <style name="ThemeOverlay.MediaRouter.Light" parent="ThemeOverlay.AppCompat.Light">
+        <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_light</item>
+        <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_light</item>
+        <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_light</item>
+        <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_light</item>
+    </style>
+
+</resources>
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
new file mode 100644
index 0000000..633a342
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.update;
+
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.media.update.MediaControlView2Provider;
+import android.media.update.VideoView2Provider;
+import android.media.update.StaticProvider;
+import android.media.update.ViewProvider;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.MediaControlView2;
+import android.widget.VideoView2;
+
+import com.android.widget.MediaControlView2Impl;
+import com.android.widget.VideoView2Impl;
+
+public class ApiFactory implements StaticProvider {
+    public static Object initialize(Resources libResources, Theme libTheme)
+            throws ReflectiveOperationException {
+        ApiHelper.initialize(libResources, libTheme);
+        return new ApiFactory();
+    }
+
+    @Override
+    public MediaControlView2Provider createMediaControlView2(
+            MediaControlView2 instance, ViewProvider superProvider) {
+        return new MediaControlView2Impl(instance, superProvider);
+    }
+
+    @Override
+    public VideoView2Provider createVideoView2(
+            VideoView2 instance, ViewProvider superProvider,
+            @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        return new VideoView2Impl(instance, superProvider, attrs, defStyleAttr, defStyleRes);
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
new file mode 100644
index 0000000..26f858c
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -0,0 +1,49 @@
+/*
+ * 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.update;
+
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+
+public class ApiHelper {
+    private static ApiHelper sInstance;
+    private final Resources mLibResources;
+    private final Theme mLibTheme;
+
+    public static ApiHelper getInstance() {
+        return sInstance;
+    }
+
+    static void initialize(Resources libResources, Theme libTheme) {
+        if (sInstance == null) {
+            sInstance = new ApiHelper(libResources, libTheme);
+        }
+    }
+
+    private ApiHelper(Resources libResources, Theme libTheme) {
+        mLibResources = libResources;
+        mLibTheme = libTheme;
+    }
+
+    public static Resources getLibResources() {
+        return sInstance.mLibResources;
+    }
+
+    public static Resources.Theme getLibTheme() {
+        return sInstance.mLibTheme;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/api24/media/MediaRouterApi24.java b/packages/MediaComponents/src/com/android/support/mediarouter/api24/media/MediaRouterApi24.java
new file mode 100644
index 0000000..1146af6
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/api24/media/MediaRouterApi24.java
@@ -0,0 +1,26 @@
+/*
+ * 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.support.mediarouter.media;
+
+// @@RequiresApi(24)
+final class MediaRouterApi24 {
+    public static final class RouteInfo {
+        public static int getDeviceType(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getDeviceType();
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteActionProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteActionProvider.java
new file mode 100644
index 0000000..d3e8d47
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteActionProvider.java
@@ -0,0 +1,333 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.support.v4.view.ActionProvider;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.MediaRouter;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * The media route action provider displays a {@link MediaRouteButton media route button}
+ * in the application's {@link ActionBar} to allow the user to select routes and
+ * to control the currently selected route.
+ * <p>
+ * The application must specify the kinds of routes that the user should be allowed
+ * to select by specifying a {@link MediaRouteSelector selector} with the
+ * {@link #setRouteSelector} method.
+ * </p><p>
+ * Refer to {@link MediaRouteButton} for a description of the button that will
+ * appear in the action bar menu.  Note that instead of disabling the button
+ * when no routes are available, the action provider will instead make the
+ * menu item invisible.  In this way, the button will only be visible when it
+ * is possible for the user to discover and select a matching route.
+ * </p>
+ *
+ * <h3>Prerequisites</h3>
+ * <p>
+ * To use the media route action provider, the activity must be a subclass of
+ * {@link AppCompatActivity} from the <code>android.support.v7.appcompat</code>
+ * support library.  Refer to support library documentation for details.
+ * </p>
+ *
+ * <h3>Example</h3>
+ * <p>
+ * </p><p>
+ * The application should define a menu resource to include the provider in the
+ * action bar options menu.  Note that the support library action bar uses attributes
+ * that are defined in the application's resource namespace rather than the framework's
+ * resource namespace to configure each item.
+ * </p><pre>
+ * &lt;menu xmlns:android="http://schemas.android.com/apk/res/android"
+ *         xmlns:app="http://schemas.android.com/apk/res-auto">
+ *     &lt;item android:id="@+id/media_route_menu_item"
+ *         android:title="@string/media_route_menu_title"
+ *         app:showAsAction="always"
+ *         app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"/>
+ * &lt;/menu>
+ * </pre><p>
+ * Then configure the menu and set the route selector for the chooser.
+ * </p><pre>
+ * public class MyActivity extends AppCompatActivity {
+ *     private MediaRouter mRouter;
+ *     private MediaRouter.Callback mCallback;
+ *     private MediaRouteSelector mSelector;
+ *
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *
+ *         mRouter = Mediarouter.getInstance(this);
+ *         mSelector = new MediaRouteSelector.Builder()
+ *                 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+ *                 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ *                 .build();
+ *         mCallback = new MyCallback();
+ *     }
+ *
+ *     // Add the callback on start to tell the media router what kinds of routes
+ *     // the application is interested in so that it can try to discover suitable ones.
+ *     public void onStart() {
+ *         super.onStart();
+ *
+ *         mediaRouter.addCallback(mSelector, mCallback,
+ *                 MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ *
+ *         MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
+ *         // do something with the route...
+ *     }
+ *
+ *     // Remove the selector on stop to tell the media router that it no longer
+ *     // needs to invest effort trying to discover routes of these kinds for now.
+ *     public void onStop() {
+ *         super.onStop();
+ *
+ *         mediaRouter.removeCallback(mCallback);
+ *     }
+ *
+ *     public boolean onCreateOptionsMenu(Menu menu) {
+ *         super.onCreateOptionsMenu(menu);
+ *
+ *         getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
+ *
+ *         MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
+ *         MediaRouteActionProvider mediaRouteActionProvider =
+ *                 (MediaRouteActionProvider)MenuItemCompat.getActionProvider(mediaRouteMenuItem);
+ *         mediaRouteActionProvider.setRouteSelector(mSelector);
+ *         return true;
+ *     }
+ *
+ *     private final class MyCallback extends MediaRouter.Callback {
+ *         // Implement callback methods as needed.
+ *     }
+ * }
+ * </pre>
+ *
+ * @see #setRouteSelector
+ */
+public class MediaRouteActionProvider extends ActionProvider {
+    private static final String TAG = "MediaRouteActionProvider";
+
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+
+    private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
+    private MediaRouteDialogFactory mDialogFactory = MediaRouteDialogFactory.getDefault();
+    private MediaRouteButton mButton;
+
+    /**
+     * Creates the action provider.
+     *
+     * @param context The context.
+     */
+    public MediaRouteActionProvider(Context context) {
+        super(context);
+
+        mRouter = MediaRouter.getInstance(context);
+        mCallback = new MediaRouterCallback(this);
+    }
+
+    /**
+     * Gets the media route selector for filtering the routes that the user can
+     * select using the media route chooser dialog.
+     *
+     * @return The selector, never null.
+     */
+    @NonNull
+    public MediaRouteSelector getRouteSelector() {
+        return mSelector;
+    }
+
+    /**
+     * Sets the media route selector for filtering the routes that the user can
+     * select using the media route chooser dialog.
+     *
+     * @param selector The selector, must not be null.
+     */
+    public void setRouteSelector(@NonNull MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        if (!mSelector.equals(selector)) {
+            // FIXME: We currently have no way of knowing whether the action provider
+            // is still needed by the UI.  Unfortunately this means the action provider
+            // may leak callbacks until garbage collection occurs.  This may result in
+            // media route providers doing more work than necessary in the short term
+            // while trying to discover routes that are no longer of interest to the
+            // application.  To solve this problem, the action provider will need some
+            // indication from the framework that it is being destroyed.
+            if (!mSelector.isEmpty()) {
+                mRouter.removeCallback(mCallback);
+            }
+            if (!selector.isEmpty()) {
+                mRouter.addCallback(selector, mCallback);
+            }
+            mSelector = selector;
+            refreshRoute();
+
+            if (mButton != null) {
+                mButton.setRouteSelector(selector);
+            }
+        }
+    }
+
+    /**
+     * Gets the media route dialog factory to use when showing the route chooser
+     * or controller dialog.
+     *
+     * @return The dialog factory, never null.
+     */
+    @NonNull
+    public MediaRouteDialogFactory getDialogFactory() {
+        return mDialogFactory;
+    }
+
+    /**
+     * Sets the media route dialog factory to use when showing the route chooser
+     * or controller dialog.
+     *
+     * @param factory The dialog factory, must not be null.
+     */
+    public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
+        if (factory == null) {
+            throw new IllegalArgumentException("factory must not be null");
+        }
+
+        if (mDialogFactory != factory) {
+            mDialogFactory = factory;
+
+            if (mButton != null) {
+                mButton.setDialogFactory(factory);
+            }
+        }
+    }
+
+    /**
+     * Gets the associated media route button, or null if it has not yet been created.
+     */
+    @Nullable
+    public MediaRouteButton getMediaRouteButton() {
+        return mButton;
+    }
+
+    /**
+     * Called when the media route button is being created.
+     * <p>
+     * Subclasses may override this method to customize the button.
+     * </p>
+     */
+    public MediaRouteButton onCreateMediaRouteButton() {
+        return new MediaRouteButton(getContext());
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public View onCreateActionView() {
+        if (mButton != null) {
+            Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " +
+                    "with a menu item. Don't reuse MediaRouteActionProvider instances! " +
+                    "Abandoning the old menu item...");
+        }
+
+        mButton = onCreateMediaRouteButton();
+        mButton.setCheatSheetEnabled(true);
+        mButton.setRouteSelector(mSelector);
+        mButton.setDialogFactory(mDialogFactory);
+        mButton.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+        return mButton;
+    }
+
+    @Override
+    public boolean onPerformDefaultAction() {
+        if (mButton != null) {
+            return mButton.showDialog();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean overridesItemVisibility() {
+        return true;
+    }
+
+    @Override
+    public boolean isVisible() {
+        return mRouter.isRouteAvailable(mSelector,
+                MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
+    }
+
+    void refreshRoute() {
+        refreshVisibility();
+    }
+
+    private static final class MediaRouterCallback extends MediaRouter.Callback {
+        private final WeakReference<MediaRouteActionProvider> mProviderWeak;
+
+        public MediaRouterCallback(MediaRouteActionProvider provider) {
+            mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider);
+        }
+
+        @Override
+        public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoute(router);
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoute(router);
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoute(router);
+        }
+
+        @Override
+        public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) {
+            refreshRoute(router);
+        }
+
+        @Override
+        public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) {
+            refreshRoute(router);
+        }
+
+        @Override
+        public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) {
+            refreshRoute(router);
+        }
+
+        private void refreshRoute(MediaRouter router) {
+            MediaRouteActionProvider provider = mProviderWeak.get();
+            if (provider != null) {
+                provider.refreshRoute();
+            } else {
+                router.removeCallback(this);
+            }
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
new file mode 100644
index 0000000..68e8d3a
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
@@ -0,0 +1,614 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.widget.TooltipCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.SoundEffectConstants;
+import android.view.View;
+
+import com.android.media.update.R;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.MediaRouter;
+
+/**
+ * The media route button allows the user to select routes and to control the
+ * currently selected route.
+ * <p>
+ * The application must specify the kinds of routes that the user should be allowed
+ * to select by specifying a {@link MediaRouteSelector selector} with the
+ * {@link #setRouteSelector} method.
+ * </p><p>
+ * When the default route is selected or when the currently selected route does not
+ * match the {@link #getRouteSelector() selector}, the button will appear in
+ * an inactive state indicating that the application is not connected to a
+ * route of the kind that it wants to use.  Clicking on the button opens
+ * a {@link MediaRouteChooserDialog} to allow the user to select a route.
+ * If no non-default routes match the selector and it is not possible for an active
+ * scan to discover any matching routes, then the button is disabled and cannot
+ * be clicked.
+ * </p><p>
+ * When a non-default route is selected that matches the selector, the button will
+ * appear in an active state indicating that the application is connected
+ * to a route of the kind that it wants to use.  The button may also appear
+ * in an intermediary connecting state if the route is in the process of connecting
+ * to the destination but has not yet completed doing so.  In either case, clicking
+ * on the button opens a {@link MediaRouteControllerDialog} to allow the user
+ * to control or disconnect from the current route.
+ * </p>
+ *
+ * <h3>Prerequisites</h3>
+ * <p>
+ * To use the media route button, the activity must be a subclass of
+ * {@link FragmentActivity} from the <code>android.support.v4</code>
+ * support library.  Refer to support library documentation for details.
+ * </p>
+ *
+ * @see MediaRouteActionProvider
+ * @see #setRouteSelector
+ */
+public class MediaRouteButton extends View {
+    private static final String TAG = "MediaRouteButton";
+
+    private static final String CHOOSER_FRAGMENT_TAG =
+            "android.support.v7.mediarouter:MediaRouteChooserDialogFragment";
+    private static final String CONTROLLER_FRAGMENT_TAG =
+            "android.support.v7.mediarouter:MediaRouteControllerDialogFragment";
+
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+
+    private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
+    private MediaRouteDialogFactory mDialogFactory = MediaRouteDialogFactory.getDefault();
+
+    private boolean mAttachedToWindow;
+
+    private static final SparseArray<Drawable.ConstantState> sRemoteIndicatorCache =
+            new SparseArray<>(2);
+    private RemoteIndicatorLoader mRemoteIndicatorLoader;
+    private Drawable mRemoteIndicator;
+    private boolean mRemoteActive;
+    private boolean mIsConnecting;
+
+    private ColorStateList mButtonTint;
+    private int mMinWidth;
+    private int mMinHeight;
+
+    // The checked state is used when connected to a remote route.
+    private static final int[] CHECKED_STATE_SET = {
+        android.R.attr.state_checked
+    };
+
+    // The checkable state is used while connecting to a remote route.
+    private static final int[] CHECKABLE_STATE_SET = {
+        android.R.attr.state_checkable
+    };
+
+    public MediaRouteButton(Context context) {
+        this(context, null);
+    }
+
+    public MediaRouteButton(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.mediaRouteButtonStyle);
+    }
+
+    public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
+        context = getContext();
+
+        mRouter = MediaRouter.getInstance(context);
+        mCallback = new MediaRouterCallback();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.MediaRouteButton, defStyleAttr, 0);
+        mButtonTint = a.getColorStateList(R.styleable.MediaRouteButton_mediaRouteButtonTint);
+        mMinWidth = a.getDimensionPixelSize(
+                R.styleable.MediaRouteButton_android_minWidth, 0);
+        mMinHeight = a.getDimensionPixelSize(
+                R.styleable.MediaRouteButton_android_minHeight, 0);
+        int remoteIndicatorResId = a.getResourceId(
+                R.styleable.MediaRouteButton_externalRouteEnabledDrawable, 0);
+        a.recycle();
+
+        if (remoteIndicatorResId != 0) {
+            Drawable.ConstantState remoteIndicatorState =
+                    sRemoteIndicatorCache.get(remoteIndicatorResId);
+            if (remoteIndicatorState != null) {
+                setRemoteIndicatorDrawable(remoteIndicatorState.newDrawable());
+            } else {
+                mRemoteIndicatorLoader = new RemoteIndicatorLoader(remoteIndicatorResId);
+                mRemoteIndicatorLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+            }
+        }
+
+        updateContentDescription();
+        setClickable(true);
+    }
+
+    /**
+     * Gets the media route selector for filtering the routes that the user can
+     * select using the media route chooser dialog.
+     *
+     * @return The selector, never null.
+     */
+    @NonNull
+    public MediaRouteSelector getRouteSelector() {
+        return mSelector;
+    }
+
+    /**
+     * Sets the media route selector for filtering the routes that the user can
+     * select using the media route chooser dialog.
+     *
+     * @param selector The selector, must not be null.
+     */
+    public void setRouteSelector(MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        if (!mSelector.equals(selector)) {
+            if (mAttachedToWindow) {
+                if (!mSelector.isEmpty()) {
+                    mRouter.removeCallback(mCallback);
+                }
+                if (!selector.isEmpty()) {
+                    mRouter.addCallback(selector, mCallback);
+                }
+            }
+            mSelector = selector;
+            refreshRoute();
+        }
+    }
+
+    /**
+     * Gets the media route dialog factory to use when showing the route chooser
+     * or controller dialog.
+     *
+     * @return The dialog factory, never null.
+     */
+    @NonNull
+    public MediaRouteDialogFactory getDialogFactory() {
+        return mDialogFactory;
+    }
+
+    /**
+     * Sets the media route dialog factory to use when showing the route chooser
+     * or controller dialog.
+     *
+     * @param factory The dialog factory, must not be null.
+     */
+    public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
+        if (factory == null) {
+            throw new IllegalArgumentException("factory must not be null");
+        }
+
+        mDialogFactory = factory;
+    }
+
+    /**
+     * Show the route chooser or controller dialog.
+     * <p>
+     * If the default route is selected or if the currently selected route does
+     * not match the {@link #getRouteSelector selector}, then shows the route chooser dialog.
+     * Otherwise, shows the route controller dialog to offer the user
+     * a choice to disconnect from the route or perform other control actions
+     * such as setting the route's volume.
+     * </p><p>
+     * The application can customize the dialogs by calling {@link #setDialogFactory}
+     * to provide a customized dialog factory.
+     * </p>
+     *
+     * @return True if the dialog was actually shown.
+     *
+     * @throws IllegalStateException if the activity is not a subclass of
+     * {@link FragmentActivity}.
+     */
+    public boolean showDialog() {
+        if (!mAttachedToWindow) {
+            return false;
+        }
+
+        final FragmentManager fm = getFragmentManager();
+        if (fm == null) {
+            throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
+        }
+
+        MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
+        if (route.isDefaultOrBluetooth() || !route.matchesSelector(mSelector)) {
+            if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
+                Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
+                return false;
+            }
+            MediaRouteChooserDialogFragment f =
+                    mDialogFactory.onCreateChooserDialogFragment();
+            f.setRouteSelector(mSelector);
+            f.show(fm, CHOOSER_FRAGMENT_TAG);
+        } else {
+            if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) {
+                Log.w(TAG, "showDialog(): Route controller dialog already showing!");
+                return false;
+            }
+            MediaRouteControllerDialogFragment f =
+                    mDialogFactory.onCreateControllerDialogFragment();
+            f.show(fm, CONTROLLER_FRAGMENT_TAG);
+        }
+        return true;
+    }
+
+    private FragmentManager getFragmentManager() {
+        Activity activity = getActivity();
+        if (activity instanceof FragmentActivity) {
+            return ((FragmentActivity)activity).getSupportFragmentManager();
+        }
+        return null;
+    }
+
+    private Activity getActivity() {
+        // Gross way of unwrapping the Activity so we can get the FragmentManager
+        Context context = getContext();
+        while (context instanceof ContextWrapper) {
+            if (context instanceof Activity) {
+                return (Activity)context;
+            }
+            context = ((ContextWrapper)context).getBaseContext();
+        }
+        return null;
+    }
+
+    /**
+     * Sets whether to enable showing a toast with the content descriptor of the
+     * button when the button is long pressed.
+     */
+    void setCheatSheetEnabled(boolean enable) {
+        TooltipCompat.setTooltipText(this,
+                enable ? getContext().getString(R.string.mr_button_content_description) : null);
+    }
+
+    @Override
+    public boolean performClick() {
+        // Send the appropriate accessibility events and call listeners
+        boolean handled = super.performClick();
+        if (!handled) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+        }
+        return showDialog() || handled;
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+
+        // Technically we should be handling this more completely, but these
+        // are implementation details here. Checkable is used to express the connecting
+        // drawable state and it's mutually exclusive with check for the purposes
+        // of state selection here.
+        if (mIsConnecting) {
+            mergeDrawableStates(drawableState, CHECKABLE_STATE_SET);
+        } else if (mRemoteActive) {
+            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+        }
+        return drawableState;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        if (mRemoteIndicator != null) {
+            int[] myDrawableState = getDrawableState();
+            mRemoteIndicator.setState(myDrawableState);
+            invalidate();
+        }
+    }
+
+    /**
+     * Sets a drawable to use as the remote route indicator.
+     */
+    public void setRemoteIndicatorDrawable(Drawable d) {
+        if (mRemoteIndicatorLoader != null) {
+            mRemoteIndicatorLoader.cancel(false);
+        }
+
+        if (mRemoteIndicator != null) {
+            mRemoteIndicator.setCallback(null);
+            unscheduleDrawable(mRemoteIndicator);
+        }
+        if (d != null) {
+            if (mButtonTint != null) {
+                d = DrawableCompat.wrap(d.mutate());
+                DrawableCompat.setTintList(d, mButtonTint);
+            }
+            d.setCallback(this);
+            d.setState(getDrawableState());
+            d.setVisible(getVisibility() == VISIBLE, false);
+        }
+        mRemoteIndicator = d;
+
+        refreshDrawableState();
+        if (mAttachedToWindow && mRemoteIndicator != null
+                && mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
+            AnimationDrawable curDrawable = (AnimationDrawable) mRemoteIndicator.getCurrent();
+            if (mIsConnecting) {
+                if (!curDrawable.isRunning()) {
+                    curDrawable.start();
+                }
+            } else if (mRemoteActive) {
+                if (curDrawable.isRunning()) {
+                    curDrawable.stop();
+                }
+                curDrawable.selectDrawable(curDrawable.getNumberOfFrames() - 1);
+            }
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mRemoteIndicator;
+    }
+
+    @Override
+    public void jumpDrawablesToCurrentState() {
+        // We can't call super to handle the background so we do it ourselves.
+        //super.jumpDrawablesToCurrentState();
+        if (getBackground() != null) {
+            DrawableCompat.jumpToCurrentState(getBackground());
+        }
+
+        // Handle our own remote indicator.
+        if (mRemoteIndicator != null) {
+            DrawableCompat.jumpToCurrentState(mRemoteIndicator);
+        }
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+
+        if (mRemoteIndicator != null) {
+            mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mAttachedToWindow = true;
+        if (!mSelector.isEmpty()) {
+            mRouter.addCallback(mSelector, mCallback);
+        }
+        refreshRoute();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mAttachedToWindow = false;
+        if (!mSelector.isEmpty()) {
+            mRouter.removeCallback(mCallback);
+        }
+
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        final int width = Math.max(mMinWidth, mRemoteIndicator != null ?
+                mRemoteIndicator.getIntrinsicWidth() + getPaddingLeft() + getPaddingRight() : 0);
+        final int height = Math.max(mMinHeight, mRemoteIndicator != null ?
+                mRemoteIndicator.getIntrinsicHeight() + getPaddingTop() + getPaddingBottom() : 0);
+
+        int measuredWidth;
+        switch (widthMode) {
+            case MeasureSpec.EXACTLY:
+                measuredWidth = widthSize;
+                break;
+            case MeasureSpec.AT_MOST:
+                measuredWidth = Math.min(widthSize, width);
+                break;
+            default:
+            case MeasureSpec.UNSPECIFIED:
+                measuredWidth = width;
+                break;
+        }
+
+        int measuredHeight;
+        switch (heightMode) {
+            case MeasureSpec.EXACTLY:
+                measuredHeight = heightSize;
+                break;
+            case MeasureSpec.AT_MOST:
+                measuredHeight = Math.min(heightSize, height);
+                break;
+            default:
+            case MeasureSpec.UNSPECIFIED:
+                measuredHeight = height;
+                break;
+        }
+
+        setMeasuredDimension(measuredWidth, measuredHeight);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mRemoteIndicator != null) {
+            final int left = getPaddingLeft();
+            final int right = getWidth() - getPaddingRight();
+            final int top = getPaddingTop();
+            final int bottom = getHeight() - getPaddingBottom();
+
+            final int drawWidth = mRemoteIndicator.getIntrinsicWidth();
+            final int drawHeight = mRemoteIndicator.getIntrinsicHeight();
+            final int drawLeft = left + (right - left - drawWidth) / 2;
+            final int drawTop = top + (bottom - top - drawHeight) / 2;
+
+            mRemoteIndicator.setBounds(drawLeft, drawTop,
+                    drawLeft + drawWidth, drawTop + drawHeight);
+            mRemoteIndicator.draw(canvas);
+        }
+    }
+
+    void refreshRoute() {
+        final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
+        final boolean isRemote = !route.isDefaultOrBluetooth() && route.matchesSelector(mSelector);
+        final boolean isConnecting = isRemote && route.isConnecting();
+        boolean needsRefresh = false;
+        if (mRemoteActive != isRemote) {
+            mRemoteActive = isRemote;
+            needsRefresh = true;
+        }
+        if (mIsConnecting != isConnecting) {
+            mIsConnecting = isConnecting;
+            needsRefresh = true;
+        }
+
+        if (needsRefresh) {
+            updateContentDescription();
+            refreshDrawableState();
+        }
+        if (mAttachedToWindow) {
+            setEnabled(mRouter.isRouteAvailable(mSelector,
+                    MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
+        }
+        if (mRemoteIndicator != null
+                && mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
+            AnimationDrawable curDrawable = (AnimationDrawable) mRemoteIndicator.getCurrent();
+            if (mAttachedToWindow) {
+                if ((needsRefresh || isConnecting) && !curDrawable.isRunning()) {
+                    curDrawable.start();
+                }
+            } else if (isRemote && !isConnecting) {
+                // When the route is already connected before the view is attached, show the last
+                // frame of the connected animation immediately.
+                if (curDrawable.isRunning()) {
+                    curDrawable.stop();
+                }
+                curDrawable.selectDrawable(curDrawable.getNumberOfFrames() - 1);
+            }
+        }
+    }
+
+    private void updateContentDescription() {
+        int resId;
+        if (mIsConnecting) {
+            resId = R.string.mr_cast_button_connecting;
+        } else if (mRemoteActive) {
+            resId = R.string.mr_cast_button_connected;
+        } else {
+            resId = R.string.mr_cast_button_disconnected;
+        }
+        setContentDescription(getContext().getString(resId));
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.Callback {
+        MediaRouterCallback() {
+        }
+
+        @Override
+        public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) {
+            refreshRoute();
+        }
+    }
+
+    private final class RemoteIndicatorLoader extends AsyncTask<Void, Void, Drawable> {
+        private final int mResId;
+
+        RemoteIndicatorLoader(int resId) {
+            mResId = resId;
+        }
+
+        @Override
+        protected Drawable doInBackground(Void... params) {
+            return getContext().getResources().getDrawable(mResId);
+        }
+
+        @Override
+        protected void onPostExecute(Drawable remoteIndicator) {
+            cacheAndReset(remoteIndicator);
+            setRemoteIndicatorDrawable(remoteIndicator);
+        }
+
+        @Override
+        protected void onCancelled(Drawable remoteIndicator) {
+            cacheAndReset(remoteIndicator);
+        }
+
+        private void cacheAndReset(Drawable remoteIndicator) {
+            if (remoteIndicator != null) {
+                sRemoteIndicatorCache.put(mResId, remoteIndicator.getConstantState());
+            }
+            mRemoteIndicatorLoader = null;
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
new file mode 100644
index 0000000..cc7c3d5
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
@@ -0,0 +1,392 @@
+/*
+ * 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.support.mediarouter.app;
+
+import static com.android.support.mediarouter.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
+import static com.android.support.mediarouter.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v7.app.AppCompatDialog;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.media.update.R;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.MediaRouter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * This class implements the route chooser dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to choose a route that matches a given selector.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ */
+public class MediaRouteChooserDialog extends AppCompatDialog {
+    static final String TAG = "MediaRouteChooserDialog";
+
+    // Do not update the route list immediately to avoid unnatural dialog change.
+    private static final long UPDATE_ROUTES_DELAY_MS = 300L;
+    static final int MSG_UPDATE_ROUTES = 1;
+
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+
+    private TextView mTitleView;
+    private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
+    private ArrayList<MediaRouter.RouteInfo> mRoutes;
+    private RouteAdapter mAdapter;
+    private ListView mListView;
+    private boolean mAttachedToWindow;
+    private long mLastUpdateTime;
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_UPDATE_ROUTES:
+                    updateRoutes((List<MediaRouter.RouteInfo>) message.obj);
+                    break;
+            }
+        }
+    };
+
+    public MediaRouteChooserDialog(Context context) {
+        this(context, 0);
+    }
+
+    public MediaRouteChooserDialog(Context context, int theme) {
+        super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
+                MediaRouterThemeHelper.createThemedDialogStyle(context));
+        context = getContext();
+
+        mRouter = MediaRouter.getInstance(context);
+        mCallback = new MediaRouterCallback();
+    }
+
+    /**
+     * Gets the media route selector for filtering the routes that the user can select.
+     *
+     * @return The selector, never null.
+     */
+    @NonNull
+    public MediaRouteSelector getRouteSelector() {
+        return mSelector;
+    }
+
+    /**
+     * Sets the media route selector for filtering the routes that the user can select.
+     *
+     * @param selector The selector, must not be null.
+     */
+    public void setRouteSelector(@NonNull MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        if (!mSelector.equals(selector)) {
+            mSelector = selector;
+
+            if (mAttachedToWindow) {
+                mRouter.removeCallback(mCallback);
+                mRouter.addCallback(selector, mCallback,
+                        MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+            }
+
+            refreshRoutes();
+        }
+    }
+
+    /**
+     * Called to filter the set of routes that should be included in the list.
+     * <p>
+     * The default implementation iterates over all routes in the provided list and
+     * removes those for which {@link #onFilterRoute} returns false.
+     * </p>
+     *
+     * @param routes The list of routes to filter in-place, never null.
+     */
+    public void onFilterRoutes(@NonNull List<MediaRouter.RouteInfo> routes) {
+        for (int i = routes.size(); i-- > 0; ) {
+            if (!onFilterRoute(routes.get(i))) {
+                routes.remove(i);
+            }
+        }
+    }
+
+    /**
+     * Returns true if the route should be included in the list.
+     * <p>
+     * The default implementation returns true for enabled non-default routes that
+     * match the selector.  Subclasses can override this method to filter routes
+     * differently.
+     * </p>
+     *
+     * @param route The route to consider, never null.
+     * @return True if the route should be included in the chooser dialog.
+     */
+    public boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
+        return !route.isDefaultOrBluetooth() && route.isEnabled()
+                && route.matchesSelector(mSelector);
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mTitleView.setText(title);
+    }
+
+    @Override
+    public void setTitle(int titleId) {
+        mTitleView.setText(titleId);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.mr_chooser_dialog);
+
+        mRoutes = new ArrayList<>();
+        mAdapter = new RouteAdapter(getContext(), mRoutes);
+        mListView = (ListView)findViewById(R.id.mr_chooser_list);
+        mListView.setAdapter(mAdapter);
+        mListView.setOnItemClickListener(mAdapter);
+        mListView.setEmptyView(findViewById(android.R.id.empty));
+        mTitleView = findViewById(R.id.mr_chooser_title);
+
+        updateLayout();
+    }
+
+    /**
+     * Sets the width of the dialog. Also called when configuration changes.
+     */
+    void updateLayout() {
+        getWindow().setLayout(MediaRouteDialogHelper.getDialogWidth(getContext()),
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mAttachedToWindow = true;
+        mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+        refreshRoutes();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mAttachedToWindow = false;
+        mRouter.removeCallback(mCallback);
+        mHandler.removeMessages(MSG_UPDATE_ROUTES);
+
+        super.onDetachedFromWindow();
+    }
+
+    /**
+     * Refreshes the list of routes that are shown in the chooser dialog.
+     */
+    public void refreshRoutes() {
+        if (mAttachedToWindow) {
+            ArrayList<MediaRouter.RouteInfo> routes = new ArrayList<>(mRouter.getRoutes());
+            onFilterRoutes(routes);
+            Collections.sort(routes, RouteComparator.sInstance);
+            if (SystemClock.uptimeMillis() - mLastUpdateTime >= UPDATE_ROUTES_DELAY_MS) {
+                updateRoutes(routes);
+            } else {
+                mHandler.removeMessages(MSG_UPDATE_ROUTES);
+                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ROUTES, routes),
+                        mLastUpdateTime + UPDATE_ROUTES_DELAY_MS);
+            }
+        }
+    }
+
+    void updateRoutes(List<MediaRouter.RouteInfo> routes) {
+        mLastUpdateTime = SystemClock.uptimeMillis();
+        mRoutes.clear();
+        mRoutes.addAll(routes);
+        mAdapter.notifyDataSetChanged();
+    }
+
+    private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
+            implements ListView.OnItemClickListener {
+        private final LayoutInflater mInflater;
+        private final Drawable mDefaultIcon;
+        private final Drawable mTvIcon;
+        private final Drawable mSpeakerIcon;
+        private final Drawable mSpeakerGroupIcon;
+
+        public RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes) {
+            super(context, 0, routes);
+            mInflater = LayoutInflater.from(context);
+            TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
+                    R.attr.mediaRouteDefaultIconDrawable,
+                    R.attr.mediaRouteTvIconDrawable,
+                    R.attr.mediaRouteSpeakerIconDrawable,
+                    R.attr.mediaRouteSpeakerGroupIconDrawable});
+            mDefaultIcon = styledAttributes.getDrawable(0);
+            mTvIcon = styledAttributes.getDrawable(1);
+            mSpeakerIcon = styledAttributes.getDrawable(2);
+            mSpeakerGroupIcon = styledAttributes.getDrawable(3);
+            styledAttributes.recycle();
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return getItem(position).isEnabled();
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view = convertView;
+            if (view == null) {
+                view = mInflater.inflate(R.layout.mr_chooser_list_item, parent, false);
+            }
+
+            MediaRouter.RouteInfo route = getItem(position);
+            TextView text1 = (TextView) view.findViewById(R.id.mr_chooser_route_name);
+            TextView text2 = (TextView) view.findViewById(R.id.mr_chooser_route_desc);
+            text1.setText(route.getName());
+            String description = route.getDescription();
+            boolean isConnectedOrConnecting =
+                    route.getConnectionState() == CONNECTION_STATE_CONNECTED
+                            || route.getConnectionState() == CONNECTION_STATE_CONNECTING;
+            if (isConnectedOrConnecting && !TextUtils.isEmpty(description)) {
+                text1.setGravity(Gravity.BOTTOM);
+                text2.setVisibility(View.VISIBLE);
+                text2.setText(description);
+            } else {
+                text1.setGravity(Gravity.CENTER_VERTICAL);
+                text2.setVisibility(View.GONE);
+                text2.setText("");
+            }
+            view.setEnabled(route.isEnabled());
+
+            ImageView iconView = (ImageView) view.findViewById(R.id.mr_chooser_route_icon);
+            if (iconView != null) {
+                iconView.setImageDrawable(getIconDrawable(route));
+            }
+            return view;
+        }
+
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            MediaRouter.RouteInfo route = getItem(position);
+            if (route.isEnabled()) {
+                route.select();
+                dismiss();
+            }
+        }
+
+        private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
+            Uri iconUri = route.getIconUri();
+            if (iconUri != null) {
+                try {
+                    InputStream is = getContext().getContentResolver().openInputStream(iconUri);
+                    Drawable drawable = Drawable.createFromStream(is, null);
+                    if (drawable != null) {
+                        return drawable;
+                    }
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to load " + iconUri, e);
+                    // Falls back.
+                }
+            }
+            return getDefaultIconDrawable(route);
+        }
+
+        private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
+            // If the type of the receiver device is specified, use it.
+            switch (route.getDeviceType()) {
+                case  MediaRouter.RouteInfo.DEVICE_TYPE_TV:
+                    return mTvIcon;
+                case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
+                    return mSpeakerIcon;
+            }
+
+            // Otherwise, make the best guess based on other route information.
+            if (route instanceof MediaRouter.RouteGroup) {
+                // Only speakers can be grouped for now.
+                return mSpeakerGroupIcon;
+            }
+            return mDefaultIcon;
+        }
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.Callback {
+        MediaRouterCallback() {
+        }
+
+        @Override
+        public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+            dismiss();
+        }
+    }
+
+    static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+        public static final RouteComparator sInstance = new RouteComparator();
+
+        @Override
+        public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
+            return lhs.getName().compareToIgnoreCase(rhs.getName());
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java
new file mode 100644
index 0000000..2f85fb3
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java
@@ -0,0 +1,126 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+import com.android.support.mediarouter.media.MediaRouteSelector;
+
+/**
+ * Media route chooser dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteChooserDialog}.  The application may subclass
+ * this dialog fragment to customize the media route chooser dialog.
+ * </p>
+ */
+public class MediaRouteChooserDialogFragment extends DialogFragment {
+    private final String ARGUMENT_SELECTOR = "selector";
+
+    private MediaRouteChooserDialog mDialog;
+    private MediaRouteSelector mSelector;
+
+    /**
+     * Creates a media route chooser dialog fragment.
+     * <p>
+     * All subclasses of this class must also possess a default constructor.
+     * </p>
+     */
+    public MediaRouteChooserDialogFragment() {
+        setCancelable(true);
+    }
+
+    /**
+     * Gets the media route selector for filtering the routes that the user can select.
+     *
+     * @return The selector, never null.
+     */
+    public MediaRouteSelector getRouteSelector() {
+        ensureRouteSelector();
+        return mSelector;
+    }
+
+    private void ensureRouteSelector() {
+        if (mSelector == null) {
+            Bundle args = getArguments();
+            if (args != null) {
+                mSelector = MediaRouteSelector.fromBundle(args.getBundle(ARGUMENT_SELECTOR));
+            }
+            if (mSelector == null) {
+                mSelector = MediaRouteSelector.EMPTY;
+            }
+        }
+    }
+
+    /**
+     * Sets the media route selector for filtering the routes that the user can select.
+     * This method must be called before the fragment is added.
+     *
+     * @param selector The selector to set.
+     */
+    public void setRouteSelector(MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        ensureRouteSelector();
+        if (!mSelector.equals(selector)) {
+            mSelector = selector;
+
+            Bundle args = getArguments();
+            if (args == null) {
+                args = new Bundle();
+            }
+            args.putBundle(ARGUMENT_SELECTOR, selector.asBundle());
+            setArguments(args);
+
+            MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+            if (dialog != null) {
+                dialog.setRouteSelector(selector);
+            }
+        }
+    }
+
+    /**
+     * Called when the chooser dialog is being created.
+     * <p>
+     * Subclasses may override this method to customize the dialog.
+     * </p>
+     */
+    public MediaRouteChooserDialog onCreateChooserDialog(
+            Context context, Bundle savedInstanceState) {
+        return new MediaRouteChooserDialog(context);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        mDialog = onCreateChooserDialog(getContext(), savedInstanceState);
+        mDialog.setRouteSelector(getRouteSelector());
+        return mDialog;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mDialog != null) {
+            mDialog.updateLayout();
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
new file mode 100644
index 0000000..942797b
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
@@ -0,0 +1,1481 @@
+/*
+ * 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.support.mediarouter.app;
+
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE;
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE;
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.util.ObjectsCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.graphics.Palette;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.media.update.R;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.MediaRouter;
+import com.android.support.mediarouter.app.OverlayListView.OverlayObject;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class implements the route controller dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to control or disconnect from the currently selected route.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ */
+public class MediaRouteControllerDialog extends AlertDialog {
+    // Tags should be less than 24 characters long (see docs for android.util.Log.isLoggable())
+    static final String TAG = "MediaRouteCtrlDialog";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // Time to wait before updating the volume when the user lets go of the seek bar
+    // to allow the route provider time to propagate the change and publish a new
+    // route descriptor.
+    static final int VOLUME_UPDATE_DELAY_MILLIS = 500;
+    static final int CONNECTION_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(30L);
+
+    private static final int BUTTON_NEUTRAL_RES_ID = android.R.id.button3;
+    static final int BUTTON_DISCONNECT_RES_ID = android.R.id.button2;
+    static final int BUTTON_STOP_RES_ID = android.R.id.button1;
+
+    final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+    final MediaRouter.RouteInfo mRoute;
+
+    Context mContext;
+    private boolean mCreated;
+    private boolean mAttachedToWindow;
+
+    private int mDialogContentWidth;
+
+    private View mCustomControlView;
+
+    private Button mDisconnectButton;
+    private Button mStopCastingButton;
+    private ImageButton mPlaybackControlButton;
+    private ImageButton mCloseButton;
+    private MediaRouteExpandCollapseButton mGroupExpandCollapseButton;
+
+    private FrameLayout mExpandableAreaLayout;
+    private LinearLayout mDialogAreaLayout;
+    FrameLayout mDefaultControlLayout;
+    private FrameLayout mCustomControlLayout;
+    private ImageView mArtView;
+    private TextView mTitleView;
+    private TextView mSubtitleView;
+    private TextView mRouteNameTextView;
+
+    private boolean mVolumeControlEnabled = true;
+    // Layout for media controllers including play/pause button and the main volume slider.
+    private LinearLayout mMediaMainControlLayout;
+    private RelativeLayout mPlaybackControlLayout;
+    private LinearLayout mVolumeControlLayout;
+    private View mDividerView;
+
+    OverlayListView mVolumeGroupList;
+    VolumeGroupAdapter mVolumeGroupAdapter;
+    private List<MediaRouter.RouteInfo> mGroupMemberRoutes;
+    Set<MediaRouter.RouteInfo> mGroupMemberRoutesAdded;
+    private Set<MediaRouter.RouteInfo> mGroupMemberRoutesRemoved;
+    Set<MediaRouter.RouteInfo> mGroupMemberRoutesAnimatingWithBitmap;
+    SeekBar mVolumeSlider;
+    VolumeChangeListener mVolumeChangeListener;
+    MediaRouter.RouteInfo mRouteInVolumeSliderTouched;
+    private int mVolumeGroupListItemIconSize;
+    private int mVolumeGroupListItemHeight;
+    private int mVolumeGroupListMaxHeight;
+    private final int mVolumeGroupListPaddingTop;
+    Map<MediaRouter.RouteInfo, SeekBar> mVolumeSliderMap;
+
+    MediaControllerCompat mMediaController;
+    MediaControllerCallback mControllerCallback;
+    PlaybackStateCompat mState;
+    MediaDescriptionCompat mDescription;
+
+    FetchArtTask mFetchArtTask;
+    Bitmap mArtIconBitmap;
+    Uri mArtIconUri;
+    boolean mArtIconIsLoaded;
+    Bitmap mArtIconLoadedBitmap;
+    int mArtIconBackgroundColor;
+
+    boolean mHasPendingUpdate;
+    boolean mPendingUpdateAnimationNeeded;
+
+    boolean mIsGroupExpanded;
+    boolean mIsGroupListAnimating;
+    boolean mIsGroupListAnimationPending;
+    int mGroupListAnimationDurationMs;
+    private int mGroupListFadeInDurationMs;
+    private int mGroupListFadeOutDurationMs;
+
+    private Interpolator mInterpolator;
+    private Interpolator mLinearOutSlowInInterpolator;
+    private Interpolator mFastOutSlowInInterpolator;
+    private Interpolator mAccelerateDecelerateInterpolator;
+
+    final AccessibilityManager mAccessibilityManager;
+
+    Runnable mGroupListFadeInAnimation = new Runnable() {
+        @Override
+        public void run() {
+            startGroupListFadeInAnimation();
+        }
+    };
+
+    public MediaRouteControllerDialog(Context context) {
+        this(context, 0);
+    }
+
+    public MediaRouteControllerDialog(Context context, int theme) {
+        super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true),
+                MediaRouterThemeHelper.createThemedDialogStyle(context));
+        mContext = getContext();
+
+        mControllerCallback = new MediaControllerCallback();
+        mRouter = MediaRouter.getInstance(mContext);
+        mCallback = new MediaRouterCallback();
+        mRoute = mRouter.getSelectedRoute();
+        setMediaSession(mRouter.getMediaSessionToken());
+        mVolumeGroupListPaddingTop = mContext.getResources().getDimensionPixelSize(
+                R.dimen.mr_controller_volume_group_list_padding_top);
+        mAccessibilityManager =
+                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (android.os.Build.VERSION.SDK_INT >= 21) {
+            mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                    R.interpolator.mr_linear_out_slow_in);
+            mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                    R.interpolator.mr_fast_out_slow_in);
+        }
+        mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
+    }
+
+    /**
+     * Gets the route that this dialog is controlling.
+     */
+    public MediaRouter.RouteInfo getRoute() {
+        return mRoute;
+    }
+
+    private MediaRouter.RouteGroup getGroup() {
+        if (mRoute instanceof MediaRouter.RouteGroup) {
+            return (MediaRouter.RouteGroup) mRoute;
+        }
+        return null;
+    }
+
+    /**
+     * Provides the subclass an opportunity to create a view that will replace the default media
+     * controls for the currently playing content.
+     *
+     * @param savedInstanceState The dialog's saved instance state.
+     * @return The media control view, or null if none.
+     */
+    public View onCreateMediaControlView(Bundle savedInstanceState) {
+        return null;
+    }
+
+    /**
+     * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
+     *
+     * @return The media control view, or null if none.
+     */
+    public View getMediaControlView() {
+        return mCustomControlView;
+    }
+
+    /**
+     * Sets whether to enable the volume slider and volume control using the volume keys
+     * when the route supports it.
+     * <p>
+     * The default value is true.
+     * </p>
+     */
+    public void setVolumeControlEnabled(boolean enable) {
+        if (mVolumeControlEnabled != enable) {
+            mVolumeControlEnabled = enable;
+            if (mCreated) {
+                update(false);
+            }
+        }
+    }
+
+    /**
+     * Returns whether to enable the volume slider and volume control using the volume keys
+     * when the route supports it.
+     */
+    public boolean isVolumeControlEnabled() {
+        return mVolumeControlEnabled;
+    }
+
+    /**
+     * Set the session to use for metadata and transport controls. The dialog
+     * will listen to changes on this session and update the UI automatically in
+     * response to changes.
+     *
+     * @param sessionToken The token for the session to use.
+     */
+    private void setMediaSession(MediaSessionCompat.Token sessionToken) {
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mControllerCallback);
+            mMediaController = null;
+        }
+        if (sessionToken == null) {
+            return;
+        }
+        if (!mAttachedToWindow) {
+            return;
+        }
+        try {
+            mMediaController = new MediaControllerCompat(mContext, sessionToken);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error creating media controller in setMediaSession.", e);
+        }
+        if (mMediaController != null) {
+            mMediaController.registerCallback(mControllerCallback);
+        }
+        MediaMetadataCompat metadata = mMediaController == null ? null
+                : mMediaController.getMetadata();
+        mDescription = metadata == null ? null : metadata.getDescription();
+        mState = mMediaController == null ? null : mMediaController.getPlaybackState();
+        updateArtIconIfNeeded();
+        update(false);
+    }
+
+    /**
+     * Gets the session to use for metadata and transport controls.
+     *
+     * @return The token for the session to use or null if none.
+     */
+    public MediaSessionCompat.Token getMediaSession() {
+        return mMediaController == null ? null : mMediaController.getSessionToken();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().setBackgroundDrawableResource(android.R.color.transparent);
+        setContentView(R.layout.mr_controller_material_dialog_b);
+
+        // Remove the neutral button.
+        findViewById(BUTTON_NEUTRAL_RES_ID).setVisibility(View.GONE);
+
+        ClickListener listener = new ClickListener();
+
+        mExpandableAreaLayout = findViewById(R.id.mr_expandable_area);
+        mExpandableAreaLayout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                dismiss();
+            }
+        });
+        mDialogAreaLayout = findViewById(R.id.mr_dialog_area);
+        mDialogAreaLayout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // Eat unhandled touch events.
+            }
+        });
+        int color = MediaRouterThemeHelper.getButtonTextColor(mContext);
+        mDisconnectButton = findViewById(BUTTON_DISCONNECT_RES_ID);
+        mDisconnectButton.setText(R.string.mr_controller_disconnect);
+        mDisconnectButton.setTextColor(color);
+        mDisconnectButton.setOnClickListener(listener);
+
+        mStopCastingButton = findViewById(BUTTON_STOP_RES_ID);
+        mStopCastingButton.setText(R.string.mr_controller_stop_casting);
+        mStopCastingButton.setTextColor(color);
+        mStopCastingButton.setOnClickListener(listener);
+
+        mRouteNameTextView = findViewById(R.id.mr_name);
+        mCloseButton = findViewById(R.id.mr_close);
+        mCloseButton.setOnClickListener(listener);
+        mCustomControlLayout = findViewById(R.id.mr_custom_control);
+        mDefaultControlLayout = findViewById(R.id.mr_default_control);
+
+        // Start the session activity when a content item (album art, title or subtitle) is clicked.
+        View.OnClickListener onClickListener = new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mMediaController != null) {
+                    PendingIntent pi = mMediaController.getSessionActivity();
+                    if (pi != null) {
+                        try {
+                            pi.send();
+                            dismiss();
+                        } catch (PendingIntent.CanceledException e) {
+                            Log.e(TAG, pi + " was not sent, it had been canceled.");
+                        }
+                    }
+                }
+            }
+        };
+        mArtView = findViewById(R.id.mr_art);
+        mArtView.setOnClickListener(onClickListener);
+        findViewById(R.id.mr_control_title_container).setOnClickListener(onClickListener);
+
+        mMediaMainControlLayout = findViewById(R.id.mr_media_main_control);
+        mDividerView = findViewById(R.id.mr_control_divider);
+
+        mPlaybackControlLayout = findViewById(R.id.mr_playback_control);
+        mTitleView = findViewById(R.id.mr_control_title);
+        mSubtitleView = findViewById(R.id.mr_control_subtitle);
+        mPlaybackControlButton = findViewById(R.id.mr_control_playback_ctrl);
+        mPlaybackControlButton.setOnClickListener(listener);
+
+        mVolumeControlLayout = findViewById(R.id.mr_volume_control);
+        mVolumeControlLayout.setVisibility(View.GONE);
+        mVolumeSlider = findViewById(R.id.mr_volume_slider);
+        mVolumeSlider.setTag(mRoute);
+        mVolumeChangeListener = new VolumeChangeListener();
+        mVolumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
+
+        mVolumeGroupList = findViewById(R.id.mr_volume_group_list);
+        mGroupMemberRoutes = new ArrayList<MediaRouter.RouteInfo>();
+        mVolumeGroupAdapter = new VolumeGroupAdapter(mVolumeGroupList.getContext(),
+                mGroupMemberRoutes);
+        mVolumeGroupList.setAdapter(mVolumeGroupAdapter);
+        mGroupMemberRoutesAnimatingWithBitmap = new HashSet<>();
+
+        MediaRouterThemeHelper.setMediaControlsBackgroundColor(mContext,
+                mMediaMainControlLayout, mVolumeGroupList, getGroup() != null);
+        MediaRouterThemeHelper.setVolumeSliderColor(mContext,
+                (MediaRouteVolumeSlider) mVolumeSlider, mMediaMainControlLayout);
+        mVolumeSliderMap = new HashMap<>();
+        mVolumeSliderMap.put(mRoute, mVolumeSlider);
+
+        mGroupExpandCollapseButton =
+                findViewById(R.id.mr_group_expand_collapse);
+        mGroupExpandCollapseButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mIsGroupExpanded = !mIsGroupExpanded;
+                if (mIsGroupExpanded) {
+                    mVolumeGroupList.setVisibility(View.VISIBLE);
+                }
+                loadInterpolator();
+                updateLayoutHeight(true);
+            }
+        });
+        loadInterpolator();
+        mGroupListAnimationDurationMs = mContext.getResources().getInteger(
+                R.integer.mr_controller_volume_group_list_animation_duration_ms);
+        mGroupListFadeInDurationMs = mContext.getResources().getInteger(
+                R.integer.mr_controller_volume_group_list_fade_in_duration_ms);
+        mGroupListFadeOutDurationMs = mContext.getResources().getInteger(
+                R.integer.mr_controller_volume_group_list_fade_out_duration_ms);
+
+        mCustomControlView = onCreateMediaControlView(savedInstanceState);
+        if (mCustomControlView != null) {
+            mCustomControlLayout.addView(mCustomControlView);
+            mCustomControlLayout.setVisibility(View.VISIBLE);
+        }
+        mCreated = true;
+        updateLayout();
+    }
+
+    /**
+     * Sets the width of the dialog. Also called when configuration changes.
+     */
+    void updateLayout() {
+        int width = MediaRouteDialogHelper.getDialogWidth(mContext);
+        getWindow().setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+        View decorView = getWindow().getDecorView();
+        mDialogContentWidth = width - decorView.getPaddingLeft() - decorView.getPaddingRight();
+
+        Resources res = mContext.getResources();
+        mVolumeGroupListItemIconSize = res.getDimensionPixelSize(
+                R.dimen.mr_controller_volume_group_list_item_icon_size);
+        mVolumeGroupListItemHeight = res.getDimensionPixelSize(
+                R.dimen.mr_controller_volume_group_list_item_height);
+        mVolumeGroupListMaxHeight = res.getDimensionPixelSize(
+                R.dimen.mr_controller_volume_group_list_max_height);
+
+        // Fetch art icons again for layout changes to resize it accordingly
+        mArtIconBitmap = null;
+        mArtIconUri = null;
+        updateArtIconIfNeeded();
+        update(false);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mAttachedToWindow = true;
+
+        mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback,
+                MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
+        setMediaSession(mRouter.getMediaSessionToken());
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mRouter.removeCallback(mCallback);
+        setMediaSession(null);
+        mAttachedToWindow = false;
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+            mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    void update(boolean animate) {
+        // Defer dialog updates if a user is adjusting a volume in the list
+        if (mRouteInVolumeSliderTouched != null) {
+            mHasPendingUpdate = true;
+            mPendingUpdateAnimationNeeded |= animate;
+            return;
+        }
+        mHasPendingUpdate = false;
+        mPendingUpdateAnimationNeeded = false;
+        if (!mRoute.isSelected() || mRoute.isDefaultOrBluetooth()) {
+            dismiss();
+            return;
+        }
+        if (!mCreated) {
+            return;
+        }
+
+        mRouteNameTextView.setText(mRoute.getName());
+        mDisconnectButton.setVisibility(mRoute.canDisconnect() ? View.VISIBLE : View.GONE);
+        if (mCustomControlView == null && mArtIconIsLoaded) {
+            if (isBitmapRecycled(mArtIconLoadedBitmap)) {
+                Log.w(TAG, "Can't set artwork image with recycled bitmap: " + mArtIconLoadedBitmap);
+            } else {
+                mArtView.setImageBitmap(mArtIconLoadedBitmap);
+                mArtView.setBackgroundColor(mArtIconBackgroundColor);
+            }
+            clearLoadedBitmap();
+        }
+        updateVolumeControlLayout();
+        updatePlaybackControlLayout();
+        updateLayoutHeight(animate);
+    }
+
+    private boolean isBitmapRecycled(Bitmap bitmap) {
+        return bitmap != null && bitmap.isRecycled();
+    }
+
+    private boolean canShowPlaybackControlLayout() {
+        return mCustomControlView == null && (mDescription != null || mState != null);
+    }
+
+    /**
+     * Returns the height of main media controller which includes playback control and master
+     * volume control.
+     */
+    private int getMainControllerHeight(boolean showPlaybackControl) {
+        int height = 0;
+        if (showPlaybackControl || mVolumeControlLayout.getVisibility() == View.VISIBLE) {
+            height += mMediaMainControlLayout.getPaddingTop()
+                    + mMediaMainControlLayout.getPaddingBottom();
+            if (showPlaybackControl) {
+                height +=  mPlaybackControlLayout.getMeasuredHeight();
+            }
+            if (mVolumeControlLayout.getVisibility() == View.VISIBLE) {
+                height += mVolumeControlLayout.getMeasuredHeight();
+            }
+            if (showPlaybackControl && mVolumeControlLayout.getVisibility() == View.VISIBLE) {
+                height += mDividerView.getMeasuredHeight();
+            }
+        }
+        return height;
+    }
+
+    private void updateMediaControlVisibility(boolean canShowPlaybackControlLayout) {
+        // TODO: Update the top and bottom padding of the control layout according to the display
+        // height.
+        mDividerView.setVisibility((mVolumeControlLayout.getVisibility() == View.VISIBLE
+                && canShowPlaybackControlLayout) ? View.VISIBLE : View.GONE);
+        mMediaMainControlLayout.setVisibility((mVolumeControlLayout.getVisibility() == View.GONE
+                && !canShowPlaybackControlLayout) ? View.GONE : View.VISIBLE);
+    }
+
+    void updateLayoutHeight(final boolean animate) {
+        // We need to defer the update until the first layout has occurred, as we don't yet know the
+        // overall visible display size in which the window this view is attached to has been
+        // positioned in.
+        mDefaultControlLayout.requestLayout();
+        ViewTreeObserver observer = mDefaultControlLayout.getViewTreeObserver();
+        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                mDefaultControlLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+                if (mIsGroupListAnimating) {
+                    mIsGroupListAnimationPending = true;
+                } else {
+                    updateLayoutHeightInternal(animate);
+                }
+            }
+        });
+    }
+
+    /**
+     * Updates the height of views and hide artwork or metadata if space is limited.
+     */
+    void updateLayoutHeightInternal(boolean animate) {
+        // Measure the size of widgets and get the height of main components.
+        int oldHeight = getLayoutHeight(mMediaMainControlLayout);
+        setLayoutHeight(mMediaMainControlLayout, ViewGroup.LayoutParams.MATCH_PARENT);
+        updateMediaControlVisibility(canShowPlaybackControlLayout());
+        View decorView = getWindow().getDecorView();
+        decorView.measure(
+                MeasureSpec.makeMeasureSpec(getWindow().getAttributes().width, MeasureSpec.EXACTLY),
+                MeasureSpec.UNSPECIFIED);
+        setLayoutHeight(mMediaMainControlLayout, oldHeight);
+        int artViewHeight = 0;
+        if (mCustomControlView == null && mArtView.getDrawable() instanceof BitmapDrawable) {
+            Bitmap art = ((BitmapDrawable) mArtView.getDrawable()).getBitmap();
+            if (art != null) {
+                artViewHeight = getDesiredArtHeight(art.getWidth(), art.getHeight());
+                mArtView.setScaleType(art.getWidth() >= art.getHeight()
+                        ? ImageView.ScaleType.FIT_XY : ImageView.ScaleType.FIT_CENTER);
+            }
+        }
+        int mainControllerHeight = getMainControllerHeight(canShowPlaybackControlLayout());
+        int volumeGroupListCount = mGroupMemberRoutes.size();
+        // Scale down volume group list items in landscape mode.
+        int expandedGroupListHeight = getGroup() == null ? 0 :
+                mVolumeGroupListItemHeight * getGroup().getRoutes().size();
+        if (volumeGroupListCount > 0) {
+            expandedGroupListHeight += mVolumeGroupListPaddingTop;
+        }
+        expandedGroupListHeight = Math.min(expandedGroupListHeight, mVolumeGroupListMaxHeight);
+        int visibleGroupListHeight = mIsGroupExpanded ? expandedGroupListHeight : 0;
+
+        int desiredControlLayoutHeight =
+                Math.max(artViewHeight, visibleGroupListHeight) + mainControllerHeight;
+        Rect visibleRect = new Rect();
+        decorView.getWindowVisibleDisplayFrame(visibleRect);
+        // Height of non-control views in decor view.
+        // This includes title bar, button bar, and dialog's vertical padding which should be
+        // always shown.
+        int nonControlViewHeight = mDialogAreaLayout.getMeasuredHeight()
+                - mDefaultControlLayout.getMeasuredHeight();
+        // Maximum allowed height for controls to fit screen.
+        int maximumControlViewHeight = visibleRect.height() - nonControlViewHeight;
+
+        // Show artwork if it fits the screen.
+        if (mCustomControlView == null && artViewHeight > 0
+                && desiredControlLayoutHeight <= maximumControlViewHeight) {
+            mArtView.setVisibility(View.VISIBLE);
+            setLayoutHeight(mArtView, artViewHeight);
+        } else {
+            if (getLayoutHeight(mVolumeGroupList) + mMediaMainControlLayout.getMeasuredHeight()
+                    >= mDefaultControlLayout.getMeasuredHeight()) {
+                mArtView.setVisibility(View.GONE);
+            }
+            artViewHeight = 0;
+            desiredControlLayoutHeight = visibleGroupListHeight + mainControllerHeight;
+        }
+        // Show the playback control if it fits the screen.
+        if (canShowPlaybackControlLayout()
+                && desiredControlLayoutHeight <= maximumControlViewHeight) {
+            mPlaybackControlLayout.setVisibility(View.VISIBLE);
+        } else {
+            mPlaybackControlLayout.setVisibility(View.GONE);
+        }
+        updateMediaControlVisibility(mPlaybackControlLayout.getVisibility() == View.VISIBLE);
+        mainControllerHeight = getMainControllerHeight(
+                mPlaybackControlLayout.getVisibility() == View.VISIBLE);
+        desiredControlLayoutHeight =
+                Math.max(artViewHeight, visibleGroupListHeight) + mainControllerHeight;
+
+        // Limit the volume group list height to fit the screen.
+        if (desiredControlLayoutHeight > maximumControlViewHeight) {
+            visibleGroupListHeight -= (desiredControlLayoutHeight - maximumControlViewHeight);
+            desiredControlLayoutHeight = maximumControlViewHeight;
+        }
+        // Update the layouts with the computed heights.
+        mMediaMainControlLayout.clearAnimation();
+        mVolumeGroupList.clearAnimation();
+        mDefaultControlLayout.clearAnimation();
+        if (animate) {
+            animateLayoutHeight(mMediaMainControlLayout, mainControllerHeight);
+            animateLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
+            animateLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
+        } else {
+            setLayoutHeight(mMediaMainControlLayout, mainControllerHeight);
+            setLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
+            setLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
+        }
+        // Maximize the window size with a transparent layout in advance for smooth animation.
+        setLayoutHeight(mExpandableAreaLayout, visibleRect.height());
+        rebuildVolumeGroupList(animate);
+    }
+
+    void updateVolumeGroupItemHeight(View item) {
+        LinearLayout container = (LinearLayout) item.findViewById(R.id.volume_item_container);
+        setLayoutHeight(container, mVolumeGroupListItemHeight);
+        View icon = item.findViewById(R.id.mr_volume_item_icon);
+        ViewGroup.LayoutParams lp = icon.getLayoutParams();
+        lp.width = mVolumeGroupListItemIconSize;
+        lp.height = mVolumeGroupListItemIconSize;
+        icon.setLayoutParams(lp);
+    }
+
+    private void animateLayoutHeight(final View view, int targetHeight) {
+        final int startValue = getLayoutHeight(view);
+        final int endValue = targetHeight;
+        Animation anim = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int height = startValue - (int) ((startValue - endValue) * interpolatedTime);
+                setLayoutHeight(view, height);
+            }
+        };
+        anim.setDuration(mGroupListAnimationDurationMs);
+        if (android.os.Build.VERSION.SDK_INT >= 21) {
+            anim.setInterpolator(mInterpolator);
+        }
+        view.startAnimation(anim);
+    }
+
+    void loadInterpolator() {
+        if (android.os.Build.VERSION.SDK_INT >= 21) {
+            mInterpolator = mIsGroupExpanded ? mLinearOutSlowInInterpolator
+                    : mFastOutSlowInInterpolator;
+        } else {
+            mInterpolator = mAccelerateDecelerateInterpolator;
+        }
+    }
+
+    private void updateVolumeControlLayout() {
+        if (isVolumeControlAvailable(mRoute)) {
+            if (mVolumeControlLayout.getVisibility() == View.GONE) {
+                mVolumeControlLayout.setVisibility(View.VISIBLE);
+                mVolumeSlider.setMax(mRoute.getVolumeMax());
+                mVolumeSlider.setProgress(mRoute.getVolume());
+                mGroupExpandCollapseButton.setVisibility(getGroup() == null ? View.GONE
+                        : View.VISIBLE);
+            }
+        } else {
+            mVolumeControlLayout.setVisibility(View.GONE);
+        }
+    }
+
+    private void rebuildVolumeGroupList(boolean animate) {
+        List<MediaRouter.RouteInfo> routes = getGroup() == null ? null : getGroup().getRoutes();
+        if (routes == null) {
+            mGroupMemberRoutes.clear();
+            mVolumeGroupAdapter.notifyDataSetChanged();
+        } else if (MediaRouteDialogHelper.listUnorderedEquals(mGroupMemberRoutes, routes)) {
+            mVolumeGroupAdapter.notifyDataSetChanged();
+        } else {
+            HashMap<MediaRouter.RouteInfo, Rect> previousRouteBoundMap = animate
+                    ? MediaRouteDialogHelper.getItemBoundMap(mVolumeGroupList, mVolumeGroupAdapter)
+                    : null;
+            HashMap<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap = animate
+                    ? MediaRouteDialogHelper.getItemBitmapMap(mContext, mVolumeGroupList,
+                            mVolumeGroupAdapter) : null;
+            mGroupMemberRoutesAdded =
+                    MediaRouteDialogHelper.getItemsAdded(mGroupMemberRoutes, routes);
+            mGroupMemberRoutesRemoved = MediaRouteDialogHelper.getItemsRemoved(mGroupMemberRoutes,
+                    routes);
+            mGroupMemberRoutes.addAll(0, mGroupMemberRoutesAdded);
+            mGroupMemberRoutes.removeAll(mGroupMemberRoutesRemoved);
+            mVolumeGroupAdapter.notifyDataSetChanged();
+            if (animate && mIsGroupExpanded
+                    && mGroupMemberRoutesAdded.size() + mGroupMemberRoutesRemoved.size() > 0) {
+                animateGroupListItems(previousRouteBoundMap, previousRouteBitmapMap);
+            } else {
+                mGroupMemberRoutesAdded = null;
+                mGroupMemberRoutesRemoved = null;
+            }
+        }
+    }
+
+    private void animateGroupListItems(final Map<MediaRouter.RouteInfo, Rect> previousRouteBoundMap,
+            final Map<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap) {
+        mVolumeGroupList.setEnabled(false);
+        mVolumeGroupList.requestLayout();
+        mIsGroupListAnimating = true;
+        ViewTreeObserver observer = mVolumeGroupList.getViewTreeObserver();
+        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                mVolumeGroupList.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+                animateGroupListItemsInternal(previousRouteBoundMap, previousRouteBitmapMap);
+            }
+        });
+    }
+
+    void animateGroupListItemsInternal(
+            Map<MediaRouter.RouteInfo, Rect> previousRouteBoundMap,
+            Map<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap) {
+        if (mGroupMemberRoutesAdded == null || mGroupMemberRoutesRemoved == null) {
+            return;
+        }
+        int groupSizeDelta = mGroupMemberRoutesAdded.size() - mGroupMemberRoutesRemoved.size();
+        boolean listenerRegistered = false;
+        Animation.AnimationListener listener = new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+                mVolumeGroupList.startAnimationAll();
+                mVolumeGroupList.postDelayed(mGroupListFadeInAnimation,
+                        mGroupListAnimationDurationMs);
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) { }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) { }
+        };
+
+        // Animate visible items from previous positions to current positions except routes added
+        // just before. Added routes will remain hidden until translate animation finishes.
+        int first = mVolumeGroupList.getFirstVisiblePosition();
+        for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+            View view = mVolumeGroupList.getChildAt(i);
+            int position = first + i;
+            MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+            Rect previousBounds = previousRouteBoundMap.get(route);
+            int currentTop = view.getTop();
+            int previousTop = previousBounds != null ? previousBounds.top
+                    : (currentTop + mVolumeGroupListItemHeight * groupSizeDelta);
+            AnimationSet animSet = new AnimationSet(true);
+            if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.contains(route)) {
+                previousTop = currentTop;
+                Animation alphaAnim = new AlphaAnimation(0.0f, 0.0f);
+                alphaAnim.setDuration(mGroupListFadeInDurationMs);
+                animSet.addAnimation(alphaAnim);
+            }
+            Animation translationAnim = new TranslateAnimation(0, 0, previousTop - currentTop, 0);
+            translationAnim.setDuration(mGroupListAnimationDurationMs);
+            animSet.addAnimation(translationAnim);
+            animSet.setFillAfter(true);
+            animSet.setFillEnabled(true);
+            animSet.setInterpolator(mInterpolator);
+            if (!listenerRegistered) {
+                listenerRegistered = true;
+                animSet.setAnimationListener(listener);
+            }
+            view.clearAnimation();
+            view.startAnimation(animSet);
+            previousRouteBoundMap.remove(route);
+            previousRouteBitmapMap.remove(route);
+        }
+
+        // If a member route doesn't exist any longer, it can be either removed or moved out of the
+        // ListView layout boundary. In this case, use the previously captured bitmaps for
+        // animation.
+        for (Map.Entry<MediaRouter.RouteInfo, BitmapDrawable> item
+                : previousRouteBitmapMap.entrySet()) {
+            final MediaRouter.RouteInfo route = item.getKey();
+            final BitmapDrawable bitmap = item.getValue();
+            final Rect bounds = previousRouteBoundMap.get(route);
+            OverlayObject object = null;
+            if (mGroupMemberRoutesRemoved.contains(route)) {
+                object = new OverlayObject(bitmap, bounds).setAlphaAnimation(1.0f, 0.0f)
+                        .setDuration(mGroupListFadeOutDurationMs)
+                        .setInterpolator(mInterpolator);
+            } else {
+                int deltaY = groupSizeDelta * mVolumeGroupListItemHeight;
+                object = new OverlayObject(bitmap, bounds).setTranslateYAnimation(deltaY)
+                        .setDuration(mGroupListAnimationDurationMs)
+                        .setInterpolator(mInterpolator)
+                        .setAnimationEndListener(new OverlayObject.OnAnimationEndListener() {
+                            @Override
+                            public void onAnimationEnd() {
+                                mGroupMemberRoutesAnimatingWithBitmap.remove(route);
+                                mVolumeGroupAdapter.notifyDataSetChanged();
+                            }
+                        });
+                mGroupMemberRoutesAnimatingWithBitmap.add(route);
+            }
+            mVolumeGroupList.addOverlayObject(object);
+        }
+    }
+
+    void startGroupListFadeInAnimation() {
+        clearGroupListAnimation(true);
+        mVolumeGroupList.requestLayout();
+        ViewTreeObserver observer = mVolumeGroupList.getViewTreeObserver();
+        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                mVolumeGroupList.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+                startGroupListFadeInAnimationInternal();
+            }
+        });
+    }
+
+    void startGroupListFadeInAnimationInternal() {
+        if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.size() != 0) {
+            fadeInAddedRoutes();
+        } else {
+            finishAnimation(true);
+        }
+    }
+
+    void finishAnimation(boolean animate) {
+        mGroupMemberRoutesAdded = null;
+        mGroupMemberRoutesRemoved = null;
+        mIsGroupListAnimating = false;
+        if (mIsGroupListAnimationPending) {
+            mIsGroupListAnimationPending = false;
+            updateLayoutHeight(animate);
+        }
+        mVolumeGroupList.setEnabled(true);
+    }
+
+    private void fadeInAddedRoutes() {
+        Animation.AnimationListener listener = new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) { }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                finishAnimation(true);
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) { }
+        };
+        boolean listenerRegistered = false;
+        int first = mVolumeGroupList.getFirstVisiblePosition();
+        for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+            View view = mVolumeGroupList.getChildAt(i);
+            int position = first + i;
+            MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+            if (mGroupMemberRoutesAdded.contains(route)) {
+                Animation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
+                alphaAnim.setDuration(mGroupListFadeInDurationMs);
+                alphaAnim.setFillEnabled(true);
+                alphaAnim.setFillAfter(true);
+                if (!listenerRegistered) {
+                    listenerRegistered = true;
+                    alphaAnim.setAnimationListener(listener);
+                }
+                view.clearAnimation();
+                view.startAnimation(alphaAnim);
+            }
+        }
+    }
+
+    void clearGroupListAnimation(boolean exceptAddedRoutes) {
+        int first = mVolumeGroupList.getFirstVisiblePosition();
+        for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+            View view = mVolumeGroupList.getChildAt(i);
+            int position = first + i;
+            MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+            if (exceptAddedRoutes && mGroupMemberRoutesAdded != null
+                    && mGroupMemberRoutesAdded.contains(route)) {
+                continue;
+            }
+            LinearLayout container = (LinearLayout) view.findViewById(R.id.volume_item_container);
+            container.setVisibility(View.VISIBLE);
+            AnimationSet animSet = new AnimationSet(true);
+            Animation alphaAnim = new AlphaAnimation(1.0f, 1.0f);
+            alphaAnim.setDuration(0);
+            animSet.addAnimation(alphaAnim);
+            Animation translationAnim = new TranslateAnimation(0, 0, 0, 0);
+            translationAnim.setDuration(0);
+            animSet.setFillAfter(true);
+            animSet.setFillEnabled(true);
+            view.clearAnimation();
+            view.startAnimation(animSet);
+        }
+        mVolumeGroupList.stopAnimationAll();
+        if (!exceptAddedRoutes) {
+            finishAnimation(false);
+        }
+    }
+
+    private void updatePlaybackControlLayout() {
+        if (canShowPlaybackControlLayout()) {
+            CharSequence title = mDescription == null ? null : mDescription.getTitle();
+            boolean hasTitle = !TextUtils.isEmpty(title);
+
+            CharSequence subtitle = mDescription == null ? null : mDescription.getSubtitle();
+            boolean hasSubtitle = !TextUtils.isEmpty(subtitle);
+
+            boolean showTitle = false;
+            boolean showSubtitle = false;
+            if (mRoute.getPresentationDisplayId()
+                    != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
+                // The user is currently casting screen.
+                mTitleView.setText(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(R.string.mr_controller_no_media_selected);
+                showTitle = true;
+            } else if (!hasTitle && !hasSubtitle) {
+                mTitleView.setText(R.string.mr_controller_no_info_available);
+                showTitle = true;
+            } else {
+                if (hasTitle) {
+                    mTitleView.setText(title);
+                    showTitle = true;
+                }
+                if (hasSubtitle) {
+                    mSubtitleView.setText(subtitle);
+                    showSubtitle = true;
+                }
+            }
+            mTitleView.setVisibility(showTitle ? View.VISIBLE : View.GONE);
+            mSubtitleView.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
+
+            if (mState != null) {
+                boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_BUFFERING
+                        || mState.getState() == PlaybackStateCompat.STATE_PLAYING;
+                Context playbackControlButtonContext = mPlaybackControlButton.getContext();
+                boolean visible = true;
+                int iconDrawableAttr = 0;
+                int iconDescResId = 0;
+                if (isPlaying && isPauseActionSupported()) {
+                    iconDrawableAttr = R.attr.mediaRoutePauseDrawable;
+                    iconDescResId = R.string.mr_controller_pause;
+                } else if (isPlaying && isStopActionSupported()) {
+                    iconDrawableAttr = R.attr.mediaRouteStopDrawable;
+                    iconDescResId = R.string.mr_controller_stop;
+                } else if (!isPlaying && isPlayActionSupported()) {
+                    iconDrawableAttr = R.attr.mediaRoutePlayDrawable;
+                    iconDescResId = R.string.mr_controller_play;
+                } else {
+                    visible = false;
+                }
+                mPlaybackControlButton.setVisibility(visible ? View.VISIBLE : View.GONE);
+                if (visible) {
+                    mPlaybackControlButton.setImageResource(
+                            MediaRouterThemeHelper.getThemeResource(
+                                    playbackControlButtonContext, iconDrawableAttr));
+                    mPlaybackControlButton.setContentDescription(
+                            playbackControlButtonContext.getResources()
+                                    .getText(iconDescResId));
+                }
+            }
+        }
+    }
+
+    private boolean isPlayActionSupported() {
+        return (mState.getActions() & (ACTION_PLAY | ACTION_PLAY_PAUSE)) != 0;
+    }
+
+    private boolean isPauseActionSupported() {
+        return (mState.getActions() & (ACTION_PAUSE | ACTION_PLAY_PAUSE)) != 0;
+    }
+
+    private boolean isStopActionSupported() {
+        return (mState.getActions() & ACTION_STOP) != 0;
+    }
+
+    boolean isVolumeControlAvailable(MediaRouter.RouteInfo route) {
+        return mVolumeControlEnabled && route.getVolumeHandling()
+                == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+    }
+
+    private static int getLayoutHeight(View view) {
+        return view.getLayoutParams().height;
+    }
+
+    static void setLayoutHeight(View view, int height) {
+        ViewGroup.LayoutParams lp = view.getLayoutParams();
+        lp.height = height;
+        view.setLayoutParams(lp);
+    }
+
+    private static boolean uriEquals(Uri uri1, Uri uri2) {
+        if (uri1 != null && uri1.equals(uri2)) {
+            return true;
+        } else if (uri1 == null && uri2 == null) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns desired art height to fit into controller dialog.
+     */
+    int getDesiredArtHeight(int originalWidth, int originalHeight) {
+        if (originalWidth >= originalHeight) {
+            // For landscape art, fit width to dialog width.
+            return (int) ((float) mDialogContentWidth * originalHeight / originalWidth + 0.5f);
+        }
+        // For portrait art, fit height to 16:9 ratio case's height.
+        return (int) ((float) mDialogContentWidth * 9 / 16 + 0.5f);
+    }
+
+    void updateArtIconIfNeeded() {
+        if (mCustomControlView != null || !isIconChanged()) {
+            return;
+        }
+        if (mFetchArtTask != null) {
+            mFetchArtTask.cancel(true);
+        }
+        mFetchArtTask = new FetchArtTask();
+        mFetchArtTask.execute();
+    }
+
+    /**
+     * Clear the bitmap loaded by FetchArtTask. Will be called after the loaded bitmaps are applied
+     * to artwork, or no longer valid.
+     */
+    void clearLoadedBitmap() {
+        mArtIconIsLoaded = false;
+        mArtIconLoadedBitmap = null;
+        mArtIconBackgroundColor = 0;
+    }
+
+    /**
+     * Returns whether a new art image is different from an original art image. Compares
+     * Bitmap objects first, and then compares URIs only if bitmap is unchanged with
+     * a null value.
+     */
+    private boolean isIconChanged() {
+        Bitmap newBitmap = mDescription == null ? null : mDescription.getIconBitmap();
+        Uri newUri = mDescription == null ? null : mDescription.getIconUri();
+        Bitmap oldBitmap = mFetchArtTask == null ? mArtIconBitmap : mFetchArtTask.getIconBitmap();
+        Uri oldUri = mFetchArtTask == null ? mArtIconUri : mFetchArtTask.getIconUri();
+        if (oldBitmap != newBitmap) {
+            return true;
+        } else if (oldBitmap == null && !uriEquals(oldUri, newUri)) {
+            return true;
+        }
+        return false;
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.Callback {
+        MediaRouterCallback() {
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
+            update(false);
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+            update(true);
+        }
+
+        @Override
+        public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+            SeekBar volumeSlider = mVolumeSliderMap.get(route);
+            int volume = route.getVolume();
+            if (DEBUG) {
+                Log.d(TAG, "onRouteVolumeChanged(), route.getVolume:" + volume);
+            }
+            if (volumeSlider != null && mRouteInVolumeSliderTouched != route) {
+                volumeSlider.setProgress(volume);
+            }
+        }
+    }
+
+    private final class MediaControllerCallback extends MediaControllerCompat.Callback {
+        MediaControllerCallback() {
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            if (mMediaController != null) {
+                mMediaController.unregisterCallback(mControllerCallback);
+                mMediaController = null;
+            }
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+            mState = state;
+            update(false);
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+            mDescription = metadata == null ? null : metadata.getDescription();
+            updateArtIconIfNeeded();
+            update(false);
+        }
+    }
+
+    private final class ClickListener implements View.OnClickListener {
+        ClickListener() {
+        }
+
+        @Override
+        public void onClick(View v) {
+            int id = v.getId();
+            if (id == BUTTON_STOP_RES_ID || id == BUTTON_DISCONNECT_RES_ID) {
+                if (mRoute.isSelected()) {
+                    mRouter.unselect(id == BUTTON_STOP_RES_ID ?
+                            MediaRouter.UNSELECT_REASON_STOPPED :
+                            MediaRouter.UNSELECT_REASON_DISCONNECTED);
+                }
+                dismiss();
+            } else if (id == R.id.mr_control_playback_ctrl) {
+                if (mMediaController != null && mState != null) {
+                    boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_PLAYING;
+                    int actionDescResId = 0;
+                    if (isPlaying && isPauseActionSupported()) {
+                        mMediaController.getTransportControls().pause();
+                        actionDescResId = R.string.mr_controller_pause;
+                    } else if (isPlaying && isStopActionSupported()) {
+                        mMediaController.getTransportControls().stop();
+                        actionDescResId = R.string.mr_controller_stop;
+                    } else if (!isPlaying && isPlayActionSupported()){
+                        mMediaController.getTransportControls().play();
+                        actionDescResId = R.string.mr_controller_play;
+                    }
+                    // Announce the action for accessibility.
+                    if (mAccessibilityManager != null && mAccessibilityManager.isEnabled()
+                            && actionDescResId != 0) {
+                        AccessibilityEvent event = AccessibilityEvent.obtain(
+                                AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
+                        event.setPackageName(mContext.getPackageName());
+                        event.setClassName(getClass().getName());
+                        event.getText().add(mContext.getString(actionDescResId));
+                        mAccessibilityManager.sendAccessibilityEvent(event);
+                    }
+                }
+            } else if (id == R.id.mr_close) {
+                dismiss();
+            }
+        }
+    }
+
+    private class VolumeChangeListener implements SeekBar.OnSeekBarChangeListener {
+        private final Runnable mStopTrackingTouch = new Runnable() {
+            @Override
+            public void run() {
+                if (mRouteInVolumeSliderTouched != null) {
+                    mRouteInVolumeSliderTouched = null;
+                    if (mHasPendingUpdate) {
+                        update(mPendingUpdateAnimationNeeded);
+                    }
+                }
+            }
+        };
+
+        VolumeChangeListener() {
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            if (mRouteInVolumeSliderTouched != null) {
+                mVolumeSlider.removeCallbacks(mStopTrackingTouch);
+            }
+            mRouteInVolumeSliderTouched = (MediaRouter.RouteInfo) seekBar.getTag();
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            // Defer resetting mVolumeSliderTouched to allow the media route provider
+            // a little time to settle into its new state and publish the final
+            // volume update.
+            mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
+        }
+
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            if (fromUser) {
+                MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) seekBar.getTag();
+                if (DEBUG) {
+                    Log.d(TAG, "onProgressChanged(): calling "
+                            + "MediaRouter.RouteInfo.requestSetVolume(" + progress + ")");
+                }
+                route.requestSetVolume(progress);
+            }
+        }
+    }
+
+    private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
+        final float mDisabledAlpha;
+
+        public VolumeGroupAdapter(Context context, List<MediaRouter.RouteInfo> objects) {
+            super(context, 0, objects);
+            mDisabledAlpha = MediaRouterThemeHelper.getDisabledAlpha(context);
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return false;
+        }
+
+        @Override
+        public View getView(final int position, View convertView, ViewGroup parent) {
+            View v = convertView;
+            if (v == null) {
+                v = LayoutInflater.from(parent.getContext()).inflate(
+                        R.layout.mr_controller_volume_item, parent, false);
+            } else {
+                updateVolumeGroupItemHeight(v);
+            }
+
+            MediaRouter.RouteInfo route = getItem(position);
+            if (route != null) {
+                boolean isEnabled = route.isEnabled();
+
+                TextView routeName = (TextView) v.findViewById(R.id.mr_name);
+                routeName.setEnabled(isEnabled);
+                routeName.setText(route.getName());
+
+                MediaRouteVolumeSlider volumeSlider =
+                        (MediaRouteVolumeSlider) v.findViewById(R.id.mr_volume_slider);
+                MediaRouterThemeHelper.setVolumeSliderColor(
+                        parent.getContext(), volumeSlider, mVolumeGroupList);
+                volumeSlider.setTag(route);
+                mVolumeSliderMap.put(route, volumeSlider);
+                volumeSlider.setHideThumb(!isEnabled);
+                volumeSlider.setEnabled(isEnabled);
+                if (isEnabled) {
+                    if (isVolumeControlAvailable(route)) {
+                        volumeSlider.setMax(route.getVolumeMax());
+                        volumeSlider.setProgress(route.getVolume());
+                        volumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
+                    } else {
+                        volumeSlider.setMax(100);
+                        volumeSlider.setProgress(100);
+                        volumeSlider.setEnabled(false);
+                    }
+                }
+
+                ImageView volumeItemIcon =
+                        (ImageView) v.findViewById(R.id.mr_volume_item_icon);
+                volumeItemIcon.setAlpha(isEnabled ? 0xFF : (int) (0xFF * mDisabledAlpha));
+
+                // If overlay bitmap exists, real view should remain hidden until
+                // the animation ends.
+                LinearLayout container = (LinearLayout) v.findViewById(R.id.volume_item_container);
+                container.setVisibility(mGroupMemberRoutesAnimatingWithBitmap.contains(route)
+                        ? View.INVISIBLE : View.VISIBLE);
+
+                // Routes which are being added will be invisible until animation ends.
+                if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.contains(route)) {
+                    Animation alphaAnim = new AlphaAnimation(0.0f, 0.0f);
+                    alphaAnim.setDuration(0);
+                    alphaAnim.setFillEnabled(true);
+                    alphaAnim.setFillAfter(true);
+                    v.clearAnimation();
+                    v.startAnimation(alphaAnim);
+                }
+            }
+            return v;
+        }
+    }
+
+    private class FetchArtTask extends AsyncTask<Void, Void, Bitmap> {
+        // Show animation only when fetching takes a long time.
+        private static final long SHOW_ANIM_TIME_THRESHOLD_MILLIS = 120L;
+
+        private final Bitmap mIconBitmap;
+        private final Uri mIconUri;
+        private int mBackgroundColor;
+        private long mStartTimeMillis;
+
+        FetchArtTask() {
+            Bitmap bitmap = mDescription == null ? null : mDescription.getIconBitmap();
+            if (isBitmapRecycled(bitmap)) {
+                Log.w(TAG, "Can't fetch the given art bitmap because it's already recycled.");
+                bitmap = null;
+            }
+            mIconBitmap = bitmap;
+            mIconUri = mDescription == null ? null : mDescription.getIconUri();
+        }
+
+        public Bitmap getIconBitmap() {
+            return mIconBitmap;
+        }
+
+        public Uri getIconUri() {
+            return mIconUri;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            mStartTimeMillis = SystemClock.uptimeMillis();
+            clearLoadedBitmap();
+        }
+
+        @Override
+        protected Bitmap doInBackground(Void... arg) {
+            Bitmap art = null;
+            if (mIconBitmap != null) {
+                art = mIconBitmap;
+            } else if (mIconUri != null) {
+                InputStream stream = null;
+                try {
+                    if ((stream = openInputStreamByScheme(mIconUri)) == null) {
+                        Log.w(TAG, "Unable to open: " + mIconUri);
+                        return null;
+                    }
+                    // Query art size.
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inJustDecodeBounds = true;
+                    BitmapFactory.decodeStream(stream, null, options);
+                    if (options.outWidth == 0 || options.outHeight == 0) {
+                        return null;
+                    }
+                    // Rewind the stream in order to restart art decoding.
+                    try {
+                        stream.reset();
+                    } catch (IOException e) {
+                        // Failed to rewind the stream, try to reopen it.
+                        stream.close();
+                        if ((stream = openInputStreamByScheme(mIconUri)) == null) {
+                            Log.w(TAG, "Unable to open: " + mIconUri);
+                            return null;
+                        }
+                    }
+                    // Calculate required size to decode the art and possibly resize it.
+                    options.inJustDecodeBounds = false;
+                    int reqHeight = getDesiredArtHeight(options.outWidth, options.outHeight);
+                    int ratio = options.outHeight / reqHeight;
+                    options.inSampleSize = Math.max(1, Integer.highestOneBit(ratio));
+                    if (isCancelled()) {
+                        return null;
+                    }
+                    art = BitmapFactory.decodeStream(stream, null, options);
+                } catch (IOException e){
+                    Log.w(TAG, "Unable to open: " + mIconUri, e);
+                } finally {
+                    if (stream != null) {
+                        try {
+                            stream.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            }
+            if (isBitmapRecycled(art)) {
+                Log.w(TAG, "Can't use recycled bitmap: " + art);
+                return null;
+            }
+            if (art != null && art.getWidth() < art.getHeight()) {
+                // Portrait art requires dominant color as background color.
+                Palette palette = new Palette.Builder(art).maximumColorCount(1).generate();
+                mBackgroundColor = palette.getSwatches().isEmpty()
+                        ? 0 : palette.getSwatches().get(0).getRgb();
+            }
+            return art;
+        }
+
+        @Override
+        protected void onPostExecute(Bitmap art) {
+            mFetchArtTask = null;
+            if (!ObjectsCompat.equals(mArtIconBitmap, mIconBitmap)
+                    || !ObjectsCompat.equals(mArtIconUri, mIconUri)) {
+                mArtIconBitmap = mIconBitmap;
+                mArtIconLoadedBitmap = art;
+                mArtIconUri = mIconUri;
+                mArtIconBackgroundColor = mBackgroundColor;
+                mArtIconIsLoaded = true;
+                long elapsedTimeMillis = SystemClock.uptimeMillis() - mStartTimeMillis;
+                // Loaded bitmap will be applied on the next update
+                update(elapsedTimeMillis > SHOW_ANIM_TIME_THRESHOLD_MILLIS);
+            }
+        }
+
+        private InputStream openInputStreamByScheme(Uri uri) throws IOException {
+            String scheme = uri.getScheme().toLowerCase();
+            InputStream stream = null;
+            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
+                    || ContentResolver.SCHEME_CONTENT.equals(scheme)
+                    || ContentResolver.SCHEME_FILE.equals(scheme)) {
+                stream = mContext.getContentResolver().openInputStream(uri);
+            } else {
+                URL url = new URL(uri.toString());
+                URLConnection conn = url.openConnection();
+                conn.setConnectTimeout(CONNECTION_TIMEOUT_MILLIS);
+                conn.setReadTimeout(CONNECTION_TIMEOUT_MILLIS);
+                stream = conn.getInputStream();
+            }
+            return (stream == null) ? null : new BufferedInputStream(stream);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java
new file mode 100644
index 0000000..9442df7
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java
@@ -0,0 +1,76 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+/**
+ * Media route controller dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteControllerDialog}.  The application may subclass
+ * this dialog fragment to customize the media route controller dialog.
+ * </p>
+ */
+public class MediaRouteControllerDialogFragment extends DialogFragment {
+    private MediaRouteControllerDialog mDialog;
+    /**
+     * Creates a media route controller dialog fragment.
+     * <p>
+     * All subclasses of this class must also possess a default constructor.
+     * </p>
+     */
+    public MediaRouteControllerDialogFragment() {
+        setCancelable(true);
+    }
+
+    /**
+     * Called when the controller dialog is being created.
+     * <p>
+     * Subclasses may override this method to customize the dialog.
+     * </p>
+     */
+    public MediaRouteControllerDialog onCreateControllerDialog(
+            Context context, Bundle savedInstanceState) {
+        return new MediaRouteControllerDialog(context);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        mDialog = onCreateControllerDialog(getContext(), savedInstanceState);
+        return mDialog;
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mDialog != null) {
+            mDialog.clearGroupListAnimation(false);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mDialog != null) {
+            mDialog.updateLayout();
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogFactory.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogFactory.java
new file mode 100644
index 0000000..a9eaf39
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogFactory.java
@@ -0,0 +1,74 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.support.annotation.NonNull;
+
+/**
+ * The media route dialog factory is responsible for creating the media route
+ * chooser and controller dialogs as needed.
+ * <p>
+ * The application can customize the dialogs by providing a subclass of the
+ * dialog factory to the {@link MediaRouteButton} using the
+ * {@link MediaRouteButton#setDialogFactory setDialogFactory} method.
+ * </p>
+ */
+public class MediaRouteDialogFactory {
+    private static final MediaRouteDialogFactory sDefault = new MediaRouteDialogFactory();
+
+    /**
+     * Creates a default media route dialog factory.
+     */
+    public MediaRouteDialogFactory() {
+    }
+
+    /**
+     * Gets the default factory instance.
+     *
+     * @return The default media route dialog factory, never null.
+     */
+    @NonNull
+    public static MediaRouteDialogFactory getDefault() {
+        return sDefault;
+    }
+
+    /**
+     * Called when the chooser dialog is being opened and it is time to create the fragment.
+     * <p>
+     * Subclasses may override this method to create a customized fragment.
+     * </p>
+     *
+     * @return The media route chooser dialog fragment, must not be null.
+     */
+    @NonNull
+    public MediaRouteChooserDialogFragment onCreateChooserDialogFragment() {
+        return new MediaRouteChooserDialogFragment();
+    }
+
+    /**
+     * Called when the controller dialog is being opened and it is time to create the fragment.
+     * <p>
+     * Subclasses may override this method to create a customized fragment.
+     * </p>
+     *
+     * @return The media route controller dialog fragment, must not be null.
+     */
+    @NonNull
+    public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
+        return new MediaRouteControllerDialogFragment();
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
new file mode 100644
index 0000000..6f75b46
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
@@ -0,0 +1,152 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.media.update.R;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+final class MediaRouteDialogHelper {
+    /**
+     * 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(Context context) {
+        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+
+        TypedValue value = new TypedValue();
+        context.getResources().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);
+        } else if (value.type == TypedValue.TYPE_FRACTION) {
+            return (int) value.getFraction(metrics.widthPixels, metrics.widthPixels);
+        }
+        return ViewGroup.LayoutParams.WRAP_CONTENT;
+    }
+
+    /**
+     * Compares two lists regardless of order.
+     *
+     * @param list1 A list
+     * @param list2 A list to be compared with {@code list1}
+     * @return True if two lists have exactly same items regardless of order, false otherwise.
+     */
+    public static <E> boolean listUnorderedEquals(List<E> list1, List<E> list2) {
+        HashSet<E> set1 = new HashSet<>(list1);
+        HashSet<E> set2 = new HashSet<>(list2);
+        return set1.equals(set2);
+    }
+
+    /**
+     * Compares two lists and returns a set of items which exist
+     * after-list but before-list, which means newly added items.
+     *
+     * @param before A list
+     * @param after A list to be compared with {@code before}
+     * @return A set of items which contains newly added items while
+     * comparing {@code after} to {@code before}.
+     */
+    public static <E> Set<E> getItemsAdded(List<E> before, List<E> after) {
+        HashSet<E> set = new HashSet<>(after);
+        set.removeAll(before);
+        return set;
+    }
+
+    /**
+     * Compares two lists and returns a set of items which exist
+     * before-list but after-list, which means removed items.
+     *
+     * @param before A list
+     * @param after A list to be compared with {@code before}
+     * @return A set of items which contains removed items while
+     * comparing {@code after} to {@code before}.
+     */
+    public static <E> Set<E> getItemsRemoved(List<E> before, List<E> after) {
+        HashSet<E> set = new HashSet<>(before);
+        set.removeAll(after);
+        return set;
+    }
+
+    /**
+     * Generates an item-Rect map which indicates where member
+     * items are located in the given ListView.
+     *
+     * @param listView A list view
+     * @param adapter An array adapter which contains an array of items.
+     * @return A map of items and bounds of their views located in the given list view.
+     */
+    public static <E> HashMap<E, Rect> getItemBoundMap(ListView listView,
+            ArrayAdapter<E> adapter) {
+        HashMap<E, Rect> itemBoundMap = new HashMap<>();
+        int firstVisiblePosition = listView.getFirstVisiblePosition();
+        for (int i = 0; i < listView.getChildCount(); ++i) {
+            int position = firstVisiblePosition + i;
+            E item = adapter.getItem(position);
+            View view = listView.getChildAt(i);
+            itemBoundMap.put(item,
+                    new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+        }
+        return itemBoundMap;
+    }
+
+    /**
+     * Generates an item-BitmapDrawable map which stores snapshots
+     * of member items in the given ListView.
+     *
+     * @param context A context
+     * @param listView A list view
+     * @param adapter An array adapter which contains an array of items.
+     * @return A map of items and snapshots of their views in the given list view.
+     */
+    public static <E> HashMap<E, BitmapDrawable> getItemBitmapMap(Context context,
+            ListView listView, ArrayAdapter<E> adapter) {
+        HashMap<E, BitmapDrawable> itemBitmapMap = new HashMap<>();
+        int firstVisiblePosition = listView.getFirstVisiblePosition();
+        for (int i = 0; i < listView.getChildCount(); ++i) {
+            int position = firstVisiblePosition + i;
+            E item = adapter.getItem(position);
+            View view = listView.getChildAt(i);
+            itemBitmapMap.put(item, getViewBitmap(context, view));
+        }
+        return itemBitmapMap;
+    }
+
+    private static BitmapDrawable getViewBitmap(Context context, View view) {
+        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        view.draw(canvas);
+        return new BitmapDrawable(context.getResources(), bitmap);
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDiscoveryFragment.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDiscoveryFragment.java
new file mode 100644
index 0000000..02ee118
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDiscoveryFragment.java
@@ -0,0 +1,164 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+
+import com.android.support.mediarouter.media.MediaRouter;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+
+/**
+ * Media route discovery fragment.
+ * <p>
+ * This fragment takes care of registering a callback for media route discovery
+ * during the {@link Fragment#onStart onStart()} phase
+ * and removing it during the {@link Fragment#onStop onStop()} phase.
+ * </p><p>
+ * The application must supply a route selector to specify the kinds of routes
+ * to discover.  The application may also override {@link #onCreateCallback} to
+ * provide the {@link MediaRouter} callback to register.
+ * </p><p>
+ * Note that the discovery callback makes the application be connected with all the
+ * {@link android.support.v7.media.MediaRouteProviderService media route provider services}
+ * while it is registered.
+ * </p>
+ */
+public class MediaRouteDiscoveryFragment extends Fragment {
+    private final String ARGUMENT_SELECTOR = "selector";
+
+    private MediaRouter mRouter;
+    private MediaRouteSelector mSelector;
+    private MediaRouter.Callback mCallback;
+
+    public MediaRouteDiscoveryFragment() {
+    }
+
+    /**
+     * Gets the media router instance.
+     */
+    public MediaRouter getMediaRouter() {
+        ensureRouter();
+        return mRouter;
+    }
+
+    private void ensureRouter() {
+        if (mRouter == null) {
+            mRouter = MediaRouter.getInstance(getContext());
+        }
+    }
+
+    /**
+     * Gets the media route selector for filtering the routes to be discovered.
+     *
+     * @return The selector, never null.
+     */
+    public MediaRouteSelector getRouteSelector() {
+        ensureRouteSelector();
+        return mSelector;
+    }
+
+    /**
+     * Sets the media route selector for filtering the routes to be discovered.
+     * This method must be called before the fragment is added.
+     *
+     * @param selector The selector to set.
+     */
+    public void setRouteSelector(MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        ensureRouteSelector();
+        if (!mSelector.equals(selector)) {
+            mSelector = selector;
+
+            Bundle args = getArguments();
+            if (args == null) {
+                args = new Bundle();
+            }
+            args.putBundle(ARGUMENT_SELECTOR, selector.asBundle());
+            setArguments(args);
+
+            if (mCallback != null) {
+                mRouter.removeCallback(mCallback);
+                mRouter.addCallback(mSelector, mCallback, onPrepareCallbackFlags());
+            }
+        }
+    }
+
+    private void ensureRouteSelector() {
+        if (mSelector == null) {
+            Bundle args = getArguments();
+            if (args != null) {
+                mSelector = MediaRouteSelector.fromBundle(args.getBundle(ARGUMENT_SELECTOR));
+            }
+            if (mSelector == null) {
+                mSelector = MediaRouteSelector.EMPTY;
+            }
+        }
+    }
+
+    /**
+     * Called to create the {@link android.support.v7.media.MediaRouter.Callback callback}
+     * that will be registered.
+     * <p>
+     * The default callback does nothing.  The application may override this method to
+     * supply its own callback.
+     * </p>
+     *
+     * @return The new callback, or null if no callback should be registered.
+     */
+    public MediaRouter.Callback onCreateCallback() {
+        return new MediaRouter.Callback() { };
+    }
+
+    /**
+     * Called to prepare the callback flags that will be used when the
+     * {@link android.support.v7.media.MediaRouter.Callback callback} is registered.
+     * <p>
+     * The default implementation returns {@link MediaRouter#CALLBACK_FLAG_REQUEST_DISCOVERY}.
+     * </p>
+     *
+     * @return The desired callback flags.
+     */
+    public int onPrepareCallbackFlags() {
+        return MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        ensureRouteSelector();
+        ensureRouter();
+        mCallback = onCreateCallback();
+        if (mCallback != null) {
+            mRouter.addCallback(mSelector, mCallback, onPrepareCallbackFlags());
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (mCallback != null) {
+            mRouter.removeCallback(mCallback);
+            mCallback = null;
+        }
+
+        super.onStop();
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
new file mode 100644
index 0000000..392b39d
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
@@ -0,0 +1,93 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.AnimationDrawable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.media.update.R;
+
+/**
+ * Chevron/Caret button to expand/collapse group volume list with animation.
+ */
+class MediaRouteExpandCollapseButton extends ImageButton {
+    final AnimationDrawable mExpandAnimationDrawable;
+    final AnimationDrawable mCollapseAnimationDrawable;
+    final String mExpandGroupDescription;
+    final String mCollapseGroupDescription;
+    boolean mIsGroupExpanded;
+    OnClickListener mListener;
+
+    public MediaRouteExpandCollapseButton(Context context) {
+        this(context, null);
+    }
+
+    public MediaRouteExpandCollapseButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public MediaRouteExpandCollapseButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mExpandAnimationDrawable = (AnimationDrawable) ContextCompat.getDrawable(
+                context, R.drawable.mr_group_expand);
+        mCollapseAnimationDrawable = (AnimationDrawable) ContextCompat.getDrawable(
+                context, R.drawable.mr_group_collapse);
+
+        ColorFilter filter = new PorterDuffColorFilter(
+                MediaRouterThemeHelper.getControllerColor(context, defStyleAttr),
+                PorterDuff.Mode.SRC_IN);
+        mExpandAnimationDrawable.setColorFilter(filter);
+        mCollapseAnimationDrawable.setColorFilter(filter);
+
+        mExpandGroupDescription = context.getString(R.string.mr_controller_expand_group);
+        mCollapseGroupDescription = context.getString(R.string.mr_controller_collapse_group);
+
+        setImageDrawable(mExpandAnimationDrawable.getFrame(0));
+        setContentDescription(mExpandGroupDescription);
+
+        super.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mIsGroupExpanded = !mIsGroupExpanded;
+                if (mIsGroupExpanded) {
+                    setImageDrawable(mExpandAnimationDrawable);
+                    mExpandAnimationDrawable.start();
+                    setContentDescription(mCollapseGroupDescription);
+                } else {
+                    setImageDrawable(mCollapseAnimationDrawable);
+                    mCollapseAnimationDrawable.start();
+                    setContentDescription(mExpandGroupDescription);
+                }
+                if (mListener != null) {
+                    mListener.onClick(view);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener listener) {
+        mListener = listener;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteVolumeSlider.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteVolumeSlider.java
new file mode 100644
index 0000000..7a34fb5
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteVolumeSlider.java
@@ -0,0 +1,99 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.AppCompatSeekBar;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * Volume slider with showing, hiding, and applying alpha supports to the thumb.
+ */
+class MediaRouteVolumeSlider extends AppCompatSeekBar {
+    private static final String TAG = "MediaRouteVolumeSlider";
+
+    private final float mDisabledAlpha;
+
+    private boolean mHideThumb;
+    private Drawable mThumb;
+    private int mColor;
+
+    public MediaRouteVolumeSlider(Context context) {
+        this(context, null);
+    }
+
+    public MediaRouteVolumeSlider(Context context, AttributeSet attrs) {
+        this(context, attrs, android.support.v7.appcompat.R.attr.seekBarStyle);
+    }
+
+    public MediaRouteVolumeSlider(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mDisabledAlpha = MediaRouterThemeHelper.getDisabledAlpha(context);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        int alpha = isEnabled() ? 0xFF : (int) (0xFF * mDisabledAlpha);
+
+        // The thumb drawable is a collection of drawables and its current drawables are changed per
+        // state. Apply the color filter and alpha on every state change.
+        mThumb.setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
+        mThumb.setAlpha(alpha);
+
+        getProgressDrawable().setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
+        getProgressDrawable().setAlpha(alpha);
+    }
+
+    @Override
+    public void setThumb(Drawable thumb) {
+        mThumb = thumb;
+        super.setThumb(mHideThumb ? null : mThumb);
+    }
+
+    /**
+     * Sets whether to show or hide thumb.
+     */
+    public void setHideThumb(boolean hideThumb) {
+        if (mHideThumb == hideThumb) {
+            return;
+        }
+        mHideThumb = hideThumb;
+        super.setThumb(mHideThumb ? null : mThumb);
+    }
+
+    /**
+     * Sets the volume slider color. The change takes effect next time drawable state is changed.
+     * <p>
+     * The color cannot be translucent, otherwise the underlying progress bar will be seen through
+     * the thumb.
+     * </p>
+     */
+    public void setColor(int color) {
+        if (mColor == color) {
+            return;
+        }
+        if (Color.alpha(color) != 0xFF) {
+            Log.e(TAG, "Volume slider color cannot be translucent: #" + Integer.toHexString(color));
+        }
+        mColor = color;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
new file mode 100644
index 0000000..b4b49df
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
@@ -0,0 +1,216 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.support.annotation.IntDef;
+import android.support.v4.graphics.ColorUtils;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+
+import com.android.media.update.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+final class MediaRouterThemeHelper {
+    private static final float MIN_CONTRAST = 3.0f;
+
+    @IntDef({COLOR_DARK_ON_LIGHT_BACKGROUND, COLOR_WHITE_ON_DARK_BACKGROUND})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface ControllerColorType {}
+
+    static final int COLOR_DARK_ON_LIGHT_BACKGROUND = 0xDE000000; /* Opacity of 87% */
+    static final int COLOR_WHITE_ON_DARK_BACKGROUND = Color.WHITE;
+
+    private MediaRouterThemeHelper() {
+    }
+
+    static Context createThemedButtonContext(Context context) {
+        // Apply base Media Router theme.
+        context = new ContextThemeWrapper(context, getRouterThemeId(context));
+
+        // Apply custom Media Router theme.
+        int style = getThemeResource(context, R.attr.mediaRouteTheme);
+        if (style != 0) {
+            context = new ContextThemeWrapper(context, style);
+        }
+
+        return context;
+    }
+
+    /*
+     * The following two methods are to be used in conjunction. They should be used to prepare
+     * the context and theme for a super class constructor (the latter method relies on the
+     * former method to properly prepare the context):
+     *   super(context = createThemedDialogContext(context, theme),
+     *           createThemedDialogStyle(context));
+     *
+     * It will apply theme in the following order (style lookups will be done in reverse):
+     *   1) Current theme
+     *   2) Supplied theme
+     *   3) Base Media Router theme
+     *   4) Custom Media Router theme, if provided
+     */
+    static Context createThemedDialogContext(Context context, int theme, boolean alertDialog) {
+        // 1) Current theme is already applied to the context
+
+        // 2) If no theme is supplied, look it up from the context (dialogTheme/alertDialogTheme)
+        if (theme == 0) {
+            theme = getThemeResource(context, !alertDialog
+                    ? android.support.v7.appcompat.R.attr.dialogTheme
+                    : android.support.v7.appcompat.R.attr.alertDialogTheme);
+        }
+        //    Apply it
+        context = new ContextThemeWrapper(context, theme);
+
+        // 3) If a custom Media Router theme is provided then apply the base theme
+        if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) {
+            context = new ContextThemeWrapper(context, getRouterThemeId(context));
+        }
+
+        return context;
+    }
+    // This method should be used in conjunction with the previous method.
+    static int createThemedDialogStyle(Context context) {
+        // 4) Apply the custom Media Router theme
+        int theme = getThemeResource(context, R.attr.mediaRouteTheme);
+        if (theme == 0) {
+            // 3) No custom MediaRouther theme was provided so apply the base theme instead
+            theme = getRouterThemeId(context);
+        }
+
+        return theme;
+    }
+    // END. Previous two methods should be used in conjunction.
+
+    static int getThemeResource(Context context, int attr) {
+        TypedValue value = new TypedValue();
+        return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
+    }
+
+    static float getDisabledAlpha(Context context) {
+        TypedValue value = new TypedValue();
+        return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
+                ? value.getFloat() : 0.5f;
+    }
+
+    static @ControllerColorType int getControllerColor(Context context, int style) {
+        int primaryColor = getThemeColor(context, style,
+                android.support.v7.appcompat.R.attr.colorPrimary);
+        if (primaryColor == 0) {
+            primaryColor = getThemeColor(context, style, android.R.attr.colorPrimary);
+            if (primaryColor == 0) {
+                primaryColor = 0xFF000000;
+            }
+        }
+        if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
+                >= MIN_CONTRAST) {
+            return COLOR_WHITE_ON_DARK_BACKGROUND;
+        }
+        return COLOR_DARK_ON_LIGHT_BACKGROUND;
+    }
+
+    static int getButtonTextColor(Context context) {
+        int primaryColor = getThemeColor(context, 0,
+                android.support.v7.appcompat.R.attr.colorPrimary);
+        int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
+
+        if (ColorUtils.calculateContrast(primaryColor, backgroundColor) < MIN_CONTRAST) {
+            // Default to colorAccent if the contrast ratio is low.
+            return getThemeColor(context, 0, android.support.v7.appcompat.R.attr.colorAccent);
+        }
+        return primaryColor;
+    }
+
+    static void setMediaControlsBackgroundColor(
+            Context context, View mainControls, View groupControls, boolean hasGroup) {
+        int primaryColor = getThemeColor(context, 0,
+                android.support.v7.appcompat.R.attr.colorPrimary);
+        int primaryDarkColor = getThemeColor(context, 0,
+                android.support.v7.appcompat.R.attr.colorPrimaryDark);
+        if (hasGroup && getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+            // Instead of showing dark controls in a possibly dark (i.e. the primary dark), model
+            // the white dialog and use the primary color for the group controls.
+            primaryDarkColor = primaryColor;
+            primaryColor = Color.WHITE;
+        }
+        mainControls.setBackgroundColor(primaryColor);
+        groupControls.setBackgroundColor(primaryDarkColor);
+        // Also store the background colors to the view tags. They are used in
+        // setVolumeSliderColor() below.
+        mainControls.setTag(primaryColor);
+        groupControls.setTag(primaryDarkColor);
+    }
+
+    static void setVolumeSliderColor(
+            Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
+        int controllerColor = getControllerColor(context, 0);
+        if (Color.alpha(controllerColor) != 0xFF) {
+            // Composite with the background in order not to show the underlying progress bar
+            // through the thumb.
+            int backgroundColor = (int) backgroundView.getTag();
+            controllerColor = ColorUtils.compositeColors(controllerColor, backgroundColor);
+        }
+        volumeSlider.setColor(controllerColor);
+    }
+
+    private static boolean isLightTheme(Context context) {
+        TypedValue value = new TypedValue();
+        return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme,
+                value, true) && value.data != 0;
+    }
+
+    private static int getThemeColor(Context context, int style, int attr) {
+        if (style != 0) {
+            int[] attrs = { attr };
+            TypedArray ta = context.obtainStyledAttributes(style, attrs);
+            int color = ta.getColor(0, 0);
+            ta.recycle();
+            if (color != 0) {
+                return color;
+            }
+        }
+        TypedValue value = new TypedValue();
+        context.getTheme().resolveAttribute(attr, value, true);
+        if (value.resourceId != 0) {
+            return context.getResources().getColor(value.resourceId);
+        }
+        return value.data;
+    }
+
+    private static int getRouterThemeId(Context context) {
+        int themeId;
+        if (isLightTheme(context)) {
+            if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+                themeId = R.style.Theme_MediaRouter_Light;
+            } else {
+                themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
+            }
+        } else {
+            if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+                themeId = R.style.Theme_MediaRouter_LightControlPanel;
+            } else {
+                themeId = R.style.Theme_MediaRouter;
+            }
+        }
+        return themeId;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/OverlayListView.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/OverlayListView.java
new file mode 100644
index 0000000..59019ff
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/OverlayListView.java
@@ -0,0 +1,265 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.view.animation.Interpolator;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A ListView which has an additional overlay layer. {@link BitmapDrawable}
+ * can be added to the layer and can be animated.
+ */
+final class OverlayListView extends ListView {
+    private final List<OverlayObject> mOverlayObjects = new ArrayList<>();
+
+    public OverlayListView(Context context) {
+        super(context);
+    }
+
+    public OverlayListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public OverlayListView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    /**
+     * Adds an object to the overlay layer.
+     *
+     * @param object An object to be added.
+     */
+    public void addOverlayObject(OverlayObject object) {
+        mOverlayObjects.add(object);
+    }
+
+    /**
+     * Starts all animations of objects in the overlay layer.
+     */
+    public void startAnimationAll() {
+        for (OverlayObject object : mOverlayObjects) {
+            if (!object.isAnimationStarted()) {
+                object.startAnimation(getDrawingTime());
+            }
+        }
+    }
+
+    /**
+     * Stops all animations of objects in the overlay layer.
+     */
+    public void stopAnimationAll() {
+        for (OverlayObject object : mOverlayObjects) {
+            object.stopAnimation();
+        }
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mOverlayObjects.size() > 0) {
+            Iterator<OverlayObject> it = mOverlayObjects.iterator();
+            while (it.hasNext()) {
+                OverlayObject object = it.next();
+                BitmapDrawable bitmap = object.getBitmapDrawable();
+                if (bitmap != null) {
+                    bitmap.draw(canvas);
+                }
+                if (!object.update(getDrawingTime())) {
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * A class that represents an object to be shown in the overlay layer.
+     */
+    public static class OverlayObject {
+        private BitmapDrawable mBitmap;
+        private float mCurrentAlpha = 1.0f;
+        private Rect mCurrentBounds;
+        private Interpolator mInterpolator;
+        private long mDuration;
+        private Rect mStartRect;
+        private int mDeltaY;
+        private float mStartAlpha = 1.0f;
+        private float mEndAlpha = 1.0f;
+        private long mStartTime;
+        private boolean mIsAnimationStarted;
+        private boolean mIsAnimationEnded;
+        private OnAnimationEndListener mListener;
+
+        public OverlayObject(BitmapDrawable bitmap, Rect startRect) {
+            mBitmap = bitmap;
+            mStartRect = startRect;
+            mCurrentBounds = new Rect(startRect);
+            if (mBitmap != null && mCurrentBounds != null) {
+                mBitmap.setAlpha((int) (mCurrentAlpha * 255));
+                mBitmap.setBounds(mCurrentBounds);
+            }
+        }
+
+        /**
+         * Returns the bitmap that this object represents.
+         *
+         * @return BitmapDrawable that this object has.
+         */
+        public BitmapDrawable getBitmapDrawable() {
+            return mBitmap;
+        }
+
+        /**
+         * Returns the started status of the animation.
+         *
+         * @return True if the animation has started, false otherwise.
+         */
+        public boolean isAnimationStarted() {
+            return mIsAnimationStarted;
+        }
+
+        /**
+         * Sets animation for varying alpha.
+         *
+         * @param startAlpha Starting alpha value for the animation, where 1.0 means
+         * fully opaque and 0.0 means fully transparent.
+         * @param endAlpha Ending alpha value for the animation.
+         * @return This OverlayObject to allow for chaining of calls.
+         */
+        public OverlayObject setAlphaAnimation(float startAlpha, float endAlpha) {
+            mStartAlpha = startAlpha;
+            mEndAlpha = endAlpha;
+            return this;
+        }
+
+        /**
+         * Sets animation for moving objects vertically.
+         *
+         * @param deltaY Distance to move in pixels.
+         * @return This OverlayObject to allow for chaining of calls.
+         */
+        public OverlayObject setTranslateYAnimation(int deltaY) {
+            mDeltaY = deltaY;
+            return this;
+        }
+
+        /**
+         * Sets how long the animation will last.
+         *
+         * @param duration Duration in milliseconds
+         * @return This OverlayObject to allow for chaining of calls.
+         */
+        public OverlayObject setDuration(long duration) {
+            mDuration = duration;
+            return this;
+        }
+
+        /**
+         * Sets the acceleration curve for this animation.
+         *
+         * @param interpolator The interpolator which defines the acceleration curve
+         * @return This OverlayObject to allow for chaining of calls.
+         */
+        public OverlayObject setInterpolator(Interpolator interpolator) {
+            mInterpolator = interpolator;
+            return this;
+        }
+
+        /**
+         * Binds an animation end listener to the animation.
+         *
+         * @param listener the animation end listener to be notified.
+         * @return This OverlayObject to allow for chaining of calls.
+         */
+        public OverlayObject setAnimationEndListener(OnAnimationEndListener listener) {
+            mListener = listener;
+            return this;
+        }
+
+        /**
+         * Starts the animation and sets the start time.
+         *
+         * @param startTime Start time to be set in Millis
+         */
+        public void startAnimation(long startTime) {
+            mStartTime = startTime;
+            mIsAnimationStarted = true;
+        }
+
+        /**
+         * Stops the animation.
+         */
+        public void stopAnimation() {
+            mIsAnimationStarted = true;
+            mIsAnimationEnded = true;
+            if (mListener != null) {
+                mListener.onAnimationEnd();
+            }
+        }
+
+        /**
+         * Calculates and updates current bounds and alpha value.
+         *
+         * @param currentTime Current time.in millis
+         */
+        public boolean update(long currentTime) {
+            if (mIsAnimationEnded) {
+                return false;
+            }
+            float normalizedTime = (currentTime - mStartTime) / (float) mDuration;
+            normalizedTime = Math.max(0.0f, Math.min(1.0f, normalizedTime));
+            if (!mIsAnimationStarted) {
+                normalizedTime = 0.0f;
+            }
+            float interpolatedTime = (mInterpolator == null) ? normalizedTime
+                    : mInterpolator.getInterpolation(normalizedTime);
+            int deltaY = (int) (mDeltaY * interpolatedTime);
+            mCurrentBounds.top = mStartRect.top + deltaY;
+            mCurrentBounds.bottom = mStartRect.bottom + deltaY;
+            mCurrentAlpha = mStartAlpha + (mEndAlpha - mStartAlpha) * interpolatedTime;
+            if (mBitmap != null && mCurrentBounds != null) {
+                mBitmap.setAlpha((int) (mCurrentAlpha * 255));
+                mBitmap.setBounds(mCurrentBounds);
+            }
+            if (mIsAnimationStarted && normalizedTime >= 1.0f) {
+                mIsAnimationEnded = true;
+                if (mListener != null) {
+                    mListener.onAnimationEnd();
+                }
+            }
+            return !mIsAnimationEnded;
+        }
+
+        /**
+         * An animation listener that receives notifications when the animation ends.
+         */
+        public interface OnAnimationEndListener {
+            /**
+             * Notifies the end of the animation.
+             */
+            public void onAnimationEnd();
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr1/MediaRouterJellybeanMr1.java b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr1/MediaRouterJellybeanMr1.java
new file mode 100644
index 0000000..f8539bd
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr1/MediaRouterJellybeanMr1.java
@@ -0,0 +1,185 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.os.Handler;
+import android.support.annotation.RequiresApi;
+import android.util.Log;
+import android.view.Display;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+// @@RequiresApi(17)
+final class MediaRouterJellybeanMr1 {
+    private static final String TAG = "MediaRouterJellybeanMr1";
+
+    public static Object createCallback(Callback callback) {
+        return new CallbackProxy<Callback>(callback);
+    }
+
+    public static final class RouteInfo {
+        public static boolean isEnabled(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).isEnabled();
+        }
+
+        public static Display getPresentationDisplay(Object routeObj) {
+            // android.media.MediaRouter.RouteInfo.getPresentationDisplay() was
+            // added in API 17. However, some factory releases of JB MR1 missed it.
+            try {
+                return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay();
+            } catch (NoSuchMethodError ex) {
+                Log.w(TAG, "Cannot get presentation display for the route.", ex);
+            }
+            return null;
+        }
+    }
+
+    public static interface Callback extends MediaRouterJellybean.Callback {
+        public void onRoutePresentationDisplayChanged(Object routeObj);
+    }
+
+    /**
+     * Workaround the fact that the version of MediaRouter.addCallback() that accepts a
+     * flag to perform an active scan does not exist in JB MR1 so we need to force
+     * wifi display scans directly through the DisplayManager.
+     * Do not use on JB MR2 and above.
+     */
+    public static final class ActiveScanWorkaround implements Runnable {
+        // Time between wifi display scans when actively scanning in milliseconds.
+        private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
+
+        private final DisplayManager mDisplayManager;
+        private final Handler mHandler;
+        private Method mScanWifiDisplaysMethod;
+
+        private boolean mActivelyScanningWifiDisplays;
+
+        public ActiveScanWorkaround(Context context, Handler handler) {
+            if (Build.VERSION.SDK_INT != 17) {
+                throw new UnsupportedOperationException();
+            }
+
+            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+            mHandler = handler;
+            try {
+                mScanWifiDisplaysMethod = DisplayManager.class.getMethod("scanWifiDisplays");
+            } catch (NoSuchMethodException ex) {
+            }
+        }
+
+        public void setActiveScanRouteTypes(int routeTypes) {
+            // On JB MR1, there is no API to scan wifi display routes.
+            // Instead we must make a direct call into the DisplayManager to scan
+            // wifi displays on this version but only when live video routes are requested.
+            // See also the JellybeanMr2Impl implementation of this method.
+            // This was fixed in JB MR2 by adding a new overload of addCallback() to
+            // enable active scanning on request.
+            if ((routeTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
+                if (!mActivelyScanningWifiDisplays) {
+                    if (mScanWifiDisplaysMethod != null) {
+                        mActivelyScanningWifiDisplays = true;
+                        mHandler.post(this);
+                    } else {
+                        Log.w(TAG, "Cannot scan for wifi displays because the "
+                                + "DisplayManager.scanWifiDisplays() method is "
+                                + "not available on this device.");
+                    }
+                }
+            } else {
+                if (mActivelyScanningWifiDisplays) {
+                    mActivelyScanningWifiDisplays = false;
+                    mHandler.removeCallbacks(this);
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            if (mActivelyScanningWifiDisplays) {
+                try {
+                    mScanWifiDisplaysMethod.invoke(mDisplayManager);
+                } catch (IllegalAccessException ex) {
+                    Log.w(TAG, "Cannot scan for wifi displays.", ex);
+                } catch (InvocationTargetException ex) {
+                    Log.w(TAG, "Cannot scan for wifi displays.", ex);
+                }
+                mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL);
+            }
+        }
+    }
+
+    /**
+     * Workaround the fact that the isConnecting() method does not exist in JB MR1.
+     * Do not use on JB MR2 and above.
+     */
+    public static final class IsConnectingWorkaround {
+        private Method mGetStatusCodeMethod;
+        private int mStatusConnecting;
+
+        public IsConnectingWorkaround() {
+            if (Build.VERSION.SDK_INT != 17) {
+                throw new UnsupportedOperationException();
+            }
+
+            try {
+                Field statusConnectingField =
+                        android.media.MediaRouter.RouteInfo.class.getField("STATUS_CONNECTING");
+                mStatusConnecting = statusConnectingField.getInt(null);
+                mGetStatusCodeMethod =
+                        android.media.MediaRouter.RouteInfo.class.getMethod("getStatusCode");
+            } catch (NoSuchFieldException ex) {
+            } catch (NoSuchMethodException ex) {
+            } catch (IllegalAccessException ex) {
+            }
+        }
+
+        public boolean isConnecting(Object routeObj) {
+            android.media.MediaRouter.RouteInfo route =
+                    (android.media.MediaRouter.RouteInfo)routeObj;
+
+            if (mGetStatusCodeMethod != null) {
+                try {
+                    int statusCode = (Integer)mGetStatusCodeMethod.invoke(route);
+                    return statusCode == mStatusConnecting;
+                } catch (IllegalAccessException ex) {
+                } catch (InvocationTargetException ex) {
+                }
+            }
+
+            // Assume not connecting.
+            return false;
+        }
+    }
+
+    static class CallbackProxy<T extends Callback>
+            extends MediaRouterJellybean.CallbackProxy<T> {
+        public CallbackProxy(T callback) {
+            super(callback);
+        }
+
+        @Override
+        public void onRoutePresentationDisplayChanged(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRoutePresentationDisplayChanged(route);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr2/MediaRouterJellybeanMr2.java b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr2/MediaRouterJellybeanMr2.java
new file mode 100644
index 0000000..1103549
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr2/MediaRouterJellybeanMr2.java
@@ -0,0 +1,45 @@
+/*
+ * 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.support.mediarouter.media;
+
+// @@RequiresApi(18)
+final class MediaRouterJellybeanMr2 {
+    public static Object getDefaultRoute(Object routerObj) {
+        return ((android.media.MediaRouter)routerObj).getDefaultRoute();
+    }
+
+    public static void addCallback(Object routerObj, int types, Object callbackObj, int flags) {
+        ((android.media.MediaRouter)routerObj).addCallback(types,
+                (android.media.MediaRouter.Callback)callbackObj, flags);
+    }
+
+    public static final class RouteInfo {
+        public static CharSequence getDescription(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getDescription();
+        }
+
+        public static boolean isConnecting(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).isConnecting();
+        }
+    }
+
+    public static final class UserRouteInfo {
+        public static void setDescription(Object routeObj, CharSequence description) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setDescription(description);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/jellybean/MediaRouterJellybean.java b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean/MediaRouterJellybean.java
new file mode 100644
index 0000000..0bb59b8
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean/MediaRouterJellybean.java
@@ -0,0 +1,462 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.os.Build;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+// @@RequiresApi(16)
+final class MediaRouterJellybean {
+    private static final String TAG = "MediaRouterJellybean";
+
+    // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP = 0x80;
+    // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100;
+    // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200;
+    public static final int DEVICE_OUT_BLUETOOTH = 0x80 | 0x100 | 0x200;
+
+    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
+    public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
+    public static final int ROUTE_TYPE_USER = 0x00800000;
+
+    public static final int ALL_ROUTE_TYPES =
+            MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO
+            | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO
+            | MediaRouterJellybean.ROUTE_TYPE_USER;
+
+    public static Object getMediaRouter(Context context) {
+        return context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static List getRoutes(Object routerObj) {
+        final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+        final int count = router.getRouteCount();
+        List out = new ArrayList(count);
+        for (int i = 0; i < count; i++) {
+            out.add(router.getRouteAt(i));
+        }
+        return out;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static List getCategories(Object routerObj) {
+        final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+        final int count = router.getCategoryCount();
+        List out = new ArrayList(count);
+        for (int i = 0; i < count; i++) {
+            out.add(router.getCategoryAt(i));
+        }
+        return out;
+    }
+
+    public static Object getSelectedRoute(Object routerObj, int type) {
+        return ((android.media.MediaRouter)routerObj).getSelectedRoute(type);
+    }
+
+    public static void selectRoute(Object routerObj, int types, Object routeObj) {
+        ((android.media.MediaRouter)routerObj).selectRoute(types,
+                (android.media.MediaRouter.RouteInfo)routeObj);
+    }
+
+    public static void addCallback(Object routerObj, int types, Object callbackObj) {
+        ((android.media.MediaRouter)routerObj).addCallback(types,
+                (android.media.MediaRouter.Callback)callbackObj);
+    }
+
+    public static void removeCallback(Object routerObj, Object callbackObj) {
+        ((android.media.MediaRouter)routerObj).removeCallback(
+                (android.media.MediaRouter.Callback)callbackObj);
+    }
+
+    public static Object createRouteCategory(Object routerObj,
+            String name, boolean isGroupable) {
+        return ((android.media.MediaRouter)routerObj).createRouteCategory(name, isGroupable);
+    }
+
+    public static Object createUserRoute(Object routerObj, Object categoryObj) {
+        return ((android.media.MediaRouter)routerObj).createUserRoute(
+                (android.media.MediaRouter.RouteCategory)categoryObj);
+    }
+
+    public static void addUserRoute(Object routerObj, Object routeObj) {
+        ((android.media.MediaRouter)routerObj).addUserRoute(
+                (android.media.MediaRouter.UserRouteInfo)routeObj);
+    }
+
+    public static void removeUserRoute(Object routerObj, Object routeObj) {
+        ((android.media.MediaRouter)routerObj).removeUserRoute(
+                (android.media.MediaRouter.UserRouteInfo)routeObj);
+    }
+
+    public static Object createCallback(Callback callback) {
+        return new CallbackProxy<Callback>(callback);
+    }
+
+    public static Object createVolumeCallback(VolumeCallback callback) {
+        return new VolumeCallbackProxy<VolumeCallback>(callback);
+    }
+
+    static boolean checkRoutedToBluetooth(Context context) {
+        try {
+            AudioManager audioManager = (AudioManager) context.getSystemService(
+                    Context.AUDIO_SERVICE);
+            Method method = audioManager.getClass().getDeclaredMethod(
+                    "getDevicesForStream", int.class);
+            int device = (Integer) method.invoke(audioManager, AudioManager.STREAM_MUSIC);
+            return (device & DEVICE_OUT_BLUETOOTH) != 0;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public static final class RouteInfo {
+        public static CharSequence getName(Object routeObj, Context context) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getName(context);
+        }
+
+        public static CharSequence getStatus(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getStatus();
+        }
+
+        public static int getSupportedTypes(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getSupportedTypes();
+        }
+
+        public static Object getCategory(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getCategory();
+        }
+
+        public static Drawable getIconDrawable(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getIconDrawable();
+        }
+
+        public static int getPlaybackType(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackType();
+        }
+
+        public static int getPlaybackStream(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackStream();
+        }
+
+        public static int getVolume(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolume();
+        }
+
+        public static int getVolumeMax(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeMax();
+        }
+
+        public static int getVolumeHandling(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeHandling();
+        }
+
+        public static Object getTag(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getTag();
+        }
+
+        public static void setTag(Object routeObj, Object tag) {
+            ((android.media.MediaRouter.RouteInfo)routeObj).setTag(tag);
+        }
+
+        public static void requestSetVolume(Object routeObj, int volume) {
+            ((android.media.MediaRouter.RouteInfo)routeObj).requestSetVolume(volume);
+        }
+
+        public static void requestUpdateVolume(Object routeObj, int direction) {
+            ((android.media.MediaRouter.RouteInfo)routeObj).requestUpdateVolume(direction);
+        }
+
+        public static Object getGroup(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getGroup();
+        }
+
+        public static boolean isGroup(Object routeObj) {
+            return routeObj instanceof android.media.MediaRouter.RouteGroup;
+        }
+    }
+
+    public static final class RouteGroup {
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public static List getGroupedRoutes(Object groupObj) {
+            final android.media.MediaRouter.RouteGroup group =
+                    (android.media.MediaRouter.RouteGroup)groupObj;
+            final int count = group.getRouteCount();
+            List out = new ArrayList(count);
+            for (int i = 0; i < count; i++) {
+                out.add(group.getRouteAt(i));
+            }
+            return out;
+        }
+    }
+
+    public static final class UserRouteInfo {
+        public static void setName(Object routeObj, CharSequence name) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setName(name);
+        }
+
+        public static void setStatus(Object routeObj, CharSequence status) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setStatus(status);
+        }
+
+        public static void setIconDrawable(Object routeObj, Drawable icon) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setIconDrawable(icon);
+        }
+
+        public static void setPlaybackType(Object routeObj, int type) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackType(type);
+        }
+
+        public static void setPlaybackStream(Object routeObj, int stream) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackStream(stream);
+        }
+
+        public static void setVolume(Object routeObj, int volume) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolume(volume);
+        }
+
+        public static void setVolumeMax(Object routeObj, int volumeMax) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeMax(volumeMax);
+        }
+
+        public static void setVolumeHandling(Object routeObj, int volumeHandling) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeHandling(volumeHandling);
+        }
+
+        public static void setVolumeCallback(Object routeObj, Object volumeCallbackObj) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeCallback(
+                    (android.media.MediaRouter.VolumeCallback)volumeCallbackObj);
+        }
+
+        public static void setRemoteControlClient(Object routeObj, Object rccObj) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setRemoteControlClient(
+                    (android.media.RemoteControlClient)rccObj);
+        }
+    }
+
+    public static final class RouteCategory {
+        public static CharSequence getName(Object categoryObj, Context context) {
+            return ((android.media.MediaRouter.RouteCategory)categoryObj).getName(context);
+        }
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public static List getRoutes(Object categoryObj) {
+            ArrayList out = new ArrayList();
+            ((android.media.MediaRouter.RouteCategory)categoryObj).getRoutes(out);
+            return out;
+        }
+
+        public static int getSupportedTypes(Object categoryObj) {
+            return ((android.media.MediaRouter.RouteCategory)categoryObj).getSupportedTypes();
+        }
+
+        public static boolean isGroupable(Object categoryObj) {
+            return ((android.media.MediaRouter.RouteCategory)categoryObj).isGroupable();
+        }
+    }
+
+    public static interface Callback {
+        public void onRouteSelected(int type, Object routeObj);
+        public void onRouteUnselected(int type, Object routeObj);
+        public void onRouteAdded(Object routeObj);
+        public void onRouteRemoved(Object routeObj);
+        public void onRouteChanged(Object routeObj);
+        public void onRouteGrouped(Object routeObj, Object groupObj, int index);
+        public void onRouteUngrouped(Object routeObj, Object groupObj);
+        public void onRouteVolumeChanged(Object routeObj);
+    }
+
+    public static interface VolumeCallback {
+        public void onVolumeSetRequest(Object routeObj, int volume);
+        public void onVolumeUpdateRequest(Object routeObj, int direction);
+    }
+
+    /**
+     * Workaround for limitations of selectRoute() on JB and JB MR1.
+     * Do not use on JB MR2 and above.
+     */
+    public static final class SelectRouteWorkaround {
+        private Method mSelectRouteIntMethod;
+
+        public SelectRouteWorkaround() {
+            if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
+                throw new UnsupportedOperationException();
+            }
+            try {
+                mSelectRouteIntMethod = android.media.MediaRouter.class.getMethod(
+                        "selectRouteInt", int.class, android.media.MediaRouter.RouteInfo.class);
+            } catch (NoSuchMethodException ex) {
+            }
+        }
+
+        public void selectRoute(Object routerObj, int types, Object routeObj) {
+            android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+            android.media.MediaRouter.RouteInfo route =
+                    (android.media.MediaRouter.RouteInfo)routeObj;
+
+            int routeTypes = route.getSupportedTypes();
+            if ((routeTypes & ROUTE_TYPE_USER) == 0) {
+                // Handle non-user routes.
+                // On JB and JB MR1, the selectRoute() API only supports programmatically
+                // selecting user routes.  So instead we rely on the hidden selectRouteInt()
+                // method on these versions of the platform.
+                // This limitation was removed in JB MR2.
+                if (mSelectRouteIntMethod != null) {
+                    try {
+                        mSelectRouteIntMethod.invoke(router, types, route);
+                        return; // success!
+                    } catch (IllegalAccessException ex) {
+                        Log.w(TAG, "Cannot programmatically select non-user route.  "
+                                + "Media routing may not work.", ex);
+                    } catch (InvocationTargetException ex) {
+                        Log.w(TAG, "Cannot programmatically select non-user route.  "
+                                + "Media routing may not work.", ex);
+                    }
+                } else {
+                    Log.w(TAG, "Cannot programmatically select non-user route "
+                            + "because the platform is missing the selectRouteInt() "
+                            + "method.  Media routing may not work.");
+                }
+            }
+
+            // Default handling.
+            router.selectRoute(types, route);
+        }
+    }
+
+    /**
+     * Workaround the fact that the getDefaultRoute() method does not exist in JB and JB MR1.
+     * Do not use on JB MR2 and above.
+     */
+    public static final class GetDefaultRouteWorkaround {
+        private Method mGetSystemAudioRouteMethod;
+
+        public GetDefaultRouteWorkaround() {
+            if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
+                throw new UnsupportedOperationException();
+            }
+            try {
+                mGetSystemAudioRouteMethod =
+                        android.media.MediaRouter.class.getMethod("getSystemAudioRoute");
+            } catch (NoSuchMethodException ex) {
+            }
+        }
+
+        public Object getDefaultRoute(Object routerObj) {
+            android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+
+            if (mGetSystemAudioRouteMethod != null) {
+                try {
+                    return mGetSystemAudioRouteMethod.invoke(router);
+                } catch (IllegalAccessException ex) {
+                } catch (InvocationTargetException ex) {
+                }
+            }
+
+            // Could not find the method or it does not work.
+            // Return the first route and hope for the best.
+            return router.getRouteAt(0);
+        }
+    }
+
+    static class CallbackProxy<T extends Callback>
+            extends android.media.MediaRouter.Callback {
+        protected final T mCallback;
+
+        public CallbackProxy(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onRouteSelected(android.media.MediaRouter router,
+                int type, android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteSelected(type, route);
+        }
+
+        @Override
+        public void onRouteUnselected(android.media.MediaRouter router,
+                int type, android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteUnselected(type, route);
+        }
+
+        @Override
+        public void onRouteAdded(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteAdded(route);
+        }
+
+        @Override
+        public void onRouteRemoved(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteRemoved(route);
+        }
+
+        @Override
+        public void onRouteChanged(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteChanged(route);
+        }
+
+        @Override
+        public void onRouteGrouped(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route,
+                android.media.MediaRouter.RouteGroup group, int index) {
+            mCallback.onRouteGrouped(route, group, index);
+        }
+
+        @Override
+        public void onRouteUngrouped(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route,
+                android.media.MediaRouter.RouteGroup group) {
+            mCallback.onRouteUngrouped(route, group);
+        }
+
+        @Override
+        public void onRouteVolumeChanged(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteVolumeChanged(route);
+        }
+    }
+
+    static class VolumeCallbackProxy<T extends VolumeCallback>
+            extends android.media.MediaRouter.VolumeCallback {
+        protected final T mCallback;
+
+        public VolumeCallbackProxy(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route,
+                int volume) {
+            mCallback.onVolumeSetRequest(route, volume);
+        }
+
+        @Override
+        public void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo route,
+                int direction) {
+            mCallback.onVolumeUpdateRequest(route, direction);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaControlIntent.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaControlIntent.java
new file mode 100644
index 0000000..1d9e777
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaControlIntent.java
@@ -0,0 +1,1228 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+
+/**
+ * Constants for media control intents.
+ * <p>
+ * This class declares a set of standard media control intent categories and actions that
+ * applications can use to identify the capabilities of media routes and control them.
+ * </p>
+ *
+ * <h3>Media control intent categories</h3>
+ * <p>
+ * Media control intent categories specify means by which applications can
+ * send media to the destination of a media route.  Categories are sometimes referred
+ * to as describing "types" or "kinds" of routes.
+ * </p><p>
+ * For example, if a route supports the {@link #CATEGORY_REMOTE_PLAYBACK remote playback category},
+ * then an application can ask it to play media remotely by sending a
+ * {@link #ACTION_PLAY play} or {@link #ACTION_ENQUEUE enqueue} intent with the Uri of the
+ * media content to play.  Such a route may then be referred to as
+ * a "remote playback route" because it supports remote playback requests.  It is common
+ * for a route to support multiple categories of requests at the same time, such as
+ * live audio and live video.
+ * </p><p>
+ * The following standard route categories are defined.
+ * </p><ul>
+ * <li>{@link #CATEGORY_LIVE_AUDIO Live audio}: The route supports streaming live audio
+ * from the device to the destination.  Live audio routes include local speakers
+ * and Bluetooth headsets.
+ * <li>{@link #CATEGORY_LIVE_VIDEO Live video}: The route supports streaming live video
+ * from the device to the destination.  Live video routes include local displays
+ * and wireless displays that support mirroring and
+ * {@link android.app.Presentation presentations}.  Live video routes typically also
+ * support live audio capabilities.
+ * <li>{@link #CATEGORY_REMOTE_PLAYBACK Remote playback}: The route supports sending
+ * remote playback requests for media content to the destination.  The content to be
+ * played is identified by a Uri and mime-type.
+ * </ul><p>
+ * Media route providers may define custom media control intent categories of their own in
+ * addition to the standard ones.  Custom categories can be used to provide a variety
+ * of features to applications that recognize and know how to use them.  For example,
+ * a media route provider might define a custom category to indicate that its routes
+ * support a special device-specific control interface in addition to other
+ * standard features.
+ * </p><p>
+ * Applications can determine which categories a route supports by using the
+ * {@link MediaRouter.RouteInfo#supportsControlCategory MediaRouter.RouteInfo.supportsControlCategory}
+ * or {@link MediaRouter.RouteInfo#getControlFilters MediaRouter.RouteInfo.getControlFilters}
+ * methods.  Applications can also specify the types of routes that they want to use by
+ * creating {@link MediaRouteSelector media route selectors} that contain the desired
+ * categories and are used to filter routes in several parts of the media router API.
+ * </p>
+ *
+ * <h3>Media control intent actions</h3>
+ * <p>
+ * Media control intent actions specify particular functions that applications
+ * can ask the destination of a media route to perform.  Media route control requests
+ * take the form of intents in a similar manner to other intents used to start activities
+ * or send broadcasts.  The difference is that media control intents are directed to
+ * routes rather than activity or broadcast receiver components.
+ * </p><p>
+ * Each media route control intent specifies an action, a category and some number of parameters
+ * that are supplied as extras.  Applications send media control requests to routes using the
+ * {@link MediaRouter.RouteInfo#sendControlRequest MediaRouter.RouteInfo.sendControlRequest}
+ * method and receive results via a callback.
+ * </p><p>
+ * All media control intent actions are associated with the media control intent categories
+ * that support them.  Thus only remote playback routes may perform remote playback actions.
+ * The documentation of each action specifies the category to which the action belongs,
+ * the parameters it requires, and the results it returns.
+ * </p>
+ *
+ * <h3>Live audio and live video routes</h3>
+ * <p>
+ * {@link #CATEGORY_LIVE_AUDIO Live audio} and {@link #CATEGORY_LIVE_VIDEO live video}
+ * routes present media using standard system interfaces such as audio streams,
+ * {@link android.app.Presentation presentations} or display mirroring.  These routes are
+ * the easiest to use because applications simply render content locally on the device
+ * and the system streams it to the route destination automatically.
+ * </p><p>
+ * In most cases, applications can stream content to live audio and live video routes in
+ * the same way they would play the content locally without any modification.  However,
+ * applications may also be able to take advantage of more sophisticated features such
+ * as second-screen presentation APIs that are particular to these routes.
+ * </p>
+ *
+ * <h3>Remote playback routes</h3>
+ * <p>
+ * {@link #CATEGORY_REMOTE_PLAYBACK Remote playback} routes present media remotely
+ * by playing content from a Uri.
+ * These routes destinations take responsibility for fetching and rendering content
+ * on their own.  Applications do not render the content themselves; instead, applications
+ * send control requests to initiate play, pause, resume, or stop media items and receive
+ * status updates as they change state.
+ * </p>
+ *
+ * <h4>Sessions</h4>
+ * <p>
+ * Each remote media playback action is conducted within the scope of a session.
+ * Sessions are used to prevent applications from accidentally interfering with one
+ * another because at most one session can be valid at a time.
+ * </p><p>
+ * A session can be created using the {@link #ACTION_START_SESSION start session action}
+ * and terminated using the {@link #ACTION_END_SESSION end session action} when the
+ * route provides explicit session management features.
+ * </p><p>
+ * Explicit session management was added in a later revision of the protocol so not
+ * all routes support it.  If the route does not support explicit session management
+ * then implicit session management may still be used.  Implicit session management
+ * relies on the use of the {@link #ACTION_PLAY play} and {@link #ACTION_ENQUEUE enqueue}
+ * actions which have the side-effect of creating a new session if none is provided
+ * as argument.
+ * </p><p>
+ * When a new session is created, the previous session is invalidated and any ongoing
+ * media playback is stopped before the requested action is performed.  Any attempt
+ * to use an invalidated session will result in an error.  (Protocol implementations
+ * are encouraged to aggressively discard information associated with invalidated sessions
+ * since it is no longer of use.)
+ * </p><p>
+ * Each session is identified by a unique session id that may be used to control
+ * the session using actions such as pause, resume, stop and end session.
+ * </p>
+ *
+ * <h4>Media items</h4>
+ * <p>
+ * Each successful {@link #ACTION_PLAY play} or {@link #ACTION_ENQUEUE enqueue} action
+ * returns a unique media item id that an application can use to monitor and control
+ * playback.  The media item id may be passed to other actions such as
+ * {@link #ACTION_SEEK seek} or {@link #ACTION_GET_STATUS get status}.  It will also appear
+ * as a parameter in status update broadcasts to identify the associated playback request.
+ * </p><p>
+ * Each media item is scoped to the session in which it was created.  Therefore media item
+ * ids are only ever used together with session ids.  Media item ids are meaningless
+ * on their own.  When the session is invalidated, all of its media items are also
+ * invalidated.
+ * </p>
+ *
+ * <h4>The playback queue</h4>
+ * <p>
+ * Each session has its own playback queue that consists of the media items that
+ * are pending, playing, buffering or paused.  Items are added to the queue when
+ * a playback request is issued.  Items are removed from the queue when they are no
+ * longer eligible for playback (enter terminal states).
+ * </p><p>
+ * As described in the {@link MediaItemStatus} class, media items initially
+ * start in a pending state, transition to the playing (or buffering or paused) state
+ * during playback, and end in a finished, canceled, invalidated or error state.
+ * Once the current item enters a terminal state, playback proceeds on to the
+ * next item.
+ * </p><p>
+ * The application should determine whether the route supports queuing by checking
+ * whether the {@link #ACTION_ENQUEUE} action is declared in the route's control filter
+ * using {@link MediaRouter.RouteInfo#supportsControlRequest RouteInfo.supportsControlRequest}.
+ * </p><p>
+ * If the {@link #ACTION_ENQUEUE} action is supported by the route, then the route promises
+ * to allow at least two items (possibly more) to be enqueued at a time.  Enqueued items play
+ * back to back one after the other as the previous item completes.  Ideally there should
+ * be no audible pause between items for standard audio content types.
+ * </p><p>
+ * If the {@link #ACTION_ENQUEUE} action is not supported by the route, then the queue
+ * effectively contains at most one item at a time.  Each play action has the effect of
+ * clearing the queue and resetting its state before the next item is played.
+ * </p>
+ *
+ * <h4>Impact of pause, resume, stop and play actions on the playback queue</h4>
+ * <p>
+ * The pause, resume and stop actions affect the session's whole queue.  Pause causes
+ * the playback queue to be suspended no matter which item is currently playing.
+ * Resume reverses the effects of pause.  Stop clears the queue and also resets
+ * the pause flag just like resume.
+ * </p><p>
+ * As described earlier, the play action has the effect of clearing the queue
+ * and completely resetting its state (like the stop action) then enqueuing a
+ * new media item to be played immediately.  Play is therefore equivalent
+ * to stop followed by an action to enqueue an item.
+ * </p><p>
+ * The play action is also special in that it can be used to create new sessions.
+ * An application with simple needs may find that it only needs to use play
+ * (and occasionally stop) to control playback.
+ * </p>
+ *
+ * <h4>Resolving conflicts between applications</h4>
+ * <p>
+ * When an application has a valid session, it is essentially in control of remote playback
+ * on the route.  No other application can view or modify the remote playback state
+ * of that application's session without knowing its id.
+ * </p><p>
+ * However, other applications can perform actions that have the effect of stopping
+ * playback and invalidating the current session.  When this occurs, the former application
+ * will be informed that it has lost control by way of individual media item status
+ * update broadcasts that indicate that its queued media items have become
+ * {@link MediaItemStatus#PLAYBACK_STATE_INVALIDATED invalidated}.  This broadcast
+ * implies that playback was terminated abnormally by an external cause.
+ * </p><p>
+ * Applications should handle conflicts conservatively to allow other applications to
+ * smoothly assume control over the route.  When a conflict occurs, the currently playing
+ * application should release its session and allow the new application to use the
+ * route until such time as the user intervenes to take over the route again and begin
+ * a new playback session.
+ * </p>
+ *
+ * <h4>Basic actions</h4>
+ * <p>
+ * The following basic actions must be supported (all or nothing) by all remote
+ * playback routes.  These actions form the basis of the remote playback protocol
+ * and are required in all implementations.
+ * </p><ul>
+ * <li>{@link #ACTION_PLAY Play}: Starts playing content specified by a given Uri
+ * and returns a new media item id to describe the request.  Implicitly creates a new
+ * session if no session id was specified as a parameter.
+ * <li>{@link #ACTION_SEEK Seek}: Sets the content playback position of a specific media item.
+ * <li>{@link #ACTION_GET_STATUS Get status}: Gets the status of a media item
+ * including the item's current playback position and progress.
+ * <li>{@link #ACTION_PAUSE Pause}: Pauses playback of the queue.
+ * <li>{@link #ACTION_RESUME Resume}: Resumes playback of the queue.
+ * <li>{@link #ACTION_STOP Stop}: Stops playback, clears the queue, and resets the
+ * pause state.
+ * </ul>
+ *
+ * <h4>Queue actions</h4>
+ * <p>
+ * The following queue actions must be supported (all or nothing) by remote
+ * playback routes that offer optional queuing capabilities.
+ * </p><ul>
+ * <li>{@link #ACTION_ENQUEUE Enqueue}: Enqueues content specified by a given Uri
+ * and returns a new media item id to describe the request.  Implicitly creates a new
+ * session if no session id was specified as a parameter.
+ * <li>{@link #ACTION_REMOVE Remove}: Removes a specified media item from the queue.
+ * </ul>
+ *
+ * <h4>Session actions</h4>
+ * <p>
+ * The following session actions must be supported (all or nothing) by remote
+ * playback routes that offer optional session management capabilities.
+ * </p><ul>
+ * <li>{@link #ACTION_START_SESSION Start session}: Starts a new session explicitly.
+ * <li>{@link #ACTION_GET_SESSION_STATUS Get session status}: Gets the status of a session.
+ * <li>{@link #ACTION_END_SESSION End session}: Ends a session explicitly.
+ * </ul>
+ *
+ * <h4>Implementation note</h4>
+ * <p>
+ * Implementations of the remote playback protocol must implement <em>all</em> of the
+ * documented actions, parameters and results.  Note that the documentation is written from
+ * the perspective of a client of the protocol.  In particular, whenever a parameter
+ * is described as being "optional", it is only from the perspective of the client.
+ * Compliant media route provider implementations of this protocol must support all
+ * of the features described herein.
+ * </p>
+ */
+public final class MediaControlIntent {
+    /* Route categories. */
+
+    /**
+     * Media control category: Live audio.
+     * <p>
+     * A route that supports live audio routing will allow the media audio stream
+     * to be sent to supported destinations.  This can include internal speakers or
+     * audio jacks on the device itself, A2DP devices, and more.
+     * </p><p>
+     * When a live audio route is selected, audio routing is transparent to the application.
+     * All audio played on the media stream will be routed to the selected destination.
+     * </p><p>
+     * Refer to the class documentation for details about live audio routes.
+     * </p>
+     */
+    public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+
+    /**
+     * Media control category: Live video.
+     * <p>
+     * A route that supports live video routing will allow a mirrored version
+     * of the device's primary display or a customized
+     * {@link android.app.Presentation Presentation} to be sent to supported
+     * destinations.
+     * </p><p>
+     * When a live video route is selected, audio and video routing is transparent
+     * to the application.  By default, audio and video is routed to the selected
+     * destination.  For certain live video routes, the application may also use a
+     * {@link android.app.Presentation Presentation} to replace the mirrored view
+     * on the external display with different content.
+     * </p><p>
+     * Refer to the class documentation for details about live video routes.
+     * </p>
+     *
+     * @see MediaRouter.RouteInfo#getPresentationDisplay()
+     * @see android.app.Presentation
+     */
+    public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+
+    /**
+     * Media control category: Remote playback.
+     * <p>
+     * A route that supports remote playback routing will allow an application to send
+     * requests to play content remotely to supported destinations.
+     * </p><p>
+     * Remote playback routes destinations operate independently of the local device.
+     * When a remote playback route is selected, the application can control the content
+     * playing on the destination by sending media control actions to the route.
+     * The application may also receive status updates from the route regarding
+     * remote playback.
+     * </p><p>
+     * Refer to the class documentation for details about remote playback routes.
+     * </p>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     */
+    public static final String CATEGORY_REMOTE_PLAYBACK =
+            "android.media.intent.category.REMOTE_PLAYBACK";
+
+    /* Remote playback actions that affect individual items. */
+
+    /**
+     * Remote playback media control action: Play media item.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action causes a remote playback route to start playing content with
+     * the {@link Uri} specified in the {@link Intent}'s {@link Intent#getData() data uri}.
+     * The action returns a media session id and media item id which can be used
+     * to control playback using other remote playback actions.
+     * </p><p>
+     * Once initiated, playback of the specified content will be managed independently
+     * by the destination.  The application will receive status updates as the state
+     * of the media item changes.
+     * </p><p>
+     * If the data uri specifies an HTTP or HTTPS scheme, then the destination is
+     * responsible for following HTTP redirects to a reasonable depth of at least 3
+     * levels as might typically be handled by a web browser.  If an HTTP error
+     * occurs, then the destination should send a {@link MediaItemStatus status update}
+     * back to the client indicating the {@link MediaItemStatus#PLAYBACK_STATE_ERROR error}
+     * {@link MediaItemStatus#getPlaybackState() playback state}.
+     * </p>
+     *
+     * <h3>One item at a time</h3>
+     * <p>
+     * Each successful play action <em>replaces</em> the previous play action.
+     * If an item is already playing, then it is canceled, the session's playback queue
+     * is cleared and the new item begins playing immediately (regardless of
+     * whether the previously playing item had been paused).
+     * </p><p>
+     * Play is therefore equivalent to {@link #ACTION_STOP stop} followed by an action
+     * to enqueue a new media item to be played immediately.
+     * </p>
+     *
+     * <h3>Sessions</h3>
+     * <p>
+     * This request has the effect of implicitly creating a media session whenever the
+     * application does not specify the {@link #EXTRA_SESSION_ID session id} parameter.
+     * Because there can only be at most one valid session at a time, creating a new session
+     * has the side-effect of invalidating any existing sessions and their media items,
+     * then handling the playback request with a new session.
+     * </p><p>
+     * If the application specifies an invalid session id, then an error is returned.
+     * When this happens, the application should assume that its session
+     * is no longer valid.  To obtain a new session, the application may try again
+     * and omit the session id parameter.  However, the application should
+     * only retry requests due to an explicit action performed by the user,
+     * such as the user clicking on a "play" button in the UI, since another
+     * application may be trying to take control of the route and the former
+     * application should try to stay out of its way.
+     * </p><p>
+     * For more information on sessions, queues and media items, please refer to the
+     * class documentation.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(optional)</em>: Specifies the session id of the
+     * session to which the playback request belongs.  If omitted, a new session
+     * is created implicitly.
+     * <li>{@link #EXTRA_ITEM_CONTENT_POSITION} <em>(optional)</em>: Specifies the initial
+     * content playback position as a long integer number of milliseconds from
+     * the beginning of the content.
+     * <li>{@link #EXTRA_ITEM_METADATA} <em>(optional)</em>: Specifies metadata associated
+     * with the content such as the title of a song.
+     * <li>{@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER} <em>(optional)</em>: Specifies a
+     * {@link PendingIntent} for a broadcast receiver that will receive status updates
+     * about the media item.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(always returned)</em>: Specifies the session id of the
+     * session that was affected by the request.  This will be a new session in
+     * the case where no session id was supplied as a parameter.
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+     * omit this key)</em>: Specifies the status of the media session.
+     * <li>{@link #EXTRA_ITEM_ID} <em>(always returned)</em>: Specifies an opaque string identifier
+     * to use to refer to the media item in subsequent requests such as
+     * {@link #ACTION_GET_STATUS}.
+     * <li>{@link #EXTRA_ITEM_STATUS} <em>(always returned)</em>: Specifies the initial status of
+     * the new media item.
+     * </ul>
+     *
+     * <h3>Status updates</h3>
+     * <p>
+     * If the client supplies an
+     * {@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receiver}
+     * then the media route provider is responsible for sending status updates to the receiver
+     * when significant media item state changes occur such as when playback starts or
+     * stops.  The receiver will not be invoked for content playback position changes.
+     * The application may retrieve the current playback position when necessary
+     * using the {@link #ACTION_GET_STATUS} request.
+     * </p><p>
+     * Refer to {@link MediaItemStatus} for details.
+     * </p>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if a session id was provided but is unknown or
+     * no longer valid, if the item Uri or content type is not supported, or if
+     * any other arguments are invalid.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * <h3>Example</h3>
+     * <pre>
+     * MediaRouter mediaRouter = MediaRouter.getInstance(context);
+     * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
+     * Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
+     * intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+     * intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
+     * if (route.supportsControlRequest(intent)) {
+     *     MediaRouter.ControlRequestCallback callback = new MediaRouter.ControlRequestCallback() {
+     *         public void onResult(Bundle data) {
+     *             // The request succeeded.
+     *             // Playback may be controlled using the returned session and item id.
+     *             String sessionId = data.getString(MediaControlIntent.EXTRA_SESSION_ID);
+     *             String itemId = data.getString(MediaControlIntent.EXTRA_ITEM_ID);
+     *             MediaItemStatus status = MediaItemStatus.fromBundle(data.getBundle(
+     *                     MediaControlIntent.EXTRA_ITEM_STATUS));
+     *             // ...
+     *         }
+     *
+     *         public void onError(String message, Bundle data) {
+     *             // An error occurred!
+     *         }
+     *     };
+     *     route.sendControlRequest(intent, callback);
+     * }</pre>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     * @see #ACTION_SEEK
+     * @see #ACTION_GET_STATUS
+     * @see #ACTION_PAUSE
+     * @see #ACTION_RESUME
+     * @see #ACTION_STOP
+     */
+    public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+
+    /**
+     * Remote playback media control action: Enqueue media item.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action works just like {@link #ACTION_PLAY play} except that it does
+     * not clear the queue or reset the pause state when it enqueues the
+     * new media item into the session's playback queue.  This action only
+     * enqueues a media item with no other side-effects on the queue.
+     * </p><p>
+     * If the queue is currently empty and then the item will play immediately
+     * (assuming the queue is not paused).  Otherwise, the item will play
+     * after all earlier items in the queue have finished or been removed.
+     * </p><p>
+     * The enqueue action can be used to create new sessions just like play.
+     * Its parameters and results are also the same.  Only the queuing behavior
+     * is different.
+     * </p>
+     *
+     * @see #ACTION_PLAY
+     */
+    public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+
+    /**
+     * Remote playback media control action: Seek media item to a new playback position.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action causes a remote playback route to modify the current playback position
+     * of the specified media item.
+     * </p><p>
+     * This action only affects the playback position of the media item; not its playback state.
+     * If the playback queue is paused, then seeking sets the position but the item
+     * remains paused.  Likewise if the item is playing, then seeking will cause playback
+     * to jump to the new position and continue playing from that point.  If the item has
+     * not yet started playing, then the new playback position is remembered by the
+     * queue and used as the item's initial content position when playback eventually begins.
+     * </p><p>
+     * If successful, the media item's playback position is changed.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+     * to which the media item belongs.
+     * <li>{@link #EXTRA_ITEM_ID} <em>(required)</em>: Specifies the media item id of
+     * the media item to seek.
+     * <li>{@link #EXTRA_ITEM_CONTENT_POSITION} <em>(required)</em>: Specifies the new
+     * content position for playback as a long integer number of milliseconds from
+     * the beginning of the content.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+     * omit this key)</em>: Specifies the status of the media session.
+     * <li>{@link #EXTRA_ITEM_STATUS} <em>(always returned)</em>: Specifies the new status of
+     * the media item.
+     * </ul>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session id or media item id are unknown
+     * or no longer valid, if the content position is invalid, or if the media item
+     * is in a terminal state.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     */
+    public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+
+    /**
+     * Remote playback media control action: Get media item playback status
+     * and progress information.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action asks a remote playback route to provide updated playback status and progress
+     * information about the specified media item.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+     * to which the media item belongs.
+     * <li>{@link #EXTRA_ITEM_ID} <em>(required)</em>: Specifies the media item id of
+     * the media item to query.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+     * omit this key)</em>: Specifies the status of the media session.
+     * <li>{@link #EXTRA_ITEM_STATUS} <em>(always returned)</em>: Specifies the current status of
+     * the media item.
+     * </ul>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session id or media item id are unknown
+     * or no longer valid.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     * @see #EXTRA_ITEM_STATUS_UPDATE_RECEIVER
+     */
+    public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+
+    /**
+     * Remote playback media control action: Remove media item from session's queue.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action asks a remote playback route to remove the specified media item
+     * from the session's playback queue.  If the current item is removed, then
+     * playback will proceed to the next media item (assuming the queue has not been
+     * paused).
+     * </p><p>
+     * This action does not affect the pause state of the queue.  If the queue was paused
+     * then it remains paused (even if it is now empty) until a resume, stop or play
+     * action is issued that causes the pause state to be cleared.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+     * to which the media item belongs.
+     * <li>{@link #EXTRA_ITEM_ID} <em>(required)</em>: Specifies the media item id of
+     * the media item to remove.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+     * omit this key)</em>: Specifies the status of the media session.
+     * <li>{@link #EXTRA_ITEM_STATUS} <em>(always returned)</em>: Specifies the new status of
+     * the media item.
+     * </ul>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session id or media item id are unknown
+     * or no longer valid, or if the media item is in a terminal state (and therefore
+     * no longer in the queue).
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     */
+    public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+
+    /* Remote playback actions that affect the whole playback queue. */
+
+    /**
+     * Remote playback media control action: Pause media playback.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action causes the playback queue of the specified session to be paused.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+     * whose playback queue is to be paused.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+     * omit this key)</em>: Specifies the status of the media session.
+     * </ul>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session id is unknown or no longer valid.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     * @see #ACTION_RESUME
+     */
+    public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+
+    /**
+     * Remote playback media control action: Resume media playback (unpause).
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action causes the playback queue of the specified session to be resumed.
+     * Reverses the effects of {@link #ACTION_PAUSE}.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+     * whose playback queue is to be resumed.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+     * omit this key)</em>: Specifies the status of the media session.
+     * </ul>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session id is unknown or no longer valid.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     * @see #ACTION_PAUSE
+     */
+    public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+
+    /**
+     * Remote playback media control action: Stop media playback (clear queue and unpause).
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action causes a remote playback route to stop playback, cancel and remove
+     * all media items from the session's media item queue and, reset the queue's
+     * pause state.
+     * </p><p>
+     * If successful, the status of all media items in the queue is set to
+     * {@link MediaItemStatus#PLAYBACK_STATE_CANCELED canceled} and a status update is sent
+     * to the appropriate status update receivers indicating the new status of each item.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+     * the session whose playback queue is to be stopped (cleared and unpaused).
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+     * omit this key)</em>: Specifies the status of the media session.
+     * </ul>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session id is unknown or no longer valid.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     */
+    public static final String ACTION_STOP = "android.media.intent.action.STOP";
+
+    /**
+     * Remote playback media control action: Start session.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action causes a remote playback route to invalidate the current session
+     * and start a new session.  The new session initially has an empty queue.
+     * </p><p>
+     * If successful, the status of all media items in the previous session's queue is set to
+     * {@link MediaItemStatus#PLAYBACK_STATE_INVALIDATED invalidated} and a status update
+     * is sent to the appropriate status update receivers indicating the new status
+     * of each item.  The previous session becomes no longer valid and the new session
+     * takes control of the route.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER} <em>(optional)</em>: Specifies a
+     * {@link PendingIntent} for a broadcast receiver that will receive status updates
+     * about the media session.
+     * <li>{@link #EXTRA_MESSAGE_RECEIVER} <em>(optional)</em>: Specifies a
+     * {@link PendingIntent} for a broadcast receiver that will receive messages from
+     * the media session.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(always returned)</em>: Specifies the session id of the
+     * session that was started by the request.  This will always be a brand new session
+     * distinct from any other previously created sessions.
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(always returned)</em>: Specifies the
+     * status of the media session.
+     * </ul>
+     *
+     * <h3>Status updates</h3>
+     * <p>
+     * If the client supplies a
+     * {@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER status update receiver}
+     * then the media route provider is responsible for sending status updates to the receiver
+     * when significant media session state changes occur such as when the session's
+     * queue is paused or resumed or when the session is terminated or invalidated.
+     * </p><p>
+     * Refer to {@link MediaSessionStatus} for details.
+     * </p>
+     *
+     * <h3>Custom messages</h3>
+     * <p>
+     * If the client supplies a {@link #EXTRA_MESSAGE_RECEIVER message receiver}
+     * then the media route provider is responsible for sending messages to the receiver
+     * when the session has any messages to send.
+     * </p><p>
+     * Refer to {@link #EXTRA_MESSAGE} for details.
+     * </p>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session could not be created.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     */
+    public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+
+    /**
+     * Remote playback media control action: Get media session status information.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action asks a remote playback route to provide updated status information
+     * about the specified media session.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the
+     * session whose status is to be retrieved.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(always returned)</em>: Specifies the
+     * current status of the media session.
+     * </ul>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session id is unknown or no longer valid.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     * @see #EXTRA_SESSION_STATUS_UPDATE_RECEIVER
+     */
+    public static final String ACTION_GET_SESSION_STATUS =
+            "android.media.intent.action.GET_SESSION_STATUS";
+
+    /**
+     * Remote playback media control action: End session.
+     * <p>
+     * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+     * media control.
+     * </p><p>
+     * This action causes a remote playback route to end the specified session.
+     * The session becomes no longer valid and the route ceases to be under control
+     * of the session.
+     * </p><p>
+     * If successful, the status of the session is set to
+     * {@link MediaSessionStatus#SESSION_STATE_ENDED} and a status update is sent to
+     * the session's status update receiver.
+     * </p><p>
+     * Additionally, the status of all media items in the queue is set to
+     * {@link MediaItemStatus#PLAYBACK_STATE_CANCELED canceled} and a status update is sent
+     * to the appropriate status update receivers indicating the new status of each item.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+     * the session to end.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(always returned)</em>: Specifies the
+     * status of the media session.
+     * </ul>
+     *
+     * <h3>Errors</h3>
+     * <p>
+     * This action returns an error if the session id is unknown or no longer valid.
+     * In other words, it is an error to attempt to end a session other than the
+     * current session.
+     * </p><ul>
+     * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+     * </ul>
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     * @see #CATEGORY_REMOTE_PLAYBACK
+     */
+    public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+
+    /**
+     * Custom media control action: Send {@link #EXTRA_MESSAGE}.
+     * <p>
+     * This action asks a route to handle a message described by EXTRA_MESSAGE.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+     * to which will handle this message.
+     * <li>{@link #EXTRA_MESSAGE} <em>(required)</em>: Specifies the message to send.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * Any messages defined by each media route provider.
+     *
+     * <h3>Errors</h3>
+     * Any error messages defined by each media route provider.
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     */
+    public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+
+    /* Extras and related constants. */
+
+    /**
+     * Bundle extra: Media session id.
+     * <p>
+     * An opaque unique identifier that identifies the remote playback media session.
+     * </p><p>
+     * Used with various actions to specify the id of the media session to be controlled.
+     * </p><p>
+     * Included in broadcast intents sent to
+     * {@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receivers} to identify
+     * the session to which the item in question belongs.
+     * </p><p>
+     * Included in broadcast intents sent to
+     * {@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER session status update receivers} to identify
+     * the session.
+     * </p><p>
+     * The value is a unique string value generated by the media route provider
+     * to represent one particular media session.
+     * </p>
+     *
+     * @see #ACTION_PLAY
+     * @see #ACTION_SEEK
+     * @see #ACTION_GET_STATUS
+     * @see #ACTION_PAUSE
+     * @see #ACTION_RESUME
+     * @see #ACTION_STOP
+     * @see #ACTION_START_SESSION
+     * @see #ACTION_GET_SESSION_STATUS
+     * @see #ACTION_END_SESSION
+     */
+    public static final String EXTRA_SESSION_ID =
+            "android.media.intent.extra.SESSION_ID";
+
+    /**
+     * Bundle extra: Media session status.
+     * <p>
+     * Returned as a result from media session actions such as {@link #ACTION_START_SESSION},
+     * {@link #ACTION_PAUSE}, and {@link #ACTION_GET_SESSION_STATUS}
+     * to describe the status of the specified media session.
+     * </p><p>
+     * Included in broadcast intents sent to
+     * {@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER session status update receivers} to provide
+     * updated status information.
+     * </p><p>
+     * The value is a {@link android.os.Bundle} of data that can be converted into
+     * a {@link MediaSessionStatus} object using
+     * {@link MediaSessionStatus#fromBundle MediaSessionStatus.fromBundle}.
+     * </p>
+     *
+     * @see #ACTION_PLAY
+     * @see #ACTION_SEEK
+     * @see #ACTION_GET_STATUS
+     * @see #ACTION_PAUSE
+     * @see #ACTION_RESUME
+     * @see #ACTION_STOP
+     * @see #ACTION_START_SESSION
+     * @see #ACTION_GET_SESSION_STATUS
+     * @see #ACTION_END_SESSION
+     */
+    public static final String EXTRA_SESSION_STATUS =
+            "android.media.intent.extra.SESSION_STATUS";
+
+    /**
+     * Bundle extra: Media session status update receiver.
+     * <p>
+     * Used with {@link #ACTION_START_SESSION} to specify a {@link PendingIntent} for a
+     * broadcast receiver that will receive status updates about the media session.
+     * </p><p>
+     * Whenever the status of the media session changes, the media route provider will
+     * send a broadcast to the pending intent with extras that identify the session
+     * id and its updated status.
+     * </p><p>
+     * The value is a {@link PendingIntent}.
+     * </p>
+     *
+     * <h3>Broadcast extras</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+     * the session.
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(required)</em>: Specifies the status of the
+     * session as a bundle that can be decoded into a {@link MediaSessionStatus} object.
+     * </ul>
+     *
+     * @see #ACTION_START_SESSION
+     */
+    public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER =
+            "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+
+    /**
+     * Bundle extra: Media message receiver.
+     * <p>
+     * Used with {@link #ACTION_START_SESSION} to specify a {@link PendingIntent} for a
+     * broadcast receiver that will receive messages from the media session.
+     * </p><p>
+     * When the media session has a message to send, the media route provider will
+     * send a broadcast to the pending intent with extras that identify the session
+     * id and its message.
+     * </p><p>
+     * The value is a {@link PendingIntent}.
+     * </p>
+     *
+     * <h3>Broadcast extras</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+     * the session.
+     * <li>{@link #EXTRA_MESSAGE} <em>(required)</em>: Specifies the message from
+     * the session as a bundle object.
+     * </ul>
+     *
+     * @see #ACTION_START_SESSION
+     */
+    public static final String EXTRA_MESSAGE_RECEIVER =
+            "android.media.intent.extra.MESSAGE_RECEIVER";
+
+    /**
+     * Bundle extra: Media item id.
+     * <p>
+     * An opaque unique identifier returned as a result from {@link #ACTION_PLAY} or
+     * {@link #ACTION_ENQUEUE} that represents the media item that was created by the
+     * playback request.
+     * </p><p>
+     * Used with various actions to specify the id of the media item to be controlled.
+     * </p><p>
+     * Included in broadcast intents sent to
+     * {@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER status update receivers} to identify
+     * the item in question.
+     * </p><p>
+     * The value is a unique string value generated by the media route provider
+     * to represent one particular media item.
+     * </p>
+     *
+     * @see #ACTION_PLAY
+     * @see #ACTION_ENQUEUE
+     * @see #ACTION_SEEK
+     * @see #ACTION_GET_STATUS
+     */
+    public static final String EXTRA_ITEM_ID =
+            "android.media.intent.extra.ITEM_ID";
+
+    /**
+     * Bundle extra: Media item status.
+     * <p>
+     * Returned as a result from media item actions such as {@link #ACTION_PLAY},
+     * {@link #ACTION_ENQUEUE}, {@link #ACTION_SEEK}, and {@link #ACTION_GET_STATUS}
+     * to describe the status of the specified media item.
+     * </p><p>
+     * Included in broadcast intents sent to
+     * {@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receivers} to provide
+     * updated status information.
+     * </p><p>
+     * The value is a {@link android.os.Bundle} of data that can be converted into
+     * a {@link MediaItemStatus} object using
+     * {@link MediaItemStatus#fromBundle MediaItemStatus.fromBundle}.
+     * </p>
+     *
+     * @see #ACTION_PLAY
+     * @see #ACTION_ENQUEUE
+     * @see #ACTION_SEEK
+     * @see #ACTION_GET_STATUS
+     */
+    public static final String EXTRA_ITEM_STATUS =
+            "android.media.intent.extra.ITEM_STATUS";
+
+    /**
+     * Long extra: Media item content position.
+     * <p>
+     * Used with {@link #ACTION_PLAY} or {@link #ACTION_ENQUEUE} to specify the
+     * starting playback position.
+     * </p><p>
+     * Used with {@link #ACTION_SEEK} to set a new playback position.
+     * </p><p>
+     * The value is a long integer number of milliseconds from the beginning of the content.
+     * <p>
+     *
+     * @see #ACTION_PLAY
+     * @see #ACTION_ENQUEUE
+     * @see #ACTION_SEEK
+     */
+    public static final String EXTRA_ITEM_CONTENT_POSITION =
+            "android.media.intent.extra.ITEM_POSITION";
+
+    /**
+     * Bundle extra: Media item metadata.
+     * <p>
+     * Used with {@link #ACTION_PLAY} or {@link #ACTION_ENQUEUE} to specify metadata
+     * associated with the content of a media item.
+     * </p><p>
+     * The value is a {@link android.os.Bundle} of metadata key-value pairs as defined
+     * in {@link MediaItemMetadata}.
+     * </p>
+     *
+     * @see #ACTION_PLAY
+     * @see #ACTION_ENQUEUE
+     */
+    public static final String EXTRA_ITEM_METADATA =
+            "android.media.intent.extra.ITEM_METADATA";
+
+    /**
+     * Bundle extra: HTTP request headers.
+     * <p>
+     * Used with {@link #ACTION_PLAY} or {@link #ACTION_ENQUEUE} to specify HTTP request
+     * headers to be included when fetching to the content indicated by the media
+     * item's data Uri.
+     * </p><p>
+     * This extra may be used to provide authentication tokens and other
+     * parameters to the server separately from the media item's data Uri.
+     * </p><p>
+     * The value is a {@link android.os.Bundle} of string based key-value pairs
+     * that describe the HTTP request headers.
+     * </p>
+     *
+     * @see #ACTION_PLAY
+     * @see #ACTION_ENQUEUE
+     */
+    public static final String EXTRA_ITEM_HTTP_HEADERS =
+            "android.media.intent.extra.HTTP_HEADERS";
+
+    /**
+     * Bundle extra: Media item status update receiver.
+     * <p>
+     * Used with {@link #ACTION_PLAY} or {@link #ACTION_ENQUEUE} to specify
+     * a {@link PendingIntent} for a
+     * broadcast receiver that will receive status updates about a particular
+     * media item.
+     * </p><p>
+     * Whenever the status of the media item changes, the media route provider will
+     * send a broadcast to the pending intent with extras that identify the session
+     * to which the item belongs, the session status, the item's id
+     * and the item's updated status.
+     * </p><p>
+     * The same pending intent and broadcast receiver may be shared by any number of
+     * media items since the broadcast intent includes the media session id
+     * and media item id.
+     * </p><p>
+     * The value is a {@link PendingIntent}.
+     * </p>
+     *
+     * <h3>Broadcast extras</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+     * the session to which the item in question belongs.
+     * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+     * omit this key)</em>: Specifies the status of the media session.
+     * <li>{@link #EXTRA_ITEM_ID} <em>(required)</em>: Specifies the media item id of the
+     * media item in question.
+     * <li>{@link #EXTRA_ITEM_STATUS} <em>(required)</em>: Specifies the status of the
+     * item as a bundle that can be decoded into a {@link MediaItemStatus} object.
+     * </ul>
+     *
+     * @see #ACTION_PLAY
+     * @see #ACTION_ENQUEUE
+     */
+    public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER =
+            "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+
+    /**
+     * Bundle extra: Message.
+     * <p>
+     * Used with {@link #ACTION_SEND_MESSAGE}, and included in broadcast intents sent to
+     * {@link #EXTRA_MESSAGE_RECEIVER message receivers} to describe a message between a
+     * session and a media route provider.
+     * </p><p>
+     * The value is a {@link android.os.Bundle}.
+     * </p>
+     */
+    public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+
+    /**
+     * Integer extra: Error code.
+     * <p>
+     * Used with all media control requests to describe the cause of an error.
+     * This extra may be omitted when the error is unknown.
+     * </p><p>
+     * The value is one of: {@link #ERROR_UNKNOWN}, {@link #ERROR_UNSUPPORTED_OPERATION},
+     * {@link #ERROR_INVALID_SESSION_ID}, {@link #ERROR_INVALID_ITEM_ID}.
+     * </p>
+     */
+    public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+
+    /**
+     * Error code: An unknown error occurred.
+     *
+     * @see #EXTRA_ERROR_CODE
+     */
+    public static final int ERROR_UNKNOWN = 0;
+
+    /**
+     * Error code: The operation is not supported.
+     *
+     * @see #EXTRA_ERROR_CODE
+     */
+    public static final int ERROR_UNSUPPORTED_OPERATION = 1;
+
+    /**
+     * Error code: The session id specified in the request was invalid.
+     *
+     * @see #EXTRA_ERROR_CODE
+     */
+    public static final int ERROR_INVALID_SESSION_ID = 2;
+
+    /**
+     * Error code: The item id specified in the request was invalid.
+     *
+     * @see #EXTRA_ERROR_CODE
+     */
+    public static final int ERROR_INVALID_ITEM_ID = 3;
+
+    private MediaControlIntent() {
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemMetadata.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemMetadata.java
new file mode 100644
index 0000000..d52ddb6
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemMetadata.java
@@ -0,0 +1,138 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.os.Bundle;
+
+/**
+ * Constants for specifying metadata about a media item as a {@link Bundle}.
+ * <p>
+ * This class is part of the remote playback protocol described by the
+ * {@link MediaControlIntent MediaControlIntent} class.
+ * </p><p>
+ * Media item metadata is described as a bundle of key/value pairs as defined
+ * in this class.  The documentation specifies the type of value associated
+ * with each key.
+ * </p><p>
+ * An application may specify additional custom metadata keys but there is no guarantee
+ * that they will be recognized by the destination.
+ * </p>
+ */
+public final class MediaItemMetadata {
+    /*
+     * Note: MediaMetadataRetriever also defines a collection of metadata keys that can be
+     * retrieved from a content stream although the representation is somewhat different here
+     * since we are sending the data to a remote endpoint.
+     */
+
+    private MediaItemMetadata() {
+    }
+
+    /**
+     * String key: Album artist name.
+     * <p>
+     * The value is a string suitable for display.
+     * </p>
+     */
+    public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+    /**
+     * String key: Album title.
+     * <p>
+     * The value is a string suitable for display.
+     * </p>
+     */
+    public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+
+    /**
+     * String key: Artwork Uri.
+     * <p>
+     * The value is a string URI for an image file associated with the media item,
+     * such as album or cover art.
+     * </p>
+     */
+    public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+
+    /**
+     * String key: Artist name.
+     * <p>
+     * The value is a string suitable for display.
+     * </p>
+     */
+    public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+
+    /**
+     * String key: Author name.
+     * <p>
+     * The value is a string suitable for display.
+     * </p>
+     */
+    public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+
+    /**
+     * String key: Composer name.
+     * <p>
+     * The value is a string suitable for display.
+     * </p>
+     */
+    public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+
+    /**
+     * String key: Track title.
+     * <p>
+     * The value is a string suitable for display.
+     * </p>
+     */
+    public static final String KEY_TITLE = "android.media.metadata.TITLE";
+
+    /**
+     * Integer key: Year of publication.
+     * <p>
+     * The value is an integer year number.
+     * </p>
+     */
+    public static final String KEY_YEAR = "android.media.metadata.YEAR";
+
+    /**
+     * Integer key: Track number (such as a track on a CD).
+     * <p>
+     * The value is a one-based integer track number.
+     * </p>
+     */
+    public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+
+    /**
+     * Integer key: Disc number within a collection.
+     * <p>
+     * The value is a one-based integer disc number.
+     * </p>
+     */
+    public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+
+    /**
+     * Long key: Item playback duration in milliseconds.
+     * <p>
+     * The value is a <code>long</code> number of milliseconds.
+     * </p><p>
+     * The duration metadata is only a hint to enable a remote media player to
+     * guess the duration of the content before it actually opens the media stream.
+     * The remote media player should still determine the actual content duration from
+     * the media stream itself independent of the value that may be specified by this key.
+     * </p>
+     */
+    public static final String KEY_DURATION = "android.media.metadata.DURATION";
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemStatus.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemStatus.java
new file mode 100644
index 0000000..90ea2d5
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemStatus.java
@@ -0,0 +1,392 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.app.PendingIntent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.v4.util.TimeUtils;
+
+/**
+ * Describes the playback status of a media item.
+ * <p>
+ * This class is part of the remote playback protocol described by the
+ * {@link MediaControlIntent MediaControlIntent} class.
+ * </p><p>
+ * As a media item is played, it transitions through a sequence of states including:
+ * {@link #PLAYBACK_STATE_PENDING pending}, {@link #PLAYBACK_STATE_BUFFERING buffering},
+ * {@link #PLAYBACK_STATE_PLAYING playing}, {@link #PLAYBACK_STATE_PAUSED paused},
+ * {@link #PLAYBACK_STATE_FINISHED finished}, {@link #PLAYBACK_STATE_CANCELED canceled},
+ * {@link #PLAYBACK_STATE_INVALIDATED invalidated}, and
+ * {@link #PLAYBACK_STATE_ERROR error}.  Refer to the documentation of each state
+ * for an explanation of its meaning.
+ * </p><p>
+ * While the item is playing, the playback status may also include progress information
+ * about the {@link #getContentPosition content position} and
+ * {@link #getContentDuration content duration} although not all route destinations
+ * will report it.
+ * </p><p>
+ * To monitor playback status, the application should supply a {@link PendingIntent} to use as the
+ * {@link MediaControlIntent#EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receiver}
+ * for a given {@link MediaControlIntent#ACTION_PLAY playback request}.  Note that
+ * the status update receiver will only be invoked for major status changes such as a
+ * transition from playing to finished.
+ * </p><p class="note">
+ * The status update receiver will not be invoked for minor progress updates such as
+ * changes to playback position or duration.  If the application wants to monitor
+ * playback progress, then it must use the
+ * {@link MediaControlIntent#ACTION_GET_STATUS get status request} to poll for changes
+ * periodically and estimate the playback position while playing.  Note that there may
+ * be a significant power impact to polling so the application is advised only
+ * to poll when the screen is on and never more than about once every 5 seconds or so.
+ * </p><p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ */
+public final class MediaItemStatus {
+    static final String KEY_TIMESTAMP = "timestamp";
+    static final String KEY_PLAYBACK_STATE = "playbackState";
+    static final String KEY_CONTENT_POSITION = "contentPosition";
+    static final String KEY_CONTENT_DURATION = "contentDuration";
+    static final String KEY_EXTRAS = "extras";
+
+    final Bundle mBundle;
+
+    /**
+     * Playback state: Pending.
+     * <p>
+     * Indicates that the media item has not yet started playback but will be played eventually.
+     * </p>
+     */
+    public static final int PLAYBACK_STATE_PENDING = 0;
+
+    /**
+     * Playback state: Playing.
+     * <p>
+     * Indicates that the media item is currently playing.
+     * </p>
+     */
+    public static final int PLAYBACK_STATE_PLAYING = 1;
+
+    /**
+     * Playback state: Paused.
+     * <p>
+     * Indicates that playback of the media item has been paused.  Playback can be
+     * resumed using the {@link MediaControlIntent#ACTION_RESUME resume} action.
+     * </p>
+     */
+    public static final int PLAYBACK_STATE_PAUSED = 2;
+
+    /**
+     * Playback state: Buffering or seeking to a new position.
+     * <p>
+     * Indicates that the media item has been temporarily interrupted
+     * to fetch more content.  Playback will continue automatically
+     * when enough content has been buffered.
+     * </p>
+     */
+    public static final int PLAYBACK_STATE_BUFFERING = 3;
+
+    /**
+     * Playback state: Finished.
+     * <p>
+     * Indicates that the media item played to the end of the content and finished normally.
+     * </p><p>
+     * A finished media item cannot be resumed.  To play the content again, the application
+     * must send a new {@link MediaControlIntent#ACTION_PLAY play} or
+     * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action.
+     * </p>
+     */
+    public static final int PLAYBACK_STATE_FINISHED = 4;
+
+    /**
+     * Playback state: Canceled.
+     * <p>
+     * Indicates that the media item was explicitly removed from the queue by the
+     * application.  Items may be canceled and removed from the queue using
+     * the {@link MediaControlIntent#ACTION_REMOVE remove} or
+     * {@link MediaControlIntent#ACTION_STOP stop} action or by issuing
+     * another {@link MediaControlIntent#ACTION_PLAY play} action that has the
+     * side-effect of clearing the queue.
+     * </p><p>
+     * A canceled media item cannot be resumed.  To play the content again, the
+     * application must send a new {@link MediaControlIntent#ACTION_PLAY play} or
+     * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action.
+     * </p>
+     */
+    public static final int PLAYBACK_STATE_CANCELED = 5;
+
+    /**
+     * Playback state: Invalidated.
+     * <p>
+     * Indicates that the media item was invalidated permanently and involuntarily.
+     * This state is used to indicate that the media item was invalidated and removed
+     * from the queue because the session to which it belongs was invalidated
+     * (typically by another application taking control of the route).
+     * </p><p>
+     * When invalidation occurs, the application should generally wait for the user
+     * to perform an explicit action, such as clicking on a play button in the UI,
+     * before creating a new media session to avoid unnecessarily interrupting
+     * another application that may have just started using the route.
+     * </p><p>
+     * An invalidated media item cannot be resumed.  To play the content again, the application
+     * must send a new {@link MediaControlIntent#ACTION_PLAY play} or
+     * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action.
+     * </p>
+     */
+    public static final int PLAYBACK_STATE_INVALIDATED = 6;
+
+    /**
+     * Playback state: Playback halted or aborted due to an error.
+     * <p>
+     * Examples of errors are no network connectivity when attempting to retrieve content
+     * from a server, or expired user credentials when trying to play subscription-based
+     * content.
+     * </p><p>
+     * A media item in the error state cannot be resumed.  To play the content again,
+     * the application must send a new {@link MediaControlIntent#ACTION_PLAY play} or
+     * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action.
+     * </p>
+     */
+    public static final int PLAYBACK_STATE_ERROR = 7;
+
+    /**
+     * Integer extra: HTTP status code.
+     * <p>
+     * Specifies the HTTP status code that was encountered when the content
+     * was requested after all redirects were followed.  This key only needs to
+     * specified when the content uri uses the HTTP or HTTPS scheme and an error
+     * occurred.  This key may be omitted if the content was able to be played
+     * successfully; there is no need to report a 200 (OK) status code.
+     * </p><p>
+     * The value is an integer HTTP status code, such as 401 (Unauthorized),
+     * 404 (Not Found), or 500 (Server Error), or 0 if none.
+     * </p>
+     */
+    public static final String EXTRA_HTTP_STATUS_CODE =
+            "android.media.status.extra.HTTP_STATUS_CODE";
+
+    /**
+     * Bundle extra: HTTP response headers.
+     * <p>
+     * Specifies the HTTP response headers that were returned when the content was
+     * requested from the network.  The headers may include additional information
+     * about the content or any errors conditions that were encountered while
+     * trying to fetch the content.
+     * </p><p>
+     * The value is a {@link android.os.Bundle} of string based key-value pairs
+     * that describe the HTTP response headers.
+     * </p>
+     */
+    public static final String EXTRA_HTTP_RESPONSE_HEADERS =
+            "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+
+    MediaItemStatus(Bundle bundle) {
+        mBundle = bundle;
+    }
+
+    /**
+     * Gets the timestamp associated with the status information in
+     * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
+     *
+     * @return The status timestamp in the {@link SystemClock#elapsedRealtime()} time base.
+     */
+    public long getTimestamp() {
+        return mBundle.getLong(KEY_TIMESTAMP);
+    }
+
+    /**
+     * Gets the playback state of the media item.
+     *
+     * @return The playback state.  One of {@link #PLAYBACK_STATE_PENDING},
+     * {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
+     * {@link #PLAYBACK_STATE_BUFFERING}, {@link #PLAYBACK_STATE_FINISHED},
+     * {@link #PLAYBACK_STATE_CANCELED}, {@link #PLAYBACK_STATE_INVALIDATED},
+     * or {@link #PLAYBACK_STATE_ERROR}.
+     */
+    public int getPlaybackState() {
+        return mBundle.getInt(KEY_PLAYBACK_STATE, PLAYBACK_STATE_ERROR);
+    }
+
+    /**
+     * Gets the content playback position as a long integer number of milliseconds
+     * from the beginning of the content.
+     *
+     * @return The content playback position in milliseconds, or -1 if unknown.
+     */
+    public long getContentPosition() {
+        return mBundle.getLong(KEY_CONTENT_POSITION, -1);
+    }
+
+    /**
+     * Gets the total duration of the content to be played as a long integer number of
+     * milliseconds.
+     *
+     * @return The content duration in milliseconds, or -1 if unknown.
+     */
+    public long getContentDuration() {
+        return mBundle.getLong(KEY_CONTENT_DURATION, -1);
+    }
+
+    /**
+     * Gets a bundle of extras for this status object.
+     * The extras will be ignored by the media router but they may be used
+     * by applications.
+     */
+    public Bundle getExtras() {
+        return mBundle.getBundle(KEY_EXTRAS);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        result.append("MediaItemStatus{ ");
+        result.append("timestamp=");
+        TimeUtils.formatDuration(SystemClock.elapsedRealtime() - getTimestamp(), result);
+        result.append(" ms ago");
+        result.append(", playbackState=").append(playbackStateToString(getPlaybackState()));
+        result.append(", contentPosition=").append(getContentPosition());
+        result.append(", contentDuration=").append(getContentDuration());
+        result.append(", extras=").append(getExtras());
+        result.append(" }");
+        return result.toString();
+    }
+
+    private static String playbackStateToString(int playbackState) {
+        switch (playbackState) {
+            case PLAYBACK_STATE_PENDING:
+                return "pending";
+            case PLAYBACK_STATE_BUFFERING:
+                return "buffering";
+            case PLAYBACK_STATE_PLAYING:
+                return "playing";
+            case PLAYBACK_STATE_PAUSED:
+                return "paused";
+            case PLAYBACK_STATE_FINISHED:
+                return "finished";
+            case PLAYBACK_STATE_CANCELED:
+                return "canceled";
+            case PLAYBACK_STATE_INVALIDATED:
+                return "invalidated";
+            case PLAYBACK_STATE_ERROR:
+                return "error";
+        }
+        return Integer.toString(playbackState);
+    }
+
+    /**
+     * Converts this object to a bundle for serialization.
+     *
+     * @return The contents of the object represented as a bundle.
+     */
+    public Bundle asBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Creates an instance from a bundle.
+     *
+     * @param bundle The bundle, or null if none.
+     * @return The new instance, or null if the bundle was null.
+     */
+    public static MediaItemStatus fromBundle(Bundle bundle) {
+        return bundle != null ? new MediaItemStatus(bundle) : null;
+    }
+
+    /**
+     * Builder for {@link MediaItemStatus media item status objects}.
+     */
+    public static final class Builder {
+        private final Bundle mBundle;
+
+        /**
+         * Creates a media item status builder using the current time as the
+         * reference timestamp.
+         *
+         * @param playbackState The item playback state.
+         */
+        public Builder(int playbackState) {
+            mBundle = new Bundle();
+            setTimestamp(SystemClock.elapsedRealtime());
+            setPlaybackState(playbackState);
+        }
+
+        /**
+         * Creates a media item status builder whose initial contents are
+         * copied from an existing status.
+         */
+        public Builder(MediaItemStatus status) {
+            if (status == null) {
+                throw new IllegalArgumentException("status must not be null");
+            }
+
+            mBundle = new Bundle(status.mBundle);
+        }
+
+        /**
+         * Sets the timestamp associated with the status information in
+         * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
+         */
+        public Builder setTimestamp(long elapsedRealtimeTimestamp) {
+            mBundle.putLong(KEY_TIMESTAMP, elapsedRealtimeTimestamp);
+            return this;
+        }
+
+        /**
+         * Sets the playback state of the media item.
+         */
+        public Builder setPlaybackState(int playbackState) {
+            mBundle.putInt(KEY_PLAYBACK_STATE, playbackState);
+            return this;
+        }
+
+        /**
+         * Sets the content playback position as a long integer number of milliseconds
+         * from the beginning of the content.
+         */
+        public Builder setContentPosition(long positionMilliseconds) {
+            mBundle.putLong(KEY_CONTENT_POSITION, positionMilliseconds);
+            return this;
+        }
+
+        /**
+         * Sets the total duration of the content to be played as a long integer number
+         * of milliseconds.
+         */
+        public Builder setContentDuration(long durationMilliseconds) {
+            mBundle.putLong(KEY_CONTENT_DURATION, durationMilliseconds);
+            return this;
+        }
+
+        /**
+         * Sets a bundle of extras for this status object.
+         * The extras will be ignored by the media router but they may be used
+         * by applications.
+         */
+        public Builder setExtras(Bundle extras) {
+            mBundle.putBundle(KEY_EXTRAS, extras);
+            return this;
+        }
+
+        /**
+         * Builds the {@link MediaItemStatus media item status object}.
+         */
+        public MediaItemStatus build() {
+            return new MediaItemStatus(mBundle);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDescriptor.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDescriptor.java
new file mode 100644
index 0000000..6bc84fc
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDescriptor.java
@@ -0,0 +1,693 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Describes the properties of a route.
+ * <p>
+ * Each route is uniquely identified by an opaque id string.  This token
+ * may take any form as long as it is unique within the media route provider.
+ * </p><p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ */
+public final class MediaRouteDescriptor {
+    static final String KEY_ID = "id";
+    static final String KEY_GROUP_MEMBER_IDS = "groupMemberIds";
+    static final String KEY_NAME = "name";
+    static final String KEY_DESCRIPTION = "status";
+    static final String KEY_ICON_URI = "iconUri";
+    static final String KEY_ENABLED = "enabled";
+    static final String KEY_CONNECTING = "connecting";
+    static final String KEY_CONNECTION_STATE = "connectionState";
+    static final String KEY_CONTROL_FILTERS = "controlFilters";
+    static final String KEY_PLAYBACK_TYPE = "playbackType";
+    static final String KEY_PLAYBACK_STREAM = "playbackStream";
+    static final String KEY_DEVICE_TYPE = "deviceType";
+    static final String KEY_VOLUME = "volume";
+    static final String KEY_VOLUME_MAX = "volumeMax";
+    static final String KEY_VOLUME_HANDLING = "volumeHandling";
+    static final String KEY_PRESENTATION_DISPLAY_ID = "presentationDisplayId";
+    static final String KEY_EXTRAS = "extras";
+    static final String KEY_CAN_DISCONNECT = "canDisconnect";
+    static final String KEY_SETTINGS_INTENT = "settingsIntent";
+    static final String KEY_MIN_CLIENT_VERSION = "minClientVersion";
+    static final String KEY_MAX_CLIENT_VERSION = "maxClientVersion";
+
+    final Bundle mBundle;
+    List<IntentFilter> mControlFilters;
+
+    MediaRouteDescriptor(Bundle bundle, List<IntentFilter> controlFilters) {
+        mBundle = bundle;
+        mControlFilters = controlFilters;
+    }
+
+    /**
+     * Gets the unique id of the route.
+     * <p>
+     * The route id associated with a route descriptor functions as a stable
+     * identifier for the route and must be unique among all routes offered
+     * by the provider.
+     * </p>
+     */
+    public String getId() {
+        return mBundle.getString(KEY_ID);
+    }
+
+    /**
+     * Gets the group member ids of the route.
+     * <p>
+     * A route descriptor that has one or more group member route ids
+     * represents a route group. A member route may belong to another group.
+     * </p>
+     * @hide
+     */
+    // @RestrictTo(LIBRARY_GROUP)
+    public List<String> getGroupMemberIds() {
+        return mBundle.getStringArrayList(KEY_GROUP_MEMBER_IDS);
+    }
+
+    /**
+     * Gets the user-visible name of the route.
+     * <p>
+     * The route name identifies the destination represented by the route.
+     * It may be a user-supplied name, an alias, or device serial number.
+     * </p>
+     */
+    public String getName() {
+        return mBundle.getString(KEY_NAME);
+    }
+
+    /**
+     * Gets the user-visible description of the route.
+     * <p>
+     * The route description describes the kind of destination represented by the route.
+     * It may be a user-supplied string, a model number or brand of device.
+     * </p>
+     */
+    public String getDescription() {
+        return mBundle.getString(KEY_DESCRIPTION);
+    }
+
+    /**
+     * Gets the URI of the icon representing this route.
+     * <p>
+     * This icon will be used in picker UIs if available.
+     * </p>
+     */
+    public Uri getIconUri() {
+        String iconUri = mBundle.getString(KEY_ICON_URI);
+        return iconUri == null ? null : Uri.parse(iconUri);
+    }
+
+    /**
+     * Gets whether the route is enabled.
+     */
+    public boolean isEnabled() {
+        return mBundle.getBoolean(KEY_ENABLED, true);
+    }
+
+    /**
+     * Gets whether the route is connecting.
+     * @deprecated Use {@link #getConnectionState} instead
+     */
+    @Deprecated
+    public boolean isConnecting() {
+        return mBundle.getBoolean(KEY_CONNECTING, false);
+    }
+
+    /**
+     * Gets the connection state of the route.
+     *
+     * @return The connection state of this route:
+     * {@link MediaRouter.RouteInfo#CONNECTION_STATE_DISCONNECTED},
+     * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTING}, or
+     * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTED}.
+     */
+    public int getConnectionState() {
+        return mBundle.getInt(KEY_CONNECTION_STATE,
+                MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED);
+    }
+
+    /**
+     * Gets whether the route can be disconnected without stopping playback.
+     * <p>
+     * The route can normally be disconnected without stopping playback when
+     * the destination device on the route is connected to two or more source
+     * devices. The route provider should update the route immediately when the
+     * number of connected devices changes.
+     * </p><p>
+     * To specify that the route should disconnect without stopping use
+     * {@link MediaRouter#unselect(int)} with
+     * {@link MediaRouter#UNSELECT_REASON_DISCONNECTED}.
+     * </p>
+     */
+    public boolean canDisconnectAndKeepPlaying() {
+        return mBundle.getBoolean(KEY_CAN_DISCONNECT, false);
+    }
+
+    /**
+     * Gets an {@link IntentSender} for starting a settings activity for this
+     * route. The activity may have specific route settings or general settings
+     * for the connected device or route provider.
+     *
+     * @return An {@link IntentSender} to start a settings activity.
+     */
+    public IntentSender getSettingsActivity() {
+        return mBundle.getParcelable(KEY_SETTINGS_INTENT);
+    }
+
+    /**
+     * Gets the route's {@link MediaControlIntent media control intent} filters.
+     */
+    public List<IntentFilter> getControlFilters() {
+        ensureControlFilters();
+        return mControlFilters;
+    }
+
+    void ensureControlFilters() {
+        if (mControlFilters == null) {
+            mControlFilters = mBundle.<IntentFilter>getParcelableArrayList(KEY_CONTROL_FILTERS);
+            if (mControlFilters == null) {
+                mControlFilters = Collections.<IntentFilter>emptyList();
+            }
+        }
+    }
+
+    /**
+     * Gets the type of playback associated with this route.
+     *
+     * @return The type of playback associated with this route:
+     * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_LOCAL} or
+     * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_REMOTE}.
+     */
+    public int getPlaybackType() {
+        return mBundle.getInt(KEY_PLAYBACK_TYPE, MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE);
+    }
+
+    /**
+     * Gets the route's playback stream.
+     */
+    public int getPlaybackStream() {
+        return mBundle.getInt(KEY_PLAYBACK_STREAM, -1);
+    }
+
+    /**
+     * Gets the type of the receiver device associated with this route.
+     *
+     * @return The type of the receiver device associated with this route:
+     * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or
+     * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}.
+     */
+    public int getDeviceType() {
+        return mBundle.getInt(KEY_DEVICE_TYPE);
+    }
+
+    /**
+     * Gets the route's current volume, or 0 if unknown.
+     */
+    public int getVolume() {
+        return mBundle.getInt(KEY_VOLUME);
+    }
+
+    /**
+     * Gets the route's maximum volume, or 0 if unknown.
+     */
+    public int getVolumeMax() {
+        return mBundle.getInt(KEY_VOLUME_MAX);
+    }
+
+    /**
+     * Gets information about how volume is handled on the route.
+     *
+     * @return How volume is handled on the route:
+     * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_FIXED} or
+     * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_VARIABLE}.
+     */
+    public int getVolumeHandling() {
+        return mBundle.getInt(KEY_VOLUME_HANDLING,
+                MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED);
+    }
+
+    /**
+     * Gets the route's presentation display id, or -1 if none.
+     */
+    public int getPresentationDisplayId() {
+        return mBundle.getInt(
+                KEY_PRESENTATION_DISPLAY_ID, MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE);
+    }
+
+    /**
+     * Gets a bundle of extras for this route descriptor.
+     * The extras will be ignored by the media router but they may be used
+     * by applications.
+     */
+    public Bundle getExtras() {
+        return mBundle.getBundle(KEY_EXTRAS);
+    }
+
+    /**
+     * Gets the minimum client version required for this route.
+     * @hide
+     */
+    // @RestrictTo(LIBRARY_GROUP)
+    public int getMinClientVersion() {
+        return mBundle.getInt(KEY_MIN_CLIENT_VERSION,
+                MediaRouteProviderProtocol.CLIENT_VERSION_START);
+    }
+
+    /**
+     * Gets the maximum client version required for this route.
+     * @hide
+     */
+    // @RestrictTo(LIBRARY_GROUP)
+    public int getMaxClientVersion() {
+        return mBundle.getInt(KEY_MAX_CLIENT_VERSION, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns true if the route descriptor has all of the required fields.
+     */
+    public boolean isValid() {
+        ensureControlFilters();
+        if (TextUtils.isEmpty(getId())
+                || TextUtils.isEmpty(getName())
+                || mControlFilters.contains(null)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        result.append("MediaRouteDescriptor{ ");
+        result.append("id=").append(getId());
+        result.append(", groupMemberIds=").append(getGroupMemberIds());
+        result.append(", name=").append(getName());
+        result.append(", description=").append(getDescription());
+        result.append(", iconUri=").append(getIconUri());
+        result.append(", isEnabled=").append(isEnabled());
+        result.append(", isConnecting=").append(isConnecting());
+        result.append(", connectionState=").append(getConnectionState());
+        result.append(", controlFilters=").append(Arrays.toString(getControlFilters().toArray()));
+        result.append(", playbackType=").append(getPlaybackType());
+        result.append(", playbackStream=").append(getPlaybackStream());
+        result.append(", deviceType=").append(getDeviceType());
+        result.append(", volume=").append(getVolume());
+        result.append(", volumeMax=").append(getVolumeMax());
+        result.append(", volumeHandling=").append(getVolumeHandling());
+        result.append(", presentationDisplayId=").append(getPresentationDisplayId());
+        result.append(", extras=").append(getExtras());
+        result.append(", isValid=").append(isValid());
+        result.append(", minClientVersion=").append(getMinClientVersion());
+        result.append(", maxClientVersion=").append(getMaxClientVersion());
+        result.append(" }");
+        return result.toString();
+    }
+
+    /**
+     * Converts this object to a bundle for serialization.
+     *
+     * @return The contents of the object represented as a bundle.
+     */
+    public Bundle asBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Creates an instance from a bundle.
+     *
+     * @param bundle The bundle, or null if none.
+     * @return The new instance, or null if the bundle was null.
+     */
+    public static MediaRouteDescriptor fromBundle(Bundle bundle) {
+        return bundle != null ? new MediaRouteDescriptor(bundle, null) : null;
+    }
+
+    /**
+     * Builder for {@link MediaRouteDescriptor media route descriptors}.
+     */
+    public static final class Builder {
+        private final Bundle mBundle;
+        private ArrayList<String> mGroupMemberIds;
+        private ArrayList<IntentFilter> mControlFilters;
+
+        /**
+         * Creates a media route descriptor builder.
+         *
+         * @param id The unique id of the route.
+         * @param name The user-visible name of the route.
+         */
+        public Builder(String id, String name) {
+            mBundle = new Bundle();
+            setId(id);
+            setName(name);
+        }
+
+        /**
+         * Creates a media route descriptor builder whose initial contents are
+         * copied from an existing descriptor.
+         */
+        public Builder(MediaRouteDescriptor descriptor) {
+            if (descriptor == null) {
+                throw new IllegalArgumentException("descriptor must not be null");
+            }
+
+            mBundle = new Bundle(descriptor.mBundle);
+
+            descriptor.ensureControlFilters();
+            if (!descriptor.mControlFilters.isEmpty()) {
+                mControlFilters = new ArrayList<IntentFilter>(descriptor.mControlFilters);
+            }
+        }
+
+        /**
+         * Sets the unique id of the route.
+         * <p>
+         * The route id associated with a route descriptor functions as a stable
+         * identifier for the route and must be unique among all routes offered
+         * by the provider.
+         * </p>
+         */
+        public Builder setId(String id) {
+            mBundle.putString(KEY_ID, id);
+            return this;
+        }
+
+        /**
+         * Adds a group member id of the route.
+         * <p>
+         * A route descriptor that has one or more group member route ids
+         * represents a route group. A member route may belong to another group.
+         * </p>
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public Builder addGroupMemberId(String groupMemberId) {
+            if (TextUtils.isEmpty(groupMemberId)) {
+                throw new IllegalArgumentException("groupMemberId must not be empty");
+            }
+
+            if (mGroupMemberIds == null) {
+                mGroupMemberIds = new ArrayList<>();
+            }
+            if (!mGroupMemberIds.contains(groupMemberId)) {
+                mGroupMemberIds.add(groupMemberId);
+            }
+            return this;
+        }
+
+        /**
+         * Adds a list of group member ids of the route.
+         * <p>
+         * A route descriptor that has one or more group member route ids
+         * represents a route group. A member route may belong to another group.
+         * </p>
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public Builder addGroupMemberIds(Collection<String> groupMemberIds) {
+            if (groupMemberIds == null) {
+                throw new IllegalArgumentException("groupMemberIds must not be null");
+            }
+
+            if (!groupMemberIds.isEmpty()) {
+                for (String groupMemberId : groupMemberIds) {
+                    addGroupMemberId(groupMemberId);
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Sets the user-visible name of the route.
+         * <p>
+         * The route name identifies the destination represented by the route.
+         * It may be a user-supplied name, an alias, or device serial number.
+         * </p>
+         */
+        public Builder setName(String name) {
+            mBundle.putString(KEY_NAME, name);
+            return this;
+        }
+
+        /**
+         * Sets the user-visible description of the route.
+         * <p>
+         * The route description describes the kind of destination represented by the route.
+         * It may be a user-supplied string, a model number or brand of device.
+         * </p>
+         */
+        public Builder setDescription(String description) {
+            mBundle.putString(KEY_DESCRIPTION, description);
+            return this;
+        }
+
+        /**
+         * Sets the URI of the icon representing this route.
+         * <p>
+         * This icon will be used in picker UIs if available.
+         * </p><p>
+         * The URI must be one of the following formats:
+         * <ul>
+         * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+         * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+         * </li>
+         * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+         * </ul>
+         * </p>
+         */
+        public Builder setIconUri(Uri iconUri) {
+            if (iconUri == null) {
+                throw new IllegalArgumentException("iconUri must not be null");
+            }
+            mBundle.putString(KEY_ICON_URI, iconUri.toString());
+            return this;
+        }
+
+        /**
+         * Sets whether the route is enabled.
+         * <p>
+         * Disabled routes represent routes that a route provider knows about, such as paired
+         * Wifi Display receivers, but that are not currently available for use.
+         * </p>
+         */
+        public Builder setEnabled(boolean enabled) {
+            mBundle.putBoolean(KEY_ENABLED, enabled);
+            return this;
+        }
+
+        /**
+         * Sets whether the route is in the process of connecting and is not yet
+         * ready for use.
+         * @deprecated Use {@link #setConnectionState} instead.
+         */
+        @Deprecated
+        public Builder setConnecting(boolean connecting) {
+            mBundle.putBoolean(KEY_CONNECTING, connecting);
+            return this;
+        }
+
+        /**
+         * Sets the route's connection state.
+         *
+         * @param connectionState The connection state of the route:
+         * {@link MediaRouter.RouteInfo#CONNECTION_STATE_DISCONNECTED},
+         * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTING}, or
+         * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTED}.
+         */
+        public Builder setConnectionState(int connectionState) {
+            mBundle.putInt(KEY_CONNECTION_STATE, connectionState);
+            return this;
+        }
+
+        /**
+         * Sets whether the route can be disconnected without stopping playback.
+         */
+        public Builder setCanDisconnect(boolean canDisconnect) {
+            mBundle.putBoolean(KEY_CAN_DISCONNECT, canDisconnect);
+            return this;
+        }
+
+        /**
+         * Sets an intent sender for launching the settings activity for this
+         * route.
+         */
+        public Builder setSettingsActivity(IntentSender is) {
+            mBundle.putParcelable(KEY_SETTINGS_INTENT, is);
+            return this;
+        }
+
+        /**
+         * Adds a {@link MediaControlIntent media control intent} filter for the route.
+         */
+        public Builder addControlFilter(IntentFilter filter) {
+            if (filter == null) {
+                throw new IllegalArgumentException("filter must not be null");
+            }
+
+            if (mControlFilters == null) {
+                mControlFilters = new ArrayList<IntentFilter>();
+            }
+            if (!mControlFilters.contains(filter)) {
+                mControlFilters.add(filter);
+            }
+            return this;
+        }
+
+        /**
+         * Adds a list of {@link MediaControlIntent media control intent} filters for the route.
+         */
+        public Builder addControlFilters(Collection<IntentFilter> filters) {
+            if (filters == null) {
+                throw new IllegalArgumentException("filters must not be null");
+            }
+
+            if (!filters.isEmpty()) {
+                for (IntentFilter filter : filters) {
+                    addControlFilter(filter);
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Sets the route's playback type.
+         *
+         * @param playbackType The playback type of the route:
+         * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_LOCAL} or
+         * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_REMOTE}.
+         */
+        public Builder setPlaybackType(int playbackType) {
+            mBundle.putInt(KEY_PLAYBACK_TYPE, playbackType);
+            return this;
+        }
+
+        /**
+         * Sets the route's playback stream.
+         */
+        public Builder setPlaybackStream(int playbackStream) {
+            mBundle.putInt(KEY_PLAYBACK_STREAM, playbackStream);
+            return this;
+        }
+
+        /**
+         * Sets the route's receiver device type.
+         *
+         * @param deviceType The receive device type of the route:
+         * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or
+         * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}.
+         */
+        public Builder setDeviceType(int deviceType) {
+            mBundle.putInt(KEY_DEVICE_TYPE, deviceType);
+            return this;
+        }
+
+        /**
+         * Sets the route's current volume, or 0 if unknown.
+         */
+        public Builder setVolume(int volume) {
+            mBundle.putInt(KEY_VOLUME, volume);
+            return this;
+        }
+
+        /**
+         * Sets the route's maximum volume, or 0 if unknown.
+         */
+        public Builder setVolumeMax(int volumeMax) {
+            mBundle.putInt(KEY_VOLUME_MAX, volumeMax);
+            return this;
+        }
+
+        /**
+         * Sets the route's volume handling.
+         *
+         * @param volumeHandling how volume is handled on the route:
+         * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_FIXED} or
+         * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_VARIABLE}.
+         */
+        public Builder setVolumeHandling(int volumeHandling) {
+            mBundle.putInt(KEY_VOLUME_HANDLING, volumeHandling);
+            return this;
+        }
+
+        /**
+         * Sets the route's presentation display id, or -1 if none.
+         */
+        public Builder setPresentationDisplayId(int presentationDisplayId) {
+            mBundle.putInt(KEY_PRESENTATION_DISPLAY_ID, presentationDisplayId);
+            return this;
+        }
+
+        /**
+         * Sets a bundle of extras for this route descriptor.
+         * The extras will be ignored by the media router but they may be used
+         * by applications.
+         */
+        public Builder setExtras(Bundle extras) {
+            mBundle.putBundle(KEY_EXTRAS, extras);
+            return this;
+        }
+
+        /**
+         * Sets the route's minimum client version.
+         * A router whose version is lower than this will not be able to connect to this route.
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public Builder setMinClientVersion(int minVersion) {
+            mBundle.putInt(KEY_MIN_CLIENT_VERSION, minVersion);
+            return this;
+        }
+
+        /**
+         * Sets the route's maximum client version.
+         * A router whose version is higher than this will not be able to connect to this route.
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public Builder setMaxClientVersion(int maxVersion) {
+            mBundle.putInt(KEY_MAX_CLIENT_VERSION, maxVersion);
+            return this;
+        }
+
+        /**
+         * Builds the {@link MediaRouteDescriptor media route descriptor}.
+         */
+        public MediaRouteDescriptor build() {
+            if (mControlFilters != null) {
+                mBundle.putParcelableArrayList(KEY_CONTROL_FILTERS, mControlFilters);
+            }
+            if (mGroupMemberIds != null) {
+                mBundle.putStringArrayList(KEY_GROUP_MEMBER_IDS, mGroupMemberIds);
+            }
+            return new MediaRouteDescriptor(mBundle, mControlFilters);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDiscoveryRequest.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDiscoveryRequest.java
new file mode 100644
index 0000000..039627f
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDiscoveryRequest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.os.Bundle;
+
+/**
+ * Describes the kinds of routes that the media router would like to discover
+ * and whether to perform active scanning.
+ * <p>
+ * This object is immutable once created.
+ * </p>
+ */
+public final class MediaRouteDiscoveryRequest {
+    private static final String KEY_SELECTOR = "selector";
+    private static final String KEY_ACTIVE_SCAN = "activeScan";
+
+    private final Bundle mBundle;
+    private MediaRouteSelector mSelector;
+
+    /**
+     * Creates a media route discovery request.
+     *
+     * @param selector The route selector that specifies the kinds of routes to discover.
+     * @param activeScan True if active scanning should be performed.
+     */
+    public MediaRouteDiscoveryRequest(MediaRouteSelector selector, boolean activeScan) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+
+        mBundle = new Bundle();
+        mSelector = selector;
+        mBundle.putBundle(KEY_SELECTOR, selector.asBundle());
+        mBundle.putBoolean(KEY_ACTIVE_SCAN, activeScan);
+    }
+
+    private MediaRouteDiscoveryRequest(Bundle bundle) {
+        mBundle = bundle;
+    }
+
+    /**
+     * Gets the route selector that specifies the kinds of routes to discover.
+     */
+    public MediaRouteSelector getSelector() {
+        ensureSelector();
+        return mSelector;
+    }
+
+    private void ensureSelector() {
+        if (mSelector == null) {
+            mSelector = MediaRouteSelector.fromBundle(mBundle.getBundle(KEY_SELECTOR));
+            if (mSelector == null) {
+                mSelector = MediaRouteSelector.EMPTY;
+            }
+        }
+    }
+
+    /**
+     * Returns true if active scanning should be performed.
+     *
+     * @see MediaRouter#CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
+     */
+    public boolean isActiveScan() {
+        return mBundle.getBoolean(KEY_ACTIVE_SCAN);
+    }
+
+    /**
+     * Returns true if the discovery request has all of the required fields.
+     */
+    public boolean isValid() {
+        ensureSelector();
+        return mSelector.isValid();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof MediaRouteDiscoveryRequest) {
+            MediaRouteDiscoveryRequest other = (MediaRouteDiscoveryRequest)o;
+            return getSelector().equals(other.getSelector())
+                    && isActiveScan() == other.isActiveScan();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return getSelector().hashCode() ^ (isActiveScan() ? 1 : 0);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        result.append("DiscoveryRequest{ selector=").append(getSelector());
+        result.append(", activeScan=").append(isActiveScan());
+        result.append(", isValid=").append(isValid());
+        result.append(" }");
+        return result.toString();
+    }
+
+    /**
+     * Converts this object to a bundle for serialization.
+     *
+     * @return The contents of the object represented as a bundle.
+     */
+    public Bundle asBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Creates an instance from a bundle.
+     *
+     * @param bundle The bundle, or null if none.
+     * @return The new instance, or null if the bundle was null.
+     */
+    public static MediaRouteDiscoveryRequest fromBundle(Bundle bundle) {
+        return bundle != null ? new MediaRouteDiscoveryRequest(bundle) : null;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProvider.java
new file mode 100644
index 0000000..91a2e1a
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProvider.java
@@ -0,0 +1,447 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.util.ObjectsCompat;
+
+import com.android.support.mediarouter.media.MediaRouter.ControlRequestCallback;
+
+/**
+ * Media route providers are used to publish additional media routes for
+ * use within an application.  Media route providers may also be declared
+ * as a service to publish additional media routes to all applications
+ * in the system.
+ * <p>
+ * The purpose of a media route provider is to discover media routes that satisfy
+ * the criteria specified by the current {@link MediaRouteDiscoveryRequest} and publish a
+ * {@link MediaRouteProviderDescriptor} with information about each route by calling
+ * {@link #setDescriptor} to notify the currently registered {@link Callback}.
+ * </p><p>
+ * The provider should watch for changes to the discovery request by implementing
+ * {@link #onDiscoveryRequestChanged} and updating the set of routes that it is
+ * attempting to discover.  It should also handle route control requests such
+ * as volume changes or {@link MediaControlIntent media control intents}
+ * by implementing {@link #onCreateRouteController} to return a {@link RouteController}
+ * for a particular route.
+ * </p><p>
+ * A media route provider may be used privately within the scope of a single
+ * application process by calling {@link MediaRouter#addProvider MediaRouter.addProvider}
+ * to add it to the local {@link MediaRouter}.  A media route provider may also be made
+ * available globally to all applications by registering a {@link MediaRouteProviderService}
+ * in the provider's manifest.  When the media route provider is registered
+ * as a service, all applications that use the media router API will be able to
+ * discover and used the provider's routes without having to install anything else.
+ * </p><p>
+ * This object must only be accessed on the main thread.
+ * </p>
+ */
+public abstract class MediaRouteProvider {
+    static final int MSG_DELIVER_DESCRIPTOR_CHANGED = 1;
+    static final int MSG_DELIVER_DISCOVERY_REQUEST_CHANGED = 2;
+
+    private final Context mContext;
+    private final ProviderMetadata mMetadata;
+    private final ProviderHandler mHandler = new ProviderHandler();
+
+    private Callback mCallback;
+
+    private MediaRouteDiscoveryRequest mDiscoveryRequest;
+    private boolean mPendingDiscoveryRequestChange;
+
+    private MediaRouteProviderDescriptor mDescriptor;
+    private boolean mPendingDescriptorChange;
+
+    /**
+     * Creates a media route provider.
+     *
+     * @param context The context.
+     */
+    public MediaRouteProvider(@NonNull Context context) {
+        this(context, null);
+    }
+
+    MediaRouteProvider(Context context, ProviderMetadata metadata) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        mContext = context;
+        if (metadata == null) {
+            mMetadata = new ProviderMetadata(new ComponentName(context, getClass()));
+        } else {
+            mMetadata = metadata;
+        }
+    }
+
+    /**
+     * Gets the context of the media route provider.
+     */
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Gets the provider's handler which is associated with the main thread.
+     */
+    public final Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * Gets some metadata about the provider's implementation.
+     */
+    public final ProviderMetadata getMetadata() {
+        return mMetadata;
+    }
+
+    /**
+     * Sets a callback to invoke when the provider's descriptor changes.
+     *
+     * @param callback The callback to use, or null if none.
+     */
+    public final void setCallback(@Nullable Callback callback) {
+        MediaRouter.checkCallingThread();
+        mCallback = callback;
+    }
+
+    /**
+     * Gets the current discovery request which informs the provider about the
+     * kinds of routes to discover and whether to perform active scanning.
+     *
+     * @return The current discovery request, or null if no discovery is needed at this time.
+     *
+     * @see #onDiscoveryRequestChanged
+     */
+    @Nullable
+    public final MediaRouteDiscoveryRequest getDiscoveryRequest() {
+        return mDiscoveryRequest;
+    }
+
+    /**
+     * Sets a discovery request to inform the provider about the kinds of
+     * routes that its clients would like to discover and whether to perform active scanning.
+     *
+     * @param request The discovery request, or null if no discovery is needed at this time.
+     *
+     * @see #onDiscoveryRequestChanged
+     */
+    public final void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
+        MediaRouter.checkCallingThread();
+
+        if (ObjectsCompat.equals(mDiscoveryRequest, request)) {
+            return;
+        }
+
+        mDiscoveryRequest = request;
+        if (!mPendingDiscoveryRequestChange) {
+            mPendingDiscoveryRequestChange = true;
+            mHandler.sendEmptyMessage(MSG_DELIVER_DISCOVERY_REQUEST_CHANGED);
+        }
+    }
+
+    void deliverDiscoveryRequestChanged() {
+        mPendingDiscoveryRequestChange = false;
+        onDiscoveryRequestChanged(mDiscoveryRequest);
+    }
+
+    /**
+     * Called by the media router when the {@link MediaRouteDiscoveryRequest discovery request}
+     * has changed.
+     * <p>
+     * Whenever an applications calls {@link MediaRouter#addCallback} to register
+     * a callback, it also provides a selector to specify the kinds of routes that
+     * it is interested in.  The media router combines all of these selectors together
+     * to generate a {@link MediaRouteDiscoveryRequest} and notifies each provider when a change
+     * occurs by calling {@link #setDiscoveryRequest} which posts a message to invoke
+     * this method asynchronously.
+     * </p><p>
+     * The provider should examine the {@link MediaControlIntent media control categories}
+     * in the discovery request's {@link MediaRouteSelector selector} to determine what
+     * kinds of routes it should try to discover and whether it should perform active
+     * or passive scans.  In many cases, the provider may be able to save power by
+     * determining that the selector does not contain any categories that it supports
+     * and it can therefore avoid performing any scans at all.
+     * </p>
+     *
+     * @param request The new discovery request, or null if no discovery is needed at this time.
+     *
+     * @see MediaRouter#addCallback
+     */
+    public void onDiscoveryRequestChanged(@Nullable MediaRouteDiscoveryRequest request) {
+    }
+
+    /**
+     * Gets the provider's descriptor.
+     * <p>
+     * The descriptor describes the state of the media route provider and
+     * the routes that it publishes.  Watch for changes to the descriptor
+     * by registering a {@link Callback callback} with {@link #setCallback}.
+     * </p>
+     *
+     * @return The media route provider descriptor, or null if none.
+     *
+     * @see Callback#onDescriptorChanged
+     */
+    @Nullable
+    public final MediaRouteProviderDescriptor getDescriptor() {
+        return mDescriptor;
+    }
+
+    /**
+     * Sets the provider's descriptor.
+     * <p>
+     * The provider must call this method to notify the currently registered
+     * {@link Callback callback} about the change to the provider's descriptor.
+     * </p>
+     *
+     * @param descriptor The updated route provider descriptor, or null if none.
+     *
+     * @see Callback#onDescriptorChanged
+     */
+    public final void setDescriptor(@Nullable MediaRouteProviderDescriptor descriptor) {
+        MediaRouter.checkCallingThread();
+
+        if (mDescriptor != descriptor) {
+            mDescriptor = descriptor;
+            if (!mPendingDescriptorChange) {
+                mPendingDescriptorChange = true;
+                mHandler.sendEmptyMessage(MSG_DELIVER_DESCRIPTOR_CHANGED);
+            }
+        }
+    }
+
+    void deliverDescriptorChanged() {
+        mPendingDescriptorChange = false;
+
+        if (mCallback != null) {
+            mCallback.onDescriptorChanged(this, mDescriptor);
+        }
+    }
+
+    /**
+     * Called by the media router to obtain a route controller for a particular route.
+     * <p>
+     * The media router will invoke the {@link RouteController#onRelease} method of the route
+     * controller when it is no longer needed to allow it to free its resources.
+     * </p>
+     *
+     * @param routeId The unique id of the route.
+     * @return The route controller.  Returns null if there is no such route or if the route
+     * cannot be controlled using the route controller interface.
+     */
+    @Nullable
+    public RouteController onCreateRouteController(@NonNull String routeId) {
+        if (routeId == null) {
+            throw new IllegalArgumentException("routeId cannot be null");
+        }
+        return null;
+    }
+
+    /**
+     * Called by the media router to obtain a route controller for a particular route which is a
+     * member of {@link MediaRouter.RouteGroup}.
+     * <p>
+     * The media router will invoke the {@link RouteController#onRelease} method of the route
+     * controller when it is no longer needed to allow it to free its resources.
+     * </p>
+     *
+     * @param routeId The unique id of the member route.
+     * @param routeGroupId The unique id of the route group.
+     * @return The route controller.  Returns null if there is no such route or if the route
+     * cannot be controlled using the route controller interface.
+     * @hide
+     */
+    // @RestrictTo(LIBRARY_GROUP)
+    @Nullable
+    public RouteController onCreateRouteController(@NonNull String routeId,
+            @NonNull String routeGroupId) {
+        if (routeId == null) {
+            throw new IllegalArgumentException("routeId cannot be null");
+        }
+        if (routeGroupId == null) {
+            throw new IllegalArgumentException("routeGroupId cannot be null");
+        }
+        return onCreateRouteController(routeId);
+    }
+
+    /**
+     * Describes properties of the route provider's implementation.
+     * <p>
+     * This object is immutable once created.
+     * </p>
+     */
+    public static final class ProviderMetadata {
+        private final ComponentName mComponentName;
+
+        ProviderMetadata(ComponentName componentName) {
+            if (componentName == null) {
+                throw new IllegalArgumentException("componentName must not be null");
+            }
+            mComponentName = componentName;
+        }
+
+        /**
+         * Gets the provider's package name.
+         */
+        public String getPackageName() {
+            return mComponentName.getPackageName();
+        }
+
+        /**
+         * Gets the provider's component name.
+         */
+        public ComponentName getComponentName() {
+            return mComponentName;
+        }
+
+        @Override
+        public String toString() {
+            return "ProviderMetadata{ componentName="
+                    + mComponentName.flattenToShortString() + " }";
+        }
+    }
+
+    /**
+     * Provides control over a particular route.
+     * <p>
+     * The media router obtains a route controller for a route whenever it needs
+     * to control a route.  When a route is selected, the media router invokes
+     * the {@link #onSelect} method of its route controller.  While selected,
+     * the media router may call other methods of the route controller to
+     * request that it perform certain actions to the route.  When a route is
+     * unselected, the media router invokes the {@link #onUnselect} method of its
+     * route controller.  When the media route no longer needs the route controller
+     * it will invoke the {@link #onRelease} method to allow the route controller
+     * to free its resources.
+     * </p><p>
+     * There may be multiple route controllers simultaneously active for the
+     * same route.  Each route controller will be released separately.
+     * </p><p>
+     * All operations on the route controller are asynchronous and
+     * results are communicated via callbacks.
+     * </p>
+     */
+    public static abstract class RouteController {
+        /**
+         * Releases the route controller, allowing it to free its resources.
+         */
+        public void onRelease() {
+        }
+
+        /**
+         * Selects the route.
+         */
+        public void onSelect() {
+        }
+
+        /**
+         * Unselects the route.
+         */
+        public void onUnselect() {
+        }
+
+        /**
+         * Unselects the route and provides a reason. The default implementation
+         * calls {@link #onUnselect()}.
+         * <p>
+         * The reason provided will be one of the following:
+         * <ul>
+         * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+         * </ul>
+         *
+         * @param reason The reason for unselecting the route.
+         */
+        public void onUnselect(int reason) {
+            onUnselect();
+        }
+
+        /**
+         * Requests to set the volume of the route.
+         *
+         * @param volume The new volume value between 0 and {@link MediaRouteDescriptor#getVolumeMax}.
+         */
+        public void onSetVolume(int volume) {
+        }
+
+        /**
+         * Requests an incremental volume update for the route.
+         *
+         * @param delta The delta to add to the current volume.
+         */
+        public void onUpdateVolume(int delta) {
+        }
+
+        /**
+         * Performs a {@link MediaControlIntent media control} request
+         * asynchronously on behalf of the route.
+         *
+         * @param intent A {@link MediaControlIntent media control intent}.
+         * @param callback A {@link ControlRequestCallback} to invoke with the result
+         * of the request, or null if no result is required.
+         * @return True if the controller intends to handle the request and will
+         * invoke the callback when finished.  False if the controller will not
+         * handle the request and will not invoke the callback.
+         *
+         * @see MediaControlIntent
+         */
+        public boolean onControlRequest(Intent intent, @Nullable ControlRequestCallback callback) {
+            return false;
+        }
+    }
+
+    /**
+     * Callback which is invoked when route information becomes available or changes.
+     */
+    public static abstract class Callback {
+        /**
+         * Called when information about a route provider and its routes changes.
+         *
+         * @param provider The media route provider that changed, never null.
+         * @param descriptor The new media route provider descriptor, or null if none.
+         */
+        public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
+                @Nullable MediaRouteProviderDescriptor descriptor) {
+        }
+    }
+
+    private final class ProviderHandler extends Handler {
+        ProviderHandler() {
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DELIVER_DESCRIPTOR_CHANGED:
+                    deliverDescriptorChanged();
+                    break;
+                case MSG_DELIVER_DISCOVERY_REQUEST_CHANGED:
+                    deliverDiscoveryRequestChanged();
+                    break;
+            }
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderDescriptor.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderDescriptor.java
new file mode 100644
index 0000000..eb1ce09
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderDescriptor.java
@@ -0,0 +1,208 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Describes the state of a media route provider and the routes that it publishes.
+ * <p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ */
+public final class MediaRouteProviderDescriptor {
+    private static final String KEY_ROUTES = "routes";
+
+    private final Bundle mBundle;
+    private List<MediaRouteDescriptor> mRoutes;
+
+    private MediaRouteProviderDescriptor(Bundle bundle, List<MediaRouteDescriptor> routes) {
+        mBundle = bundle;
+        mRoutes = routes;
+    }
+
+    /**
+     * Gets the list of all routes that this provider has published.
+     */
+    public List<MediaRouteDescriptor> getRoutes() {
+        ensureRoutes();
+        return mRoutes;
+    }
+
+    private void ensureRoutes() {
+        if (mRoutes == null) {
+            ArrayList<Bundle> routeBundles = mBundle.<Bundle>getParcelableArrayList(KEY_ROUTES);
+            if (routeBundles == null || routeBundles.isEmpty()) {
+                mRoutes = Collections.<MediaRouteDescriptor>emptyList();
+            } else {
+                final int count = routeBundles.size();
+                mRoutes = new ArrayList<MediaRouteDescriptor>(count);
+                for (int i = 0; i < count; i++) {
+                    mRoutes.add(MediaRouteDescriptor.fromBundle(routeBundles.get(i)));
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns true if the route provider descriptor and all of the routes that
+     * it contains have all of the required fields.
+     * <p>
+     * This verification is deep.  If the provider descriptor is known to be
+     * valid then it is not necessary to call {@link #isValid} on each of its routes.
+     * </p>
+     */
+    public boolean isValid() {
+        ensureRoutes();
+        final int routeCount = mRoutes.size();
+        for (int i = 0; i < routeCount; i++) {
+            MediaRouteDescriptor route = mRoutes.get(i);
+            if (route == null || !route.isValid()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        result.append("MediaRouteProviderDescriptor{ ");
+        result.append("routes=").append(
+                Arrays.toString(getRoutes().toArray()));
+        result.append(", isValid=").append(isValid());
+        result.append(" }");
+        return result.toString();
+    }
+
+    /**
+     * Converts this object to a bundle for serialization.
+     *
+     * @return The contents of the object represented as a bundle.
+     */
+    public Bundle asBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Creates an instance from a bundle.
+     *
+     * @param bundle The bundle, or null if none.
+     * @return The new instance, or null if the bundle was null.
+     */
+    public static MediaRouteProviderDescriptor fromBundle(Bundle bundle) {
+        return bundle != null ? new MediaRouteProviderDescriptor(bundle, null) : null;
+    }
+
+    /**
+     * Builder for {@link MediaRouteProviderDescriptor media route provider descriptors}.
+     */
+    public static final class Builder {
+        private final Bundle mBundle;
+        private ArrayList<MediaRouteDescriptor> mRoutes;
+
+        /**
+         * Creates an empty media route provider descriptor builder.
+         */
+        public Builder() {
+            mBundle = new Bundle();
+        }
+
+        /**
+         * Creates a media route provider descriptor builder whose initial contents are
+         * copied from an existing descriptor.
+         */
+        public Builder(MediaRouteProviderDescriptor descriptor) {
+            if (descriptor == null) {
+                throw new IllegalArgumentException("descriptor must not be null");
+            }
+
+            mBundle = new Bundle(descriptor.mBundle);
+
+            descriptor.ensureRoutes();
+            if (!descriptor.mRoutes.isEmpty()) {
+                mRoutes = new ArrayList<MediaRouteDescriptor>(descriptor.mRoutes);
+            }
+        }
+
+        /**
+         * Adds a route.
+         */
+        public Builder addRoute(MediaRouteDescriptor route) {
+            if (route == null) {
+                throw new IllegalArgumentException("route must not be null");
+            }
+
+            if (mRoutes == null) {
+                mRoutes = new ArrayList<MediaRouteDescriptor>();
+            } else if (mRoutes.contains(route)) {
+                throw new IllegalArgumentException("route descriptor already added");
+            }
+            mRoutes.add(route);
+            return this;
+        }
+
+        /**
+         * Adds a list of routes.
+         */
+        public Builder addRoutes(Collection<MediaRouteDescriptor> routes) {
+            if (routes == null) {
+                throw new IllegalArgumentException("routes must not be null");
+            }
+
+            if (!routes.isEmpty()) {
+                for (MediaRouteDescriptor route : routes) {
+                    addRoute(route);
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Sets the list of routes.
+         */
+        Builder setRoutes(Collection<MediaRouteDescriptor> routes) {
+            if (routes == null || routes.isEmpty()) {
+                mRoutes = null;
+                mBundle.remove(KEY_ROUTES);
+            } else {
+                mRoutes = new ArrayList<>(routes);
+            }
+            return this;
+        }
+
+        /**
+         * Builds the {@link MediaRouteProviderDescriptor media route provider descriptor}.
+         */
+        public MediaRouteProviderDescriptor build() {
+            if (mRoutes != null) {
+                final int count = mRoutes.size();
+                ArrayList<Bundle> routeBundles = new ArrayList<Bundle>(count);
+                for (int i = 0; i < count; i++) {
+                    routeBundles.add(mRoutes.get(i).asBundle());
+                }
+                mBundle.putParcelableArrayList(KEY_ROUTES, routeBundles);
+            }
+            return new MediaRouteProviderDescriptor(mBundle, mRoutes);
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderProtocol.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderProtocol.java
new file mode 100644
index 0000000..6be9343
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderProtocol.java
@@ -0,0 +1,230 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.Intent;
+import android.os.Messenger;
+
+/**
+ * Defines the communication protocol for media route provider services.
+ */
+abstract class MediaRouteProviderProtocol {
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * Put this in your manifest.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.media.MediaRouteProviderService";
+
+    /*
+     * Messages sent from the client to the service.
+     * DO NOT RENUMBER THESE!
+     */
+
+    /** (client v1)
+     * Register client.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : client version
+     */
+    public static final int CLIENT_MSG_REGISTER = 1;
+
+    /** (client v1)
+     * Unregister client.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     */
+    public static final int CLIENT_MSG_UNREGISTER = 2;
+
+    /** (client v1)
+     * Create route controller.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     * - CLIENT_DATA_ROUTE_ID : route id string
+     */
+    public static final int CLIENT_MSG_CREATE_ROUTE_CONTROLLER = 3;
+
+    /** (client v1)
+     * Release route controller.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     */
+    public static final int CLIENT_MSG_RELEASE_ROUTE_CONTROLLER = 4;
+
+    /** (client v1)
+     * Select route.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     */
+    public static final int CLIENT_MSG_SELECT_ROUTE = 5;
+
+    /** (client v1)
+     * Unselect route.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     */
+    public static final int CLIENT_MSG_UNSELECT_ROUTE = 6;
+
+    /** (client v1)
+     * Set route volume.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     * - CLIENT_DATA_VOLUME : volume integer
+     */
+    public static final int CLIENT_MSG_SET_ROUTE_VOLUME = 7;
+
+    /** (client v1)
+     * Update route volume.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     * - CLIENT_DATA_VOLUME : volume delta integer
+     */
+    public static final int CLIENT_MSG_UPDATE_ROUTE_VOLUME = 8;
+
+    /** (client v1)
+     * Route control request.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - arg2    : route controller id
+     * - obj     : media control intent
+     */
+    public static final int CLIENT_MSG_ROUTE_CONTROL_REQUEST = 9;
+
+    /** (client v1)
+     * Sets the discovery request.
+     * - replyTo : client messenger
+     * - arg1    : request id
+     * - obj     : discovery request bundle, or null if none
+     */
+    public static final int CLIENT_MSG_SET_DISCOVERY_REQUEST = 10;
+
+    public static final String CLIENT_DATA_ROUTE_ID = "routeId";
+    public static final String CLIENT_DATA_ROUTE_LIBRARY_GROUP = "routeGroupId";
+    public static final String CLIENT_DATA_VOLUME = "volume";
+    public static final String CLIENT_DATA_UNSELECT_REASON = "unselectReason";
+
+    /*
+     * Messages sent from the service to the client.
+     * DO NOT RENUMBER THESE!
+     */
+
+    /** (service v1)
+     * Generic failure sent in response to any unrecognized or malformed request.
+     * - arg1    : request id
+     */
+    public static final int SERVICE_MSG_GENERIC_FAILURE = 0;
+
+    /** (service v1)
+     * Generic failure sent in response to a successful message.
+     * - arg1    : request id
+     */
+    public static final int SERVICE_MSG_GENERIC_SUCCESS = 1;
+
+    /** (service v1)
+     * Registration succeeded.
+     * - arg1    : request id
+     * - arg2    : server version
+     * - obj     : route provider descriptor bundle, or null
+     */
+    public static final int SERVICE_MSG_REGISTERED = 2;
+
+    /** (service v1)
+     * Route control request success result.
+     * - arg1    : request id
+     * - obj     : result data bundle, or null
+     */
+    public static final int SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED = 3;
+
+    /** (service v1)
+     * Route control request failure result.
+     * - arg1    : request id
+     * - obj     : result data bundle, or null
+     * - SERVICE_DATA_ERROR: error message
+     */
+    public static final int SERVICE_MSG_CONTROL_REQUEST_FAILED = 4;
+
+    /** (service v1)
+     * Route provider descriptor changed.  (unsolicited event)
+     * - arg1    : reserved (0)
+     * - obj     : route provider descriptor bundle, or null
+     */
+    public static final int SERVICE_MSG_DESCRIPTOR_CHANGED = 5;
+
+    public static final String SERVICE_DATA_ERROR = "error";
+
+    /*
+     * Recognized client version numbers.  (Reserved for future use.)
+     * DO NOT RENUMBER THESE!
+     */
+
+    /**
+     * The client version used from the beginning.
+     */
+    public static final int CLIENT_VERSION_1 = 1;
+
+    /**
+     * The client version used from support library v24.1.0.
+     */
+    public static final int CLIENT_VERSION_2 = 2;
+
+    /**
+     * The current client version.
+     */
+    public static final int CLIENT_VERSION_CURRENT = CLIENT_VERSION_2;
+
+    /*
+     * Recognized server version numbers.  (Reserved for future use.)
+     * DO NOT RENUMBER THESE!
+     */
+
+    /**
+     * The service version used from the beginning.
+     */
+    public static final int SERVICE_VERSION_1 = 1;
+
+    /**
+     * The current service version.
+     */
+    public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1;
+
+    static final int CLIENT_VERSION_START = CLIENT_VERSION_1;
+
+    /**
+     * Returns true if the messenger object is valid.
+     * <p>
+     * The messenger constructor and unparceling code does not check whether the
+     * provided IBinder is a valid IMessenger object.  As a result, it's possible
+     * for a peer to send an invalid IBinder that will result in crashes downstream.
+     * This method checks that the messenger is in a valid state.
+     * </p>
+     */
+    public static boolean isValidRemoteMessenger(Messenger messenger) {
+        try {
+            return messenger != null && messenger.getBinder() != null;
+        } catch (NullPointerException ex) {
+            // If the messenger was constructed with a binder interface other than
+            // IMessenger then the call to getBinder() will crash with an NPE.
+            return false;
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderService.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderService.java
new file mode 100644
index 0000000..43cde10
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderService.java
@@ -0,0 +1,759 @@
+/*
+ * 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.support.mediarouter.media;
+
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_DATA_ROUTE_LIBRARY_GROUP;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_DATA_UNSELECT_REASON;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_CREATE_ROUTE_CONTROLLER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_REGISTER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_RELEASE_ROUTE_CONTROLLER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_ROUTE_CONTROL_REQUEST;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_SELECT_ROUTE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_SET_DISCOVERY_REQUEST;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_SET_ROUTE_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UNREGISTER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_UNSELECT_ROUTE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_UPDATE_ROUTE_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_VERSION_1;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_DATA_ERROR;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_CONTROL_REQUEST_FAILED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_DESCRIPTOR_CHANGED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_GENERIC_FAILURE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_GENERIC_SUCCESS;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_REGISTERED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_VERSION_CURRENT;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.ObjectsCompat;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Base class for media route provider services.
+ * <p>
+ * A media router will bind to media route provider services when a callback is added via
+ * {@link MediaRouter#addCallback(MediaRouteSelector, MediaRouter.Callback, int)} with a discovery
+ * flag: {@link MediaRouter#CALLBACK_FLAG_REQUEST_DISCOVERY},
+ * {@link MediaRouter#CALLBACK_FLAG_FORCE_DISCOVERY}, or
+ * {@link MediaRouter#CALLBACK_FLAG_PERFORM_ACTIVE_SCAN}, and will unbind when the callback
+ * is removed via {@link MediaRouter#removeCallback(MediaRouter.Callback)}.
+ * </p><p>
+ * To implement your own media route provider service, extend this class and
+ * override the {@link #onCreateMediaRouteProvider} method to return an
+ * instance of your {@link MediaRouteProvider}.
+ * </p><p>
+ * Declare your media route provider service in your application manifest
+ * like this:
+ * </p>
+ * <pre>
+ *   &lt;service android:name=".MyMediaRouteProviderService"
+ *           android:label="@string/my_media_route_provider_service">
+ *       &lt;intent-filter>
+ *           &lt;action android:name="android.media.MediaRouteProviderService" />
+ *       &lt;/intent-filter>
+ *   &lt;/service>
+ * </pre>
+ */
+public abstract class MediaRouteProviderService extends Service {
+    static final String TAG = "MediaRouteProviderSrv"; // max. 23 chars
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final ArrayList<ClientRecord> mClients = new ArrayList<ClientRecord>();
+    private final ReceiveHandler mReceiveHandler;
+    private final Messenger mReceiveMessenger;
+    final PrivateHandler mPrivateHandler;
+    private final ProviderCallback mProviderCallback;
+
+    MediaRouteProvider mProvider;
+    private MediaRouteDiscoveryRequest mCompositeDiscoveryRequest;
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * Put this in your manifest.
+     */
+    public static final String SERVICE_INTERFACE = MediaRouteProviderProtocol.SERVICE_INTERFACE;
+
+    /*
+     * Private messages used internally.  (Yes, you can renumber these.)
+     */
+
+    static final int PRIVATE_MSG_CLIENT_DIED = 1;
+
+    /**
+     * Creates a media route provider service.
+     */
+    public MediaRouteProviderService() {
+        mReceiveHandler = new ReceiveHandler(this);
+        mReceiveMessenger = new Messenger(mReceiveHandler);
+        mPrivateHandler = new PrivateHandler();
+        mProviderCallback = new ProviderCallback();
+    }
+
+    /**
+     * Called by the system when it is time to create the media route provider.
+     *
+     * @return The media route provider offered by this service, or null if
+     * this service has decided not to offer a media route provider.
+     */
+    public abstract MediaRouteProvider onCreateMediaRouteProvider();
+
+    /**
+     * Gets the media route provider offered by this service.
+     *
+     * @return The media route provider offered by this service, or null if
+     * it has not yet been created.
+     *
+     * @see #onCreateMediaRouteProvider()
+     */
+    public MediaRouteProvider getMediaRouteProvider() {
+        return mProvider;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (intent.getAction().equals(SERVICE_INTERFACE)) {
+            if (mProvider == null) {
+                MediaRouteProvider provider = onCreateMediaRouteProvider();
+                if (provider != null) {
+                    String providerPackage = provider.getMetadata().getPackageName();
+                    if (!providerPackage.equals(getPackageName())) {
+                        throw new IllegalStateException("onCreateMediaRouteProvider() returned "
+                                + "a provider whose package name does not match the package "
+                                + "name of the service.  A media route provider service can "
+                                + "only export its own media route providers.  "
+                                + "Provider package name: " + providerPackage
+                                + ".  Service package name: " + getPackageName() + ".");
+                    }
+                    mProvider = provider;
+                    mProvider.setCallback(mProviderCallback);
+                }
+            }
+            if (mProvider != null) {
+                return mReceiveMessenger.getBinder();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (mProvider != null) {
+            mProvider.setCallback(null);
+        }
+        return super.onUnbind(intent);
+    }
+
+    boolean onRegisterClient(Messenger messenger, int requestId, int version) {
+        if (version >= CLIENT_VERSION_1) {
+            int index = findClient(messenger);
+            if (index < 0) {
+                ClientRecord client = new ClientRecord(messenger, version);
+                if (client.register()) {
+                    mClients.add(client);
+                    if (DEBUG) {
+                        Log.d(TAG, client + ": Registered, version=" + version);
+                    }
+                    if (requestId != 0) {
+                        MediaRouteProviderDescriptor descriptor = mProvider.getDescriptor();
+                        sendReply(messenger, SERVICE_MSG_REGISTERED,
+                                requestId, SERVICE_VERSION_CURRENT,
+                                createDescriptorBundleForClientVersion(descriptor,
+                                        client.mVersion), null);
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean onUnregisterClient(Messenger messenger, int requestId) {
+        int index = findClient(messenger);
+        if (index >= 0) {
+            ClientRecord client = mClients.remove(index);
+            if (DEBUG) {
+                Log.d(TAG, client + ": Unregistered");
+            }
+            client.dispose();
+            sendGenericSuccess(messenger, requestId);
+            return true;
+        }
+        return false;
+    }
+
+    void onBinderDied(Messenger messenger) {
+        int index = findClient(messenger);
+        if (index >= 0) {
+            ClientRecord client = mClients.remove(index);
+            if (DEBUG) {
+                Log.d(TAG, client + ": Binder died");
+            }
+            client.dispose();
+        }
+    }
+
+    boolean onCreateRouteController(Messenger messenger, int requestId,
+            int controllerId, String routeId, String routeGroupId) {
+        ClientRecord client = getClient(messenger);
+        if (client != null) {
+            if (client.createRouteController(routeId, routeGroupId, controllerId)) {
+                if (DEBUG) {
+                    Log.d(TAG, client + ": Route controller created, controllerId=" + controllerId
+                            + ", routeId=" + routeId + ", routeGroupId=" + routeGroupId);
+                }
+                sendGenericSuccess(messenger, requestId);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean onReleaseRouteController(Messenger messenger, int requestId,
+            int controllerId) {
+        ClientRecord client = getClient(messenger);
+        if (client != null) {
+            if (client.releaseRouteController(controllerId)) {
+                if (DEBUG) {
+                    Log.d(TAG, client + ": Route controller released"
+                            + ", controllerId=" + controllerId);
+                }
+                sendGenericSuccess(messenger, requestId);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean onSelectRoute(Messenger messenger, int requestId,
+            int controllerId) {
+        ClientRecord client = getClient(messenger);
+        if (client != null) {
+            MediaRouteProvider.RouteController controller =
+                    client.getRouteController(controllerId);
+            if (controller != null) {
+                controller.onSelect();
+                if (DEBUG) {
+                    Log.d(TAG, client + ": Route selected"
+                            + ", controllerId=" + controllerId);
+                }
+                sendGenericSuccess(messenger, requestId);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean onUnselectRoute(Messenger messenger, int requestId,
+            int controllerId, int reason) {
+        ClientRecord client = getClient(messenger);
+        if (client != null) {
+            MediaRouteProvider.RouteController controller =
+                    client.getRouteController(controllerId);
+            if (controller != null) {
+                controller.onUnselect(reason);
+                if (DEBUG) {
+                    Log.d(TAG, client + ": Route unselected"
+                            + ", controllerId=" + controllerId);
+                }
+                sendGenericSuccess(messenger, requestId);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean onSetRouteVolume(Messenger messenger, int requestId,
+            int controllerId, int volume) {
+        ClientRecord client = getClient(messenger);
+        if (client != null) {
+            MediaRouteProvider.RouteController controller =
+                    client.getRouteController(controllerId);
+            if (controller != null) {
+                controller.onSetVolume(volume);
+                if (DEBUG) {
+                    Log.d(TAG, client + ": Route volume changed"
+                            + ", controllerId=" + controllerId + ", volume=" + volume);
+                }
+                sendGenericSuccess(messenger, requestId);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean onUpdateRouteVolume(Messenger messenger, int requestId,
+            int controllerId, int delta) {
+        ClientRecord client = getClient(messenger);
+        if (client != null) {
+            MediaRouteProvider.RouteController controller =
+                    client.getRouteController(controllerId);
+            if (controller != null) {
+                controller.onUpdateVolume(delta);
+                if (DEBUG) {
+                    Log.d(TAG, client + ": Route volume updated"
+                            + ", controllerId=" + controllerId + ", delta=" + delta);
+                }
+                sendGenericSuccess(messenger, requestId);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean onRouteControlRequest(final Messenger messenger, final int requestId,
+            final int controllerId, final Intent intent) {
+        final ClientRecord client = getClient(messenger);
+        if (client != null) {
+            MediaRouteProvider.RouteController controller =
+                    client.getRouteController(controllerId);
+            if (controller != null) {
+                MediaRouter.ControlRequestCallback callback = null;
+                if (requestId != 0) {
+                    callback = new MediaRouter.ControlRequestCallback() {
+                        @Override
+                        public void onResult(Bundle data) {
+                            if (DEBUG) {
+                                Log.d(TAG, client + ": Route control request succeeded"
+                                        + ", controllerId=" + controllerId
+                                        + ", intent=" + intent
+                                        + ", data=" + data);
+                            }
+                            if (findClient(messenger) >= 0) {
+                                sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED,
+                                        requestId, 0, data, null);
+                            }
+                        }
+
+                        @Override
+                        public void onError(String error, Bundle data) {
+                            if (DEBUG) {
+                                Log.d(TAG, client + ": Route control request failed"
+                                        + ", controllerId=" + controllerId
+                                        + ", intent=" + intent
+                                        + ", error=" + error + ", data=" + data);
+                            }
+                            if (findClient(messenger) >= 0) {
+                                if (error != null) {
+                                    Bundle bundle = new Bundle();
+                                    bundle.putString(SERVICE_DATA_ERROR, error);
+                                    sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
+                                            requestId, 0, data, bundle);
+                                } else {
+                                    sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
+                                            requestId, 0, data, null);
+                                }
+                            }
+                        }
+                    };
+                }
+                if (controller.onControlRequest(intent, callback)) {
+                    if (DEBUG) {
+                        Log.d(TAG, client + ": Route control request delivered"
+                                + ", controllerId=" + controllerId + ", intent=" + intent);
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean onSetDiscoveryRequest(Messenger messenger, int requestId,
+            MediaRouteDiscoveryRequest request) {
+        ClientRecord client = getClient(messenger);
+        if (client != null) {
+            boolean actuallyChanged = client.setDiscoveryRequest(request);
+            if (DEBUG) {
+                Log.d(TAG, client + ": Set discovery request, request=" + request
+                        + ", actuallyChanged=" + actuallyChanged
+                        + ", compositeDiscoveryRequest=" + mCompositeDiscoveryRequest);
+            }
+            sendGenericSuccess(messenger, requestId);
+            return true;
+        }
+        return false;
+    }
+
+    void sendDescriptorChanged(MediaRouteProviderDescriptor descriptor) {
+        final int count = mClients.size();
+        for (int i = 0; i < count; i++) {
+            ClientRecord client = mClients.get(i);
+            sendReply(client.mMessenger, SERVICE_MSG_DESCRIPTOR_CHANGED, 0, 0,
+                    createDescriptorBundleForClientVersion(descriptor, client.mVersion), null);
+            if (DEBUG) {
+                Log.d(TAG, client + ": Sent descriptor change event, descriptor=" + descriptor);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    static Bundle createDescriptorBundleForClientVersion(MediaRouteProviderDescriptor descriptor,
+            int clientVersion) {
+        if (descriptor == null) {
+            return null;
+        }
+        MediaRouteProviderDescriptor.Builder builder =
+                new MediaRouteProviderDescriptor.Builder(descriptor);
+        builder.setRoutes(null);
+        for (MediaRouteDescriptor route : descriptor.getRoutes()) {
+            if (clientVersion >= route.getMinClientVersion()
+                    && clientVersion <= route.getMaxClientVersion()) {
+                builder.addRoute(route);
+            }
+        }
+        return builder.build().asBundle();
+    }
+
+    boolean updateCompositeDiscoveryRequest() {
+        MediaRouteDiscoveryRequest composite = null;
+        MediaRouteSelector.Builder selectorBuilder = null;
+        boolean activeScan = false;
+        final int count = mClients.size();
+        for (int i = 0; i < count; i++) {
+            MediaRouteDiscoveryRequest request = mClients.get(i).mDiscoveryRequest;
+            if (request != null
+                    && (!request.getSelector().isEmpty() || request.isActiveScan())) {
+                activeScan |= request.isActiveScan();
+                if (composite == null) {
+                    composite = request;
+                } else {
+                    if (selectorBuilder == null) {
+                        selectorBuilder = new MediaRouteSelector.Builder(composite.getSelector());
+                    }
+                    selectorBuilder.addSelector(request.getSelector());
+                }
+            }
+        }
+        if (selectorBuilder != null) {
+            composite = new MediaRouteDiscoveryRequest(selectorBuilder.build(), activeScan);
+        }
+        if (!ObjectsCompat.equals(mCompositeDiscoveryRequest, composite)) {
+            mCompositeDiscoveryRequest = composite;
+            mProvider.setDiscoveryRequest(composite);
+            return true;
+        }
+        return false;
+    }
+
+    private ClientRecord getClient(Messenger messenger) {
+        int index = findClient(messenger);
+        return index >= 0 ? mClients.get(index) : null;
+    }
+
+    int findClient(Messenger messenger) {
+        final int count = mClients.size();
+        for (int i = 0; i < count; i++) {
+            ClientRecord client = mClients.get(i);
+            if (client.hasMessenger(messenger)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    static void sendGenericFailure(Messenger messenger, int requestId) {
+        if (requestId != 0) {
+            sendReply(messenger, SERVICE_MSG_GENERIC_FAILURE, requestId, 0, null, null);
+        }
+    }
+
+    private static void sendGenericSuccess(Messenger messenger, int requestId) {
+        if (requestId != 0) {
+            sendReply(messenger, SERVICE_MSG_GENERIC_SUCCESS, requestId, 0, null, null);
+        }
+    }
+
+    static void sendReply(Messenger messenger, int what,
+            int requestId, int arg, Object obj, Bundle data) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        msg.arg1 = requestId;
+        msg.arg2 = arg;
+        msg.obj = obj;
+        msg.setData(data);
+        try {
+            messenger.send(msg);
+        } catch (DeadObjectException ex) {
+            // The client died.
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Could not send message to " + getClientId(messenger), ex);
+        }
+    }
+
+    static String getClientId(Messenger messenger) {
+        return "Client connection " + messenger.getBinder().toString();
+    }
+
+    private final class PrivateHandler extends Handler {
+        PrivateHandler() {
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case PRIVATE_MSG_CLIENT_DIED:
+                    onBinderDied((Messenger)msg.obj);
+                    break;
+            }
+        }
+    }
+
+    private final class ProviderCallback extends MediaRouteProvider.Callback {
+        ProviderCallback() {
+        }
+
+        @Override
+        public void onDescriptorChanged(MediaRouteProvider provider,
+                MediaRouteProviderDescriptor descriptor) {
+            sendDescriptorChanged(descriptor);
+        }
+    }
+
+    private final class ClientRecord implements DeathRecipient {
+        public final Messenger mMessenger;
+        public final int mVersion;
+        public MediaRouteDiscoveryRequest mDiscoveryRequest;
+
+        private final SparseArray<MediaRouteProvider.RouteController> mControllers =
+                new SparseArray<MediaRouteProvider.RouteController>();
+
+        public ClientRecord(Messenger messenger, int version) {
+            mMessenger = messenger;
+            mVersion = version;
+        }
+
+        public boolean register() {
+            try {
+                mMessenger.getBinder().linkToDeath(this, 0);
+                return true;
+            } catch (RemoteException ex) {
+                binderDied();
+            }
+            return false;
+        }
+
+        public void dispose() {
+            int count = mControllers.size();
+            for (int i = 0; i < count; i++) {
+                mControllers.valueAt(i).onRelease();
+            }
+            mControllers.clear();
+
+            mMessenger.getBinder().unlinkToDeath(this, 0);
+
+            setDiscoveryRequest(null);
+        }
+
+        public boolean hasMessenger(Messenger other) {
+            return mMessenger.getBinder() == other.getBinder();
+        }
+
+        public boolean createRouteController(String routeId, String routeGroupId,
+                int controllerId) {
+            if (mControllers.indexOfKey(controllerId) < 0) {
+                MediaRouteProvider.RouteController controller = routeGroupId == null
+                        ? mProvider.onCreateRouteController(routeId)
+                        : mProvider.onCreateRouteController(routeId, routeGroupId);
+                if (controller != null) {
+                    mControllers.put(controllerId, controller);
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public boolean releaseRouteController(int controllerId) {
+            MediaRouteProvider.RouteController controller = mControllers.get(controllerId);
+            if (controller != null) {
+                mControllers.remove(controllerId);
+                controller.onRelease();
+                return true;
+            }
+            return false;
+        }
+
+        public MediaRouteProvider.RouteController getRouteController(int controllerId) {
+            return mControllers.get(controllerId);
+        }
+
+        public boolean setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
+            if (!ObjectsCompat.equals(mDiscoveryRequest, request)) {
+                mDiscoveryRequest = request;
+                return updateCompositeDiscoveryRequest();
+            }
+            return false;
+        }
+
+        // Runs on a binder thread.
+        @Override
+        public void binderDied() {
+            mPrivateHandler.obtainMessage(PRIVATE_MSG_CLIENT_DIED, mMessenger).sendToTarget();
+        }
+
+        @Override
+        public String toString() {
+            return getClientId(mMessenger);
+        }
+    }
+
+    /**
+     * Handler that receives messages from clients.
+     * <p>
+     * This inner class is static and only retains a weak reference to the service
+     * to prevent the service from being leaked in case one of the clients is holding an
+     * active reference to the server's messenger.
+     * </p><p>
+     * This handler should not be used to handle any messages other than those
+     * that come from the client.
+     * </p>
+     */
+    private static final class ReceiveHandler extends Handler {
+        private final WeakReference<MediaRouteProviderService> mServiceRef;
+
+        public ReceiveHandler(MediaRouteProviderService service) {
+            mServiceRef = new WeakReference<MediaRouteProviderService>(service);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final Messenger messenger = msg.replyTo;
+            if (isValidRemoteMessenger(messenger)) {
+                final int what = msg.what;
+                final int requestId = msg.arg1;
+                final int arg = msg.arg2;
+                final Object obj = msg.obj;
+                final Bundle data = msg.peekData();
+                if (!processMessage(what, messenger, requestId, arg, obj, data)) {
+                    if (DEBUG) {
+                        Log.d(TAG, getClientId(messenger) + ": Message failed, what=" + what
+                                + ", requestId=" + requestId + ", arg=" + arg
+                                + ", obj=" + obj + ", data=" + data);
+                    }
+                    sendGenericFailure(messenger, requestId);
+                }
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "Ignoring message without valid reply messenger.");
+                }
+            }
+        }
+
+        private boolean processMessage(int what,
+                Messenger messenger, int requestId, int arg, Object obj, Bundle data) {
+            MediaRouteProviderService service = mServiceRef.get();
+            if (service != null) {
+                switch (what) {
+                    case CLIENT_MSG_REGISTER:
+                        return service.onRegisterClient(messenger, requestId, arg);
+
+                    case CLIENT_MSG_UNREGISTER:
+                        return service.onUnregisterClient(messenger, requestId);
+
+                    case CLIENT_MSG_CREATE_ROUTE_CONTROLLER: {
+                        String routeId = data.getString(CLIENT_DATA_ROUTE_ID);
+                        String routeGroupId = data.getString(CLIENT_DATA_ROUTE_LIBRARY_GROUP);
+                        if (routeId != null) {
+                            return service.onCreateRouteController(
+                                    messenger, requestId, arg, routeId, routeGroupId);
+                        }
+                        break;
+                    }
+
+                    case CLIENT_MSG_RELEASE_ROUTE_CONTROLLER:
+                        return service.onReleaseRouteController(messenger, requestId, arg);
+
+                    case CLIENT_MSG_SELECT_ROUTE:
+                        return service.onSelectRoute(messenger, requestId, arg);
+
+                    case CLIENT_MSG_UNSELECT_ROUTE:
+                        int reason = data == null ?
+                                MediaRouter.UNSELECT_REASON_UNKNOWN
+                                : data.getInt(CLIENT_DATA_UNSELECT_REASON,
+                                        MediaRouter.UNSELECT_REASON_UNKNOWN);
+                        return service.onUnselectRoute(messenger, requestId, arg, reason);
+
+                    case CLIENT_MSG_SET_ROUTE_VOLUME: {
+                        int volume = data.getInt(CLIENT_DATA_VOLUME, -1);
+                        if (volume >= 0) {
+                            return service.onSetRouteVolume(
+                                    messenger, requestId, arg, volume);
+                        }
+                        break;
+                    }
+
+                    case CLIENT_MSG_UPDATE_ROUTE_VOLUME: {
+                        int delta = data.getInt(CLIENT_DATA_VOLUME, 0);
+                        if (delta != 0) {
+                            return service.onUpdateRouteVolume(
+                                    messenger, requestId, arg, delta);
+                        }
+                        break;
+                    }
+
+                    case CLIENT_MSG_ROUTE_CONTROL_REQUEST:
+                        if (obj instanceof Intent) {
+                            return service.onRouteControlRequest(
+                                    messenger, requestId, arg, (Intent)obj);
+                        }
+                        break;
+
+                    case CLIENT_MSG_SET_DISCOVERY_REQUEST: {
+                        if (obj == null || obj instanceof Bundle) {
+                            MediaRouteDiscoveryRequest request =
+                                    MediaRouteDiscoveryRequest.fromBundle((Bundle)obj);
+                            return service.onSetDiscoveryRequest(
+                                    messenger, requestId,
+                                    request != null && request.isValid() ? request : null);
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteSelector.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteSelector.java
new file mode 100644
index 0000000..5669b19
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteSelector.java
@@ -0,0 +1,308 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Describes the capabilities of routes that applications would like to discover and use.
+ * <p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ *
+ * <h3>Example</h3>
+ * <pre>
+ * MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
+ *         .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
+ *         .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ *         .build();
+ *
+ * MediaRouter router = MediaRouter.getInstance(context);
+ * router.addCallback(selector, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ * </pre>
+ */
+public final class MediaRouteSelector {
+    static final String KEY_CONTROL_CATEGORIES = "controlCategories";
+
+    private final Bundle mBundle;
+    List<String> mControlCategories;
+
+    /**
+     * An empty media route selector that will not match any routes.
+     */
+    public static final MediaRouteSelector EMPTY = new MediaRouteSelector(new Bundle(), null);
+
+    MediaRouteSelector(Bundle bundle, List<String> controlCategories) {
+        mBundle = bundle;
+        mControlCategories = controlCategories;
+    }
+
+    /**
+     * Gets the list of {@link MediaControlIntent media control categories} in the selector.
+     *
+     * @return The list of categories.
+     */
+    public List<String> getControlCategories() {
+        ensureControlCategories();
+        return mControlCategories;
+    }
+
+    void ensureControlCategories() {
+        if (mControlCategories == null) {
+            mControlCategories = mBundle.getStringArrayList(KEY_CONTROL_CATEGORIES);
+            if (mControlCategories == null || mControlCategories.isEmpty()) {
+                mControlCategories = Collections.<String>emptyList();
+            }
+        }
+    }
+
+    /**
+     * Returns true if the selector contains the specified category.
+     *
+     * @param category The category to check.
+     * @return True if the category is present.
+     */
+    public boolean hasControlCategory(String category) {
+        if (category != null) {
+            ensureControlCategories();
+            final int categoryCount = mControlCategories.size();
+            for (int i = 0; i < categoryCount; i++) {
+                if (mControlCategories.get(i).equals(category)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the selector matches at least one of the specified control filters.
+     *
+     * @param filters The list of control filters to consider.
+     * @return True if a match is found.
+     */
+    public boolean matchesControlFilters(List<IntentFilter> filters) {
+        if (filters != null) {
+            ensureControlCategories();
+            final int categoryCount = mControlCategories.size();
+            if (categoryCount != 0) {
+                final int filterCount = filters.size();
+                for (int i = 0; i < filterCount; i++) {
+                    final IntentFilter filter = filters.get(i);
+                    if (filter != null) {
+                        for (int j = 0; j < categoryCount; j++) {
+                            if (filter.hasCategory(mControlCategories.get(j))) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this selector contains all of the capabilities described
+     * by the specified selector.
+     *
+     * @param selector The selector to be examined.
+     * @return True if this selector contains all of the capabilities described
+     * by the specified selector.
+     */
+    public boolean contains(MediaRouteSelector selector) {
+        if (selector != null) {
+            ensureControlCategories();
+            selector.ensureControlCategories();
+            return mControlCategories.containsAll(selector.mControlCategories);
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the selector does not specify any capabilities.
+     */
+    public boolean isEmpty() {
+        ensureControlCategories();
+        return mControlCategories.isEmpty();
+    }
+
+    /**
+     * Returns true if the selector has all of the required fields.
+     */
+    public boolean isValid() {
+        ensureControlCategories();
+        if (mControlCategories.contains(null)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof MediaRouteSelector) {
+            MediaRouteSelector other = (MediaRouteSelector)o;
+            ensureControlCategories();
+            other.ensureControlCategories();
+            return mControlCategories.equals(other.mControlCategories);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        ensureControlCategories();
+        return mControlCategories.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        result.append("MediaRouteSelector{ ");
+        result.append("controlCategories=").append(
+                Arrays.toString(getControlCategories().toArray()));
+        result.append(" }");
+        return result.toString();
+    }
+
+    /**
+     * Converts this object to a bundle for serialization.
+     *
+     * @return The contents of the object represented as a bundle.
+     */
+    public Bundle asBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Creates an instance from a bundle.
+     *
+     * @param bundle The bundle, or null if none.
+     * @return The new instance, or null if the bundle was null.
+     */
+    public static MediaRouteSelector fromBundle(@Nullable Bundle bundle) {
+        return bundle != null ? new MediaRouteSelector(bundle, null) : null;
+    }
+
+    /**
+     * Builder for {@link MediaRouteSelector media route selectors}.
+     */
+    public static final class Builder {
+        private ArrayList<String> mControlCategories;
+
+        /**
+         * Creates an empty media route selector builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Creates a media route selector descriptor builder whose initial contents are
+         * copied from an existing selector.
+         */
+        public Builder(@NonNull MediaRouteSelector selector) {
+            if (selector == null) {
+                throw new IllegalArgumentException("selector must not be null");
+            }
+
+            selector.ensureControlCategories();
+            if (!selector.mControlCategories.isEmpty()) {
+                mControlCategories = new ArrayList<String>(selector.mControlCategories);
+            }
+        }
+
+        /**
+         * Adds a {@link MediaControlIntent media control category} to the builder.
+         *
+         * @param category The category to add to the set of desired capabilities, such as
+         * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
+         * @return The builder instance for chaining.
+         */
+        @NonNull
+        public Builder addControlCategory(@NonNull String category) {
+            if (category == null) {
+                throw new IllegalArgumentException("category must not be null");
+            }
+
+            if (mControlCategories == null) {
+                mControlCategories = new ArrayList<String>();
+            }
+            if (!mControlCategories.contains(category)) {
+                mControlCategories.add(category);
+            }
+            return this;
+        }
+
+        /**
+         * Adds a list of {@link MediaControlIntent media control categories} to the builder.
+         *
+         * @param categories The list categories to add to the set of desired capabilities,
+         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
+         * @return The builder instance for chaining.
+         */
+        @NonNull
+        public Builder addControlCategories(@NonNull Collection<String> categories) {
+            if (categories == null) {
+                throw new IllegalArgumentException("categories must not be null");
+            }
+
+            if (!categories.isEmpty()) {
+                for (String category : categories) {
+                    addControlCategory(category);
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Adds the contents of an existing media route selector to the builder.
+         *
+         * @param selector The media route selector whose contents are to be added.
+         * @return The builder instance for chaining.
+         */
+        @NonNull
+        public Builder addSelector(@NonNull MediaRouteSelector selector) {
+            if (selector == null) {
+                throw new IllegalArgumentException("selector must not be null");
+            }
+
+            addControlCategories(selector.getControlCategories());
+            return this;
+        }
+
+        /**
+         * Builds the {@link MediaRouteSelector media route selector}.
+         */
+        @NonNull
+        public MediaRouteSelector build() {
+            if (mControlCategories == null) {
+                return EMPTY;
+            }
+            Bundle bundle = new Bundle();
+            bundle.putStringArrayList(KEY_CONTROL_CATEGORIES, mControlCategories);
+            return new MediaRouteSelector(bundle, mControlCategories);
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouter.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouter.java
new file mode 100644
index 0000000..db0052e
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouter.java
@@ -0,0 +1,2999 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.v4.app.ActivityManagerCompat;
+import android.support.v4.hardware.display.DisplayManagerCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.util.Pair;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.support.mediarouter.media.MediaRouteProvider.ProviderMetadata;
+import com.android.support.mediarouter.media.MediaRouteProvider.RouteController;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * MediaRouter allows applications to control the routing of media channels
+ * and streams from the current device to external speakers and destination devices.
+ * <p>
+ * A MediaRouter instance is retrieved through {@link #getInstance}.  Applications
+ * can query the media router about the currently selected route and its capabilities
+ * to determine how to send content to the route's destination.  Applications can
+ * also {@link RouteInfo#sendControlRequest send control requests} to the route
+ * to ask the route's destination to perform certain remote control functions
+ * such as playing media.
+ * </p><p>
+ * See also {@link MediaRouteProvider} for information on how an application
+ * can publish new media routes to the media router.
+ * </p><p>
+ * The media router API is not thread-safe; all interactions with it must be
+ * done from the main thread of the process.
+ * </p>
+ */
+public final class MediaRouter {
+    static final String TAG = "MediaRouter";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /**
+     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+     * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the reason the route
+     * was unselected is unknown.
+     */
+    public static final int UNSELECT_REASON_UNKNOWN = 0;
+    /**
+     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+     * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
+     * the disconnect button to disconnect and keep playing.
+     * <p>
+     *
+     * @see MediaRouteDescriptor#canDisconnectAndKeepPlaying()
+     */
+    public static final int UNSELECT_REASON_DISCONNECTED = 1;
+    /**
+     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+     * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
+     * the stop casting button.
+     */
+    public static final int UNSELECT_REASON_STOPPED = 2;
+    /**
+     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+     * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user selected
+     * a different route.
+     */
+    public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
+
+    // Maintains global media router state for the process.
+    // This field is initialized in MediaRouter.getInstance() before any
+    // MediaRouter objects are instantiated so it is guaranteed to be
+    // valid whenever any instance method is invoked.
+    static GlobalMediaRouter sGlobal;
+
+    // Context-bound state of the media router.
+    final Context mContext;
+    final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>();
+
+    @IntDef(flag = true,
+            value = {
+                    CALLBACK_FLAG_PERFORM_ACTIVE_SCAN,
+                    CALLBACK_FLAG_REQUEST_DISCOVERY,
+                    CALLBACK_FLAG_UNFILTERED_EVENTS
+            }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface CallbackFlags {}
+
+    /**
+     * Flag for {@link #addCallback}: Actively scan for routes while this callback
+     * is registered.
+     * <p>
+     * When this flag is specified, the media router will actively scan for new
+     * routes.  Certain routes, such as wifi display routes, may not be discoverable
+     * except when actively scanning.  This flag is typically used when the route picker
+     * dialog has been opened by the user to ensure that the route information is
+     * up to date.
+     * </p><p>
+     * Active scanning may consume a significant amount of power and may have intrusive
+     * effects on wireless connectivity.  Therefore it is important that active scanning
+     * only be requested when it is actually needed to satisfy a user request to
+     * discover and select a new route.
+     * </p><p>
+     * This flag implies {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} but performing
+     * active scans is much more expensive than a normal discovery request.
+     * </p>
+     *
+     * @see #CALLBACK_FLAG_REQUEST_DISCOVERY
+     */
+    public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0;
+
+    /**
+     * Flag for {@link #addCallback}: Do not filter route events.
+     * <p>
+     * When this flag is specified, the callback will be invoked for events that affect any
+     * route even if they do not match the callback's filter.
+     * </p>
+     */
+    public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
+
+    /**
+     * Flag for {@link #addCallback}: Request passive route discovery while this
+     * callback is registered, except on {@link ActivityManager#isLowRamDevice low-RAM devices}.
+     * <p>
+     * When this flag is specified, the media router will try to discover routes.
+     * Although route discovery is intended to be efficient, checking for new routes may
+     * result in some network activity and could slowly drain the battery.  Therefore
+     * applications should only specify {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} when
+     * they are running in the foreground and would like to provide the user with the
+     * option of connecting to new routes.
+     * </p><p>
+     * Applications should typically add a callback using this flag in the
+     * {@link android.app.Activity activity's} {@link android.app.Activity#onStart onStart}
+     * method and remove it in the {@link android.app.Activity#onStop onStop} method.
+     * The {@link android.support.v7.app.MediaRouteDiscoveryFragment} fragment may
+     * also be used for this purpose.
+     * </p><p class="note">
+     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag
+     * will be ignored.  Refer to
+     * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
+     * </p>
+     *
+     * @see android.support.v7.app.MediaRouteDiscoveryFragment
+     */
+    public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
+
+    /**
+     * Flag for {@link #addCallback}: Request passive route discovery while this
+     * callback is registered, even on {@link ActivityManager#isLowRamDevice low-RAM devices}.
+     * <p class="note">
+     * This flag has a significant performance impact on low-RAM devices
+     * since it may cause many media route providers to be started simultaneously.
+     * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
+     * performing passive discovery on these devices altogether.  Refer to
+     * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
+     * </p>
+     *
+     * @see android.support.v7.app.MediaRouteDiscoveryFragment
+     */
+    public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 1 << 3;
+
+    /**
+     * Flag for {@link #isRouteAvailable}: Ignore the default route.
+     * <p>
+     * This flag is used to determine whether a matching non-default route is available.
+     * This constraint may be used to decide whether to offer the route chooser dialog
+     * to the user.  There is no point offering the chooser if there are no
+     * non-default choices.
+     * </p>
+     */
+    public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
+
+    /**
+     * Flag for {@link #isRouteAvailable}: Require an actual route to be matched.
+     * <p>
+     * If this flag is not set, then {@link #isRouteAvailable} will return true
+     * if it is possible to discover a matching route even if discovery is not in
+     * progress or if no matching route has yet been found.  This feature is used to
+     * save resources by removing the need to perform passive route discovery on
+     * {@link ActivityManager#isLowRamDevice low-RAM devices}.
+     * </p><p>
+     * If this flag is set, then {@link #isRouteAvailable} will only return true if
+     * a matching route has actually been discovered.
+     * </p>
+     */
+    public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 1 << 1;
+
+    private MediaRouter(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Gets an instance of the media router service associated with the context.
+     * <p>
+     * The application is responsible for holding a strong reference to the returned
+     * {@link MediaRouter} instance, such as by storing the instance in a field of
+     * the {@link android.app.Activity}, to ensure that the media router remains alive
+     * as long as the application is using its features.
+     * </p><p>
+     * In other words, the support library only holds a {@link WeakReference weak reference}
+     * to each media router instance.  When there are no remaining strong references to the
+     * media router instance, all of its callbacks will be removed and route discovery
+     * will no longer be performed on its behalf.
+     * </p>
+     *
+     * @return The media router instance for the context.  The application must hold
+     * a strong reference to this object as long as it is in use.
+     */
+    public static MediaRouter getInstance(@NonNull Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+        checkCallingThread();
+
+        if (sGlobal == null) {
+            sGlobal = new GlobalMediaRouter(context.getApplicationContext());
+            sGlobal.start();
+        }
+        return sGlobal.getRouter(context);
+    }
+
+    /**
+     * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to
+     * this media router.
+     */
+    public List<RouteInfo> getRoutes() {
+        checkCallingThread();
+        return sGlobal.getRoutes();
+    }
+
+    /**
+     * Gets information about the {@link MediaRouter.ProviderInfo route providers}
+     * currently known to this media router.
+     */
+    public List<ProviderInfo> getProviders() {
+        checkCallingThread();
+        return sGlobal.getProviders();
+    }
+
+    /**
+     * Gets the default route for playing media content on the system.
+     * <p>
+     * The system always provides a default route.
+     * </p>
+     *
+     * @return The default route, which is guaranteed to never be null.
+     */
+    @NonNull
+    public RouteInfo getDefaultRoute() {
+        checkCallingThread();
+        return sGlobal.getDefaultRoute();
+    }
+
+    /**
+     * Gets a bluetooth route for playing media content on the system.
+     *
+     * @return A bluetooth route, if exist, otherwise null.
+     */
+    public RouteInfo getBluetoothRoute() {
+        checkCallingThread();
+        return sGlobal.getBluetoothRoute();
+    }
+
+    /**
+     * Gets the currently selected route.
+     * <p>
+     * The application should examine the route's
+     * {@link RouteInfo#getControlFilters media control intent filters} to assess the
+     * capabilities of the route before attempting to use it.
+     * </p>
+     *
+     * <h3>Example</h3>
+     * <pre>
+     * public boolean playMovie() {
+     *     MediaRouter mediaRouter = MediaRouter.getInstance(context);
+     *     MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
+     *
+     *     // First try using the remote playback interface, if supported.
+     *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+     *         // The route supports remote playback.
+     *         // Try to send it the Uri of the movie to play.
+     *         Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
+     *         intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+     *         intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
+     *         if (route.supportsControlRequest(intent)) {
+     *             route.sendControlRequest(intent, null);
+     *             return true; // sent the request to play the movie
+     *         }
+     *     }
+     *
+     *     // If remote playback was not possible, then play locally.
+     *     if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
+     *         // The route supports live video streaming.
+     *         // Prepare to play content locally in a window or in a presentation.
+     *         return playMovieInWindow();
+     *     }
+     *
+     *     // Neither interface is supported, so we can't play the movie to this route.
+     *     return false;
+     * }
+     * </pre>
+     *
+     * @return The selected route, which is guaranteed to never be null.
+     *
+     * @see RouteInfo#getControlFilters
+     * @see RouteInfo#supportsControlCategory
+     * @see RouteInfo#supportsControlRequest
+     */
+    @NonNull
+    public RouteInfo getSelectedRoute() {
+        checkCallingThread();
+        return sGlobal.getSelectedRoute();
+    }
+
+    /**
+     * Returns the selected route if it matches the specified selector, otherwise
+     * selects the default route and returns it. If there is one live audio route
+     * (usually Bluetooth A2DP), it will be selected instead of default route.
+     *
+     * @param selector The selector to match.
+     * @return The previously selected route if it matched the selector, otherwise the
+     * newly selected default route which is guaranteed to never be null.
+     *
+     * @see MediaRouteSelector
+     * @see RouteInfo#matchesSelector
+     */
+    @NonNull
+    public RouteInfo updateSelectedRoute(@NonNull MediaRouteSelector selector) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+        checkCallingThread();
+
+        if (DEBUG) {
+            Log.d(TAG, "updateSelectedRoute: " + selector);
+        }
+        RouteInfo route = sGlobal.getSelectedRoute();
+        if (!route.isDefaultOrBluetooth() && !route.matchesSelector(selector)) {
+            route = sGlobal.chooseFallbackRoute();
+            sGlobal.selectRoute(route);
+        }
+        return route;
+    }
+
+    /**
+     * Selects the specified route.
+     *
+     * @param route The route to select.
+     */
+    public void selectRoute(@NonNull RouteInfo route) {
+        if (route == null) {
+            throw new IllegalArgumentException("route must not be null");
+        }
+        checkCallingThread();
+
+        if (DEBUG) {
+            Log.d(TAG, "selectRoute: " + route);
+        }
+        sGlobal.selectRoute(route);
+    }
+
+    /**
+     * Unselects the current round and selects the default route instead.
+     * <p>
+     * The reason given must be one of:
+     * <ul>
+     * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+     * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+     * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+     * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+     * </ul>
+     *
+     * @param reason The reason for disconnecting the current route.
+     */
+    public void unselect(int reason) {
+        if (reason < MediaRouter.UNSELECT_REASON_UNKNOWN ||
+                reason > MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
+            throw new IllegalArgumentException("Unsupported reason to unselect route");
+        }
+        checkCallingThread();
+
+        // Choose the fallback route if it's not already selected.
+        // Otherwise, select the default route.
+        RouteInfo fallbackRoute = sGlobal.chooseFallbackRoute();
+        if (sGlobal.getSelectedRoute() != fallbackRoute) {
+            sGlobal.selectRoute(fallbackRoute, reason);
+        } else {
+            sGlobal.selectRoute(sGlobal.getDefaultRoute(), reason);
+        }
+    }
+
+    /**
+     * Returns true if there is a route that matches the specified selector.
+     * <p>
+     * This method returns true if there are any available routes that match the
+     * selector regardless of whether they are enabled or disabled. If the
+     * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
+     * the method will only consider non-default routes.
+     * </p>
+     * <p class="note">
+     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this method
+     * will return true if it is possible to discover a matching route even if
+     * discovery is not in progress or if no matching route has yet been found.
+     * Use {@link #AVAILABILITY_FLAG_REQUIRE_MATCH} to require an actual match.
+     * </p>
+     *
+     * @param selector The selector to match.
+     * @param flags Flags to control the determination of whether a route may be
+     *            available. May be zero or some combination of
+     *            {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and
+     *            {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
+     * @return True if a matching route may be available.
+     */
+    public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+        checkCallingThread();
+
+        return sGlobal.isRouteAvailable(selector, flags);
+    }
+
+    /**
+     * Registers a callback to discover routes that match the selector and to receive
+     * events when they change.
+     * <p>
+     * This is a convenience method that has the same effect as calling
+     * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags.
+     * </p>
+     *
+     * @param selector A route selector that indicates the kinds of routes that the
+     * callback would like to discover.
+     * @param callback The callback to add.
+     * @see #removeCallback
+     */
+    public void addCallback(MediaRouteSelector selector, Callback callback) {
+        addCallback(selector, callback, 0);
+    }
+
+    /**
+     * Registers a callback to discover routes that match the selector and to receive
+     * events when they change.
+     * <p>
+     * The selector describes the kinds of routes that the application wants to
+     * discover.  For example, if the application wants to use
+     * live audio routes then it should include the
+     * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category}
+     * in its selector when it adds a callback to the media router.
+     * The selector may include any number of categories.
+     * </p><p>
+     * If the callback has already been registered, then the selector is added to
+     * the set of selectors being monitored by the callback.
+     * </p><p>
+     * By default, the callback will only be invoked for events that affect routes
+     * that match the specified selector.  Event filtering may be disabled by specifying
+     * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
+     * </p><p>
+     * Applications should use the {@link #isRouteAvailable} method to determine
+     * whether is it possible to discover a route with the desired capabilities
+     * and therefore whether the media route button should be shown to the user.
+     * </p><p>
+     * The {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} flag should be used while the application
+     * is in the foreground to request that passive discovery be performed if there are
+     * sufficient resources to allow continuous passive discovery.
+     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag will be
+     * ignored to conserve resources.
+     * </p><p>
+     * The {@link #CALLBACK_FLAG_FORCE_DISCOVERY} flag should be used when
+     * passive discovery absolutely must be performed, even on low-RAM devices.
+     * This flag has a significant performance impact on low-RAM devices
+     * since it may cause many media route providers to be started simultaneously.
+     * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
+     * performing passive discovery on these devices altogether.
+     * </p><p>
+     * The {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag should be used when the
+     * media route chooser dialog is showing to confirm the presence of available
+     * routes that the user may connect to.  This flag may use substantially more
+     * power.
+     * </p>
+     *
+     * <h3>Example</h3>
+     * <pre>
+     * public class MyActivity extends Activity {
+     *     private MediaRouter mRouter;
+     *     private MediaRouter.Callback mCallback;
+     *     private MediaRouteSelector mSelector;
+     *
+     *     protected void onCreate(Bundle savedInstanceState) {
+     *         super.onCreate(savedInstanceState);
+     *
+     *         mRouter = Mediarouter.getInstance(this);
+     *         mCallback = new MyCallback();
+     *         mSelector = new MediaRouteSelector.Builder()
+     *                 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+     *                 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+     *                 .build();
+     *     }
+     *
+     *     // Add the callback on start to tell the media router what kinds of routes
+     *     // the application is interested in so that it can try to discover suitable ones.
+     *     public void onStart() {
+     *         super.onStart();
+     *
+     *         mediaRouter.addCallback(mSelector, mCallback,
+     *                 MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+     *
+     *         MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
+     *         // do something with the route...
+     *     }
+     *
+     *     // Remove the selector on stop to tell the media router that it no longer
+     *     // needs to invest effort trying to discover routes of these kinds for now.
+     *     public void onStop() {
+     *         super.onStop();
+     *
+     *         mediaRouter.removeCallback(mCallback);
+     *     }
+     *
+     *     private final class MyCallback extends MediaRouter.Callback {
+     *         // Implement callback methods as needed.
+     *     }
+     * }
+     * </pre>
+     *
+     * @param selector A route selector that indicates the kinds of routes that the
+     * callback would like to discover.
+     * @param callback The callback to add.
+     * @param flags Flags to control the behavior of the callback.
+     * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and
+     * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
+     * @see #removeCallback
+     */
+    public void addCallback(@NonNull MediaRouteSelector selector, @NonNull Callback callback,
+            @CallbackFlags int flags) {
+        if (selector == null) {
+            throw new IllegalArgumentException("selector must not be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+        checkCallingThread();
+
+        if (DEBUG) {
+            Log.d(TAG, "addCallback: selector=" + selector
+                    + ", callback=" + callback + ", flags=" + Integer.toHexString(flags));
+        }
+
+        CallbackRecord record;
+        int index = findCallbackRecord(callback);
+        if (index < 0) {
+            record = new CallbackRecord(this, callback);
+            mCallbackRecords.add(record);
+        } else {
+            record = mCallbackRecords.get(index);
+        }
+        boolean updateNeeded = false;
+        if ((flags & ~record.mFlags) != 0) {
+            record.mFlags |= flags;
+            updateNeeded = true;
+        }
+        if (!record.mSelector.contains(selector)) {
+            record.mSelector = new MediaRouteSelector.Builder(record.mSelector)
+                    .addSelector(selector)
+                    .build();
+            updateNeeded = true;
+        }
+        if (updateNeeded) {
+            sGlobal.updateDiscoveryRequest();
+        }
+    }
+
+    /**
+     * Removes the specified callback.  It will no longer receive events about
+     * changes to media routes.
+     *
+     * @param callback The callback to remove.
+     * @see #addCallback
+     */
+    public void removeCallback(@NonNull Callback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+        checkCallingThread();
+
+        if (DEBUG) {
+            Log.d(TAG, "removeCallback: callback=" + callback);
+        }
+
+        int index = findCallbackRecord(callback);
+        if (index >= 0) {
+            mCallbackRecords.remove(index);
+            sGlobal.updateDiscoveryRequest();
+        }
+    }
+
+    private int findCallbackRecord(Callback callback) {
+        final int count = mCallbackRecords.size();
+        for (int i = 0; i < count; i++) {
+            if (mCallbackRecords.get(i).mCallback == callback) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Registers a media route provider within this application process.
+     * <p>
+     * The provider will be added to the list of providers that all {@link MediaRouter}
+     * instances within this process can use to discover routes.
+     * </p>
+     *
+     * @param providerInstance The media route provider instance to add.
+     *
+     * @see MediaRouteProvider
+     * @see #removeCallback
+     */
+    public void addProvider(@NonNull MediaRouteProvider providerInstance) {
+        if (providerInstance == null) {
+            throw new IllegalArgumentException("providerInstance must not be null");
+        }
+        checkCallingThread();
+
+        if (DEBUG) {
+            Log.d(TAG, "addProvider: " + providerInstance);
+        }
+        sGlobal.addProvider(providerInstance);
+    }
+
+    /**
+     * Unregisters a media route provider within this application process.
+     * <p>
+     * The provider will be removed from the list of providers that all {@link MediaRouter}
+     * instances within this process can use to discover routes.
+     * </p>
+     *
+     * @param providerInstance The media route provider instance to remove.
+     *
+     * @see MediaRouteProvider
+     * @see #addCallback
+     */
+    public void removeProvider(@NonNull MediaRouteProvider providerInstance) {
+        if (providerInstance == null) {
+            throw new IllegalArgumentException("providerInstance must not be null");
+        }
+        checkCallingThread();
+
+        if (DEBUG) {
+            Log.d(TAG, "removeProvider: " + providerInstance);
+        }
+        sGlobal.removeProvider(providerInstance);
+    }
+
+    /**
+     * Adds a remote control client to enable remote control of the volume
+     * of the selected route.
+     * <p>
+     * The remote control client must have previously been registered with
+     * the audio manager using the {@link android.media.AudioManager#registerRemoteControlClient
+     * AudioManager.registerRemoteControlClient} method.
+     * </p>
+     *
+     * @param remoteControlClient The {@link android.media.RemoteControlClient} to register.
+     */
+    public void addRemoteControlClient(@NonNull Object remoteControlClient) {
+        if (remoteControlClient == null) {
+            throw new IllegalArgumentException("remoteControlClient must not be null");
+        }
+        checkCallingThread();
+
+        if (DEBUG) {
+            Log.d(TAG, "addRemoteControlClient: " + remoteControlClient);
+        }
+        sGlobal.addRemoteControlClient(remoteControlClient);
+    }
+
+    /**
+     * Removes a remote control client.
+     *
+     * @param remoteControlClient The {@link android.media.RemoteControlClient}
+     *            to unregister.
+     */
+    public void removeRemoteControlClient(@NonNull Object remoteControlClient) {
+        if (remoteControlClient == null) {
+            throw new IllegalArgumentException("remoteControlClient must not be null");
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient);
+        }
+        sGlobal.removeRemoteControlClient(remoteControlClient);
+    }
+
+    /**
+     * Sets the media session to enable remote control of the volume of the
+     * selected route. This should be used instead of
+     * {@link #addRemoteControlClient} when using media sessions. Set the
+     * session to null to clear it.
+     *
+     * @param mediaSession The {@link android.media.session.MediaSession} to
+     *            use.
+     */
+    public void setMediaSession(Object mediaSession) {
+        if (DEBUG) {
+            Log.d(TAG, "addMediaSession: " + mediaSession);
+        }
+        sGlobal.setMediaSession(mediaSession);
+    }
+
+    /**
+     * Sets a compat media session to enable remote control of the volume of the
+     * selected route. This should be used instead of
+     * {@link #addRemoteControlClient} when using {@link MediaSessionCompat}.
+     * Set the session to null to clear it.
+     *
+     * @param mediaSession
+     */
+    public void setMediaSessionCompat(MediaSessionCompat mediaSession) {
+        if (DEBUG) {
+            Log.d(TAG, "addMediaSessionCompat: " + mediaSession);
+        }
+        sGlobal.setMediaSessionCompat(mediaSession);
+    }
+
+    public MediaSessionCompat.Token getMediaSessionToken() {
+        return sGlobal.getMediaSessionToken();
+    }
+
+    /**
+     * Ensures that calls into the media router are on the correct thread.
+     * It pays to be a little paranoid when global state invariants are at risk.
+     */
+    static void checkCallingThread() {
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            throw new IllegalStateException("The media router service must only be "
+                    + "accessed on the application's main thread.");
+        }
+    }
+
+    static <T> boolean equal(T a, T b) {
+        return a == b || (a != null && b != null && a.equals(b));
+    }
+
+    /**
+     * Provides information about a media route.
+     * <p>
+     * Each media route has a list of {@link MediaControlIntent media control}
+     * {@link #getControlFilters intent filters} that describe the capabilities of the
+     * route and the manner in which it is used and controlled.
+     * </p>
+     */
+    public static class RouteInfo {
+        private final ProviderInfo mProvider;
+        private final String mDescriptorId;
+        private final String mUniqueId;
+        private String mName;
+        private String mDescription;
+        private Uri mIconUri;
+        private boolean mEnabled;
+        private boolean mConnecting;
+        private int mConnectionState;
+        private boolean mCanDisconnect;
+        private final ArrayList<IntentFilter> mControlFilters = new ArrayList<>();
+        private int mPlaybackType;
+        private int mPlaybackStream;
+        private int mDeviceType;
+        private int mVolumeHandling;
+        private int mVolume;
+        private int mVolumeMax;
+        private Display mPresentationDisplay;
+        private int mPresentationDisplayId = PRESENTATION_DISPLAY_ID_NONE;
+        private Bundle mExtras;
+        private IntentSender mSettingsIntent;
+        MediaRouteDescriptor mDescriptor;
+
+        @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
+                CONNECTION_STATE_CONNECTED})
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface ConnectionState {}
+
+        /**
+         * The default connection state indicating the route is disconnected.
+         *
+         * @see #getConnectionState
+         */
+        public static final int CONNECTION_STATE_DISCONNECTED = 0;
+
+        /**
+         * A connection state indicating the route is in the process of connecting and is not yet
+         * ready for use.
+         *
+         * @see #getConnectionState
+         */
+        public static final int CONNECTION_STATE_CONNECTING = 1;
+
+        /**
+         * A connection state indicating the route is connected.
+         *
+         * @see #getConnectionState
+         */
+        public static final int CONNECTION_STATE_CONNECTED = 2;
+
+        @IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE})
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface PlaybackType {}
+
+        /**
+         * The default playback type, "local", indicating the presentation of the media
+         * is happening on the same device (e.g. a phone, a tablet) as where it is
+         * controlled from.
+         *
+         * @see #getPlaybackType
+         */
+        public static final int PLAYBACK_TYPE_LOCAL = 0;
+
+        /**
+         * A playback type indicating the presentation of the media is happening on
+         * a different device (i.e. the remote device) than where it is controlled from.
+         *
+         * @see #getPlaybackType
+         */
+        public static final int PLAYBACK_TYPE_REMOTE = 1;
+
+        @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface DeviceType {}
+
+        /**
+         * The default receiver device type of the route indicating the type is unknown.
+         *
+         * @see #getDeviceType
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public static final int DEVICE_TYPE_UNKNOWN = 0;
+
+        /**
+         * A receiver device type of the route indicating the presentation of the media is happening
+         * on a TV.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_TV = 1;
+
+        /**
+         * A receiver device type of the route indicating the presentation of the media is happening
+         * on a speaker.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_SPEAKER = 2;
+
+        /**
+         * A receiver device type of the route indicating the presentation of the media is happening
+         * on a bluetooth device such as a bluetooth speaker.
+         *
+         * @see #getDeviceType
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public static final int DEVICE_TYPE_BLUETOOTH = 3;
+
+        @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface PlaybackVolume {}
+
+        /**
+         * Playback information indicating the playback volume is fixed, i.e. it cannot be
+         * controlled from this object. An example of fixed playback volume is a remote player,
+         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
+         * than attenuate at the source.
+         *
+         * @see #getVolumeHandling
+         */
+        public static final int PLAYBACK_VOLUME_FIXED = 0;
+
+        /**
+         * Playback information indicating the playback volume is variable and can be controlled
+         * from this object.
+         *
+         * @see #getVolumeHandling
+         */
+        public static final int PLAYBACK_VOLUME_VARIABLE = 1;
+
+        /**
+         * The default presentation display id indicating no presentation display is associated
+         * with the route.
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public static final int PRESENTATION_DISPLAY_ID_NONE = -1;
+
+        static final int CHANGE_GENERAL = 1 << 0;
+        static final int CHANGE_VOLUME = 1 << 1;
+        static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2;
+
+        // Should match to SystemMediaRouteProvider.PACKAGE_NAME.
+        static final String SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME = "android";
+
+        RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) {
+            mProvider = provider;
+            mDescriptorId = descriptorId;
+            mUniqueId = uniqueId;
+        }
+
+        /**
+         * Gets information about the provider of this media route.
+         */
+        public ProviderInfo getProvider() {
+            return mProvider;
+        }
+
+        /**
+         * Gets the unique id of the route.
+         * <p>
+         * The route unique id functions as a stable identifier by which the route is known.
+         * For example, an application can use this id as a token to remember the
+         * selected route across restarts or to communicate its identity to a service.
+         * </p>
+         *
+         * @return The unique id of the route, never null.
+         */
+        @NonNull
+        public String getId() {
+            return mUniqueId;
+        }
+
+        /**
+         * Gets the user-visible name of the route.
+         * <p>
+         * The route name identifies the destination represented by the route.
+         * It may be a user-supplied name, an alias, or device serial number.
+         * </p>
+         *
+         * @return The user-visible name of a media route.  This is the string presented
+         * to users who may select this as the active route.
+         */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Gets the user-visible description of the route.
+         * <p>
+         * The route description describes the kind of destination represented by the route.
+         * It may be a user-supplied string, a model number or brand of device.
+         * </p>
+         *
+         * @return The description of the route, or null if none.
+         */
+        @Nullable
+        public String getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Gets the URI of the icon representing this route.
+         * <p>
+         * This icon will be used in picker UIs if available.
+         * </p>
+         *
+         * @return The URI of the icon representing this route, or null if none.
+         */
+        public Uri getIconUri() {
+            return mIconUri;
+        }
+
+        /**
+         * Returns true if this route is enabled and may be selected.
+         *
+         * @return True if this route is enabled.
+         */
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+
+        /**
+         * Returns true if the route is in the process of connecting and is not
+         * yet ready for use.
+         *
+         * @return True if this route is in the process of connecting.
+         */
+        public boolean isConnecting() {
+            return mConnecting;
+        }
+
+        /**
+         * Gets the connection state of the route.
+         *
+         * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
+         * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
+         */
+        @ConnectionState
+        public int getConnectionState() {
+            return mConnectionState;
+        }
+
+        /**
+         * Returns true if this route is currently selected.
+         *
+         * @return True if this route is currently selected.
+         *
+         * @see MediaRouter#getSelectedRoute
+         */
+        public boolean isSelected() {
+            checkCallingThread();
+            return sGlobal.getSelectedRoute() == this;
+        }
+
+        /**
+         * Returns true if this route is the default route.
+         *
+         * @return True if this route is the default route.
+         *
+         * @see MediaRouter#getDefaultRoute
+         */
+        public boolean isDefault() {
+            checkCallingThread();
+            return sGlobal.getDefaultRoute() == this;
+        }
+
+        /**
+         * Returns true if this route is a bluetooth route.
+         *
+         * @return True if this route is a bluetooth route.
+         *
+         * @see MediaRouter#getBluetoothRoute
+         */
+        public boolean isBluetooth() {
+            checkCallingThread();
+            return sGlobal.getBluetoothRoute() == this;
+        }
+
+        /**
+         * Returns true if this route is the default route and the device speaker.
+         *
+         * @return True if this route is the default route and the device speaker.
+         */
+        public boolean isDeviceSpeaker() {
+            int defaultAudioRouteNameResourceId = Resources.getSystem().getIdentifier(
+                    "default_audio_route_name", "string", "android");
+            return isDefault()
+                    && Resources.getSystem().getText(defaultAudioRouteNameResourceId).equals(mName);
+        }
+
+        /**
+         * Gets a list of {@link MediaControlIntent media control intent} filters that
+         * describe the capabilities of this route and the media control actions that
+         * it supports.
+         *
+         * @return A list of intent filters that specifies the media control intents that
+         * this route supports.
+         *
+         * @see MediaControlIntent
+         * @see #supportsControlCategory
+         * @see #supportsControlRequest
+         */
+        public List<IntentFilter> getControlFilters() {
+            return mControlFilters;
+        }
+
+        /**
+         * Returns true if the route supports at least one of the capabilities
+         * described by a media route selector.
+         *
+         * @param selector The selector that specifies the capabilities to check.
+         * @return True if the route supports at least one of the capabilities
+         * described in the media route selector.
+         */
+        public boolean matchesSelector(@NonNull MediaRouteSelector selector) {
+            if (selector == null) {
+                throw new IllegalArgumentException("selector must not be null");
+            }
+            checkCallingThread();
+            return selector.matchesControlFilters(mControlFilters);
+        }
+
+        /**
+         * Returns true if the route supports the specified
+         * {@link MediaControlIntent media control} category.
+         * <p>
+         * Media control categories describe the capabilities of this route
+         * such as whether it supports live audio streaming or remote playback.
+         * </p>
+         *
+         * @param category A {@link MediaControlIntent media control} category
+         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
+         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
+         * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
+         * media control category.
+         * @return True if the route supports the specified intent category.
+         *
+         * @see MediaControlIntent
+         * @see #getControlFilters
+         */
+        public boolean supportsControlCategory(@NonNull String category) {
+            if (category == null) {
+                throw new IllegalArgumentException("category must not be null");
+            }
+            checkCallingThread();
+
+            int count = mControlFilters.size();
+            for (int i = 0; i < count; i++) {
+                if (mControlFilters.get(i).hasCategory(category)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Returns true if the route supports the specified
+         * {@link MediaControlIntent media control} category and action.
+         * <p>
+         * Media control actions describe specific requests that an application
+         * can ask a route to perform.
+         * </p>
+         *
+         * @param category A {@link MediaControlIntent media control} category
+         * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
+         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
+         * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
+         * media control category.
+         * @param action A {@link MediaControlIntent media control} action
+         * such as {@link MediaControlIntent#ACTION_PLAY}.
+         * @return True if the route supports the specified intent action.
+         *
+         * @see MediaControlIntent
+         * @see #getControlFilters
+         */
+        public boolean supportsControlAction(@NonNull String category, @NonNull String action) {
+            if (category == null) {
+                throw new IllegalArgumentException("category must not be null");
+            }
+            if (action == null) {
+                throw new IllegalArgumentException("action must not be null");
+            }
+            checkCallingThread();
+
+            int count = mControlFilters.size();
+            for (int i = 0; i < count; i++) {
+                IntentFilter filter = mControlFilters.get(i);
+                if (filter.hasCategory(category) && filter.hasAction(action)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Returns true if the route supports the specified
+         * {@link MediaControlIntent media control} request.
+         * <p>
+         * Media control requests are used to request the route to perform
+         * actions such as starting remote playback of a media item.
+         * </p>
+         *
+         * @param intent A {@link MediaControlIntent media control intent}.
+         * @return True if the route can handle the specified intent.
+         *
+         * @see MediaControlIntent
+         * @see #getControlFilters
+         */
+        public boolean supportsControlRequest(@NonNull Intent intent) {
+            if (intent == null) {
+                throw new IllegalArgumentException("intent must not be null");
+            }
+            checkCallingThread();
+
+            ContentResolver contentResolver = sGlobal.getContentResolver();
+            int count = mControlFilters.size();
+            for (int i = 0; i < count; i++) {
+                if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Sends a {@link MediaControlIntent media control} request to be performed
+         * asynchronously by the route's destination.
+         * <p>
+         * Media control requests are used to request the route to perform
+         * actions such as starting remote playback of a media item.
+         * </p><p>
+         * This function may only be called on a selected route.  Control requests
+         * sent to unselected routes will fail.
+         * </p>
+         *
+         * @param intent A {@link MediaControlIntent media control intent}.
+         * @param callback A {@link ControlRequestCallback} to invoke with the result
+         * of the request, or null if no result is required.
+         *
+         * @see MediaControlIntent
+         */
+        public void sendControlRequest(@NonNull Intent intent,
+                @Nullable ControlRequestCallback callback) {
+            if (intent == null) {
+                throw new IllegalArgumentException("intent must not be null");
+            }
+            checkCallingThread();
+
+            sGlobal.sendControlRequest(this, intent, callback);
+        }
+
+        /**
+         * Gets the type of playback associated with this route.
+         *
+         * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
+         * or {@link #PLAYBACK_TYPE_REMOTE}.
+         */
+        @PlaybackType
+        public int getPlaybackType() {
+            return mPlaybackType;
+        }
+
+        /**
+         * Gets the audio stream over which the playback associated with this route is performed.
+         *
+         * @return The stream over which the playback associated with this route is performed.
+         */
+        public int getPlaybackStream() {
+            return mPlaybackStream;
+        }
+
+        /**
+         * Gets the type of the receiver device associated with this route.
+         *
+         * @return The type of the receiver device associated with this route:
+         * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
+         */
+        public int getDeviceType() {
+            return mDeviceType;
+        }
+
+
+        /**
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public boolean isDefaultOrBluetooth() {
+            if (isDefault() || mDeviceType == DEVICE_TYPE_BLUETOOTH) {
+                return true;
+            }
+            // This is a workaround for platform version 23 or below where the system route
+            // provider doesn't specify device type for bluetooth media routes.
+            return isSystemMediaRouteProvider(this)
+                    && supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+                    && !supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+        }
+
+        /**
+         * Returns {@code true} if the route is selectable.
+         */
+        boolean isSelectable() {
+            // This tests whether the route is still valid and enabled.
+            // The route descriptor field is set to null when the route is removed.
+            return mDescriptor != null && mEnabled;
+        }
+
+        private static boolean isSystemMediaRouteProvider(MediaRouter.RouteInfo route) {
+            return TextUtils.equals(route.getProviderInstance().getMetadata().getPackageName(),
+                    SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME);
+        }
+
+        /**
+         * Gets information about how volume is handled on the route.
+         *
+         * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
+         * or {@link #PLAYBACK_VOLUME_VARIABLE}.
+         */
+        @PlaybackVolume
+        public int getVolumeHandling() {
+            return mVolumeHandling;
+        }
+
+        /**
+         * Gets the current volume for this route. Depending on the route, this may only
+         * be valid if the route is currently selected.
+         *
+         * @return The volume at which the playback associated with this route is performed.
+         */
+        public int getVolume() {
+            return mVolume;
+        }
+
+        /**
+         * Gets the maximum volume at which the playback associated with this route is performed.
+         *
+         * @return The maximum volume at which the playback associated with
+         * this route is performed.
+         */
+        public int getVolumeMax() {
+            return mVolumeMax;
+        }
+
+        /**
+         * Gets whether this route supports disconnecting without interrupting
+         * playback.
+         *
+         * @return True if this route can disconnect without stopping playback,
+         *         false otherwise.
+         */
+        public boolean canDisconnect() {
+            return mCanDisconnect;
+        }
+
+        /**
+         * Requests a volume change for this route asynchronously.
+         * <p>
+         * This function may only be called on a selected route.  It will have
+         * no effect if the route is currently unselected.
+         * </p>
+         *
+         * @param volume The new volume value between 0 and {@link #getVolumeMax}.
+         */
+        public void requestSetVolume(int volume) {
+            checkCallingThread();
+            sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
+        }
+
+        /**
+         * Requests an incremental volume update for this route asynchronously.
+         * <p>
+         * This function may only be called on a selected route.  It will have
+         * no effect if the route is currently unselected.
+         * </p>
+         *
+         * @param delta The delta to add to the current volume.
+         */
+        public void requestUpdateVolume(int delta) {
+            checkCallingThread();
+            if (delta != 0) {
+                sGlobal.requestUpdateVolume(this, delta);
+            }
+        }
+
+        /**
+         * Gets the {@link Display} that should be used by the application to show
+         * a {@link android.app.Presentation} on an external display when this route is selected.
+         * Depending on the route, this may only be valid if the route is currently
+         * selected.
+         * <p>
+         * The preferred presentation display may change independently of the route
+         * being selected or unselected.  For example, the presentation display
+         * of the default system route may change when an external HDMI display is connected
+         * or disconnected even though the route itself has not changed.
+         * </p><p>
+         * This method may return null if there is no external display associated with
+         * the route or if the display is not ready to show UI yet.
+         * </p><p>
+         * The application should listen for changes to the presentation display
+         * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
+         * show or dismiss its {@link android.app.Presentation} accordingly when the display
+         * becomes available or is removed.
+         * </p><p>
+         * This method only makes sense for
+         * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes.
+         * </p>
+         *
+         * @return The preferred presentation display to use when this route is
+         * selected or null if none.
+         *
+         * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
+         * @see android.app.Presentation
+         */
+        @Nullable
+        public Display getPresentationDisplay() {
+            checkCallingThread();
+            if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
+                mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
+            }
+            return mPresentationDisplay;
+        }
+
+        /**
+         * Gets the route's presentation display id, or -1 if none.
+         * @hide
+         */
+        // @RestrictTo(LIBRARY_GROUP)
+        public int getPresentationDisplayId() {
+            return mPresentationDisplayId;
+        }
+
+        /**
+         * Gets a collection of extra properties about this route that were supplied
+         * by its media route provider, or null if none.
+         */
+        @Nullable
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Gets an intent sender for launching a settings activity for this
+         * route.
+         */
+        @Nullable
+        public IntentSender getSettingsIntent() {
+            return mSettingsIntent;
+        }
+
+        /**
+         * Selects this media route.
+         */
+        public void select() {
+            checkCallingThread();
+            sGlobal.selectRoute(this);
+        }
+
+        @Override
+        public String toString() {
+            return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId
+                    + ", name=" + mName
+                    + ", description=" + mDescription
+                    + ", iconUri=" + mIconUri
+                    + ", enabled=" + mEnabled
+                    + ", connecting=" + mConnecting
+                    + ", connectionState=" + mConnectionState
+                    + ", canDisconnect=" + mCanDisconnect
+                    + ", playbackType=" + mPlaybackType
+                    + ", playbackStream=" + mPlaybackStream
+                    + ", deviceType=" + mDeviceType
+                    + ", volumeHandling=" + mVolumeHandling
+                    + ", volume=" + mVolume
+                    + ", volumeMax=" + mVolumeMax
+                    + ", presentationDisplayId=" + mPresentationDisplayId
+                    + ", extras=" + mExtras
+                    + ", settingsIntent=" + mSettingsIntent
+                    + ", providerPackageName=" + mProvider.getPackageName()
+                    + " }";
+        }
+
+        int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
+            int changes = 0;
+            if (mDescriptor != descriptor) {
+                changes = updateDescriptor(descriptor);
+            }
+            return changes;
+        }
+
+        int updateDescriptor(MediaRouteDescriptor descriptor) {
+            int changes = 0;
+            mDescriptor = descriptor;
+            if (descriptor != null) {
+                if (!equal(mName, descriptor.getName())) {
+                    mName = descriptor.getName();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (!equal(mDescription, descriptor.getDescription())) {
+                    mDescription = descriptor.getDescription();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (!equal(mIconUri, descriptor.getIconUri())) {
+                    mIconUri = descriptor.getIconUri();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (mEnabled != descriptor.isEnabled()) {
+                    mEnabled = descriptor.isEnabled();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (mConnecting != descriptor.isConnecting()) {
+                    mConnecting = descriptor.isConnecting();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (mConnectionState != descriptor.getConnectionState()) {
+                    mConnectionState = descriptor.getConnectionState();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (!mControlFilters.equals(descriptor.getControlFilters())) {
+                    mControlFilters.clear();
+                    mControlFilters.addAll(descriptor.getControlFilters());
+                    changes |= CHANGE_GENERAL;
+                }
+                if (mPlaybackType != descriptor.getPlaybackType()) {
+                    mPlaybackType = descriptor.getPlaybackType();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (mPlaybackStream != descriptor.getPlaybackStream()) {
+                    mPlaybackStream = descriptor.getPlaybackStream();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (mDeviceType != descriptor.getDeviceType()) {
+                    mDeviceType = descriptor.getDeviceType();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (mVolumeHandling != descriptor.getVolumeHandling()) {
+                    mVolumeHandling = descriptor.getVolumeHandling();
+                    changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+                }
+                if (mVolume != descriptor.getVolume()) {
+                    mVolume = descriptor.getVolume();
+                    changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+                }
+                if (mVolumeMax != descriptor.getVolumeMax()) {
+                    mVolumeMax = descriptor.getVolumeMax();
+                    changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+                }
+                if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
+                    mPresentationDisplayId = descriptor.getPresentationDisplayId();
+                    mPresentationDisplay = null;
+                    changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
+                }
+                if (!equal(mExtras, descriptor.getExtras())) {
+                    mExtras = descriptor.getExtras();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) {
+                    mSettingsIntent = descriptor.getSettingsActivity();
+                    changes |= CHANGE_GENERAL;
+                }
+                if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) {
+                    mCanDisconnect = descriptor.canDisconnectAndKeepPlaying();
+                    changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
+                }
+            }
+            return changes;
+        }
+
+        String getDescriptorId() {
+            return mDescriptorId;
+        }
+
+        /** @hide */
+        // @RestrictTo(LIBRARY_GROUP)
+        public MediaRouteProvider getProviderInstance() {
+            return mProvider.getProviderInstance();
+        }
+    }
+
+    /**
+     * Information about a route that consists of multiple other routes in a group.
+     * @hide
+     */
+    // @RestrictTo(LIBRARY_GROUP)
+    public static class RouteGroup extends RouteInfo {
+        private List<RouteInfo> mRoutes = new ArrayList<>();
+
+        RouteGroup(ProviderInfo provider, String descriptorId, String uniqueId) {
+            super(provider, descriptorId, uniqueId);
+        }
+
+        /**
+         * @return The number of routes in this group
+         */
+        public int getRouteCount() {
+            return mRoutes.size();
+        }
+
+        /**
+         * Returns the route in this group at the specified index
+         *
+         * @param index Index to fetch
+         * @return The route at index
+         */
+        public RouteInfo getRouteAt(int index) {
+            return mRoutes.get(index);
+        }
+
+        /**
+         * Returns the routes in this group
+         *
+         * @return The list of the routes in this group
+         */
+        public List<RouteInfo> getRoutes() {
+            return mRoutes;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(super.toString());
+            sb.append('[');
+            final int count = mRoutes.size();
+            for (int i = 0; i < count; i++) {
+                if (i > 0) sb.append(", ");
+                sb.append(mRoutes.get(i));
+            }
+            sb.append(']');
+            return sb.toString();
+        }
+
+        @Override
+        int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
+            boolean changed = false;
+            if (mDescriptor != descriptor) {
+                mDescriptor = descriptor;
+                if (descriptor != null) {
+                    List<String> groupMemberIds = descriptor.getGroupMemberIds();
+                    List<RouteInfo> routes = new ArrayList<>();
+                    changed = groupMemberIds.size() != mRoutes.size();
+                    for (String groupMemberId : groupMemberIds) {
+                        String uniqueId = sGlobal.getUniqueId(getProvider(), groupMemberId);
+                        RouteInfo groupMember = sGlobal.getRoute(uniqueId);
+                        if (groupMember != null) {
+                            routes.add(groupMember);
+                            if (!changed && !mRoutes.contains(groupMember)) {
+                                changed = true;
+                            }
+                        }
+                    }
+                    if (changed) {
+                        mRoutes = routes;
+                    }
+                }
+            }
+            return (changed ? CHANGE_GENERAL : 0) | super.updateDescriptor(descriptor);
+        }
+    }
+
+    /**
+     * Provides information about a media route provider.
+     * <p>
+     * This object may be used to determine which media route provider has
+     * published a particular route.
+     * </p>
+     */
+    public static final class ProviderInfo {
+        private final MediaRouteProvider mProviderInstance;
+        private final List<RouteInfo> mRoutes = new ArrayList<>();
+
+        private final ProviderMetadata mMetadata;
+        private MediaRouteProviderDescriptor mDescriptor;
+        private Resources mResources;
+        private boolean mResourcesNotAvailable;
+
+        ProviderInfo(MediaRouteProvider provider) {
+            mProviderInstance = provider;
+            mMetadata = provider.getMetadata();
+        }
+
+        /**
+         * Gets the provider's underlying {@link MediaRouteProvider} instance.
+         */
+        public MediaRouteProvider getProviderInstance() {
+            checkCallingThread();
+            return mProviderInstance;
+        }
+
+        /**
+         * Gets the package name of the media route provider.
+         */
+        public String getPackageName() {
+            return mMetadata.getPackageName();
+        }
+
+        /**
+         * Gets the component name of the media route provider.
+         */
+        public ComponentName getComponentName() {
+            return mMetadata.getComponentName();
+        }
+
+        /**
+         * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider.
+         */
+        public List<RouteInfo> getRoutes() {
+            checkCallingThread();
+            return mRoutes;
+        }
+
+        Resources getResources() {
+            if (mResources == null && !mResourcesNotAvailable) {
+                String packageName = getPackageName();
+                Context context = sGlobal.getProviderContext(packageName);
+                if (context != null) {
+                    mResources = context.getResources();
+                } else {
+                    Log.w(TAG, "Unable to obtain resources for route provider package: "
+                            + packageName);
+                    mResourcesNotAvailable = true;
+                }
+            }
+            return mResources;
+        }
+
+        boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
+            if (mDescriptor != descriptor) {
+                mDescriptor = descriptor;
+                return true;
+            }
+            return false;
+        }
+
+        int findRouteByDescriptorId(String id) {
+            final int count = mRoutes.size();
+            for (int i = 0; i < count; i++) {
+                if (mRoutes.get(i).mDescriptorId.equals(id)) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        @Override
+        public String toString() {
+            return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName()
+                    + " }";
+        }
+    }
+
+    /**
+     * Interface for receiving events about media routing changes.
+     * All methods of this interface will be called from the application's main thread.
+     * <p>
+     * A Callback will only receive events relevant to routes that the callback
+     * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
+     * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}.
+     * </p>
+     *
+     * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int)
+     * @see MediaRouter#removeCallback(Callback)
+     */
+    public static abstract class Callback {
+        /**
+         * Called when the supplied media route becomes selected as the active route.
+         *
+         * @param router The media router reporting the event.
+         * @param route The route that has been selected.
+         */
+        public void onRouteSelected(MediaRouter router, RouteInfo route) {
+        }
+
+        /**
+         * Called when the supplied media route becomes unselected as the active route.
+         * For detailed reason, override {@link #onRouteUnselected(MediaRouter, RouteInfo, int)}
+         * instead.
+         *
+         * @param router The media router reporting the event.
+         * @param route The route that has been unselected.
+         */
+        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
+        }
+
+        /**
+         * Called when the supplied media route becomes unselected as the active route.
+         * The default implementation calls {@link #onRouteUnselected}.
+         * <p>
+         * The reason provided will be one of the following:
+         * <ul>
+         * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+         * </ul>
+         *
+         * @param router The media router reporting the event.
+         * @param route The route that has been unselected.
+         * @param reason The reason for unselecting the route.
+         */
+        public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
+            onRouteUnselected(router, route);
+        }
+
+        /**
+         * Called when a media route has been added.
+         *
+         * @param router The media router reporting the event.
+         * @param route The route that has become available for use.
+         */
+        public void onRouteAdded(MediaRouter router, RouteInfo route) {
+        }
+
+        /**
+         * Called when a media route has been removed.
+         *
+         * @param router The media router reporting the event.
+         * @param route The route that has been removed from availability.
+         */
+        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
+        }
+
+        /**
+         * Called when a property of the indicated media route has changed.
+         *
+         * @param router The media router reporting the event.
+         * @param route The route that was changed.
+         */
+        public void onRouteChanged(MediaRouter router, RouteInfo route) {
+        }
+
+        /**
+         * Called when a media route's volume changes.
+         *
+         * @param router The media router reporting the event.
+         * @param route The route whose volume changed.
+         */
+        public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
+        }
+
+        /**
+         * Called when a media route's presentation display changes.
+         * <p>
+         * This method is called whenever the route's presentation display becomes
+         * available, is removed or has changes to some of its properties (such as its size).
+         * </p>
+         *
+         * @param router The media router reporting the event.
+         * @param route The route whose presentation display changed.
+         *
+         * @see RouteInfo#getPresentationDisplay()
+         */
+        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
+        }
+
+        /**
+         * Called when a media route provider has been added.
+         *
+         * @param router The media router reporting the event.
+         * @param provider The provider that has become available for use.
+         */
+        public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
+        }
+
+        /**
+         * Called when a media route provider has been removed.
+         *
+         * @param router The media router reporting the event.
+         * @param provider The provider that has been removed from availability.
+         */
+        public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
+        }
+
+        /**
+         * Called when a property of the indicated media route provider has changed.
+         *
+         * @param router The media router reporting the event.
+         * @param provider The provider that was changed.
+         */
+        public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
+        }
+    }
+
+    /**
+     * Callback which is invoked with the result of a media control request.
+     *
+     * @see RouteInfo#sendControlRequest
+     */
+    public static abstract class ControlRequestCallback {
+        /**
+         * Called when a media control request succeeds.
+         *
+         * @param data Result data, or null if none.
+         * Contents depend on the {@link MediaControlIntent media control action}.
+         */
+        public void onResult(Bundle data) {
+        }
+
+        /**
+         * Called when a media control request fails.
+         *
+         * @param error A localized error message which may be shown to the user, or null
+         * if the cause of the error is unclear.
+         * @param data Error data, or null if none.
+         * Contents depend on the {@link MediaControlIntent media control action}.
+         */
+        public void onError(String error, Bundle data) {
+        }
+    }
+
+    private static final class CallbackRecord {
+        public final MediaRouter mRouter;
+        public final Callback mCallback;
+        public MediaRouteSelector mSelector;
+        public int mFlags;
+
+        public CallbackRecord(MediaRouter router, Callback callback) {
+            mRouter = router;
+            mCallback = callback;
+            mSelector = MediaRouteSelector.EMPTY;
+        }
+
+        public boolean filterRouteEvent(RouteInfo route) {
+            return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
+                    || route.matchesSelector(mSelector);
+        }
+    }
+
+    /**
+     * Global state for the media router.
+     * <p>
+     * Media routes and media route providers are global to the process; their
+     * state and the bulk of the media router implementation lives here.
+     * </p>
+     */
+    private static final class GlobalMediaRouter
+            implements SystemMediaRouteProvider.SyncCallback,
+            RegisteredMediaRouteProviderWatcher.Callback {
+        final Context mApplicationContext;
+        final ArrayList<WeakReference<MediaRouter>> mRouters = new ArrayList<>();
+        private final ArrayList<RouteInfo> mRoutes = new ArrayList<>();
+        private final Map<Pair<String, String>, String> mUniqueIdMap = new HashMap<>();
+        private final ArrayList<ProviderInfo> mProviders = new ArrayList<>();
+        private final ArrayList<RemoteControlClientRecord> mRemoteControlClients =
+                new ArrayList<>();
+        final RemoteControlClientCompat.PlaybackInfo mPlaybackInfo =
+                new RemoteControlClientCompat.PlaybackInfo();
+        private final ProviderCallback mProviderCallback = new ProviderCallback();
+        final CallbackHandler mCallbackHandler = new CallbackHandler();
+        private final DisplayManagerCompat mDisplayManager;
+        final SystemMediaRouteProvider mSystemProvider;
+        private final boolean mLowRam;
+
+        private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
+        private RouteInfo mDefaultRoute;
+        private RouteInfo mBluetoothRoute;
+        RouteInfo mSelectedRoute;
+        private RouteController mSelectedRouteController;
+        // A map from route descriptor ID to RouteController for the member routes in the currently
+        // selected route group.
+        private final Map<String, RouteController> mRouteControllerMap = new HashMap<>();
+        private MediaRouteDiscoveryRequest mDiscoveryRequest;
+        private MediaSessionRecord mMediaSession;
+        MediaSessionCompat mRccMediaSession;
+        private MediaSessionCompat mCompatSession;
+        private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
+                new MediaSessionCompat.OnActiveChangeListener() {
+            @Override
+            public void onActiveChanged() {
+                if(mRccMediaSession != null) {
+                    if (mRccMediaSession.isActive()) {
+                        addRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+                    } else {
+                        removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+                    }
+                }
+            }
+        };
+
+        GlobalMediaRouter(Context applicationContext) {
+            mApplicationContext = applicationContext;
+            mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
+            mLowRam = ActivityManagerCompat.isLowRamDevice(
+                    (ActivityManager)applicationContext.getSystemService(
+                            Context.ACTIVITY_SERVICE));
+
+            // Add the system media route provider for interoperating with
+            // the framework media router.  This one is special and receives
+            // synchronization messages from the media router.
+            mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
+        }
+
+        public void start() {
+            addProvider(mSystemProvider);
+
+            // Start watching for routes published by registered media route
+            // provider services.
+            mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
+                    mApplicationContext, this);
+            mRegisteredProviderWatcher.start();
+        }
+
+        public MediaRouter getRouter(Context context) {
+            MediaRouter router;
+            for (int i = mRouters.size(); --i >= 0; ) {
+                router = mRouters.get(i).get();
+                if (router == null) {
+                    mRouters.remove(i);
+                } else if (router.mContext == context) {
+                    return router;
+                }
+            }
+            router = new MediaRouter(context);
+            mRouters.add(new WeakReference<MediaRouter>(router));
+            return router;
+        }
+
+        public ContentResolver getContentResolver() {
+            return mApplicationContext.getContentResolver();
+        }
+
+        public Context getProviderContext(String packageName) {
+            if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) {
+                return mApplicationContext;
+            }
+            try {
+                return mApplicationContext.createPackageContext(
+                        packageName, Context.CONTEXT_RESTRICTED);
+            } catch (NameNotFoundException ex) {
+                return null;
+            }
+        }
+
+        public Display getDisplay(int displayId) {
+            return mDisplayManager.getDisplay(displayId);
+        }
+
+        public void sendControlRequest(RouteInfo route,
+                Intent intent, ControlRequestCallback callback) {
+            if (route == mSelectedRoute && mSelectedRouteController != null) {
+                if (mSelectedRouteController.onControlRequest(intent, callback)) {
+                    return;
+                }
+            }
+            if (callback != null) {
+                callback.onError(null, null);
+            }
+        }
+
+        public void requestSetVolume(RouteInfo route, int volume) {
+            if (route == mSelectedRoute && mSelectedRouteController != null) {
+                mSelectedRouteController.onSetVolume(volume);
+            } else if (!mRouteControllerMap.isEmpty()) {
+                RouteController controller = mRouteControllerMap.get(route.mDescriptorId);
+                if (controller != null) {
+                    controller.onSetVolume(volume);
+                }
+            }
+        }
+
+        public void requestUpdateVolume(RouteInfo route, int delta) {
+            if (route == mSelectedRoute && mSelectedRouteController != null) {
+                mSelectedRouteController.onUpdateVolume(delta);
+            }
+        }
+
+        public RouteInfo getRoute(String uniqueId) {
+            for (RouteInfo info : mRoutes) {
+                if (info.mUniqueId.equals(uniqueId)) {
+                    return info;
+                }
+            }
+            return null;
+        }
+
+        public List<RouteInfo> getRoutes() {
+            return mRoutes;
+        }
+
+        List<ProviderInfo> getProviders() {
+            return mProviders;
+        }
+
+        @NonNull RouteInfo getDefaultRoute() {
+            if (mDefaultRoute == null) {
+                // This should never happen once the media router has been fully
+                // initialized but it is good to check for the error in case there
+                // is a bug in provider initialization.
+                throw new IllegalStateException("There is no default route.  "
+                        + "The media router has not yet been fully initialized.");
+            }
+            return mDefaultRoute;
+        }
+
+        RouteInfo getBluetoothRoute() {
+            return mBluetoothRoute;
+        }
+
+        @NonNull RouteInfo getSelectedRoute() {
+            if (mSelectedRoute == null) {
+                // This should never happen once the media router has been fully
+                // initialized but it is good to check for the error in case there
+                // is a bug in provider initialization.
+                throw new IllegalStateException("There is no currently selected route.  "
+                        + "The media router has not yet been fully initialized.");
+            }
+            return mSelectedRoute;
+        }
+
+        void selectRoute(@NonNull RouteInfo route) {
+            selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+        }
+
+        void selectRoute(@NonNull RouteInfo route, int unselectReason) {
+            if (!mRoutes.contains(route)) {
+                Log.w(TAG, "Ignoring attempt to select removed route: " + route);
+                return;
+            }
+            if (!route.mEnabled) {
+                Log.w(TAG, "Ignoring attempt to select disabled route: " + route);
+                return;
+            }
+            setSelectedRouteInternal(route, unselectReason);
+        }
+
+        public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
+            if (selector.isEmpty()) {
+                return false;
+            }
+
+            // On low-RAM devices, do not rely on actual discovery results unless asked to.
+            if ((flags & AVAILABILITY_FLAG_REQUIRE_MATCH) == 0 && mLowRam) {
+                return true;
+            }
+
+            // Check whether any existing routes match the selector.
+            final int routeCount = mRoutes.size();
+            for (int i = 0; i < routeCount; i++) {
+                RouteInfo route = mRoutes.get(i);
+                if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0
+                        && route.isDefaultOrBluetooth()) {
+                    continue;
+                }
+                if (route.matchesSelector(selector)) {
+                    return true;
+                }
+            }
+
+            // It doesn't look like we can find a matching route right now.
+            return false;
+        }
+
+        public void updateDiscoveryRequest() {
+            // Combine all of the callback selectors and active scan flags.
+            boolean discover = false;
+            boolean activeScan = false;
+            MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
+            for (int i = mRouters.size(); --i >= 0; ) {
+                MediaRouter router = mRouters.get(i).get();
+                if (router == null) {
+                    mRouters.remove(i);
+                } else {
+                    final int count = router.mCallbackRecords.size();
+                    for (int j = 0; j < count; j++) {
+                        CallbackRecord callback = router.mCallbackRecords.get(j);
+                        builder.addSelector(callback.mSelector);
+                        if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
+                            activeScan = true;
+                            discover = true; // perform active scan implies request discovery
+                        }
+                        if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) {
+                            if (!mLowRam) {
+                                discover = true;
+                            }
+                        }
+                        if ((callback.mFlags & CALLBACK_FLAG_FORCE_DISCOVERY) != 0) {
+                            discover = true;
+                        }
+                    }
+                }
+            }
+            MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY;
+
+            // Create a new discovery request.
+            if (mDiscoveryRequest != null
+                    && mDiscoveryRequest.getSelector().equals(selector)
+                    && mDiscoveryRequest.isActiveScan() == activeScan) {
+                return; // no change
+            }
+            if (selector.isEmpty() && !activeScan) {
+                // Discovery is not needed.
+                if (mDiscoveryRequest == null) {
+                    return; // no change
+                }
+                mDiscoveryRequest = null;
+            } else {
+                // Discovery is needed.
+                mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan);
+            }
+            if (DEBUG) {
+                Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest);
+            }
+            if (discover && !activeScan && mLowRam) {
+                Log.i(TAG, "Forcing passive route discovery on a low-RAM device, "
+                        + "system performance may be affected.  Please consider using "
+                        + "CALLBACK_FLAG_REQUEST_DISCOVERY instead of "
+                        + "CALLBACK_FLAG_FORCE_DISCOVERY.");
+            }
+
+            // Notify providers.
+            final int providerCount = mProviders.size();
+            for (int i = 0; i < providerCount; i++) {
+                mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest);
+            }
+        }
+
+        @Override
+        public void addProvider(MediaRouteProvider providerInstance) {
+            int index = findProviderInfo(providerInstance);
+            if (index < 0) {
+                // 1. Add the provider to the list.
+                ProviderInfo provider = new ProviderInfo(providerInstance);
+                mProviders.add(provider);
+                if (DEBUG) {
+                    Log.d(TAG, "Provider added: " + provider);
+                }
+                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider);
+                // 2. Create the provider's contents.
+                updateProviderContents(provider, providerInstance.getDescriptor());
+                // 3. Register the provider callback.
+                providerInstance.setCallback(mProviderCallback);
+                // 4. Set the discovery request.
+                providerInstance.setDiscoveryRequest(mDiscoveryRequest);
+            }
+        }
+
+        @Override
+        public void removeProvider(MediaRouteProvider providerInstance) {
+            int index = findProviderInfo(providerInstance);
+            if (index >= 0) {
+                // 1. Unregister the provider callback.
+                providerInstance.setCallback(null);
+                // 2. Clear the discovery request.
+                providerInstance.setDiscoveryRequest(null);
+                // 3. Delete the provider's contents.
+                ProviderInfo provider = mProviders.get(index);
+                updateProviderContents(provider, null);
+                // 4. Remove the provider from the list.
+                if (DEBUG) {
+                    Log.d(TAG, "Provider removed: " + provider);
+                }
+                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider);
+                mProviders.remove(index);
+            }
+        }
+
+        void updateProviderDescriptor(MediaRouteProvider providerInstance,
+                MediaRouteProviderDescriptor descriptor) {
+            int index = findProviderInfo(providerInstance);
+            if (index >= 0) {
+                // Update the provider's contents.
+                ProviderInfo provider = mProviders.get(index);
+                updateProviderContents(provider, descriptor);
+            }
+        }
+
+        private int findProviderInfo(MediaRouteProvider providerInstance) {
+            final int count = mProviders.size();
+            for (int i = 0; i < count; i++) {
+                if (mProviders.get(i).mProviderInstance == providerInstance) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        private void updateProviderContents(ProviderInfo provider,
+                MediaRouteProviderDescriptor providerDescriptor) {
+            if (provider.updateDescriptor(providerDescriptor)) {
+                // Update all existing routes and reorder them to match
+                // the order of their descriptors.
+                int targetIndex = 0;
+                boolean selectedRouteDescriptorChanged = false;
+                if (providerDescriptor != null) {
+                    if (providerDescriptor.isValid()) {
+                        final List<MediaRouteDescriptor> routeDescriptors =
+                                providerDescriptor.getRoutes();
+                        final int routeCount = routeDescriptors.size();
+                        // Updating route group's contents requires all member routes' information.
+                        // Add the groups to the lists and update them later.
+                        List<Pair<RouteInfo, MediaRouteDescriptor>> addedGroups = new ArrayList<>();
+                        List<Pair<RouteInfo, MediaRouteDescriptor>> updatedGroups =
+                                new ArrayList<>();
+                        for (int i = 0; i < routeCount; i++) {
+                            final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i);
+                            final String id = routeDescriptor.getId();
+                            final int sourceIndex = provider.findRouteByDescriptorId(id);
+                            if (sourceIndex < 0) {
+                                // 1. Add the route to the list.
+                                String uniqueId = assignRouteUniqueId(provider, id);
+                                boolean isGroup = routeDescriptor.getGroupMemberIds() != null;
+                                RouteInfo route = isGroup ? new RouteGroup(provider, id, uniqueId) :
+                                        new RouteInfo(provider, id, uniqueId);
+                                provider.mRoutes.add(targetIndex++, route);
+                                mRoutes.add(route);
+                                // 2. Create the route's contents.
+                                if (isGroup) {
+                                    addedGroups.add(new Pair<>(route, routeDescriptor));
+                                } else {
+                                    route.maybeUpdateDescriptor(routeDescriptor);
+                                    // 3. Notify clients about addition.
+                                    if (DEBUG) {
+                                        Log.d(TAG, "Route added: " + route);
+                                    }
+                                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
+                                }
+
+                            } else if (sourceIndex < targetIndex) {
+                                Log.w(TAG, "Ignoring route descriptor with duplicate id: "
+                                        + routeDescriptor);
+                            } else {
+                                // 1. Reorder the route within the list.
+                                RouteInfo route = provider.mRoutes.get(sourceIndex);
+                                Collections.swap(provider.mRoutes,
+                                        sourceIndex, targetIndex++);
+                                // 2. Update the route's contents.
+                                if (route instanceof RouteGroup) {
+                                    updatedGroups.add(new Pair<>(route, routeDescriptor));
+                                } else {
+                                    // 3. Notify clients about changes.
+                                    if (updateRouteDescriptorAndNotify(route, routeDescriptor)
+                                            != 0) {
+                                        if (route == mSelectedRoute) {
+                                            selectedRouteDescriptorChanged = true;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        // Update the new and/or existing groups.
+                        for (Pair<RouteInfo, MediaRouteDescriptor> pair : addedGroups) {
+                            RouteInfo route = pair.first;
+                            route.maybeUpdateDescriptor(pair.second);
+                            if (DEBUG) {
+                                Log.d(TAG, "Route added: " + route);
+                            }
+                            mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
+                        }
+                        for (Pair<RouteInfo, MediaRouteDescriptor> pair : updatedGroups) {
+                            RouteInfo route = pair.first;
+                            if (updateRouteDescriptorAndNotify(route, pair.second) != 0) {
+                                if (route == mSelectedRoute) {
+                                    selectedRouteDescriptorChanged = true;
+                                }
+                            }
+                        }
+                    } else {
+                        Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor);
+                    }
+                }
+
+                // Dispose all remaining routes that do not have matching descriptors.
+                for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
+                    // 1. Delete the route's contents.
+                    RouteInfo route = provider.mRoutes.get(i);
+                    route.maybeUpdateDescriptor(null);
+                    // 2. Remove the route from the list.
+                    mRoutes.remove(route);
+                }
+
+                // Update the selected route if needed.
+                updateSelectedRouteIfNeeded(selectedRouteDescriptorChanged);
+
+                // Now notify clients about routes that were removed.
+                // We do this after updating the selected route to ensure
+                // that the framework media router observes the new route
+                // selection before the removal since removing the currently
+                // selected route may have side-effects.
+                for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
+                    RouteInfo route = provider.mRoutes.remove(i);
+                    if (DEBUG) {
+                        Log.d(TAG, "Route removed: " + route);
+                    }
+                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route);
+                }
+
+                // Notify provider changed.
+                if (DEBUG) {
+                    Log.d(TAG, "Provider changed: " + provider);
+                }
+                mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider);
+            }
+        }
+
+        private int updateRouteDescriptorAndNotify(RouteInfo route,
+                MediaRouteDescriptor routeDescriptor) {
+            int changes = route.maybeUpdateDescriptor(routeDescriptor);
+            if (changes != 0) {
+                if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Route changed: " + route);
+                    }
+                    mCallbackHandler.post(
+                            CallbackHandler.MSG_ROUTE_CHANGED, route);
+                }
+                if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Route volume changed: " + route);
+                    }
+                    mCallbackHandler.post(
+                            CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
+                }
+                if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Route presentation display changed: "
+                                + route);
+                    }
+                    mCallbackHandler.post(CallbackHandler.
+                            MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
+                }
+            }
+            return changes;
+        }
+
+        private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) {
+            // Although route descriptor ids are unique within a provider, it's
+            // possible for there to be two providers with the same package name.
+            // Therefore we must dedupe the composite id.
+            String componentName = provider.getComponentName().flattenToShortString();
+            String uniqueId = componentName + ":" + routeDescriptorId;
+            if (findRouteByUniqueId(uniqueId) < 0) {
+                mUniqueIdMap.put(new Pair<>(componentName, routeDescriptorId), uniqueId);
+                return uniqueId;
+            }
+            Log.w(TAG, "Either " + routeDescriptorId + " isn't unique in " + componentName
+                    + " or we're trying to assign a unique ID for an already added route");
+            for (int i = 2; ; i++) {
+                String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i);
+                if (findRouteByUniqueId(newUniqueId) < 0) {
+                    mUniqueIdMap.put(new Pair<>(componentName, routeDescriptorId), newUniqueId);
+                    return newUniqueId;
+                }
+            }
+        }
+
+        private int findRouteByUniqueId(String uniqueId) {
+            final int count = mRoutes.size();
+            for (int i = 0; i < count; i++) {
+                if (mRoutes.get(i).mUniqueId.equals(uniqueId)) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        private String getUniqueId(ProviderInfo provider, String routeDescriptorId) {
+            String componentName = provider.getComponentName().flattenToShortString();
+            return mUniqueIdMap.get(new Pair<>(componentName, routeDescriptorId));
+        }
+
+        private void updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged) {
+            // Update default route.
+            if (mDefaultRoute != null && !mDefaultRoute.isSelectable()) {
+                Log.i(TAG, "Clearing the default route because it "
+                        + "is no longer selectable: " + mDefaultRoute);
+                mDefaultRoute = null;
+            }
+            if (mDefaultRoute == null && !mRoutes.isEmpty()) {
+                for (RouteInfo route : mRoutes) {
+                    if (isSystemDefaultRoute(route) && route.isSelectable()) {
+                        mDefaultRoute = route;
+                        Log.i(TAG, "Found default route: " + mDefaultRoute);
+                        break;
+                    }
+                }
+            }
+
+            // Update bluetooth route.
+            if (mBluetoothRoute != null && !mBluetoothRoute.isSelectable()) {
+                Log.i(TAG, "Clearing the bluetooth route because it "
+                        + "is no longer selectable: " + mBluetoothRoute);
+                mBluetoothRoute = null;
+            }
+            if (mBluetoothRoute == null && !mRoutes.isEmpty()) {
+                for (RouteInfo route : mRoutes) {
+                    if (isSystemLiveAudioOnlyRoute(route) && route.isSelectable()) {
+                        mBluetoothRoute = route;
+                        Log.i(TAG, "Found bluetooth route: " + mBluetoothRoute);
+                        break;
+                    }
+                }
+            }
+
+            // Update selected route.
+            if (mSelectedRoute == null || !mSelectedRoute.isSelectable()) {
+                Log.i(TAG, "Unselecting the current route because it "
+                        + "is no longer selectable: " + mSelectedRoute);
+                setSelectedRouteInternal(chooseFallbackRoute(),
+                        MediaRouter.UNSELECT_REASON_UNKNOWN);
+            } else if (selectedRouteDescriptorChanged) {
+                // In case the selected route is a route group, select/unselect route controllers
+                // for the added/removed route members.
+                if (mSelectedRoute instanceof RouteGroup) {
+                    List<RouteInfo> routes = ((RouteGroup) mSelectedRoute).getRoutes();
+                    // Build a set of descriptor IDs for the new route group.
+                    Set<String> idSet = new HashSet<>();
+                    for (RouteInfo route : routes) {
+                        idSet.add(route.mDescriptorId);
+                    }
+                    // Unselect route controllers for the removed routes.
+                    Iterator<Map.Entry<String, RouteController>> iter =
+                            mRouteControllerMap.entrySet().iterator();
+                    while (iter.hasNext()) {
+                        Map.Entry<String, RouteController> entry = iter.next();
+                        if (!idSet.contains(entry.getKey())) {
+                            RouteController controller = entry.getValue();
+                            controller.onUnselect();
+                            controller.onRelease();
+                            iter.remove();
+                        }
+                    }
+                    // Select route controllers for the added routes.
+                    for (RouteInfo route : routes) {
+                        if (!mRouteControllerMap.containsKey(route.mDescriptorId)) {
+                            RouteController controller = route.getProviderInstance()
+                                    .onCreateRouteController(
+                                            route.mDescriptorId, mSelectedRoute.mDescriptorId);
+                            controller.onSelect();
+                            mRouteControllerMap.put(route.mDescriptorId, controller);
+                        }
+                    }
+                }
+                // Update the playback info because the properties of the route have changed.
+                updatePlaybackInfoFromSelectedRoute();
+            }
+        }
+
+        RouteInfo chooseFallbackRoute() {
+            // When the current route is removed or no longer selectable,
+            // we want to revert to a live audio route if there is
+            // one (usually Bluetooth A2DP).  Failing that, use
+            // the default route.
+            for (RouteInfo route : mRoutes) {
+                if (route != mDefaultRoute
+                        && isSystemLiveAudioOnlyRoute(route)
+                        && route.isSelectable()) {
+                    return route;
+                }
+            }
+            return mDefaultRoute;
+        }
+
+        private boolean isSystemLiveAudioOnlyRoute(RouteInfo route) {
+            return route.getProviderInstance() == mSystemProvider
+                    && route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+                    && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+        }
+
+        private boolean isSystemDefaultRoute(RouteInfo route) {
+            return route.getProviderInstance() == mSystemProvider
+                    && route.mDescriptorId.equals(
+                            SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
+        }
+
+        private void setSelectedRouteInternal(@NonNull RouteInfo route, int unselectReason) {
+            // TODO: Remove the following logging when no longer needed.
+            if (sGlobal == null || (mBluetoothRoute != null && route.isDefault())) {
+                final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+                StringBuilder sb = new StringBuilder();
+                // callStack[3] is the caller of this method.
+                for (int i = 3; i < callStack.length; i++) {
+                    StackTraceElement caller = callStack[i];
+                    sb.append(caller.getClassName())
+                            .append(".")
+                            .append(caller.getMethodName())
+                            .append(":")
+                            .append(caller.getLineNumber())
+                            .append("  ");
+                }
+                if (sGlobal == null) {
+                    Log.w(TAG, "setSelectedRouteInternal is called while sGlobal is null: pkgName="
+                            + mApplicationContext.getPackageName() + ", callers=" + sb.toString());
+                } else {
+                    Log.w(TAG, "Default route is selected while a BT route is available: pkgName="
+                            + mApplicationContext.getPackageName() + ", callers=" + sb.toString());
+                }
+            }
+
+            if (mSelectedRoute != route) {
+                if (mSelectedRoute != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: "
+                                + unselectReason);
+                    }
+                    mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute,
+                            unselectReason);
+                    if (mSelectedRouteController != null) {
+                        mSelectedRouteController.onUnselect(unselectReason);
+                        mSelectedRouteController.onRelease();
+                        mSelectedRouteController = null;
+                    }
+                    if (!mRouteControllerMap.isEmpty()) {
+                        for (RouteController controller : mRouteControllerMap.values()) {
+                            controller.onUnselect(unselectReason);
+                            controller.onRelease();
+                        }
+                        mRouteControllerMap.clear();
+                    }
+                }
+
+                mSelectedRoute = route;
+                mSelectedRouteController = route.getProviderInstance().onCreateRouteController(
+                        route.mDescriptorId);
+                if (mSelectedRouteController != null) {
+                    mSelectedRouteController.onSelect();
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "Route selected: " + mSelectedRoute);
+                }
+                mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
+
+                if (mSelectedRoute instanceof RouteGroup) {
+                    List<RouteInfo> routes = ((RouteGroup) mSelectedRoute).getRoutes();
+                    mRouteControllerMap.clear();
+                    for (RouteInfo r : routes) {
+                        RouteController controller =
+                                r.getProviderInstance().onCreateRouteController(
+                                        r.mDescriptorId, mSelectedRoute.mDescriptorId);
+                        controller.onSelect();
+                        mRouteControllerMap.put(r.mDescriptorId, controller);
+                    }
+                }
+
+                updatePlaybackInfoFromSelectedRoute();
+            }
+        }
+
+        @Override
+        public void onSystemRouteSelectedByDescriptorId(String id) {
+            // System route is selected, do not sync the route we selected before.
+            mCallbackHandler.removeMessages(CallbackHandler.MSG_ROUTE_SELECTED);
+            int providerIndex = findProviderInfo(mSystemProvider);
+            if (providerIndex >= 0) {
+                ProviderInfo provider = mProviders.get(providerIndex);
+                int routeIndex = provider.findRouteByDescriptorId(id);
+                if (routeIndex >= 0) {
+                    provider.mRoutes.get(routeIndex).select();
+                }
+            }
+        }
+
+        public void addRemoteControlClient(Object rcc) {
+            int index = findRemoteControlClientRecord(rcc);
+            if (index < 0) {
+                RemoteControlClientRecord record = new RemoteControlClientRecord(rcc);
+                mRemoteControlClients.add(record);
+            }
+        }
+
+        public void removeRemoteControlClient(Object rcc) {
+            int index = findRemoteControlClientRecord(rcc);
+            if (index >= 0) {
+                RemoteControlClientRecord record = mRemoteControlClients.remove(index);
+                record.disconnect();
+            }
+        }
+
+        public void setMediaSession(Object session) {
+            setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
+        }
+
+        public void setMediaSessionCompat(final MediaSessionCompat session) {
+            mCompatSession = session;
+            if (android.os.Build.VERSION.SDK_INT >= 21) {
+                setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
+            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
+                if (mRccMediaSession != null) {
+                    removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+                    mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
+                }
+                mRccMediaSession = session;
+                if (session != null) {
+                    session.addOnActiveChangeListener(mSessionActiveListener);
+                    if (session.isActive()) {
+                        addRemoteControlClient(session.getRemoteControlClient());
+                    }
+                }
+            }
+        }
+
+        private void setMediaSessionRecord(MediaSessionRecord mediaSessionRecord) {
+            if (mMediaSession != null) {
+                mMediaSession.clearVolumeHandling();
+            }
+            mMediaSession = mediaSessionRecord;
+            if (mediaSessionRecord != null) {
+                updatePlaybackInfoFromSelectedRoute();
+            }
+        }
+
+        public MediaSessionCompat.Token getMediaSessionToken() {
+            if (mMediaSession != null) {
+                return mMediaSession.getToken();
+            } else if (mCompatSession != null) {
+                return mCompatSession.getSessionToken();
+            }
+            return null;
+        }
+
+        private int findRemoteControlClientRecord(Object rcc) {
+            final int count = mRemoteControlClients.size();
+            for (int i = 0; i < count; i++) {
+                RemoteControlClientRecord record = mRemoteControlClients.get(i);
+                if (record.getRemoteControlClient() == rcc) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        private void updatePlaybackInfoFromSelectedRoute() {
+            if (mSelectedRoute != null) {
+                mPlaybackInfo.volume = mSelectedRoute.getVolume();
+                mPlaybackInfo.volumeMax = mSelectedRoute.getVolumeMax();
+                mPlaybackInfo.volumeHandling = mSelectedRoute.getVolumeHandling();
+                mPlaybackInfo.playbackStream = mSelectedRoute.getPlaybackStream();
+                mPlaybackInfo.playbackType = mSelectedRoute.getPlaybackType();
+
+                final int count = mRemoteControlClients.size();
+                for (int i = 0; i < count; i++) {
+                    RemoteControlClientRecord record = mRemoteControlClients.get(i);
+                    record.updatePlaybackInfo();
+                }
+                if (mMediaSession != null) {
+                    if (mSelectedRoute == getDefaultRoute()
+                            || mSelectedRoute == getBluetoothRoute()) {
+                        // Local route
+                        mMediaSession.clearVolumeHandling();
+                    } else {
+                        @VolumeProviderCompat.ControlType int controlType =
+                                VolumeProviderCompat.VOLUME_CONTROL_FIXED;
+                        if (mPlaybackInfo.volumeHandling
+                                == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
+                            controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+                        }
+                        mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax,
+                                mPlaybackInfo.volume);
+                    }
+                }
+            } else {
+                if (mMediaSession != null) {
+                    mMediaSession.clearVolumeHandling();
+                }
+            }
+        }
+
+        private final class ProviderCallback extends MediaRouteProvider.Callback {
+            ProviderCallback() {
+            }
+
+            @Override
+            public void onDescriptorChanged(MediaRouteProvider provider,
+                    MediaRouteProviderDescriptor descriptor) {
+                updateProviderDescriptor(provider, descriptor);
+            }
+        }
+
+        private final class MediaSessionRecord {
+            private final MediaSessionCompat mMsCompat;
+
+            private @VolumeProviderCompat.ControlType int mControlType;
+            private int mMaxVolume;
+            private VolumeProviderCompat mVpCompat;
+
+            public MediaSessionRecord(Object mediaSession) {
+                mMsCompat = MediaSessionCompat.fromMediaSession(mApplicationContext, mediaSession);
+            }
+
+            public MediaSessionRecord(MediaSessionCompat mediaSessionCompat) {
+                mMsCompat = mediaSessionCompat;
+            }
+
+            public void configureVolume(@VolumeProviderCompat.ControlType int controlType,
+                    int max, int current) {
+                if (mVpCompat != null && controlType == mControlType && max == mMaxVolume) {
+                    // If we haven't changed control type or max just set the
+                    // new current volume
+                    mVpCompat.setCurrentVolume(current);
+                } else {
+                    // Otherwise create a new provider and update
+                    mVpCompat = new VolumeProviderCompat(controlType, max, current) {
+                        @Override
+                        public void onSetVolumeTo(final int volume) {
+                            mCallbackHandler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (mSelectedRoute != null) {
+                                        mSelectedRoute.requestSetVolume(volume);
+                                    }
+                                }
+                            });
+                        }
+
+                        @Override
+                        public void onAdjustVolume(final int direction) {
+                            mCallbackHandler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (mSelectedRoute != null) {
+                                        mSelectedRoute.requestUpdateVolume(direction);
+                                    }
+                                }
+                            });
+                        }
+                    };
+                    mMsCompat.setPlaybackToRemote(mVpCompat);
+                }
+            }
+
+            public void clearVolumeHandling() {
+                mMsCompat.setPlaybackToLocal(mPlaybackInfo.playbackStream);
+                mVpCompat = null;
+            }
+
+            public MediaSessionCompat.Token getToken() {
+                return mMsCompat.getSessionToken();
+            }
+        }
+
+        private final class RemoteControlClientRecord
+                implements RemoteControlClientCompat.VolumeCallback {
+            private final RemoteControlClientCompat mRccCompat;
+            private boolean mDisconnected;
+
+            public RemoteControlClientRecord(Object rcc) {
+                mRccCompat = RemoteControlClientCompat.obtain(mApplicationContext, rcc);
+                mRccCompat.setVolumeCallback(this);
+                updatePlaybackInfo();
+            }
+
+            public Object getRemoteControlClient() {
+                return mRccCompat.getRemoteControlClient();
+            }
+
+            public void disconnect() {
+                mDisconnected = true;
+                mRccCompat.setVolumeCallback(null);
+            }
+
+            public void updatePlaybackInfo() {
+                mRccCompat.setPlaybackInfo(mPlaybackInfo);
+            }
+
+            @Override
+            public void onVolumeSetRequest(int volume) {
+                if (!mDisconnected && mSelectedRoute != null) {
+                    mSelectedRoute.requestSetVolume(volume);
+                }
+            }
+
+            @Override
+            public void onVolumeUpdateRequest(int direction) {
+                if (!mDisconnected && mSelectedRoute != null) {
+                    mSelectedRoute.requestUpdateVolume(direction);
+                }
+            }
+        }
+
+        private final class CallbackHandler extends Handler {
+            private final ArrayList<CallbackRecord> mTempCallbackRecords =
+                    new ArrayList<CallbackRecord>();
+
+            private static final int MSG_TYPE_MASK = 0xff00;
+            private static final int MSG_TYPE_ROUTE = 0x0100;
+            private static final int MSG_TYPE_PROVIDER = 0x0200;
+
+            public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1;
+            public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2;
+            public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3;
+            public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4;
+            public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5;
+            public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6;
+            public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7;
+
+            public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1;
+            public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2;
+            public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3;
+
+            CallbackHandler() {
+            }
+
+            public void post(int msg, Object obj) {
+                obtainMessage(msg, obj).sendToTarget();
+            }
+
+            public void post(int msg, Object obj, int arg) {
+                Message message = obtainMessage(msg, obj);
+                message.arg1 = arg;
+                message.sendToTarget();
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                final int what = msg.what;
+                final Object obj = msg.obj;
+                final int arg = msg.arg1;
+
+                if (what == MSG_ROUTE_CHANGED
+                        && getSelectedRoute().getId().equals(((RouteInfo) obj).getId())) {
+                    updateSelectedRouteIfNeeded(true);
+                }
+
+                // Synchronize state with the system media router.
+                syncWithSystemProvider(what, obj);
+
+                // Invoke all registered callbacks.
+                // Build a list of callbacks before invoking them in case callbacks
+                // are added or removed during dispatch.
+                try {
+                    for (int i = mRouters.size(); --i >= 0; ) {
+                        MediaRouter router = mRouters.get(i).get();
+                        if (router == null) {
+                            mRouters.remove(i);
+                        } else {
+                            mTempCallbackRecords.addAll(router.mCallbackRecords);
+                        }
+                    }
+
+                    final int callbackCount = mTempCallbackRecords.size();
+                    for (int i = 0; i < callbackCount; i++) {
+                        invokeCallback(mTempCallbackRecords.get(i), what, obj, arg);
+                    }
+                } finally {
+                    mTempCallbackRecords.clear();
+                }
+            }
+
+            private void syncWithSystemProvider(int what, Object obj) {
+                switch (what) {
+                    case MSG_ROUTE_ADDED:
+                        mSystemProvider.onSyncRouteAdded((RouteInfo) obj);
+                        break;
+                    case MSG_ROUTE_REMOVED:
+                        mSystemProvider.onSyncRouteRemoved((RouteInfo) obj);
+                        break;
+                    case MSG_ROUTE_CHANGED:
+                        mSystemProvider.onSyncRouteChanged((RouteInfo) obj);
+                        break;
+                    case MSG_ROUTE_SELECTED:
+                        mSystemProvider.onSyncRouteSelected((RouteInfo) obj);
+                        break;
+                }
+            }
+
+            private void invokeCallback(CallbackRecord record, int what, Object obj, int arg) {
+                final MediaRouter router = record.mRouter;
+                final MediaRouter.Callback callback = record.mCallback;
+                switch (what & MSG_TYPE_MASK) {
+                    case MSG_TYPE_ROUTE: {
+                        final RouteInfo route = (RouteInfo)obj;
+                        if (!record.filterRouteEvent(route)) {
+                            break;
+                        }
+                        switch (what) {
+                            case MSG_ROUTE_ADDED:
+                                callback.onRouteAdded(router, route);
+                                break;
+                            case MSG_ROUTE_REMOVED:
+                                callback.onRouteRemoved(router, route);
+                                break;
+                            case MSG_ROUTE_CHANGED:
+                                callback.onRouteChanged(router, route);
+                                break;
+                            case MSG_ROUTE_VOLUME_CHANGED:
+                                callback.onRouteVolumeChanged(router, route);
+                                break;
+                            case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED:
+                                callback.onRoutePresentationDisplayChanged(router, route);
+                                break;
+                            case MSG_ROUTE_SELECTED:
+                                callback.onRouteSelected(router, route);
+                                break;
+                            case MSG_ROUTE_UNSELECTED:
+                                callback.onRouteUnselected(router, route, arg);
+                                break;
+                        }
+                        break;
+                    }
+                    case MSG_TYPE_PROVIDER: {
+                        final ProviderInfo provider = (ProviderInfo)obj;
+                        switch (what) {
+                            case MSG_PROVIDER_ADDED:
+                                callback.onProviderAdded(router, provider);
+                                break;
+                            case MSG_PROVIDER_REMOVED:
+                                callback.onProviderRemoved(router, provider);
+                                break;
+                            case MSG_PROVIDER_CHANGED:
+                                callback.onProviderChanged(router, provider);
+                                break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaSessionStatus.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaSessionStatus.java
new file mode 100644
index 0000000..3206596
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaSessionStatus.java
@@ -0,0 +1,244 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.app.PendingIntent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.v4.util.TimeUtils;
+
+/**
+ * Describes the playback status of a media session.
+ * <p>
+ * This class is part of the remote playback protocol described by the
+ * {@link MediaControlIntent MediaControlIntent} class.
+ * </p><p>
+ * When a media session is created, it is initially in the
+ * {@link #SESSION_STATE_ACTIVE active} state.  When the media session ends
+ * normally, it transitions to the {@link #SESSION_STATE_ENDED ended} state.
+ * If the media session is invalidated due to another session forcibly taking
+ * control of the route, then it transitions to the
+ * {@link #SESSION_STATE_INVALIDATED invalidated} state.
+ * Refer to the documentation of each state for an explanation of its meaning.
+ * </p><p>
+ * To monitor session status, the application should supply a {@link PendingIntent} to use as the
+ * {@link MediaControlIntent#EXTRA_SESSION_STATUS_UPDATE_RECEIVER session status update receiver}
+ * for a given {@link MediaControlIntent#ACTION_START_SESSION session start request}.
+ * </p><p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ */
+public final class MediaSessionStatus {
+    static final String KEY_TIMESTAMP = "timestamp";
+    static final String KEY_SESSION_STATE = "sessionState";
+    static final String KEY_QUEUE_PAUSED = "queuePaused";
+    static final String KEY_EXTRAS = "extras";
+
+    final Bundle mBundle;
+
+    /**
+     * Session state: Active.
+     * <p>
+     * Indicates that the media session is active and in control of the route.
+     * </p>
+     */
+    public static final int SESSION_STATE_ACTIVE = 0;
+
+    /**
+     * Session state: Ended.
+     * <p>
+     * Indicates that the media session was ended normally using the
+     * {@link MediaControlIntent#ACTION_END_SESSION end session} action.
+     * </p><p>
+     * A terminated media session cannot be used anymore.  To play more media, the
+     * application must start a new session.
+     * </p>
+     */
+    public static final int SESSION_STATE_ENDED = 1;
+
+    /**
+     * Session state: Invalidated.
+     * <p>
+     * Indicates that the media session was invalidated involuntarily due to
+     * another session taking control of the route.
+     * </p><p>
+     * An invalidated media session cannot be used anymore.  To play more media, the
+     * application must start a new session.
+     * </p>
+     */
+    public static final int SESSION_STATE_INVALIDATED = 2;
+
+    MediaSessionStatus(Bundle bundle) {
+        mBundle = bundle;
+    }
+
+    /**
+     * Gets the timestamp associated with the status information in
+     * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
+     *
+     * @return The status timestamp in the {@link SystemClock#elapsedRealtime()} time base.
+     */
+    public long getTimestamp() {
+        return mBundle.getLong(KEY_TIMESTAMP);
+    }
+
+    /**
+     * Gets the session state.
+     *
+     * @return The session state.  One of {@link #SESSION_STATE_ACTIVE},
+     * {@link #SESSION_STATE_ENDED}, or {@link #SESSION_STATE_INVALIDATED}.
+     */
+    public int getSessionState() {
+        return mBundle.getInt(KEY_SESSION_STATE, SESSION_STATE_INVALIDATED);
+    }
+
+    /**
+     * Returns true if the session's queue is paused.
+     *
+     * @return True if the session's queue is paused.
+     */
+    public boolean isQueuePaused() {
+        return mBundle.getBoolean(KEY_QUEUE_PAUSED);
+    }
+
+    /**
+     * Gets a bundle of extras for this status object.
+     * The extras will be ignored by the media router but they may be used
+     * by applications.
+     */
+    public Bundle getExtras() {
+        return mBundle.getBundle(KEY_EXTRAS);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        result.append("MediaSessionStatus{ ");
+        result.append("timestamp=");
+        TimeUtils.formatDuration(SystemClock.elapsedRealtime() - getTimestamp(), result);
+        result.append(" ms ago");
+        result.append(", sessionState=").append(sessionStateToString(getSessionState()));
+        result.append(", queuePaused=").append(isQueuePaused());
+        result.append(", extras=").append(getExtras());
+        result.append(" }");
+        return result.toString();
+    }
+
+    private static String sessionStateToString(int sessionState) {
+        switch (sessionState) {
+            case SESSION_STATE_ACTIVE:
+                return "active";
+            case SESSION_STATE_ENDED:
+                return "ended";
+            case SESSION_STATE_INVALIDATED:
+                return "invalidated";
+        }
+        return Integer.toString(sessionState);
+    }
+
+    /**
+     * Converts this object to a bundle for serialization.
+     *
+     * @return The contents of the object represented as a bundle.
+     */
+    public Bundle asBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Creates an instance from a bundle.
+     *
+     * @param bundle The bundle, or null if none.
+     * @return The new instance, or null if the bundle was null.
+     */
+    public static MediaSessionStatus fromBundle(Bundle bundle) {
+        return bundle != null ? new MediaSessionStatus(bundle) : null;
+    }
+
+    /**
+     * Builder for {@link MediaSessionStatus media session status objects}.
+     */
+    public static final class Builder {
+        private final Bundle mBundle;
+
+        /**
+         * Creates a media session status builder using the current time as the
+         * reference timestamp.
+         *
+         * @param sessionState The session state.
+         */
+        public Builder(int sessionState) {
+            mBundle = new Bundle();
+            setTimestamp(SystemClock.elapsedRealtime());
+            setSessionState(sessionState);
+        }
+
+        /**
+         * Creates a media session status builder whose initial contents are
+         * copied from an existing status.
+         */
+        public Builder(MediaSessionStatus status) {
+            if (status == null) {
+                throw new IllegalArgumentException("status must not be null");
+            }
+
+            mBundle = new Bundle(status.mBundle);
+        }
+
+        /**
+         * Sets the timestamp associated with the status information in
+         * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
+         */
+        public Builder setTimestamp(long elapsedRealtimeTimestamp) {
+            mBundle.putLong(KEY_TIMESTAMP, elapsedRealtimeTimestamp);
+            return this;
+        }
+
+        /**
+         * Sets the session state.
+         */
+        public Builder setSessionState(int sessionState) {
+            mBundle.putInt(KEY_SESSION_STATE, sessionState);
+            return this;
+        }
+
+        /**
+         * Sets whether the queue is paused.
+         */
+        public Builder setQueuePaused(boolean queuePaused) {
+            mBundle.putBoolean(KEY_QUEUE_PAUSED, queuePaused);
+            return this;
+        }
+
+        /**
+         * Sets a bundle of extras for this status object.
+         * The extras will be ignored by the media router but they may be used
+         * by applications.
+         */
+        public Builder setExtras(Bundle extras) {
+            mBundle.putBundle(KEY_EXTRAS, extras);
+            return this;
+        }
+
+        /**
+         * Builds the {@link MediaSessionStatus media session status object}.
+         */
+        public MediaSessionStatus build() {
+            return new MediaSessionStatus(mBundle);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProvider.java
new file mode 100644
index 0000000..98e4e28
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProvider.java
@@ -0,0 +1,741 @@
+/*
+ * 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.support.mediarouter.media;
+
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_DATA_ROUTE_LIBRARY_GROUP;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_DATA_UNSELECT_REASON;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_CREATE_ROUTE_CONTROLLER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_REGISTER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_RELEASE_ROUTE_CONTROLLER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_ROUTE_CONTROL_REQUEST;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_SELECT_ROUTE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_SET_DISCOVERY_REQUEST;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_SET_ROUTE_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UNREGISTER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_UNSELECT_ROUTE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .CLIENT_MSG_UPDATE_ROUTE_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_VERSION_CURRENT;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_DATA_ERROR;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_CONTROL_REQUEST_FAILED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_DESCRIPTOR_CHANGED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_GENERIC_FAILURE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+        .SERVICE_MSG_GENERIC_SUCCESS;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_REGISTERED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_VERSION_1;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.support.mediarouter.media.MediaRouter.ControlRequestCallback;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Maintains a connection to a particular media route provider service.
+ */
+final class RegisteredMediaRouteProvider extends MediaRouteProvider
+        implements ServiceConnection {
+    static final String TAG = "MediaRouteProviderProxy";  // max. 23 chars
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final ComponentName mComponentName;
+    final PrivateHandler mPrivateHandler;
+    private final ArrayList<Controller> mControllers = new ArrayList<Controller>();
+
+    private boolean mStarted;
+    private boolean mBound;
+    private Connection mActiveConnection;
+    private boolean mConnectionReady;
+
+    public RegisteredMediaRouteProvider(Context context, ComponentName componentName) {
+        super(context, new ProviderMetadata(componentName));
+
+        mComponentName = componentName;
+        mPrivateHandler = new PrivateHandler();
+    }
+
+    @Override
+    public RouteController onCreateRouteController(@NonNull String routeId) {
+        if (routeId == null) {
+            throw new IllegalArgumentException("routeId cannot be null");
+        }
+        return createRouteController(routeId, null);
+    }
+
+    @Override
+    public RouteController onCreateRouteController(
+            @NonNull String routeId, @NonNull String routeGroupId) {
+        if (routeId == null) {
+            throw new IllegalArgumentException("routeId cannot be null");
+        }
+        if (routeGroupId == null) {
+            throw new IllegalArgumentException("routeGroupId cannot be null");
+        }
+        return createRouteController(routeId, routeGroupId);
+    }
+
+    @Override
+    public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
+        if (mConnectionReady) {
+            mActiveConnection.setDiscoveryRequest(request);
+        }
+        updateBinding();
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        if (DEBUG) {
+            Log.d(TAG, this + ": Connected");
+        }
+
+        if (mBound) {
+            disconnect();
+
+            Messenger messenger = (service != null ? new Messenger(service) : null);
+            if (isValidRemoteMessenger(messenger)) {
+                Connection connection = new Connection(messenger);
+                if (connection.register()) {
+                    mActiveConnection = connection;
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, this + ": Registration failed");
+                    }
+                }
+            } else {
+                Log.e(TAG, this + ": Service returned invalid messenger binder");
+            }
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        if (DEBUG) {
+            Log.d(TAG, this + ": Service disconnected");
+        }
+        disconnect();
+    }
+
+    @Override
+    public String toString() {
+        return "Service connection " + mComponentName.flattenToShortString();
+    }
+
+    public boolean hasComponentName(String packageName, String className) {
+        return mComponentName.getPackageName().equals(packageName)
+                && mComponentName.getClassName().equals(className);
+    }
+
+    public void start() {
+        if (!mStarted) {
+            if (DEBUG) {
+                Log.d(TAG, this + ": Starting");
+            }
+
+            mStarted = true;
+            updateBinding();
+        }
+    }
+
+    public void stop() {
+        if (mStarted) {
+            if (DEBUG) {
+                Log.d(TAG, this + ": Stopping");
+            }
+
+            mStarted = false;
+            updateBinding();
+        }
+    }
+
+    public void rebindIfDisconnected() {
+        if (mActiveConnection == null && shouldBind()) {
+            unbind();
+            bind();
+        }
+    }
+
+    private void updateBinding() {
+        if (shouldBind()) {
+            bind();
+        } else {
+            unbind();
+        }
+    }
+
+    private boolean shouldBind() {
+        if (mStarted) {
+            // Bind whenever there is a discovery request.
+            if (getDiscoveryRequest() != null) {
+                return true;
+            }
+
+            // Bind whenever the application has an active route controller.
+            // This means that one of this provider's routes is selected.
+            if (!mControllers.isEmpty()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void bind() {
+        if (!mBound) {
+            if (DEBUG) {
+                Log.d(TAG, this + ": Binding");
+            }
+
+            Intent service = new Intent(MediaRouteProviderProtocol.SERVICE_INTERFACE);
+            service.setComponent(mComponentName);
+            try {
+                mBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE);
+                if (!mBound && DEBUG) {
+                    Log.d(TAG, this + ": Bind failed");
+                }
+            } catch (SecurityException ex) {
+                if (DEBUG) {
+                    Log.d(TAG, this + ": Bind failed", ex);
+                }
+            }
+        }
+    }
+
+    private void unbind() {
+        if (mBound) {
+            if (DEBUG) {
+                Log.d(TAG, this + ": Unbinding");
+            }
+
+            mBound = false;
+            disconnect();
+            getContext().unbindService(this);
+        }
+    }
+
+    private RouteController createRouteController(String routeId, String routeGroupId) {
+        MediaRouteProviderDescriptor descriptor = getDescriptor();
+        if (descriptor != null) {
+            List<MediaRouteDescriptor> routes = descriptor.getRoutes();
+            final int count = routes.size();
+            for (int i = 0; i < count; i++) {
+                final MediaRouteDescriptor route = routes.get(i);
+                if (route.getId().equals(routeId)) {
+                    Controller controller = new Controller(routeId, routeGroupId);
+                    mControllers.add(controller);
+                    if (mConnectionReady) {
+                        controller.attachConnection(mActiveConnection);
+                    }
+                    updateBinding();
+                    return controller;
+                }
+            }
+        }
+        return null;
+    }
+
+    void onConnectionReady(Connection connection) {
+        if (mActiveConnection == connection) {
+            mConnectionReady = true;
+            attachControllersToConnection();
+
+            MediaRouteDiscoveryRequest request = getDiscoveryRequest();
+            if (request != null) {
+                mActiveConnection.setDiscoveryRequest(request);
+            }
+        }
+    }
+
+    void onConnectionDied(Connection connection) {
+        if (mActiveConnection == connection) {
+            if (DEBUG) {
+                Log.d(TAG, this + ": Service connection died");
+            }
+            disconnect();
+        }
+    }
+
+    void onConnectionError(Connection connection, String error) {
+        if (mActiveConnection == connection) {
+            if (DEBUG) {
+                Log.d(TAG, this + ": Service connection error - " + error);
+            }
+            unbind();
+        }
+    }
+
+    void onConnectionDescriptorChanged(Connection connection,
+            MediaRouteProviderDescriptor descriptor) {
+        if (mActiveConnection == connection) {
+            if (DEBUG) {
+                Log.d(TAG, this + ": Descriptor changed, descriptor=" + descriptor);
+            }
+            setDescriptor(descriptor);
+        }
+    }
+
+    private void disconnect() {
+        if (mActiveConnection != null) {
+            setDescriptor(null);
+            mConnectionReady = false;
+            detachControllersFromConnection();
+            mActiveConnection.dispose();
+            mActiveConnection = null;
+        }
+    }
+
+    void onControllerReleased(Controller controller) {
+        mControllers.remove(controller);
+        controller.detachConnection();
+        updateBinding();
+    }
+
+    private void attachControllersToConnection() {
+        int count = mControllers.size();
+        for (int i = 0; i < count; i++) {
+            mControllers.get(i).attachConnection(mActiveConnection);
+        }
+    }
+
+    private void detachControllersFromConnection() {
+        int count = mControllers.size();
+        for (int i = 0; i < count; i++) {
+            mControllers.get(i).detachConnection();
+        }
+    }
+
+    private final class Controller extends RouteController {
+        private final String mRouteId;
+        private final String mRouteGroupId;
+
+        private boolean mSelected;
+        private int mPendingSetVolume = -1;
+        private int mPendingUpdateVolumeDelta;
+
+        private Connection mConnection;
+        private int mControllerId;
+
+        public Controller(String routeId, String routeGroupId) {
+            mRouteId = routeId;
+            mRouteGroupId = routeGroupId;
+        }
+
+        public void attachConnection(Connection connection) {
+            mConnection = connection;
+            mControllerId = connection.createRouteController(mRouteId, mRouteGroupId);
+            if (mSelected) {
+                connection.selectRoute(mControllerId);
+                if (mPendingSetVolume >= 0) {
+                    connection.setVolume(mControllerId, mPendingSetVolume);
+                    mPendingSetVolume = -1;
+                }
+                if (mPendingUpdateVolumeDelta != 0) {
+                    connection.updateVolume(mControllerId, mPendingUpdateVolumeDelta);
+                    mPendingUpdateVolumeDelta = 0;
+                }
+            }
+        }
+
+        public void detachConnection() {
+            if (mConnection != null) {
+                mConnection.releaseRouteController(mControllerId);
+                mConnection = null;
+                mControllerId = 0;
+            }
+        }
+
+        @Override
+        public void onRelease() {
+            onControllerReleased(this);
+        }
+
+        @Override
+        public void onSelect() {
+            mSelected = true;
+            if (mConnection != null) {
+                mConnection.selectRoute(mControllerId);
+            }
+        }
+
+        @Override
+        public void onUnselect() {
+            onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN);
+        }
+
+        @Override
+        public void onUnselect(int reason) {
+            mSelected = false;
+            if (mConnection != null) {
+                mConnection.unselectRoute(mControllerId, reason);
+            }
+        }
+
+        @Override
+        public void onSetVolume(int volume) {
+            if (mConnection != null) {
+                mConnection.setVolume(mControllerId, volume);
+            } else {
+                mPendingSetVolume = volume;
+                mPendingUpdateVolumeDelta = 0;
+            }
+        }
+
+        @Override
+        public void onUpdateVolume(int delta) {
+            if (mConnection != null) {
+                mConnection.updateVolume(mControllerId, delta);
+            } else {
+                mPendingUpdateVolumeDelta += delta;
+            }
+        }
+
+        @Override
+        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+            if (mConnection != null) {
+                return mConnection.sendControlRequest(mControllerId, intent, callback);
+            }
+            return false;
+        }
+    }
+
+    private final class Connection implements DeathRecipient {
+        private final Messenger mServiceMessenger;
+        private final ReceiveHandler mReceiveHandler;
+        private final Messenger mReceiveMessenger;
+
+        private int mNextRequestId = 1;
+        private int mNextControllerId = 1;
+        private int mServiceVersion; // non-zero when registration complete
+
+        private int mPendingRegisterRequestId;
+        private final SparseArray<ControlRequestCallback> mPendingCallbacks =
+                new SparseArray<ControlRequestCallback>();
+
+        public Connection(Messenger serviceMessenger) {
+            mServiceMessenger = serviceMessenger;
+            mReceiveHandler = new ReceiveHandler(this);
+            mReceiveMessenger = new Messenger(mReceiveHandler);
+        }
+
+        public boolean register() {
+            mPendingRegisterRequestId = mNextRequestId++;
+            if (!sendRequest(CLIENT_MSG_REGISTER,
+                    mPendingRegisterRequestId,
+                    CLIENT_VERSION_CURRENT, null, null)) {
+                return false;
+            }
+
+            try {
+                mServiceMessenger.getBinder().linkToDeath(this, 0);
+                return true;
+            } catch (RemoteException ex) {
+                binderDied();
+            }
+            return false;
+        }
+
+        public void dispose() {
+            sendRequest(CLIENT_MSG_UNREGISTER, 0, 0, null, null);
+            mReceiveHandler.dispose();
+            mServiceMessenger.getBinder().unlinkToDeath(this, 0);
+
+            mPrivateHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    failPendingCallbacks();
+                }
+            });
+        }
+
+        void failPendingCallbacks() {
+            int count = 0;
+            for (int i = 0; i < mPendingCallbacks.size(); i++) {
+                mPendingCallbacks.valueAt(i).onError(null, null);
+            }
+            mPendingCallbacks.clear();
+        }
+
+        public boolean onGenericFailure(int requestId) {
+            if (requestId == mPendingRegisterRequestId) {
+                mPendingRegisterRequestId = 0;
+                onConnectionError(this, "Registration failed");
+            }
+            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
+            if (callback != null) {
+                mPendingCallbacks.remove(requestId);
+                callback.onError(null, null);
+            }
+            return true;
+        }
+
+        public boolean onGenericSuccess(int requestId) {
+            return true;
+        }
+
+        public boolean onRegistered(int requestId, int serviceVersion,
+                Bundle descriptorBundle) {
+            if (mServiceVersion == 0
+                    && requestId == mPendingRegisterRequestId
+                    && serviceVersion >= SERVICE_VERSION_1) {
+                mPendingRegisterRequestId = 0;
+                mServiceVersion = serviceVersion;
+                onConnectionDescriptorChanged(this,
+                        MediaRouteProviderDescriptor.fromBundle(descriptorBundle));
+                onConnectionReady(this);
+                return true;
+            }
+            return false;
+        }
+
+        public boolean onDescriptorChanged(Bundle descriptorBundle) {
+            if (mServiceVersion != 0) {
+                onConnectionDescriptorChanged(this,
+                        MediaRouteProviderDescriptor.fromBundle(descriptorBundle));
+                return true;
+            }
+            return false;
+        }
+
+        public boolean onControlRequestSucceeded(int requestId, Bundle data) {
+            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
+            if (callback != null) {
+                mPendingCallbacks.remove(requestId);
+                callback.onResult(data);
+                return true;
+            }
+            return false;
+        }
+
+        public boolean onControlRequestFailed(int requestId, String error, Bundle data) {
+            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
+            if (callback != null) {
+                mPendingCallbacks.remove(requestId);
+                callback.onError(error, data);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void binderDied() {
+            mPrivateHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    onConnectionDied(Connection.this);
+                }
+            });
+        }
+
+        public int createRouteController(String routeId, String routeGroupId) {
+            int controllerId = mNextControllerId++;
+            Bundle data = new Bundle();
+            data.putString(CLIENT_DATA_ROUTE_ID, routeId);
+            data.putString(CLIENT_DATA_ROUTE_LIBRARY_GROUP, routeGroupId);
+            sendRequest(CLIENT_MSG_CREATE_ROUTE_CONTROLLER,
+                    mNextRequestId++, controllerId, null, data);
+            return controllerId;
+        }
+
+        public void releaseRouteController(int controllerId) {
+            sendRequest(CLIENT_MSG_RELEASE_ROUTE_CONTROLLER,
+                    mNextRequestId++, controllerId, null, null);
+        }
+
+        public void selectRoute(int controllerId) {
+            sendRequest(CLIENT_MSG_SELECT_ROUTE,
+                    mNextRequestId++, controllerId, null, null);
+        }
+
+        public void unselectRoute(int controllerId, int reason) {
+            Bundle extras = new Bundle();
+            extras.putInt(CLIENT_DATA_UNSELECT_REASON, reason);
+            sendRequest(CLIENT_MSG_UNSELECT_ROUTE,
+                    mNextRequestId++, controllerId, null, extras);
+        }
+
+        public void setVolume(int controllerId, int volume) {
+            Bundle data = new Bundle();
+            data.putInt(CLIENT_DATA_VOLUME, volume);
+            sendRequest(CLIENT_MSG_SET_ROUTE_VOLUME,
+                    mNextRequestId++, controllerId, null, data);
+        }
+
+        public void updateVolume(int controllerId, int delta) {
+            Bundle data = new Bundle();
+            data.putInt(CLIENT_DATA_VOLUME, delta);
+            sendRequest(CLIENT_MSG_UPDATE_ROUTE_VOLUME,
+                    mNextRequestId++, controllerId, null, data);
+        }
+
+        public boolean sendControlRequest(int controllerId, Intent intent,
+                ControlRequestCallback callback) {
+            int requestId = mNextRequestId++;
+            if (sendRequest(CLIENT_MSG_ROUTE_CONTROL_REQUEST,
+                    requestId, controllerId, intent, null)) {
+                if (callback != null) {
+                    mPendingCallbacks.put(requestId, callback);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        public void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
+            sendRequest(CLIENT_MSG_SET_DISCOVERY_REQUEST,
+                    mNextRequestId++, 0, request != null ? request.asBundle() : null, null);
+        }
+
+        private boolean sendRequest(int what, int requestId, int arg, Object obj, Bundle data) {
+            Message msg = Message.obtain();
+            msg.what = what;
+            msg.arg1 = requestId;
+            msg.arg2 = arg;
+            msg.obj = obj;
+            msg.setData(data);
+            msg.replyTo = mReceiveMessenger;
+            try {
+                mServiceMessenger.send(msg);
+                return true;
+            } catch (DeadObjectException ex) {
+                // The service died.
+            } catch (RemoteException ex) {
+                if (what != CLIENT_MSG_UNREGISTER) {
+                    Log.e(TAG, "Could not send message to service.", ex);
+                }
+            }
+            return false;
+        }
+    }
+
+    private static final class PrivateHandler extends Handler {
+        PrivateHandler() {
+        }
+    }
+
+    /**
+     * Handler that receives messages from the server.
+     * <p>
+     * This inner class is static and only retains a weak reference to the connection
+     * to prevent the client from being leaked in case the service is holding an
+     * active reference to the client's messenger.
+     * </p><p>
+     * This handler should not be used to handle any messages other than those
+     * that come from the service.
+     * </p>
+     */
+    private static final class ReceiveHandler extends Handler {
+        private final WeakReference<Connection> mConnectionRef;
+
+        public ReceiveHandler(Connection connection) {
+            mConnectionRef = new WeakReference<Connection>(connection);
+        }
+
+        public void dispose() {
+            mConnectionRef.clear();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Connection connection = mConnectionRef.get();
+            if (connection != null) {
+                final int what = msg.what;
+                final int requestId = msg.arg1;
+                final int arg = msg.arg2;
+                final Object obj = msg.obj;
+                final Bundle data = msg.peekData();
+                if (!processMessage(connection, what, requestId, arg, obj, data)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Unhandled message from server: " + msg);
+                    }
+                }
+            }
+        }
+
+        private boolean processMessage(Connection connection,
+                int what, int requestId, int arg, Object obj, Bundle data) {
+            switch (what) {
+                case SERVICE_MSG_GENERIC_FAILURE:
+                    connection.onGenericFailure(requestId);
+                    return true;
+
+                case SERVICE_MSG_GENERIC_SUCCESS:
+                    connection.onGenericSuccess(requestId);
+                    return true;
+
+                case SERVICE_MSG_REGISTERED:
+                    if (obj == null || obj instanceof Bundle) {
+                        return connection.onRegistered(requestId, arg, (Bundle)obj);
+                    }
+                    break;
+
+                case SERVICE_MSG_DESCRIPTOR_CHANGED:
+                    if (obj == null || obj instanceof Bundle) {
+                        return connection.onDescriptorChanged((Bundle)obj);
+                    }
+                    break;
+
+                case SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED:
+                    if (obj == null || obj instanceof Bundle) {
+                        return connection.onControlRequestSucceeded(
+                                requestId, (Bundle)obj);
+                    }
+                    break;
+
+                case SERVICE_MSG_CONTROL_REQUEST_FAILED:
+                    if (obj == null || obj instanceof Bundle) {
+                        String error = (data == null ? null :
+                                data.getString(SERVICE_DATA_ERROR));
+                        return connection.onControlRequestFailed(
+                                requestId, error, (Bundle)obj);
+                    }
+                    break;
+            }
+            return false;
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProviderWatcher.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProviderWatcher.java
new file mode 100644
index 0000000..ba1f647
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProviderWatcher.java
@@ -0,0 +1,157 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Watches for media route provider services to be installed.
+ * Adds a provider to the media router for each registered service.
+ *
+ * @see RegisteredMediaRouteProvider
+ */
+final class RegisteredMediaRouteProviderWatcher {
+    private final Context mContext;
+    private final Callback mCallback;
+    private final Handler mHandler;
+    private final PackageManager mPackageManager;
+
+    private final ArrayList<RegisteredMediaRouteProvider> mProviders =
+            new ArrayList<RegisteredMediaRouteProvider>();
+    private boolean mRunning;
+
+    public RegisteredMediaRouteProviderWatcher(Context context, Callback callback) {
+        mContext = context;
+        mCallback = callback;
+        mHandler = new Handler();
+        mPackageManager = context.getPackageManager();
+    }
+
+    public void start() {
+        if (!mRunning) {
+            mRunning = true;
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            filter.addDataScheme("package");
+            mContext.registerReceiver(mScanPackagesReceiver, filter, null, mHandler);
+
+            // Scan packages.
+            // Also has the side-effect of restarting providers if needed.
+            mHandler.post(mScanPackagesRunnable);
+        }
+    }
+
+    public void stop() {
+        if (mRunning) {
+            mRunning = false;
+
+            mContext.unregisterReceiver(mScanPackagesReceiver);
+            mHandler.removeCallbacks(mScanPackagesRunnable);
+
+            // Stop all providers.
+            for (int i = mProviders.size() - 1; i >= 0; i--) {
+                mProviders.get(i).stop();
+            }
+        }
+    }
+
+    void scanPackages() {
+        if (!mRunning) {
+            return;
+        }
+
+        // Add providers for all new services.
+        // Reorder the list so that providers left at the end will be the ones to remove.
+        int targetIndex = 0;
+        Intent intent = new Intent(MediaRouteProviderService.SERVICE_INTERFACE);
+        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServices(intent, 0)) {
+            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            if (serviceInfo != null) {
+                int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
+                if (sourceIndex < 0) {
+                    RegisteredMediaRouteProvider provider =
+                            new RegisteredMediaRouteProvider(mContext,
+                            new ComponentName(serviceInfo.packageName, serviceInfo.name));
+                    provider.start();
+                    mProviders.add(targetIndex++, provider);
+                    mCallback.addProvider(provider);
+                } else if (sourceIndex >= targetIndex) {
+                    RegisteredMediaRouteProvider provider = mProviders.get(sourceIndex);
+                    provider.start(); // restart the provider if needed
+                    provider.rebindIfDisconnected();
+                    Collections.swap(mProviders, sourceIndex, targetIndex++);
+                }
+            }
+        }
+
+        // Remove providers for missing services.
+        if (targetIndex < mProviders.size()) {
+            for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
+                RegisteredMediaRouteProvider provider = mProviders.get(i);
+                mCallback.removeProvider(provider);
+                mProviders.remove(provider);
+                provider.stop();
+            }
+        }
+    }
+
+    private int findProvider(String packageName, String className) {
+        int count = mProviders.size();
+        for (int i = 0; i < count; i++) {
+            RegisteredMediaRouteProvider provider = mProviders.get(i);
+            if (provider.hasComponentName(packageName, className)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            scanPackages();
+        }
+    };
+
+    private final Runnable mScanPackagesRunnable = new Runnable() {
+        @Override
+        public void run() {
+            scanPackages();
+        }
+    };
+
+    public interface Callback {
+        void addProvider(MediaRouteProvider provider);
+        void removeProvider(MediaRouteProvider provider);
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/RemoteControlClientCompat.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/RemoteControlClientCompat.java
new file mode 100644
index 0000000..826449b
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/RemoteControlClientCompat.java
@@ -0,0 +1,190 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides access to features of the remote control client.
+ *
+ * Hidden for now but we might want to make this available to applications
+ * in the future.
+ */
+abstract class RemoteControlClientCompat {
+    protected final Context mContext;
+    protected final Object mRcc;
+    protected VolumeCallback mVolumeCallback;
+
+    protected RemoteControlClientCompat(Context context, Object rcc) {
+        mContext = context;
+        mRcc = rcc;
+    }
+
+    public static RemoteControlClientCompat obtain(Context context, Object rcc) {
+        if (Build.VERSION.SDK_INT >= 16) {
+            return new JellybeanImpl(context, rcc);
+        }
+        return new LegacyImpl(context, rcc);
+    }
+
+    public Object getRemoteControlClient() {
+        return mRcc;
+    }
+
+    /**
+     * Sets the current playback information.
+     * Must be called at least once to attach to the remote control client.
+     *
+     * @param info The playback information.  Must not be null.
+     */
+    public void setPlaybackInfo(PlaybackInfo info) {
+    }
+
+    /**
+     * Sets a callback to receive volume change requests from the remote control client.
+     *
+     * @param callback The volume callback to use or null if none.
+     */
+    public void setVolumeCallback(VolumeCallback callback) {
+        mVolumeCallback = callback;
+    }
+
+    /**
+     * Specifies information about the playback.
+     */
+    public static final class PlaybackInfo {
+        public int volume;
+        public int volumeMax;
+        public int volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+        public int playbackStream = AudioManager.STREAM_MUSIC;
+        public int playbackType = MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+    }
+
+    /**
+     * Called when volume updates are requested by the remote control client.
+     */
+    public interface VolumeCallback {
+        /**
+         * Called when the volume should be increased or decreased.
+         *
+         * @param direction An integer indicating whether the volume is to be increased
+         * (positive value) or decreased (negative value).
+         * For bundled changes, the absolute value indicates the number of changes
+         * in the same direction, e.g. +3 corresponds to three "volume up" changes.
+         */
+        public void onVolumeUpdateRequest(int direction);
+
+        /**
+         * Called when the volume for the route should be set to the given value.
+         *
+         * @param volume An integer indicating the new volume value that should be used,
+         * always between 0 and the value set by {@link PlaybackInfo#volumeMax}.
+         */
+        public void onVolumeSetRequest(int volume);
+    }
+
+    /**
+     * Legacy implementation for platform versions prior to Jellybean.
+     * Does nothing.
+     */
+    static class LegacyImpl extends RemoteControlClientCompat {
+        public LegacyImpl(Context context, Object rcc) {
+            super(context, rcc);
+        }
+    }
+
+    /**
+     * Implementation for Jellybean.
+     *
+     * The basic idea of this implementation is to attach the RCC to a UserRouteInfo
+     * in order to hook up stream metadata and volume callbacks because there is no
+     * other API available to do so in this platform version.  The UserRouteInfo itself
+     * is not attached to the MediaRouter so it is transparent to the user.
+     */
+    // @@RequiresApi(16)
+    static class JellybeanImpl extends RemoteControlClientCompat {
+        private final Object mRouterObj;
+        private final Object mUserRouteCategoryObj;
+        private final Object mUserRouteObj;
+        private boolean mRegistered;
+
+        public JellybeanImpl(Context context, Object rcc) {
+            super(context, rcc);
+
+            mRouterObj = MediaRouterJellybean.getMediaRouter(context);
+            mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
+                    mRouterObj, "", false);
+            mUserRouteObj = MediaRouterJellybean.createUserRoute(
+                    mRouterObj, mUserRouteCategoryObj);
+        }
+
+        @Override
+        public void setPlaybackInfo(PlaybackInfo info) {
+            MediaRouterJellybean.UserRouteInfo.setVolume(
+                    mUserRouteObj, info.volume);
+            MediaRouterJellybean.UserRouteInfo.setVolumeMax(
+                    mUserRouteObj, info.volumeMax);
+            MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
+                    mUserRouteObj, info.volumeHandling);
+            MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
+                    mUserRouteObj, info.playbackStream);
+            MediaRouterJellybean.UserRouteInfo.setPlaybackType(
+                    mUserRouteObj, info.playbackType);
+
+            if (!mRegistered) {
+                mRegistered = true;
+                MediaRouterJellybean.UserRouteInfo.setVolumeCallback(mUserRouteObj,
+                        MediaRouterJellybean.createVolumeCallback(
+                                new VolumeCallbackWrapper(this)));
+                MediaRouterJellybean.UserRouteInfo.setRemoteControlClient(mUserRouteObj, mRcc);
+            }
+        }
+
+        private static final class VolumeCallbackWrapper
+                implements MediaRouterJellybean.VolumeCallback {
+            // Unfortunately, the framework never unregisters its volume observer from
+            // the audio service so the UserRouteInfo object may leak along with
+            // any callbacks that we attach to it.  Use a weak reference to prevent
+            // the volume callback from holding strong references to anything important.
+            private final WeakReference<JellybeanImpl> mImplWeak;
+
+            public VolumeCallbackWrapper(JellybeanImpl impl) {
+                mImplWeak = new WeakReference<JellybeanImpl>(impl);
+            }
+
+            @Override
+            public void onVolumeUpdateRequest(Object routeObj, int direction) {
+                JellybeanImpl impl = mImplWeak.get();
+                if (impl != null && impl.mVolumeCallback != null) {
+                    impl.mVolumeCallback.onVolumeUpdateRequest(direction);
+                }
+            }
+
+            @Override
+            public void onVolumeSetRequest(Object routeObj, int volume) {
+                JellybeanImpl impl = mImplWeak.get();
+                if (impl != null && impl.mVolumeCallback != null) {
+                    impl.mVolumeCallback.onVolumeSetRequest(volume);
+                }
+            }
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/RemotePlaybackClient.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/RemotePlaybackClient.java
new file mode 100644
index 0000000..f6e1497
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/RemotePlaybackClient.java
@@ -0,0 +1,1044 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.util.ObjectsCompat;
+import android.util.Log;
+
+/**
+ * A helper class for playing media on remote routes using the remote playback protocol
+ * defined by {@link MediaControlIntent}.
+ * <p>
+ * The client maintains session state and offers a simplified interface for issuing
+ * remote playback media control intents to a single route.
+ * </p>
+ */
+public class RemotePlaybackClient {
+    static final String TAG = "RemotePlaybackClient";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Context mContext;
+    private final MediaRouter.RouteInfo mRoute;
+    private final ActionReceiver mActionReceiver;
+    private final PendingIntent mItemStatusPendingIntent;
+    private final PendingIntent mSessionStatusPendingIntent;
+    private final PendingIntent mMessagePendingIntent;
+
+    private boolean mRouteSupportsRemotePlayback;
+    private boolean mRouteSupportsQueuing;
+    private boolean mRouteSupportsSessionManagement;
+    private boolean mRouteSupportsMessaging;
+
+    String mSessionId;
+    StatusCallback mStatusCallback;
+    OnMessageReceivedListener mOnMessageReceivedListener;
+
+    /**
+     * Creates a remote playback client for a route.
+     *
+     * @param route The media route.
+     */
+    public RemotePlaybackClient(Context context, MediaRouter.RouteInfo route) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+        if (route == null) {
+            throw new IllegalArgumentException("route must not be null");
+        }
+
+        mContext = context;
+        mRoute = route;
+
+        IntentFilter actionFilter = new IntentFilter();
+        actionFilter.addAction(ActionReceiver.ACTION_ITEM_STATUS_CHANGED);
+        actionFilter.addAction(ActionReceiver.ACTION_SESSION_STATUS_CHANGED);
+        actionFilter.addAction(ActionReceiver.ACTION_MESSAGE_RECEIVED);
+        mActionReceiver = new ActionReceiver();
+        context.registerReceiver(mActionReceiver, actionFilter);
+
+        Intent itemStatusIntent = new Intent(ActionReceiver.ACTION_ITEM_STATUS_CHANGED);
+        itemStatusIntent.setPackage(context.getPackageName());
+        mItemStatusPendingIntent = PendingIntent.getBroadcast(
+                context, 0, itemStatusIntent, 0);
+
+        Intent sessionStatusIntent = new Intent(ActionReceiver.ACTION_SESSION_STATUS_CHANGED);
+        sessionStatusIntent.setPackage(context.getPackageName());
+        mSessionStatusPendingIntent = PendingIntent.getBroadcast(
+                context, 0, sessionStatusIntent, 0);
+
+        Intent messageIntent = new Intent(ActionReceiver.ACTION_MESSAGE_RECEIVED);
+        messageIntent.setPackage(context.getPackageName());
+        mMessagePendingIntent = PendingIntent.getBroadcast(
+                context, 0, messageIntent, 0);
+        detectFeatures();
+    }
+
+    /**
+     * Releases resources owned by the client.
+     */
+    public void release() {
+        mContext.unregisterReceiver(mActionReceiver);
+    }
+
+    /**
+     * Returns true if the route supports remote playback.
+     * <p>
+     * If the route does not support remote playback, then none of the functionality
+     * offered by the client will be available.
+     * </p><p>
+     * This method returns true if the route supports all of the following
+     * actions: {@link MediaControlIntent#ACTION_PLAY play},
+     * {@link MediaControlIntent#ACTION_SEEK seek},
+     * {@link MediaControlIntent#ACTION_GET_STATUS get status},
+     * {@link MediaControlIntent#ACTION_PAUSE pause},
+     * {@link MediaControlIntent#ACTION_RESUME resume},
+     * {@link MediaControlIntent#ACTION_STOP stop}.
+     * </p>
+     *
+     * @return True if remote playback is supported.
+     */
+    public boolean isRemotePlaybackSupported() {
+        return mRouteSupportsRemotePlayback;
+    }
+
+    /**
+     * Returns true if the route supports queuing features.
+     * <p>
+     * If the route does not support queuing, then at most one media item can be played
+     * at a time and the {@link #enqueue} method will not be available.
+     * </p><p>
+     * This method returns true if the route supports all of the basic remote playback
+     * actions and all of the following actions:
+     * {@link MediaControlIntent#ACTION_ENQUEUE enqueue},
+     * {@link MediaControlIntent#ACTION_REMOVE remove}.
+     * </p>
+     *
+     * @return True if queuing is supported.  Implies {@link #isRemotePlaybackSupported}
+     * is also true.
+     *
+     * @see #isRemotePlaybackSupported
+     */
+    public boolean isQueuingSupported() {
+        return mRouteSupportsQueuing;
+    }
+
+    /**
+     * Returns true if the route supports session management features.
+     * <p>
+     * If the route does not support session management, then the session will
+     * not be created until the first media item is played.
+     * </p><p>
+     * This method returns true if the route supports all of the basic remote playback
+     * actions and all of the following actions:
+     * {@link MediaControlIntent#ACTION_START_SESSION start session},
+     * {@link MediaControlIntent#ACTION_GET_SESSION_STATUS get session status},
+     * {@link MediaControlIntent#ACTION_END_SESSION end session}.
+     * </p>
+     *
+     * @return True if session management is supported.
+     * Implies {@link #isRemotePlaybackSupported} is also true.
+     *
+     * @see #isRemotePlaybackSupported
+     */
+    public boolean isSessionManagementSupported() {
+        return mRouteSupportsSessionManagement;
+    }
+
+    /**
+     * Returns true if the route supports messages.
+     * <p>
+     * This method returns true if the route supports all of the basic remote playback
+     * actions and all of the following actions:
+     * {@link MediaControlIntent#ACTION_START_SESSION start session},
+     * {@link MediaControlIntent#ACTION_SEND_MESSAGE send message},
+     * {@link MediaControlIntent#ACTION_END_SESSION end session}.
+     * </p>
+     *
+     * @return True if session management is supported.
+     * Implies {@link #isRemotePlaybackSupported} is also true.
+     *
+     * @see #isRemotePlaybackSupported
+     */
+    public boolean isMessagingSupported() {
+        return mRouteSupportsMessaging;
+    }
+
+    /**
+     * Gets the current session id if there is one.
+     *
+     * @return The current session id, or null if none.
+     */
+    public String getSessionId() {
+        return mSessionId;
+    }
+
+    /**
+     * Sets the current session id.
+     * <p>
+     * It is usually not necessary to set the session id explicitly since
+     * it is created as a side-effect of other requests such as
+     * {@link #play}, {@link #enqueue}, and {@link #startSession}.
+     * </p>
+     *
+     * @param sessionId The new session id, or null if none.
+     */
+    public void setSessionId(String sessionId) {
+        if (!ObjectsCompat.equals(mSessionId, sessionId)) {
+            if (DEBUG) {
+                Log.d(TAG, "Session id is now: " + sessionId);
+            }
+            mSessionId = sessionId;
+            if (mStatusCallback != null) {
+                mStatusCallback.onSessionChanged(sessionId);
+            }
+        }
+    }
+
+    /**
+     * Returns true if the client currently has a session.
+     * <p>
+     * Equivalent to checking whether {@link #getSessionId} returns a non-null result.
+     * </p>
+     *
+     * @return True if there is a current session.
+     */
+    public boolean hasSession() {
+        return mSessionId != null;
+    }
+
+    /**
+     * Sets a callback that should receive status updates when the state of
+     * media sessions or media items created by this instance of the remote
+     * playback client changes.
+     * <p>
+     * The callback should be set before the session is created or any play
+     * commands are issued.
+     * </p>
+     *
+     * @param callback The callback to set.  May be null to remove the previous callback.
+     */
+    public void setStatusCallback(StatusCallback callback) {
+        mStatusCallback = callback;
+    }
+
+    /**
+     * Sets a callback that should receive messages when a message is sent from
+     * media sessions created by this instance of the remote playback client changes.
+     * <p>
+     * The callback should be set before the session is created.
+     * </p>
+     *
+     * @param listener The callback to set.  May be null to remove the previous callback.
+     */
+    public void setOnMessageReceivedListener(OnMessageReceivedListener listener) {
+        mOnMessageReceivedListener = listener;
+    }
+
+    /**
+     * Sends a request to play a media item.
+     * <p>
+     * Clears the queue and starts playing the new item immediately.  If the queue
+     * was previously paused, then it is resumed as a side-effect of this request.
+     * </p><p>
+     * The request is issued in the current session.  If no session is available, then
+     * one is created implicitly.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_PLAY ACTION_PLAY} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param contentUri The content Uri to play.
+     * @param mimeType The mime type of the content, or null if unknown.
+     * @param positionMillis The initial content position for the item in milliseconds,
+     * or <code>0</code> to start at the beginning.
+     * @param metadata The media item metadata bundle, or null if none.
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_PLAY} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws UnsupportedOperationException if the route does not support remote playback.
+     *
+     * @see MediaControlIntent#ACTION_PLAY
+     * @see #isRemotePlaybackSupported
+     */
+    public void play(Uri contentUri, String mimeType, Bundle metadata,
+            long positionMillis, Bundle extras, ItemActionCallback callback) {
+        playOrEnqueue(contentUri, mimeType, metadata, positionMillis,
+                extras, callback, MediaControlIntent.ACTION_PLAY);
+    }
+
+    /**
+     * Sends a request to enqueue a media item.
+     * <p>
+     * Enqueues a new item to play.  If the queue was previously paused, then will
+     * remain paused.
+     * </p><p>
+     * The request is issued in the current session.  If no session is available, then
+     * one is created implicitly.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_ENQUEUE ACTION_ENQUEUE} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param contentUri The content Uri to enqueue.
+     * @param mimeType The mime type of the content, or null if unknown.
+     * @param positionMillis The initial content position for the item in milliseconds,
+     * or <code>0</code> to start at the beginning.
+     * @param metadata The media item metadata bundle, or null if none.
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_ENQUEUE} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws UnsupportedOperationException if the route does not support queuing.
+     *
+     * @see MediaControlIntent#ACTION_ENQUEUE
+     * @see #isRemotePlaybackSupported
+     * @see #isQueuingSupported
+     */
+    public void enqueue(Uri contentUri, String mimeType, Bundle metadata,
+            long positionMillis, Bundle extras, ItemActionCallback callback) {
+        playOrEnqueue(contentUri, mimeType, metadata, positionMillis,
+                extras, callback, MediaControlIntent.ACTION_ENQUEUE);
+    }
+
+    private void playOrEnqueue(Uri contentUri, String mimeType, Bundle metadata,
+            long positionMillis, Bundle extras,
+            final ItemActionCallback callback, String action) {
+        if (contentUri == null) {
+            throw new IllegalArgumentException("contentUri must not be null");
+        }
+        throwIfRemotePlaybackNotSupported();
+        if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
+            throwIfQueuingNotSupported();
+        }
+
+        Intent intent = new Intent(action);
+        intent.setDataAndType(contentUri, mimeType);
+        intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER,
+                mItemStatusPendingIntent);
+        if (metadata != null) {
+            intent.putExtra(MediaControlIntent.EXTRA_ITEM_METADATA, metadata);
+        }
+        if (positionMillis != 0) {
+            intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, positionMillis);
+        }
+        performItemAction(intent, mSessionId, null, extras, callback);
+    }
+
+    /**
+     * Sends a request to seek to a new position in a media item.
+     * <p>
+     * Seeks to a new position.  If the queue was previously paused then it
+     * remains paused but the item's new position is still remembered.
+     * </p><p>
+     * The request is issued in the current session.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_SEEK ACTION_SEEK} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param itemId The item id.
+     * @param positionMillis The new content position for the item in milliseconds,
+     * or <code>0</code> to start at the beginning.
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_SEEK} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     *
+     * @see MediaControlIntent#ACTION_SEEK
+     * @see #isRemotePlaybackSupported
+     */
+    public void seek(String itemId, long positionMillis, Bundle extras,
+            ItemActionCallback callback) {
+        if (itemId == null) {
+            throw new IllegalArgumentException("itemId must not be null");
+        }
+        throwIfNoCurrentSession();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_SEEK);
+        intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, positionMillis);
+        performItemAction(intent, mSessionId, itemId, extras, callback);
+    }
+
+    /**
+     * Sends a request to get the status of a media item.
+     * <p>
+     * The request is issued in the current session.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_GET_STATUS ACTION_GET_STATUS} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param itemId The item id.
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_GET_STATUS} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     *
+     * @see MediaControlIntent#ACTION_GET_STATUS
+     * @see #isRemotePlaybackSupported
+     */
+    public void getStatus(String itemId, Bundle extras, ItemActionCallback callback) {
+        if (itemId == null) {
+            throw new IllegalArgumentException("itemId must not be null");
+        }
+        throwIfNoCurrentSession();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_GET_STATUS);
+        performItemAction(intent, mSessionId, itemId, extras, callback);
+    }
+
+    /**
+     * Sends a request to remove a media item from the queue.
+     * <p>
+     * The request is issued in the current session.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_REMOVE ACTION_REMOVE} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param itemId The item id.
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_REMOVE} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     * @throws UnsupportedOperationException if the route does not support queuing.
+     *
+     * @see MediaControlIntent#ACTION_REMOVE
+     * @see #isRemotePlaybackSupported
+     * @see #isQueuingSupported
+     */
+    public void remove(String itemId, Bundle extras, ItemActionCallback callback) {
+        if (itemId == null) {
+            throw new IllegalArgumentException("itemId must not be null");
+        }
+        throwIfQueuingNotSupported();
+        throwIfNoCurrentSession();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_REMOVE);
+        performItemAction(intent, mSessionId, itemId, extras, callback);
+    }
+
+    /**
+     * Sends a request to pause media playback.
+     * <p>
+     * The request is issued in the current session.  If playback is already paused
+     * then the request has no effect.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_PAUSE ACTION_PAUSE} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_PAUSE} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     *
+     * @see MediaControlIntent#ACTION_PAUSE
+     * @see #isRemotePlaybackSupported
+     */
+    public void pause(Bundle extras, SessionActionCallback callback) {
+        throwIfNoCurrentSession();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE);
+        performSessionAction(intent, mSessionId, extras, callback);
+    }
+
+    /**
+     * Sends a request to resume (unpause) media playback.
+     * <p>
+     * The request is issued in the current session.  If playback is not paused
+     * then the request has no effect.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_RESUME ACTION_RESUME} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_RESUME} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     *
+     * @see MediaControlIntent#ACTION_RESUME
+     * @see #isRemotePlaybackSupported
+     */
+    public void resume(Bundle extras, SessionActionCallback callback) {
+        throwIfNoCurrentSession();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_RESUME);
+        performSessionAction(intent, mSessionId, extras, callback);
+    }
+
+    /**
+     * Sends a request to stop media playback and clear the media playback queue.
+     * <p>
+     * The request is issued in the current session.  If the queue is already
+     * empty then the request has no effect.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_STOP ACTION_STOP} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_STOP} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     *
+     * @see MediaControlIntent#ACTION_STOP
+     * @see #isRemotePlaybackSupported
+     */
+    public void stop(Bundle extras, SessionActionCallback callback) {
+        throwIfNoCurrentSession();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_STOP);
+        performSessionAction(intent, mSessionId, extras, callback);
+    }
+
+    /**
+     * Sends a request to start a new media playback session.
+     * <p>
+     * The application must wait for the callback to indicate that this request
+     * is complete before issuing other requests that affect the session.  If this
+     * request is successful then the previous session will be invalidated.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_START_SESSION ACTION_START_SESSION}
+     * for more information about the semantics of this request.
+     * </p>
+     *
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_START_SESSION} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws UnsupportedOperationException if the route does not support session management.
+     *
+     * @see MediaControlIntent#ACTION_START_SESSION
+     * @see #isRemotePlaybackSupported
+     * @see #isSessionManagementSupported
+     */
+    public void startSession(Bundle extras, SessionActionCallback callback) {
+        throwIfSessionManagementNotSupported();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_START_SESSION);
+        intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER,
+                mSessionStatusPendingIntent);
+        if (mRouteSupportsMessaging) {
+            intent.putExtra(MediaControlIntent.EXTRA_MESSAGE_RECEIVER, mMessagePendingIntent);
+        }
+        performSessionAction(intent, null, extras, callback);
+    }
+
+    /**
+     * Sends a message.
+     * <p>
+     * The request is issued in the current session.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_SEND_MESSAGE} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
+     * @param callback A callback to invoke when the request has been processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     * @throws UnsupportedOperationException if the route does not support messages.
+     *
+     * @see MediaControlIntent#ACTION_SEND_MESSAGE
+     * @see #isMessagingSupported
+     */
+    public void sendMessage(Bundle message, SessionActionCallback callback) {
+        throwIfNoCurrentSession();
+        throwIfMessageNotSupported();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_SEND_MESSAGE);
+        performSessionAction(intent, mSessionId, message, callback);
+    }
+
+    /**
+     * Sends a request to get the status of the media playback session.
+     * <p>
+     * The request is issued in the current session.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_GET_SESSION_STATUS
+     * ACTION_GET_SESSION_STATUS} for more information about the semantics of this request.
+     * </p>
+     *
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_GET_SESSION_STATUS} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     * @throws UnsupportedOperationException if the route does not support session management.
+     *
+     * @see MediaControlIntent#ACTION_GET_SESSION_STATUS
+     * @see #isRemotePlaybackSupported
+     * @see #isSessionManagementSupported
+     */
+    public void getSessionStatus(Bundle extras, SessionActionCallback callback) {
+        throwIfSessionManagementNotSupported();
+        throwIfNoCurrentSession();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_GET_SESSION_STATUS);
+        performSessionAction(intent, mSessionId, extras, callback);
+    }
+
+    /**
+     * Sends a request to end the media playback session.
+     * <p>
+     * The request is issued in the current session.  If this request is successful,
+     * the {@link #getSessionId session id property} will be set to null after
+     * the callback is invoked.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_END_SESSION ACTION_END_SESSION}
+     * for more information about the semantics of this request.
+     * </p>
+     *
+     * @param extras A bundle of extra arguments to be added to the
+     * {@link MediaControlIntent#ACTION_END_SESSION} intent, or null if none.
+     * @param callback A callback to invoke when the request has been
+     * processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     * @throws UnsupportedOperationException if the route does not support session management.
+     *
+     * @see MediaControlIntent#ACTION_END_SESSION
+     * @see #isRemotePlaybackSupported
+     * @see #isSessionManagementSupported
+     */
+    public void endSession(Bundle extras, SessionActionCallback callback) {
+        throwIfSessionManagementNotSupported();
+        throwIfNoCurrentSession();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_END_SESSION);
+        performSessionAction(intent, mSessionId, extras, callback);
+    }
+
+    private void performItemAction(final Intent intent,
+            final String sessionId, final String itemId,
+            Bundle extras, final ItemActionCallback callback) {
+        intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        if (sessionId != null) {
+            intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sessionId);
+        }
+        if (itemId != null) {
+            intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, itemId);
+        }
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        logRequest(intent);
+        mRoute.sendControlRequest(intent, new MediaRouter.ControlRequestCallback() {
+            @Override
+            public void onResult(Bundle data) {
+                if (data != null) {
+                    String sessionIdResult = inferMissingResult(sessionId,
+                            data.getString(MediaControlIntent.EXTRA_SESSION_ID));
+                    MediaSessionStatus sessionStatus = MediaSessionStatus.fromBundle(
+                            data.getBundle(MediaControlIntent.EXTRA_SESSION_STATUS));
+                    String itemIdResult = inferMissingResult(itemId,
+                            data.getString(MediaControlIntent.EXTRA_ITEM_ID));
+                    MediaItemStatus itemStatus = MediaItemStatus.fromBundle(
+                            data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS));
+                    adoptSession(sessionIdResult);
+                    if (sessionIdResult != null && itemIdResult != null && itemStatus != null) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Received result from " + intent.getAction()
+                                    + ": data=" + bundleToString(data)
+                                    + ", sessionId=" + sessionIdResult
+                                    + ", sessionStatus=" + sessionStatus
+                                    + ", itemId=" + itemIdResult
+                                    + ", itemStatus=" + itemStatus);
+                        }
+                        callback.onResult(data, sessionIdResult, sessionStatus,
+                                itemIdResult, itemStatus);
+                        return;
+                    }
+                }
+                handleInvalidResult(intent, callback, data);
+            }
+
+            @Override
+            public void onError(String error, Bundle data) {
+                handleError(intent, callback, error, data);
+            }
+        });
+    }
+
+    private void performSessionAction(final Intent intent, final String sessionId,
+            Bundle extras, final SessionActionCallback callback) {
+        intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        if (sessionId != null) {
+            intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sessionId);
+        }
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        logRequest(intent);
+        mRoute.sendControlRequest(intent, new MediaRouter.ControlRequestCallback() {
+            @Override
+            public void onResult(Bundle data) {
+                if (data != null) {
+                    String sessionIdResult = inferMissingResult(sessionId,
+                            data.getString(MediaControlIntent.EXTRA_SESSION_ID));
+                    MediaSessionStatus sessionStatus = MediaSessionStatus.fromBundle(
+                            data.getBundle(MediaControlIntent.EXTRA_SESSION_STATUS));
+                    adoptSession(sessionIdResult);
+                    if (sessionIdResult != null) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Received result from " + intent.getAction()
+                                    + ": data=" + bundleToString(data)
+                                    + ", sessionId=" + sessionIdResult
+                                    + ", sessionStatus=" + sessionStatus);
+                        }
+                        try {
+                            callback.onResult(data, sessionIdResult, sessionStatus);
+                        } finally {
+                            if (intent.getAction().equals(MediaControlIntent.ACTION_END_SESSION)
+                                    && sessionIdResult.equals(mSessionId)) {
+                                setSessionId(null);
+                            }
+                        }
+                        return;
+                    }
+                }
+                handleInvalidResult(intent, callback, data);
+            }
+
+            @Override
+            public void onError(String error, Bundle data) {
+                handleError(intent, callback, error, data);
+            }
+        });
+    }
+
+    void adoptSession(String sessionId) {
+        if (sessionId != null) {
+            setSessionId(sessionId);
+        }
+    }
+
+    void handleInvalidResult(Intent intent, ActionCallback callback,
+            Bundle data) {
+        Log.w(TAG, "Received invalid result data from " + intent.getAction()
+                + ": data=" + bundleToString(data));
+        callback.onError(null, MediaControlIntent.ERROR_UNKNOWN, data);
+    }
+
+    void handleError(Intent intent, ActionCallback callback,
+            String error, Bundle data) {
+        final int code;
+        if (data != null) {
+            code = data.getInt(MediaControlIntent.EXTRA_ERROR_CODE,
+                    MediaControlIntent.ERROR_UNKNOWN);
+        } else {
+            code = MediaControlIntent.ERROR_UNKNOWN;
+        }
+        if (DEBUG) {
+            Log.w(TAG, "Received error from " + intent.getAction()
+                    + ": error=" + error
+                    + ", code=" + code
+                    + ", data=" + bundleToString(data));
+        }
+        callback.onError(error, code, data);
+    }
+
+    private void detectFeatures() {
+        mRouteSupportsRemotePlayback = routeSupportsAction(MediaControlIntent.ACTION_PLAY)
+                && routeSupportsAction(MediaControlIntent.ACTION_SEEK)
+                && routeSupportsAction(MediaControlIntent.ACTION_GET_STATUS)
+                && routeSupportsAction(MediaControlIntent.ACTION_PAUSE)
+                && routeSupportsAction(MediaControlIntent.ACTION_RESUME)
+                && routeSupportsAction(MediaControlIntent.ACTION_STOP);
+        mRouteSupportsQueuing = mRouteSupportsRemotePlayback
+                && routeSupportsAction(MediaControlIntent.ACTION_ENQUEUE)
+                && routeSupportsAction(MediaControlIntent.ACTION_REMOVE);
+        mRouteSupportsSessionManagement = mRouteSupportsRemotePlayback
+                && routeSupportsAction(MediaControlIntent.ACTION_START_SESSION)
+                && routeSupportsAction(MediaControlIntent.ACTION_GET_SESSION_STATUS)
+                && routeSupportsAction(MediaControlIntent.ACTION_END_SESSION);
+        mRouteSupportsMessaging = doesRouteSupportMessaging();
+    }
+
+    private boolean routeSupportsAction(String action) {
+        return mRoute.supportsControlAction(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK, action);
+    }
+
+    private boolean doesRouteSupportMessaging() {
+        for (IntentFilter filter : mRoute.getControlFilters()) {
+            if (filter.hasAction(MediaControlIntent.ACTION_SEND_MESSAGE)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void throwIfRemotePlaybackNotSupported() {
+        if (!mRouteSupportsRemotePlayback) {
+            throw new UnsupportedOperationException("The route does not support remote playback.");
+        }
+    }
+
+    private void throwIfQueuingNotSupported() {
+        if (!mRouteSupportsQueuing) {
+            throw new UnsupportedOperationException("The route does not support queuing.");
+        }
+    }
+
+    private void throwIfSessionManagementNotSupported() {
+        if (!mRouteSupportsSessionManagement) {
+            throw new UnsupportedOperationException("The route does not support "
+                    + "session management.");
+        }
+    }
+
+    private void throwIfMessageNotSupported() {
+        if (!mRouteSupportsMessaging) {
+            throw new UnsupportedOperationException("The route does not support message.");
+        }
+    }
+
+    private void throwIfNoCurrentSession() {
+        if (mSessionId == null) {
+            throw new IllegalStateException("There is no current session.");
+        }
+    }
+
+    static String inferMissingResult(String request, String result) {
+        if (result == null) {
+            // Result is missing.
+            return request;
+        }
+        if (request == null || request.equals(result)) {
+            // Request didn't specify a value or result matches request.
+            return result;
+        }
+        // Result conflicts with request.
+        return null;
+    }
+
+    private static void logRequest(Intent intent) {
+        if (DEBUG) {
+            Log.d(TAG, "Sending request: " + intent);
+        }
+    }
+
+    static String bundleToString(Bundle bundle) {
+        if (bundle != null) {
+            bundle.size(); // force bundle to be unparcelled
+            return bundle.toString();
+        }
+        return "null";
+    }
+
+    private final class ActionReceiver extends BroadcastReceiver {
+        public static final String ACTION_ITEM_STATUS_CHANGED =
+                "android.support.v7.media.actions.ACTION_ITEM_STATUS_CHANGED";
+        public static final String ACTION_SESSION_STATUS_CHANGED =
+                "android.support.v7.media.actions.ACTION_SESSION_STATUS_CHANGED";
+        public static final String ACTION_MESSAGE_RECEIVED =
+                "android.support.v7.media.actions.ACTION_MESSAGE_RECEIVED";
+
+        ActionReceiver() {
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String sessionId = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            if (sessionId == null || !sessionId.equals(mSessionId)) {
+                Log.w(TAG, "Discarding spurious status callback "
+                        + "with missing or invalid session id: sessionId=" + sessionId);
+                return;
+            }
+
+            MediaSessionStatus sessionStatus = MediaSessionStatus.fromBundle(
+                    intent.getBundleExtra(MediaControlIntent.EXTRA_SESSION_STATUS));
+            String action = intent.getAction();
+            if (action.equals(ACTION_ITEM_STATUS_CHANGED)) {
+                String itemId = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+                if (itemId == null) {
+                    Log.w(TAG, "Discarding spurious status callback with missing item id.");
+                    return;
+                }
+
+                MediaItemStatus itemStatus = MediaItemStatus.fromBundle(
+                        intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_STATUS));
+                if (itemStatus == null) {
+                    Log.w(TAG, "Discarding spurious status callback with missing item status.");
+                    return;
+                }
+
+                if (DEBUG) {
+                    Log.d(TAG, "Received item status callback: sessionId=" + sessionId
+                            + ", sessionStatus=" + sessionStatus
+                            + ", itemId=" + itemId
+                            + ", itemStatus=" + itemStatus);
+                }
+
+                if (mStatusCallback != null) {
+                    mStatusCallback.onItemStatusChanged(intent.getExtras(),
+                            sessionId, sessionStatus, itemId, itemStatus);
+                }
+            } else if (action.equals(ACTION_SESSION_STATUS_CHANGED)) {
+                if (sessionStatus == null) {
+                    Log.w(TAG, "Discarding spurious media status callback with "
+                            +"missing session status.");
+                    return;
+                }
+
+                if (DEBUG) {
+                    Log.d(TAG, "Received session status callback: sessionId=" + sessionId
+                            + ", sessionStatus=" + sessionStatus);
+                }
+
+                if (mStatusCallback != null) {
+                    mStatusCallback.onSessionStatusChanged(intent.getExtras(),
+                            sessionId, sessionStatus);
+                }
+            } else if (action.equals(ACTION_MESSAGE_RECEIVED)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Received message callback: sessionId=" + sessionId);
+                }
+
+                if (mOnMessageReceivedListener != null) {
+                    mOnMessageReceivedListener.onMessageReceived(sessionId,
+                            intent.getBundleExtra(MediaControlIntent.EXTRA_MESSAGE));
+                }
+            }
+        }
+    }
+
+    /**
+     * A callback that will receive media status updates.
+     */
+    public static abstract class StatusCallback {
+        /**
+         * Called when the status of a media item changes.
+         *
+         * @param data The result data bundle.
+         * @param sessionId The session id.
+         * @param sessionStatus The session status, or null if unknown.
+         * @param itemId The item id.
+         * @param itemStatus The item status.
+         */
+        public void onItemStatusChanged(Bundle data,
+                String sessionId, MediaSessionStatus sessionStatus,
+                String itemId, MediaItemStatus itemStatus) {
+        }
+
+        /**
+         * Called when the status of a media session changes.
+         *
+         * @param data The result data bundle.
+         * @param sessionId The session id.
+         * @param sessionStatus The session status, or null if unknown.
+         */
+        public void onSessionStatusChanged(Bundle data,
+                String sessionId, MediaSessionStatus sessionStatus) {
+        }
+
+        /**
+         * Called when the session of the remote playback client changes.
+         *
+         * @param sessionId The new session id.
+         */
+        public void onSessionChanged(String sessionId) {
+        }
+    }
+
+    /**
+     * Base callback type for remote playback requests.
+     */
+    public static abstract class ActionCallback {
+        /**
+         * Called when a media control request fails.
+         *
+         * @param error A localized error message which may be shown to the user, or null
+         * if the cause of the error is unclear.
+         * @param code The error code, or {@link MediaControlIntent#ERROR_UNKNOWN} if unknown.
+         * @param data The error data bundle, or null if none.
+         */
+        public void onError(String error, int code, Bundle data) {
+        }
+    }
+
+    /**
+     * Callback for remote playback requests that operate on items.
+     */
+    public static abstract class ItemActionCallback extends ActionCallback {
+        /**
+         * Called when the request succeeds.
+         *
+         * @param data The result data bundle.
+         * @param sessionId The session id.
+         * @param sessionStatus The session status, or null if unknown.
+         * @param itemId The item id.
+         * @param itemStatus The item status.
+         */
+        public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+                String itemId, MediaItemStatus itemStatus) {
+        }
+    }
+
+    /**
+     * Callback for remote playback requests that operate on sessions.
+     */
+    public static abstract class SessionActionCallback extends ActionCallback {
+        /**
+         * Called when the request succeeds.
+         *
+         * @param data The result data bundle.
+         * @param sessionId The session id.
+         * @param sessionStatus The session status, or null if unknown.
+         */
+        public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+        }
+    }
+
+    /**
+     * A callback that will receive messages from media sessions.
+     */
+    public interface OnMessageReceivedListener {
+        /**
+         * Called when a message received.
+         *
+         * @param sessionId The session id.
+         * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
+         */
+        void onMessageReceived(String sessionId, Bundle message);
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
new file mode 100644
index 0000000..f5e1e61
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
@@ -0,0 +1,882 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.view.Display;
+
+import com.android.media.update.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Provides routes for built-in system destinations such as the local display
+ * and speaker.  On Jellybean and newer platform releases, queries the framework
+ * MediaRouter for framework-provided routes and registers non-framework-provided
+ * routes as user routes.
+ */
+abstract class SystemMediaRouteProvider extends MediaRouteProvider {
+    private static final String TAG = "SystemMediaRouteProvider";
+
+    public static final String PACKAGE_NAME = "android";
+    public static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
+
+    protected SystemMediaRouteProvider(Context context) {
+        super(context, new ProviderMetadata(new ComponentName(PACKAGE_NAME,
+                SystemMediaRouteProvider.class.getName())));
+    }
+
+    public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
+        if (Build.VERSION.SDK_INT >= 24) {
+            return new Api24Impl(context, syncCallback);
+        }
+        if (Build.VERSION.SDK_INT >= 18) {
+            return new JellybeanMr2Impl(context, syncCallback);
+        }
+        if (Build.VERSION.SDK_INT >= 17) {
+            return new JellybeanMr1Impl(context, syncCallback);
+        }
+        if (Build.VERSION.SDK_INT >= 16) {
+            return new JellybeanImpl(context, syncCallback);
+        }
+        return new LegacyImpl(context);
+    }
+
+    /**
+     * Called by the media router when a route is added to synchronize state with
+     * the framework media router.
+     */
+    public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
+    }
+
+    /**
+     * Called by the media router when a route is removed to synchronize state with
+     * the framework media router.
+     */
+    public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
+    }
+
+    /**
+     * Called by the media router when a route is changed to synchronize state with
+     * the framework media router.
+     */
+    public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
+    }
+
+    /**
+     * Called by the media router when a route is selected to synchronize state with
+     * the framework media router.
+     */
+    public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
+    }
+
+    /**
+     * Callbacks into the media router to synchronize state with the framework media router.
+     */
+    public interface SyncCallback {
+        void onSystemRouteSelectedByDescriptorId(String id);
+    }
+
+    protected Object getDefaultRoute() {
+        return null;
+    }
+
+    protected Object getSystemRoute(MediaRouter.RouteInfo route) {
+        return null;
+    }
+
+    /**
+     * Legacy implementation for platform versions prior to Jellybean.
+     */
+    static class LegacyImpl extends SystemMediaRouteProvider {
+        static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
+
+        private static final ArrayList<IntentFilter> CONTROL_FILTERS;
+        static {
+            IntentFilter f = new IntentFilter();
+            f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
+            f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+
+            CONTROL_FILTERS = new ArrayList<IntentFilter>();
+            CONTROL_FILTERS.add(f);
+        }
+
+        final AudioManager mAudioManager;
+        private final VolumeChangeReceiver mVolumeChangeReceiver;
+        int mLastReportedVolume = -1;
+
+        public LegacyImpl(Context context) {
+            super(context);
+            mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
+            mVolumeChangeReceiver = new VolumeChangeReceiver();
+
+            context.registerReceiver(mVolumeChangeReceiver,
+                    new IntentFilter(VolumeChangeReceiver.VOLUME_CHANGED_ACTION));
+            publishRoutes();
+        }
+
+        void publishRoutes() {
+            Resources r = getContext().getResources();
+            int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
+            mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
+            MediaRouteDescriptor defaultRoute = new MediaRouteDescriptor.Builder(
+                    DEFAULT_ROUTE_ID, r.getString(R.string.mr_system_route_name))
+                    .addControlFilters(CONTROL_FILTERS)
+                    .setPlaybackStream(PLAYBACK_STREAM)
+                    .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL)
+                    .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+                    .setVolumeMax(maxVolume)
+                    .setVolume(mLastReportedVolume)
+                    .build();
+
+            MediaRouteProviderDescriptor providerDescriptor =
+                    new MediaRouteProviderDescriptor.Builder()
+                    .addRoute(defaultRoute)
+                    .build();
+            setDescriptor(providerDescriptor);
+        }
+
+        @Override
+        public RouteController onCreateRouteController(String routeId) {
+            if (routeId.equals(DEFAULT_ROUTE_ID)) {
+                return new DefaultRouteController();
+            }
+            return null;
+        }
+
+        final class DefaultRouteController extends RouteController {
+            @Override
+            public void onSetVolume(int volume) {
+                mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
+                publishRoutes();
+            }
+
+            @Override
+            public void onUpdateVolume(int delta) {
+                int volume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
+                int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
+                int newVolume = Math.min(maxVolume, Math.max(0, volume + delta));
+                if (newVolume != volume) {
+                    mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
+                }
+                publishRoutes();
+            }
+        }
+
+        final class VolumeChangeReceiver extends BroadcastReceiver {
+            // These constants come from AudioManager.
+            public static final String VOLUME_CHANGED_ACTION =
+                    "android.media.VOLUME_CHANGED_ACTION";
+            public static final String EXTRA_VOLUME_STREAM_TYPE =
+                    "android.media.EXTRA_VOLUME_STREAM_TYPE";
+            public static final String EXTRA_VOLUME_STREAM_VALUE =
+                    "android.media.EXTRA_VOLUME_STREAM_VALUE";
+
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) {
+                    final int streamType = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1);
+                    if (streamType == PLAYBACK_STREAM) {
+                        final int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1);
+                        if (volume >= 0 && volume != mLastReportedVolume) {
+                            publishRoutes();
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Jellybean implementation.
+     */
+    // @@RequiresApi(16)
+    static class JellybeanImpl extends SystemMediaRouteProvider
+            implements MediaRouterJellybean.Callback, MediaRouterJellybean.VolumeCallback {
+        private static final ArrayList<IntentFilter> LIVE_AUDIO_CONTROL_FILTERS;
+        static {
+            IntentFilter f = new IntentFilter();
+            f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
+
+            LIVE_AUDIO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
+            LIVE_AUDIO_CONTROL_FILTERS.add(f);
+        }
+
+        private static final ArrayList<IntentFilter> LIVE_VIDEO_CONTROL_FILTERS;
+        static {
+            IntentFilter f = new IntentFilter();
+            f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+
+            LIVE_VIDEO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
+            LIVE_VIDEO_CONTROL_FILTERS.add(f);
+        }
+
+        private final SyncCallback mSyncCallback;
+
+        protected final Object mRouterObj;
+        protected final Object mCallbackObj;
+        protected final Object mVolumeCallbackObj;
+        protected final Object mUserRouteCategoryObj;
+        protected int mRouteTypes;
+        protected boolean mActiveScan;
+        protected boolean mCallbackRegistered;
+
+        // Maintains an association from framework routes to support library routes.
+        // Note that we cannot use the tag field for this because an application may
+        // have published its own user routes to the framework media router and already
+        // used the tag for its own purposes.
+        protected final ArrayList<SystemRouteRecord> mSystemRouteRecords =
+                new ArrayList<SystemRouteRecord>();
+
+        // Maintains an association from support library routes to framework routes.
+        protected final ArrayList<UserRouteRecord> mUserRouteRecords =
+                new ArrayList<UserRouteRecord>();
+
+        private MediaRouterJellybean.SelectRouteWorkaround mSelectRouteWorkaround;
+        private MediaRouterJellybean.GetDefaultRouteWorkaround mGetDefaultRouteWorkaround;
+
+        public JellybeanImpl(Context context, SyncCallback syncCallback) {
+            super(context);
+            mSyncCallback = syncCallback;
+            mRouterObj = MediaRouterJellybean.getMediaRouter(context);
+            mCallbackObj = createCallbackObj();
+            mVolumeCallbackObj = createVolumeCallbackObj();
+
+            Resources r = context.getResources();
+            mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
+                    mRouterObj, r.getString(R.string.mr_user_route_category_name), false);
+
+            updateSystemRoutes();
+        }
+
+        @Override
+        public RouteController onCreateRouteController(String routeId) {
+            int index = findSystemRouteRecordByDescriptorId(routeId);
+            if (index >= 0) {
+                SystemRouteRecord record = mSystemRouteRecords.get(index);
+                return new SystemRouteController(record.mRouteObj);
+            }
+            return null;
+        }
+
+        @Override
+        public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
+            int newRouteTypes = 0;
+            boolean newActiveScan = false;
+            if (request != null) {
+                final MediaRouteSelector selector = request.getSelector();
+                final List<String> categories = selector.getControlCategories();
+                final int count = categories.size();
+                for (int i = 0; i < count; i++) {
+                    String category = categories.get(i);
+                    if (category.equals(MediaControlIntent.CATEGORY_LIVE_AUDIO)) {
+                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO;
+                    } else if (category.equals(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
+                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO;
+                    } else {
+                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_USER;
+                    }
+                }
+                newActiveScan = request.isActiveScan();
+            }
+
+            if (mRouteTypes != newRouteTypes || mActiveScan != newActiveScan) {
+                mRouteTypes = newRouteTypes;
+                mActiveScan = newActiveScan;
+                updateSystemRoutes();
+            }
+        }
+
+        @Override
+        public void onRouteAdded(Object routeObj) {
+            if (addSystemRouteNoPublish(routeObj)) {
+                publishRoutes();
+            }
+        }
+
+        private void updateSystemRoutes() {
+            updateCallback();
+            boolean changed = false;
+            for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) {
+                changed |= addSystemRouteNoPublish(routeObj);
+            }
+            if (changed) {
+                publishRoutes();
+            }
+        }
+
+        private boolean addSystemRouteNoPublish(Object routeObj) {
+            if (getUserRouteRecord(routeObj) == null
+                    && findSystemRouteRecord(routeObj) < 0) {
+                String id = assignRouteId(routeObj);
+                SystemRouteRecord record = new SystemRouteRecord(routeObj, id);
+                updateSystemRouteDescriptor(record);
+                mSystemRouteRecords.add(record);
+                return true;
+            }
+            return false;
+        }
+
+        private String assignRouteId(Object routeObj) {
+            // TODO: The framework media router should supply a unique route id that
+            // we can use here.  For now we use a hash of the route name and take care
+            // to dedupe it.
+            boolean isDefault = (getDefaultRoute() == routeObj);
+            String id = isDefault ? DEFAULT_ROUTE_ID :
+                    String.format(Locale.US, "ROUTE_%08x", getRouteName(routeObj).hashCode());
+            if (findSystemRouteRecordByDescriptorId(id) < 0) {
+                return id;
+            }
+            for (int i = 2; ; i++) {
+                String newId = String.format(Locale.US, "%s_%d", id, i);
+                if (findSystemRouteRecordByDescriptorId(newId) < 0) {
+                    return newId;
+                }
+            }
+        }
+
+        @Override
+        public void onRouteRemoved(Object routeObj) {
+            if (getUserRouteRecord(routeObj) == null) {
+                int index = findSystemRouteRecord(routeObj);
+                if (index >= 0) {
+                    mSystemRouteRecords.remove(index);
+                    publishRoutes();
+                }
+            }
+        }
+
+        @Override
+        public void onRouteChanged(Object routeObj) {
+            if (getUserRouteRecord(routeObj) == null) {
+                int index = findSystemRouteRecord(routeObj);
+                if (index >= 0) {
+                    SystemRouteRecord record = mSystemRouteRecords.get(index);
+                    updateSystemRouteDescriptor(record);
+                    publishRoutes();
+                }
+            }
+        }
+
+        @Override
+        public void onRouteVolumeChanged(Object routeObj) {
+            if (getUserRouteRecord(routeObj) == null) {
+                int index = findSystemRouteRecord(routeObj);
+                if (index >= 0) {
+                    SystemRouteRecord record = mSystemRouteRecords.get(index);
+                    int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj);
+                    if (newVolume != record.mRouteDescriptor.getVolume()) {
+                        record.mRouteDescriptor =
+                                new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
+                                .setVolume(newVolume)
+                                .build();
+                        publishRoutes();
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onRouteSelected(int type, Object routeObj) {
+            if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj,
+                    MediaRouterJellybean.ALL_ROUTE_TYPES)) {
+                // The currently selected route has already changed so this callback
+                // is stale.  Drop it to prevent getting into sync loops.
+                return;
+            }
+
+            UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj);
+            if (userRouteRecord != null) {
+                userRouteRecord.mRoute.select();
+            } else {
+                // Select the route if it already exists in the compat media router.
+                // If not, we will select it instead when the route is added.
+                int index = findSystemRouteRecord(routeObj);
+                if (index >= 0) {
+                    SystemRouteRecord record = mSystemRouteRecords.get(index);
+                    mSyncCallback.onSystemRouteSelectedByDescriptorId(record.mRouteDescriptorId);
+                }
+            }
+        }
+
+        @Override
+        public void onRouteUnselected(int type, Object routeObj) {
+            // Nothing to do when a route is unselected.
+            // We only need to handle when a route is selected.
+        }
+
+        @Override
+        public void onRouteGrouped(Object routeObj, Object groupObj, int index) {
+            // Route grouping is deprecated and no longer supported.
+        }
+
+        @Override
+        public void onRouteUngrouped(Object routeObj, Object groupObj) {
+            // Route grouping is deprecated and no longer supported.
+        }
+
+        @Override
+        public void onVolumeSetRequest(Object routeObj, int volume) {
+            UserRouteRecord record = getUserRouteRecord(routeObj);
+            if (record != null) {
+                record.mRoute.requestSetVolume(volume);
+            }
+        }
+
+        @Override
+        public void onVolumeUpdateRequest(Object routeObj, int direction) {
+            UserRouteRecord record = getUserRouteRecord(routeObj);
+            if (record != null) {
+                record.mRoute.requestUpdateVolume(direction);
+            }
+        }
+
+        @Override
+        public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
+            if (route.getProviderInstance() != this) {
+                Object routeObj = MediaRouterJellybean.createUserRoute(
+                        mRouterObj, mUserRouteCategoryObj);
+                UserRouteRecord record = new UserRouteRecord(route, routeObj);
+                MediaRouterJellybean.RouteInfo.setTag(routeObj, record);
+                MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj);
+                updateUserRouteProperties(record);
+                mUserRouteRecords.add(record);
+                MediaRouterJellybean.addUserRoute(mRouterObj, routeObj);
+            } else {
+                // If the newly added route is the counterpart of the currently selected
+                // route in the framework media router then ensure it is selected in
+                // the compat media router.
+                Object routeObj = MediaRouterJellybean.getSelectedRoute(
+                        mRouterObj, MediaRouterJellybean.ALL_ROUTE_TYPES);
+                int index = findSystemRouteRecord(routeObj);
+                if (index >= 0) {
+                    SystemRouteRecord record = mSystemRouteRecords.get(index);
+                    if (record.mRouteDescriptorId.equals(route.getDescriptorId())) {
+                        route.select();
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
+            if (route.getProviderInstance() != this) {
+                int index = findUserRouteRecord(route);
+                if (index >= 0) {
+                    UserRouteRecord record = mUserRouteRecords.remove(index);
+                    MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null);
+                    MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null);
+                    MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj);
+                }
+            }
+        }
+
+        @Override
+        public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
+            if (route.getProviderInstance() != this) {
+                int index = findUserRouteRecord(route);
+                if (index >= 0) {
+                    UserRouteRecord record = mUserRouteRecords.get(index);
+                    updateUserRouteProperties(record);
+                }
+            }
+        }
+
+        @Override
+        public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
+            if (!route.isSelected()) {
+                // The currently selected route has already changed so this callback
+                // is stale.  Drop it to prevent getting into sync loops.
+                return;
+            }
+
+            if (route.getProviderInstance() != this) {
+                int index = findUserRouteRecord(route);
+                if (index >= 0) {
+                    UserRouteRecord record = mUserRouteRecords.get(index);
+                    selectRoute(record.mRouteObj);
+                }
+            } else {
+                int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
+                if (index >= 0) {
+                    SystemRouteRecord record = mSystemRouteRecords.get(index);
+                    selectRoute(record.mRouteObj);
+                }
+            }
+        }
+
+        protected void publishRoutes() {
+            MediaRouteProviderDescriptor.Builder builder =
+                    new MediaRouteProviderDescriptor.Builder();
+            int count = mSystemRouteRecords.size();
+            for (int i = 0; i < count; i++) {
+                builder.addRoute(mSystemRouteRecords.get(i).mRouteDescriptor);
+            }
+
+            setDescriptor(builder.build());
+        }
+
+        protected int findSystemRouteRecord(Object routeObj) {
+            final int count = mSystemRouteRecords.size();
+            for (int i = 0; i < count; i++) {
+                if (mSystemRouteRecords.get(i).mRouteObj == routeObj) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        protected int findSystemRouteRecordByDescriptorId(String id) {
+            final int count = mSystemRouteRecords.size();
+            for (int i = 0; i < count; i++) {
+                if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        protected int findUserRouteRecord(MediaRouter.RouteInfo route) {
+            final int count = mUserRouteRecords.size();
+            for (int i = 0; i < count; i++) {
+                if (mUserRouteRecords.get(i).mRoute == route) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        protected UserRouteRecord getUserRouteRecord(Object routeObj) {
+            Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj);
+            return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null;
+        }
+
+        protected void updateSystemRouteDescriptor(SystemRouteRecord record) {
+            // We must always recreate the route descriptor when making any changes
+            // because they are intended to be immutable once published.
+            MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder(
+                    record.mRouteDescriptorId, getRouteName(record.mRouteObj));
+            onBuildSystemRouteDescriptor(record, builder);
+            record.mRouteDescriptor = builder.build();
+        }
+
+        protected String getRouteName(Object routeObj) {
+            // Routes should not have null names but it may happen for badly configured
+            // user routes.  We tolerate this by using an empty name string here but
+            // such unnamed routes will be discarded by the media router upstream
+            // (with a log message so we can track down the problem).
+            CharSequence name = MediaRouterJellybean.RouteInfo.getName(routeObj, getContext());
+            return name != null ? name.toString() : "";
+        }
+
+        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+                MediaRouteDescriptor.Builder builder) {
+            int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes(
+                    record.mRouteObj);
+            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) {
+                builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS);
+            }
+            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
+                builder.addControlFilters(LIVE_VIDEO_CONTROL_FILTERS);
+            }
+
+            builder.setPlaybackType(
+                    MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj));
+            builder.setPlaybackStream(
+                    MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj));
+            builder.setVolume(
+                    MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj));
+            builder.setVolumeMax(
+                    MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj));
+            builder.setVolumeHandling(
+                    MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj));
+        }
+
+        protected void updateUserRouteProperties(UserRouteRecord record) {
+            MediaRouterJellybean.UserRouteInfo.setName(
+                    record.mRouteObj, record.mRoute.getName());
+            MediaRouterJellybean.UserRouteInfo.setPlaybackType(
+                    record.mRouteObj, record.mRoute.getPlaybackType());
+            MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
+                    record.mRouteObj, record.mRoute.getPlaybackStream());
+            MediaRouterJellybean.UserRouteInfo.setVolume(
+                    record.mRouteObj, record.mRoute.getVolume());
+            MediaRouterJellybean.UserRouteInfo.setVolumeMax(
+                    record.mRouteObj, record.mRoute.getVolumeMax());
+            MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
+                    record.mRouteObj, record.mRoute.getVolumeHandling());
+        }
+
+        protected void updateCallback() {
+            if (mCallbackRegistered) {
+                mCallbackRegistered = false;
+                MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
+            }
+
+            if (mRouteTypes != 0) {
+                mCallbackRegistered = true;
+                MediaRouterJellybean.addCallback(mRouterObj, mRouteTypes, mCallbackObj);
+            }
+        }
+
+        protected Object createCallbackObj() {
+            return MediaRouterJellybean.createCallback(this);
+        }
+
+        protected Object createVolumeCallbackObj() {
+            return MediaRouterJellybean.createVolumeCallback(this);
+        }
+
+        protected void selectRoute(Object routeObj) {
+            if (mSelectRouteWorkaround == null) {
+                mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround();
+            }
+            mSelectRouteWorkaround.selectRoute(mRouterObj,
+                    MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
+        }
+
+        @Override
+        protected Object getDefaultRoute() {
+            if (mGetDefaultRouteWorkaround == null) {
+                mGetDefaultRouteWorkaround = new MediaRouterJellybean.GetDefaultRouteWorkaround();
+            }
+            return mGetDefaultRouteWorkaround.getDefaultRoute(mRouterObj);
+        }
+
+        @Override
+        protected Object getSystemRoute(MediaRouter.RouteInfo route) {
+            if (route == null) {
+                return null;
+            }
+            int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
+            if (index >= 0) {
+                return mSystemRouteRecords.get(index).mRouteObj;
+            }
+            return null;
+        }
+
+        /**
+         * Represents a route that is provided by the framework media router
+         * and published by this route provider to the support library media router.
+         */
+        protected static final class SystemRouteRecord {
+            public final Object mRouteObj;
+            public final String mRouteDescriptorId;
+            public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation
+
+            public SystemRouteRecord(Object routeObj, String id) {
+                mRouteObj = routeObj;
+                mRouteDescriptorId = id;
+            }
+        }
+
+        /**
+         * Represents a route that is provided by the support library media router
+         * and published by this route provider to the framework media router.
+         */
+        protected static final class UserRouteRecord {
+            public final MediaRouter.RouteInfo mRoute;
+            public final Object mRouteObj;
+
+            public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) {
+                mRoute = route;
+                mRouteObj = routeObj;
+            }
+        }
+
+        protected static final class SystemRouteController extends RouteController {
+            private final Object mRouteObj;
+
+            public SystemRouteController(Object routeObj) {
+                mRouteObj = routeObj;
+            }
+
+            @Override
+            public void onSetVolume(int volume) {
+                MediaRouterJellybean.RouteInfo.requestSetVolume(mRouteObj, volume);
+            }
+
+            @Override
+            public void onUpdateVolume(int delta) {
+                MediaRouterJellybean.RouteInfo.requestUpdateVolume(mRouteObj, delta);
+            }
+        }
+    }
+
+    /**
+     * Jellybean MR1 implementation.
+     */
+    // @@RequiresApi(17)
+    private static class JellybeanMr1Impl extends JellybeanImpl
+            implements MediaRouterJellybeanMr1.Callback {
+        private MediaRouterJellybeanMr1.ActiveScanWorkaround mActiveScanWorkaround;
+        private MediaRouterJellybeanMr1.IsConnectingWorkaround mIsConnectingWorkaround;
+
+        public JellybeanMr1Impl(Context context, SyncCallback syncCallback) {
+            super(context, syncCallback);
+        }
+
+        @Override
+        public void onRoutePresentationDisplayChanged(Object routeObj) {
+            int index = findSystemRouteRecord(routeObj);
+            if (index >= 0) {
+                SystemRouteRecord record = mSystemRouteRecords.get(index);
+                Display newPresentationDisplay =
+                        MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj);
+                int newPresentationDisplayId = (newPresentationDisplay != null
+                        ? newPresentationDisplay.getDisplayId() : -1);
+                if (newPresentationDisplayId
+                        != record.mRouteDescriptor.getPresentationDisplayId()) {
+                    record.mRouteDescriptor =
+                            new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
+                            .setPresentationDisplayId(newPresentationDisplayId)
+                            .build();
+                    publishRoutes();
+                }
+            }
+        }
+
+        @Override
+        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+                MediaRouteDescriptor.Builder builder) {
+            super.onBuildSystemRouteDescriptor(record, builder);
+
+            if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) {
+                builder.setEnabled(false);
+            }
+
+            if (isConnecting(record)) {
+                builder.setConnecting(true);
+            }
+
+            Display presentationDisplay =
+                    MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj);
+            if (presentationDisplay != null) {
+                builder.setPresentationDisplayId(presentationDisplay.getDisplayId());
+            }
+        }
+
+        @Override
+        protected void updateCallback() {
+            super.updateCallback();
+
+            if (mActiveScanWorkaround == null) {
+                mActiveScanWorkaround = new MediaRouterJellybeanMr1.ActiveScanWorkaround(
+                        getContext(), getHandler());
+            }
+            mActiveScanWorkaround.setActiveScanRouteTypes(mActiveScan ? mRouteTypes : 0);
+        }
+
+        @Override
+        protected Object createCallbackObj() {
+            return MediaRouterJellybeanMr1.createCallback(this);
+        }
+
+        protected boolean isConnecting(SystemRouteRecord record) {
+            if (mIsConnectingWorkaround == null) {
+                mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround();
+            }
+            return mIsConnectingWorkaround.isConnecting(record.mRouteObj);
+        }
+    }
+
+    /**
+     * Jellybean MR2 implementation.
+     */
+    // @@RequiresApi(18)
+    private static class JellybeanMr2Impl extends JellybeanMr1Impl {
+        public JellybeanMr2Impl(Context context, SyncCallback syncCallback) {
+            super(context, syncCallback);
+        }
+
+        @Override
+        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+                MediaRouteDescriptor.Builder builder) {
+            super.onBuildSystemRouteDescriptor(record, builder);
+
+            CharSequence description =
+                    MediaRouterJellybeanMr2.RouteInfo.getDescription(record.mRouteObj);
+            if (description != null) {
+                builder.setDescription(description.toString());
+            }
+        }
+
+        @Override
+        protected void selectRoute(Object routeObj) {
+            MediaRouterJellybean.selectRoute(mRouterObj,
+                    MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
+        }
+
+        @Override
+        protected Object getDefaultRoute() {
+            return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj);
+        }
+
+        @Override
+        protected void updateUserRouteProperties(UserRouteRecord record) {
+            super.updateUserRouteProperties(record);
+
+            MediaRouterJellybeanMr2.UserRouteInfo.setDescription(
+                    record.mRouteObj, record.mRoute.getDescription());
+        }
+
+        @Override
+        protected void updateCallback() {
+            if (mCallbackRegistered) {
+                MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
+            }
+
+            mCallbackRegistered = true;
+            MediaRouterJellybeanMr2.addCallback(mRouterObj, mRouteTypes, mCallbackObj,
+                    MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS
+                    | (mActiveScan ? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN : 0));
+        }
+
+        @Override
+        protected boolean isConnecting(SystemRouteRecord record) {
+            return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
+        }
+    }
+
+    /**
+     * Api24 implementation.
+     */
+    // @@RequiresApi(24)
+    private static class Api24Impl extends JellybeanMr2Impl {
+        public Api24Impl(Context context, SyncCallback syncCallback) {
+            super(context, syncCallback);
+        }
+
+        @Override
+        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+                                                    MediaRouteDescriptor.Builder builder) {
+            super.onBuildSystemRouteDescriptor(record, builder);
+
+            builder.setDeviceType(MediaRouterApi24.RouteInfo.getDeviceType(record.mRouteObj));
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/package.html b/packages/MediaComponents/src/com/android/support/mediarouter/media/package.html
new file mode 100644
index 0000000..be2aaf2
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/package.html
@@ -0,0 +1,9 @@
+<html>
+
+<body>
+
+<p>Contains APIs that control the routing of media channels and streams from the current device
+  to external speakers and destination devices.</p>
+
+</body>
+</html>
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
new file mode 100644
index 0000000..8053245e
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.widget;
+
+import android.media.session.MediaController;
+import android.media.update.MediaControlView2Provider;
+import android.media.update.ViewProvider;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.MediaControlView2;
+
+public class MediaControlView2Impl implements MediaControlView2Provider {
+    private final MediaControlView2 mInstance;
+    private final ViewProvider mSuperProvider;
+
+    static final String ACTION_SHOW_SUBTITLE = "showSubtitle";
+    static final String ACTION_HIDE_SUBTITLE = "hideSubtitle";
+
+    public MediaControlView2Impl(MediaControlView2 instance, ViewProvider superProvider) {
+        mInstance = instance;
+        mSuperProvider = superProvider;
+
+        // TODO: Implement
+    }
+
+    @Override
+    public void setController_impl(MediaController controller) {
+        // TODO: Implement
+    }
+
+    @Override
+    public void show_impl() {
+        // TODO: Implement
+    }
+
+    @Override
+    public void show_impl(int timeout) {
+        // TODO: Implement
+    }
+
+    @Override
+    public boolean isShowing_impl() {
+        // TODO: Implement
+        return false;
+    }
+
+    @Override
+    public void hide_impl() {
+        // TODO: Implement
+    }
+
+    @Override
+    public void showCCButton_impl() {
+        // TODO: Implement
+    }
+
+    @Override
+    public boolean isPlaying_impl() {
+        // TODO: Implement
+        return false;
+    }
+
+    @Override
+    public int getCurrentPosition_impl() {
+        // TODO: Implement
+        return 0;
+    }
+
+    @Override
+    public int getBufferPercentage_impl() {
+        // TODO: Implement
+        return 0;
+    }
+
+    @Override
+    public boolean canPause_impl() {
+        // TODO: Implement
+        return false;
+    }
+
+    @Override
+    public boolean canSeekBackward_impl() {
+        // TODO: Implement
+        return false;
+    }
+
+    @Override
+    public boolean canSeekForward_impl() {
+        // TODO: Implement
+        return false;
+    }
+
+    @Override
+    public void showSubtitle_impl() {
+        // TODO: Implement
+    }
+
+    @Override
+    public void hideSubtitle_impl() {
+        // TODO: Implement
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName_impl() {
+        // TODO: Implement
+        return MediaControlView2.class.getName();
+    }
+
+    @Override
+    public boolean onTouchEvent_impl(MotionEvent ev) {
+        // TODO: Implement
+        return mSuperProvider.onTouchEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onTrackballEvent_impl(MotionEvent ev) {
+        // TODO: Implement
+        return mSuperProvider.onTrackballEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
+        // TODO: Implement
+        return mSuperProvider.onKeyDown_impl(keyCode, event);
+    }
+
+    @Override
+    public void onFinishInflate_impl() {
+        mSuperProvider.onFinishInflate_impl();
+        // TODO: Implement
+    }
+
+    @Override
+    public boolean dispatchKeyEvent_impl(KeyEvent event) {
+        // TODO: Implement
+        return mSuperProvider.dispatchKeyEvent_impl(event);
+    }
+
+    @Override
+    public void setEnabled_impl(boolean enabled) {
+        mSuperProvider.setEnabled_impl(enabled);
+        // TODO: Implement
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/SubtitleView.java b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
new file mode 100644
index 0000000..9071967
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
@@ -0,0 +1,142 @@
+/*
+ * 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.widget;
+
+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;
+
+class SubtitleView extends FrameLayout implements Anchor {
+    private static final String TAG = "SubtitleView";
+
+    private RenderingWidget mSubtitleWidget;
+    private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
+
+    public SubtitleView(Context context) {
+        this(context, null);
+    }
+
+    public SubtitleView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SubtitleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SubtitleView(
+            Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void setSubtitleWidget(RenderingWidget subtitleWidget) {
+        if (mSubtitleWidget == subtitleWidget) {
+            return;
+        }
+
+        final boolean attachedToWindow = isAttachedToWindow();
+        if (mSubtitleWidget != null) {
+            if (attachedToWindow) {
+                mSubtitleWidget.onDetachedFromWindow();
+            }
+
+            mSubtitleWidget.setOnChangedListener(null);
+        }
+        mSubtitleWidget = subtitleWidget;
+
+        if (subtitleWidget != null) {
+            if (mSubtitlesChangedListener == null) {
+                mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
+                    @Override
+                    public void onChanged(RenderingWidget renderingWidget) {
+                        invalidate();
+                    }
+                };
+            }
+
+            setWillNotDraw(false);
+            subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
+
+            if (attachedToWindow) {
+                subtitleWidget.onAttachedToWindow();
+                requestLayout();
+            }
+        } else {
+            setWillNotDraw(true);
+        }
+
+        invalidate();
+    }
+
+    @Override
+    public Looper getSubtitleLooper() {
+        return Looper.getMainLooper();
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (mSubtitleWidget != null) {
+            mSubtitleWidget.onAttachedToWindow();
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mSubtitleWidget != null) {
+            mSubtitleWidget.onDetachedFromWindow();
+        }
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (mSubtitleWidget != null) {
+            final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+            final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+            mSubtitleWidget.setSize(width, height);
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (mSubtitleWidget != null) {
+            final int saveCount = canvas.save();
+            canvas.translate(getPaddingLeft(), getPaddingTop());
+            mSubtitleWidget.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return SubtitleView.class.getName();
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
new file mode 100644
index 0000000..800be8e
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
@@ -0,0 +1,233 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import static android.widget.VideoView2.VIEW_TYPE_SURFACEVIEW;
+
+class VideoSurfaceView extends SurfaceView implements VideoViewInterface, SurfaceHolder.Callback {
+    private static final String TAG = "VideoSurfaceView";
+    private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+    private SurfaceHolder mSurfaceHolder = null;
+    private SurfaceListener mSurfaceListener = null;
+    private MediaPlayer mMediaPlayer;
+    // A flag to indicate taking over other view should be proceed.
+    private boolean mIsTakingOverOldView;
+    private VideoViewInterface mOldView;
+
+
+    public VideoSurfaceView(Context context) {
+        this(context, null);
+    }
+
+    public VideoSurfaceView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr,
+                            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        getHolder().addCallback(this);
+    }
+
+    ////////////////////////////////////////////////////
+    // implements VideoViewInterface
+    ////////////////////////////////////////////////////
+
+    @Override
+    public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+        Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceHolder: " + mSurfaceHolder);
+        if (mp == null || !hasAvailableSurface()) {
+            return false;
+        }
+        mp.setDisplay(mSurfaceHolder);
+        return true;
+    }
+
+    @Override
+    public void setSurfaceListener(SurfaceListener l) {
+        mSurfaceListener = l;
+    }
+
+    @Override
+    public int getViewType() {
+        return VIEW_TYPE_SURFACEVIEW;
+    }
+
+    @Override
+    public void setMediaPlayer(MediaPlayer mp) {
+        mMediaPlayer = mp;
+        if (mIsTakingOverOldView) {
+            takeOver(mOldView);
+        }
+    }
+
+    @Override
+    public void takeOver(@NonNull VideoViewInterface oldView) {
+        if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
+            ((View) oldView).setVisibility(GONE);
+            mIsTakingOverOldView = false;
+            mOldView = null;
+            if (mSurfaceListener != null) {
+                mSurfaceListener.onSurfaceTakeOverDone(this);
+            }
+        } else {
+            mIsTakingOverOldView = true;
+            mOldView = oldView;
+        }
+    }
+
+    @Override
+    public boolean hasAvailableSurface() {
+        return (mSurfaceHolder != null && mSurfaceHolder.getSurface() != null);
+    }
+
+    ////////////////////////////////////////////////////
+    // implements SurfaceHolder.Callback
+    ////////////////////////////////////////////////////
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.d(TAG, "surfaceCreated: mSurfaceHolder: " + mSurfaceHolder + ", new holder: " + holder);
+        mSurfaceHolder = holder;
+        if (mIsTakingOverOldView) {
+            takeOver(mOldView);
+        } else {
+            assignSurfaceToMediaPlayer(mMediaPlayer);
+        }
+
+        if (mSurfaceListener != null) {
+            Rect rect = mSurfaceHolder.getSurfaceFrame();
+            mSurfaceListener.onSurfaceCreated(this, rect.width(), rect.height());
+        }
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceChanged(this, width, height);
+        }
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        // After we return from this we can't use the surface any more
+        mSurfaceHolder = null;
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceDestroyed(this);
+        }
+    }
+
+    // TODO: Investigate the way to move onMeasure() code into FrameLayout.
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
+        int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
+        if (DEBUG) {
+            Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+                    + MeasureSpec.toString(heightMeasureSpec) + ")");
+            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+            Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
+            Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
+        }
+
+        int width = getDefaultSize(videoWidth, widthMeasureSpec);
+        int height = getDefaultSize(videoHeight, heightMeasureSpec);
+
+        if (videoWidth > 0 && videoHeight > 0) {
+            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+            if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+                // the size is fixed
+                width = widthSpecSize;
+                height = heightSpecSize;
+
+                // for compatibility, we adjust size based on aspect ratio
+                if (videoWidth * height < width * videoHeight) {
+                    if (DEBUG) {
+                        Log.d(TAG, "image too wide, correcting");
+                    }
+                    width = height * videoWidth / videoHeight;
+                } else if (videoWidth * height > width * videoHeight) {
+                    if (DEBUG) {
+                        Log.d(TAG, "image too tall, correcting");
+                    }
+                    height = width * videoHeight / videoWidth;
+                }
+            } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+                // only the width is fixed, adjust the height to match aspect ratio if possible
+                width = widthSpecSize;
+                height = width * videoHeight / videoWidth;
+                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    height = heightSpecSize;
+                }
+            } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+                // only the height is fixed, adjust the width to match aspect ratio if possible
+                height = heightSpecSize;
+                width = height * videoWidth / videoHeight;
+                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    width = widthSpecSize;
+                }
+            } else {
+                // neither the width nor the height are fixed, try to use actual video size
+                width = videoWidth;
+                height = videoHeight;
+                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // too tall, decrease both width and height
+                    height = heightSpecSize;
+                    width = height * videoWidth / videoHeight;
+                }
+                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // too wide, decrease both width and height
+                    width = widthSpecSize;
+                    height = width * videoHeight / videoWidth;
+                }
+            }
+        } else {
+            // no size yet, just adopt the given spec sizes
+        }
+        setMeasuredDimension(width, height);
+        if (DEBUG) {
+            Log.i(TAG, "end of onMeasure()");
+            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ViewType: SurfaceView / Visibility: " + getVisibility()
+                + " / surfaceHolder: " + mSurfaceHolder;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
new file mode 100644
index 0000000..f240301
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
@@ -0,0 +1,245 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+
+import static android.widget.VideoView2.VIEW_TYPE_TEXTUREVIEW;
+
+@RequiresApi(26)
+class VideoTextureView extends TextureView
+        implements VideoViewInterface, TextureView.SurfaceTextureListener {
+    private static final String TAG = "VideoTextureView";
+    private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+
+    private SurfaceTexture mSurfaceTexture;
+    private Surface mSurface;
+    private SurfaceListener mSurfaceListener;
+    private MediaPlayer mMediaPlayer;
+    // A flag to indicate taking over other view should be proceed.
+    private boolean mIsTakingOverOldView;
+    private VideoViewInterface mOldView;
+
+    public VideoTextureView(Context context) {
+        this(context, null);
+    }
+
+    public VideoTextureView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VideoTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public VideoTextureView(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        setSurfaceTextureListener(this);
+    }
+
+    ////////////////////////////////////////////////////
+    // implements VideoViewInterface
+    ////////////////////////////////////////////////////
+
+    @Override
+    public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+        Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceTexture: " + mSurfaceTexture);
+        if (mp == null || !hasAvailableSurface()) {
+            // Surface is not ready.
+            return false;
+        }
+        mp.setSurface(mSurface);
+        return true;
+    }
+
+    @Override
+    public void setSurfaceListener(SurfaceListener l) {
+        mSurfaceListener = l;
+    }
+
+    @Override
+    public int getViewType() {
+        return VIEW_TYPE_TEXTUREVIEW;
+    }
+
+    @Override
+    public void setMediaPlayer(MediaPlayer mp) {
+        mMediaPlayer = mp;
+        if (mIsTakingOverOldView) {
+            takeOver(mOldView);
+        }
+    }
+
+    @Override
+    public void takeOver(@NonNull VideoViewInterface oldView) {
+        if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
+            ((View) oldView).setVisibility(GONE);
+            mIsTakingOverOldView = false;
+            mOldView = null;
+            if (mSurfaceListener != null) {
+                mSurfaceListener.onSurfaceTakeOverDone(this);
+            }
+        } else {
+            mIsTakingOverOldView = true;
+            mOldView = oldView;
+        }
+    }
+
+    @Override
+    public boolean hasAvailableSurface() {
+        return (mSurfaceTexture != null && !mSurfaceTexture.isReleased() && mSurface != null);
+    }
+
+    ////////////////////////////////////////////////////
+    // implements TextureView.SurfaceTextureListener
+    ////////////////////////////////////////////////////
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+        Log.d(TAG, "onSurfaceTextureAvailable: mSurfaceTexture: " + mSurfaceTexture
+                + ", new surface: " + surfaceTexture);
+        mSurfaceTexture = surfaceTexture;
+        mSurface = new Surface(mSurfaceTexture);
+        if (mIsTakingOverOldView) {
+            takeOver(mOldView);
+        } else {
+            assignSurfaceToMediaPlayer(mMediaPlayer);
+        }
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceCreated(this, width, height);
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceChanged(this, width, height);
+        }
+        // requestLayout();  // TODO: figure out if it should be called here?
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // no-op
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceDestroyed(this);
+        }
+        mSurfaceTexture = null;
+        mSurface = null;
+        return true;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
+        int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
+        if (DEBUG) {
+            Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+                    + MeasureSpec.toString(heightMeasureSpec) + ")");
+            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+            Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
+            Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
+        }
+
+        int width = getDefaultSize(videoWidth, widthMeasureSpec);
+        int height = getDefaultSize(videoHeight, heightMeasureSpec);
+
+        if (videoWidth > 0 && videoHeight > 0) {
+            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+            if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+                // the size is fixed
+                width = widthSpecSize;
+                height = heightSpecSize;
+
+                // for compatibility, we adjust size based on aspect ratio
+                if (videoWidth * height  < width * videoHeight) {
+                    if (DEBUG) {
+                        Log.d(TAG, "image too wide, correcting");
+                    }
+                    width = height * videoWidth / videoHeight;
+                } else if (videoWidth * height  > width * videoHeight) {
+                    if (DEBUG) {
+                        Log.d(TAG, "image too tall, correcting");
+                    }
+                    height = width * videoHeight / videoWidth;
+                }
+            } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+                // only the width is fixed, adjust the height to match aspect ratio if possible
+                width = widthSpecSize;
+                height = width * videoHeight / videoWidth;
+                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    height = heightSpecSize;
+                }
+            } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+                // only the height is fixed, adjust the width to match aspect ratio if possible
+                height = heightSpecSize;
+                width = height * videoWidth / videoHeight;
+                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    width = widthSpecSize;
+                }
+            } else {
+                // neither the width nor the height are fixed, try to use actual video size
+                width = videoWidth;
+                height = videoHeight;
+                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // too tall, decrease both width and height
+                    height = heightSpecSize;
+                    width = height * videoWidth / videoHeight;
+                }
+                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // too wide, decrease both width and height
+                    width = widthSpecSize;
+                    height = width * videoHeight / videoWidth;
+                }
+            }
+        } else {
+            // no size yet, just adopt the given spec sizes
+        }
+        setMeasuredDimension(width, height);
+        if (DEBUG) {
+            Log.i(TAG, "end of onMeasure()");
+            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ViewType: TextureView / Visibility: " + getVisibility()
+                + " / surfaceTexture: " + mSurfaceTexture;
+
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
new file mode 100644
index 0000000..19a41de
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -0,0 +1,987 @@
+/*
+ * 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.widget;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.MediaPlayer;
+import android.media.Cea708CaptionRenderer;
+import android.media.ClosedCaptionRenderer;
+import android.media.Metadata;
+import android.media.PlaybackParams;
+import android.media.SubtitleController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.media.TtmlRenderer;
+import android.media.WebVttRenderer;
+import android.media.update.VideoView2Provider;
+import android.media.update.ViewProvider;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.MediaControlView2;
+import android.widget.VideoView2;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
+public class VideoView2Impl implements VideoView2Provider, VideoViewInterface.SurfaceListener {
+    private static final String TAG = "VideoView2";
+    private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+
+    private final VideoView2 mInstance;
+    private final ViewProvider mSuperProvider;
+
+    private static final int STATE_ERROR = -1;
+    private static final int STATE_IDLE = 0;
+    private static final int STATE_PREPARING = 1;
+    private static final int STATE_PREPARED = 2;
+    private static final int STATE_PLAYING = 3;
+    private static final int STATE_PAUSED = 4;
+    private static final int STATE_PLAYBACK_COMPLETED = 5;
+
+    private final AudioManager mAudioManager;
+    private AudioAttributes mAudioAttributes;
+    private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
+    private int mAudioSession;
+
+    private VideoView2.OnPreparedListener mOnPreparedListener;
+    private VideoView2.OnCompletionListener mOnCompletionListener;
+    private VideoView2.OnErrorListener mOnErrorListener;
+    private VideoView2.OnInfoListener mOnInfoListener;
+    private VideoView2.OnViewTypeChangedListener mOnViewTypeChangedListener;
+
+    private VideoViewInterface mCurrentView;
+    private VideoTextureView mTextureView;
+    private VideoSurfaceView mSurfaceView;
+
+    private MediaPlayer mMediaPlayer;
+    private MediaControlView2 mMediaControlView;
+    private MediaSession mMediaSession;
+
+    private PlaybackState.Builder mStateBuilder;
+    private int mTargetState = STATE_IDLE;
+    private int mCurrentState = STATE_IDLE;
+    private int mCurrentBufferPercentage;
+    private int mSeekWhenPrepared;  // recording the seek position while preparing
+
+    private int mSurfaceWidth;
+    private int mSurfaceHeight;
+    private int mVideoWidth;
+    private int mVideoHeight;
+
+    private boolean mCCEnabled;
+    private int mSelectedTrackIndex;
+
+    private SubtitleView mSubtitleView;
+    private float mSpeed;
+    // 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.
+
+    public VideoView2Impl(
+            VideoView2 instance, ViewProvider superProvider,
+            @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        mInstance = instance;
+        mSuperProvider = superProvider;
+
+        mVideoWidth = 0;
+        mVideoHeight = 0;
+        mSurfaceWidth = 0;
+        mSurfaceHeight = 0;
+        mSpeed = 1.0f;
+        mFallbackSpeed = mSpeed;
+
+        mAudioManager = (AudioManager) mInstance.getContext()
+                .getSystemService(Context.AUDIO_SERVICE);
+        mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
+                .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
+        mInstance.setFocusable(true);
+        mInstance.setFocusableInTouchMode(true);
+        mInstance.requestFocus();
+
+        // TODO: try to keep a single child at a time rather than always having both.
+        mTextureView = new VideoTextureView(mInstance.getContext());
+        mSurfaceView = new VideoSurfaceView(mInstance.getContext());
+        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.WRAP_CONTENT);
+        mTextureView.setLayoutParams(params);
+        mSurfaceView.setLayoutParams(params);
+        mTextureView.setSurfaceListener(this);
+        mSurfaceView.setSurfaceListener(this);
+
+        // TODO: Choose TextureView when SurfaceView cannot be created.
+        // Choose surface view by default
+        mTextureView.setVisibility(View.GONE);
+        mSurfaceView.setVisibility(View.VISIBLE);
+        mInstance.addView(mTextureView);
+        mInstance.addView(mSurfaceView);
+        mCurrentView = mSurfaceView;
+
+        LayoutParams subtitleParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT);
+        mSubtitleView = new SubtitleView(mInstance.getContext());
+        mSubtitleView.setLayoutParams(subtitleParams);
+        mSubtitleView.setBackgroundColor(0);
+        mInstance.addView(mSubtitleView);
+
+        // Create MediaSession
+        mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
+        mMediaSession.setCallback(new MediaSessionCallback());
+
+        // TODO: Need a common namespace for attributes those are defined in updatable library.
+        boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue(
+                "http://schemas.android.com/apk/com.android.media.api_provider",
+                "enableControlView", true);
+        if (enableControlView) {
+            setMediaControlView2_impl(new MediaControlView2(mInstance.getContext()));
+        }
+    }
+
+    @Override
+    public void setMediaControlView2_impl(MediaControlView2 mediaControlView) {
+        mMediaControlView = mediaControlView;
+
+        // TODO: change this so that the CC button appears only where there is a subtitle track.
+        mMediaControlView.showCCButton();
+
+        // Get MediaController from MediaSession and set it inside MediaControlView2
+        mMediaControlView.setController(mMediaSession.getController());
+
+        LayoutParams params =
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+        mInstance.addView(mMediaControlView, params);
+    }
+
+    @Override
+    public MediaControlView2 getMediaControlView2_impl() {
+        return mMediaControlView;
+    }
+
+    @Override
+    public void start_impl() {
+        if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+            applySpeed();
+            mMediaPlayer.start();
+            mCurrentState = STATE_PLAYING;
+            updatePlaybackState();
+        }
+        mTargetState = STATE_PLAYING;
+        if (DEBUG) {
+            Log.d(TAG, "start(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState);
+        }
+    }
+
+    @Override
+    public void pause_impl() {
+        if (isInPlaybackState()) {
+            if (mMediaPlayer.isPlaying()) {
+                mMediaPlayer.pause();
+                mCurrentState = STATE_PAUSED;
+                updatePlaybackState();
+            }
+        }
+        mTargetState = STATE_PAUSED;
+        if (DEBUG) {
+            Log.d(TAG, "pause(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState);
+        }
+    }
+
+    @Override
+    public int getDuration_impl() {
+        if (isInPlaybackState()) {
+            return mMediaPlayer.getDuration();
+        }
+        return -1;
+    }
+
+    @Override
+    public int getCurrentPosition_impl() {
+        if (isInPlaybackState()) {
+            return mMediaPlayer.getCurrentPosition();
+        }
+        return 0;
+    }
+
+    @Override
+    public void seekTo_impl(int msec) {
+        if (isInPlaybackState()) {
+            mMediaPlayer.seekTo(msec);
+            mSeekWhenPrepared = 0;
+            updatePlaybackState();
+        } else {
+            mSeekWhenPrepared = msec;
+        }
+    }
+
+    @Override
+    public boolean isPlaying_impl() {
+        return (isInPlaybackState()) && mMediaPlayer.isPlaying();
+    }
+
+    @Override
+    public int getBufferPercentage_impl() {
+        return mCurrentBufferPercentage;
+    }
+
+    @Override
+    public int getAudioSessionId_impl() {
+        if (mAudioSession == 0) {
+            MediaPlayer foo = new MediaPlayer();
+            mAudioSession = foo.getAudioSessionId();
+            foo.release();
+        }
+        return mAudioSession;
+    }
+
+    @Override
+    public void showSubtitle_impl() {
+        // Retrieve all tracks that belong to the current video.
+        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+
+        List<Integer> subtitleTrackIndices = new ArrayList<>();
+        for (int i = 0; i < trackInfos.length; ++i) {
+            int trackType = trackInfos[i].getTrackType();
+            if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+                subtitleTrackIndices.add(i);
+            }
+        }
+        if (subtitleTrackIndices.size() > 0) {
+            // Select first subtitle track
+            mCCEnabled = true;
+            mSelectedTrackIndex = subtitleTrackIndices.get(0);
+            mMediaPlayer.selectTrack(mSelectedTrackIndex);
+        }
+    }
+
+    @Override
+    public void hideSubtitle_impl() {
+        if (mCCEnabled) {
+            mMediaPlayer.deselectTrack(mSelectedTrackIndex);
+            mCCEnabled = false;
+        }
+    }
+
+    @Override
+    public void setSpeed_impl(float speed) {
+        if (speed <= 0.0f) {
+            Log.e(TAG, "Unsupported speed (" + speed + ") is ignored.");
+            return;
+        }
+        mSpeed = speed;
+        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+            applySpeed();
+        }
+    }
+
+    @Override
+    public float getSpeed_impl() {
+        if (DEBUG) {
+            if (mMediaPlayer != null) {
+                float speed = mMediaPlayer.getPlaybackParams().getSpeed();
+                if (speed != mSpeed) {
+                    Log.w(TAG, "VideoView2's speed : " + mSpeed + " is different from "
+                            + "MediaPlayer's speed : " + speed);
+                }
+            }
+        }
+        return mSpeed;
+    }
+
+    @Override
+    public void setAudioFocusRequest_impl(int focusGain) {
+        if (focusGain != AudioManager.AUDIOFOCUS_NONE
+                && focusGain != AudioManager.AUDIOFOCUS_GAIN
+                && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+                && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+                && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) {
+            throw new IllegalArgumentException("Illegal audio focus type " + focusGain);
+        }
+        mAudioFocusType = focusGain;
+    }
+
+    @Override
+    public void setAudioAttributes_impl(AudioAttributes attributes) {
+        if (attributes == null) {
+            throw new IllegalArgumentException("Illegal null AudioAttributes");
+        }
+        mAudioAttributes = attributes;
+    }
+
+    @Override
+    public void setVideoPath_impl(String path) {
+        mInstance.setVideoURI(Uri.parse(path));
+    }
+
+    @Override
+    public void setVideoURI_impl(Uri uri) {
+        mInstance.setVideoURI(uri, null);
+    }
+
+    @Override
+    public void setVideoURI_impl(Uri uri, Map<String, String> headers) {
+        mSeekWhenPrepared = 0;
+        openVideo(uri, headers);
+    }
+
+    @Override
+    public void setViewType_impl(int viewType) {
+        if (viewType == mCurrentView.getViewType()) {
+            return;
+        }
+        VideoViewInterface targetView;
+        if (viewType == VideoView2.VIEW_TYPE_TEXTUREVIEW) {
+            Log.d(TAG, "switching to TextureView");
+            targetView = mTextureView;
+        } else if (viewType == VideoView2.VIEW_TYPE_SURFACEVIEW) {
+            Log.d(TAG, "switching to SurfaceView");
+            targetView = mSurfaceView;
+        } else {
+            throw new IllegalArgumentException("Unknown view type: " + viewType);
+        }
+        ((View) targetView).setVisibility(View.VISIBLE);
+        targetView.takeOver(mCurrentView);
+        mInstance.requestLayout();
+    }
+
+    @Override
+    public int getViewType_impl() {
+        return mCurrentView.getViewType();
+    }
+
+    @Override
+    public void stopPlayback_impl() {
+        resetPlayer();
+    }
+
+    @Override
+    public void setOnPreparedListener_impl(VideoView2.OnPreparedListener l) {
+        mOnPreparedListener = l;
+    }
+
+    @Override
+    public void setOnCompletionListener_impl(VideoView2.OnCompletionListener l) {
+        mOnCompletionListener = l;
+    }
+
+    @Override
+    public void setOnErrorListener_impl(VideoView2.OnErrorListener l) {
+        mOnErrorListener = l;
+    }
+
+    @Override
+    public void setOnInfoListener_impl(VideoView2.OnInfoListener l) {
+        mOnInfoListener = l;
+    }
+
+    @Override
+    public void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l) {
+        mOnViewTypeChangedListener = l;
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName_impl() {
+        return VideoView2.class.getName();
+    }
+
+    @Override
+    public boolean onTouchEvent_impl(MotionEvent ev) {
+        if (DEBUG) {
+            Log.d(TAG, "onTouchEvent(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState);
+        }
+        if (ev.getAction() == MotionEvent.ACTION_UP
+                && isInPlaybackState() && mMediaControlView != null) {
+            toggleMediaControlViewVisibility();
+        }
+        return mSuperProvider.onTouchEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onTrackballEvent_impl(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_UP
+                && isInPlaybackState() && mMediaControlView != null) {
+            toggleMediaControlViewVisibility();
+        }
+        return mSuperProvider.onTrackballEvent_impl(ev);
+    }
+
+    @Override
+    public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
+        Log.v(TAG, "onKeyDown_impl: " + keyCode);
+        boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK
+                && keyCode != KeyEvent.KEYCODE_VOLUME_UP
+                && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN
+                && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
+                && keyCode != KeyEvent.KEYCODE_MENU
+                && keyCode != KeyEvent.KEYCODE_CALL
+                && keyCode != KeyEvent.KEYCODE_ENDCALL;
+        if (isInPlaybackState() && isKeyCodeSupported && mMediaControlView != null) {
+            if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+                    || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
+                if (mMediaPlayer.isPlaying()) {
+                    mInstance.pause();
+                    mMediaControlView.show();
+                } else {
+                    mInstance.start();
+                    mMediaControlView.hide();
+                }
+                return true;
+            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+                if (!mMediaPlayer.isPlaying()) {
+                    mInstance.start();
+                    mMediaControlView.hide();
+                }
+                return true;
+            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+                    || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+                if (mMediaPlayer.isPlaying()) {
+                    mInstance.pause();
+                    mMediaControlView.show();
+                }
+                return true;
+            } else {
+                toggleMediaControlViewVisibility();
+            }
+        }
+
+        return mSuperProvider.onKeyDown_impl(keyCode, event);
+    }
+
+    @Override
+    public void onFinishInflate_impl() {
+        mSuperProvider.onFinishInflate_impl();
+    }
+
+    @Override
+    public boolean dispatchKeyEvent_impl(KeyEvent event) {
+        return mSuperProvider.dispatchKeyEvent_impl(event);
+    }
+
+    @Override
+    public void setEnabled_impl(boolean enabled) {
+        mSuperProvider.setEnabled_impl(enabled);
+    }
+
+    ///////////////////////////////////////////////////
+    // Implements VideoViewInterface.SurfaceListener
+    ///////////////////////////////////////////////////
+
+    @Override
+    public void onSurfaceCreated(View view, int width, int height) {
+        if (DEBUG) {
+            Log.d(TAG, "onSurfaceCreated(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState + ", width/height: " + width + "/" + height
+                    + ", " + view.toString());
+        }
+        mSurfaceWidth = width;
+        mSurfaceHeight = height;
+
+        if (needToStart()) {
+            mInstance.start();
+        }
+    }
+
+    @Override
+    public void onSurfaceDestroyed(View view) {
+        if (DEBUG) {
+            Log.d(TAG, "onSurfaceDestroyed(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState + ", " + view.toString());
+        }
+        if (mMediaControlView != null) {
+            mMediaControlView.hide();
+        }
+    }
+
+    @Override
+    public void onSurfaceChanged(View view, int width, int height) {
+        // TODO: Do we need to call requestLayout here?
+        if (DEBUG) {
+            Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height
+                    + ", " + view.toString());
+        }
+        mSurfaceWidth = width;
+        mSurfaceHeight = height;
+    }
+
+    @Override
+    public void onSurfaceTakeOverDone(VideoViewInterface view) {
+        if (DEBUG) {
+            Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
+        }
+        mCurrentView = view;
+        if (mOnViewTypeChangedListener != null) {
+            mOnViewTypeChangedListener.onViewTypeChanged(view.getViewType());
+        }
+        if (needToStart()) {
+            mInstance.start();
+        }
+    }
+
+    ///////////////////////////////////////////////////
+    // Protected or private methods
+    ///////////////////////////////////////////////////
+
+    private boolean isInPlaybackState() {
+        return (mMediaPlayer != null
+                && mCurrentState != STATE_ERROR
+                && mCurrentState != STATE_IDLE
+                && mCurrentState != STATE_PREPARING);
+    }
+
+    private boolean needToStart() {
+        return (mMediaPlayer != null
+                && mCurrentState != STATE_PLAYING
+                && mTargetState == STATE_PLAYING);
+    }
+
+    // Creates a MediaPlayer instance and prepare playback.
+    private void openVideo(Uri uri, Map<String, String> headers) {
+        resetPlayer();
+        if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+            // TODO this should have a focus listener
+            AudioFocusRequest focusRequest;
+            focusRequest = new AudioFocusRequest.Builder(mAudioFocusType)
+                    .setAudioAttributes(mAudioAttributes)
+                    .build();
+            mAudioManager.requestAudioFocus(focusRequest);
+        }
+
+        try {
+            Log.d(TAG, "openVideo(): creating new MediaPlayer instance.");
+            mMediaPlayer = new MediaPlayer();
+            mSurfaceView.setMediaPlayer(mMediaPlayer);
+            mTextureView.setMediaPlayer(mMediaPlayer);
+            mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer);
+
+            // TODO: create SubtitleController in MediaPlayer, 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));
+            mMediaPlayer.setSubtitleAnchor(controller, (SubtitleController.Anchor) mSubtitleView);
+
+            if (mAudioSession != 0) {
+                mMediaPlayer.setAudioSessionId(mAudioSession);
+            } else {
+                mAudioSession = mMediaPlayer.getAudioSessionId();
+            }
+            mMediaPlayer.setOnPreparedListener(mPreparedListener);
+            mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
+            mMediaPlayer.setOnCompletionListener(mCompletionListener);
+            mMediaPlayer.setOnErrorListener(mErrorListener);
+            mMediaPlayer.setOnInfoListener(mInfoListener);
+            mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
+            mCurrentBufferPercentage = 0;
+            mMediaPlayer.setDataSource(mInstance.getContext(), uri, headers);
+            mMediaPlayer.setAudioAttributes(mAudioAttributes);
+            // we don't set the target state here either, but preserve the
+            // target state that was there before.
+            mCurrentState = STATE_PREPARING;
+            mMediaPlayer.prepareAsync();
+
+            if (DEBUG) {
+                Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
+                        + ", mTargetState=" + mTargetState);
+            }
+            /*
+            for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
+                try {
+                    mMediaPlayer.addSubtitleSource(pending.first, pending.second);
+                } catch (IllegalStateException e) {
+                    mInfoListener.onInfo(
+                            mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
+                }
+            }
+            */
+        } catch (IOException | IllegalArgumentException ex) {
+            Log.w(TAG, "Unable to open content: " + uri, ex);
+            mCurrentState = STATE_ERROR;
+            mTargetState = STATE_ERROR;
+            mErrorListener.onError(mMediaPlayer,
+                    MediaPlayer.MEDIA_ERROR_UNKNOWN, MediaPlayer.MEDIA_ERROR_IO);
+        } finally {
+            //mPendingSubtitleTracks.clear();
+        }
+    }
+
+    /*
+     * Reset the media player in any state
+     */
+    // TODO: Figure out if the legacy code's boolean parameter: cleartargetstate is necessary.
+    private void resetPlayer() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.reset();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+            //mPendingSubtitleTracks.clear();
+            mCurrentState = STATE_IDLE;
+            mTargetState = STATE_IDLE;
+            if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+                mAudioManager.abandonAudioFocus(null);
+            }
+        }
+        mSurfaceWidth = 0;
+        mSurfaceHeight = 0;
+        mVideoWidth = 0;
+        mVideoHeight = 0;
+    }
+
+    private void updatePlaybackState() {
+        if (mStateBuilder == null) {
+            // Get the capabilities of the player for this stream
+            Metadata data = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
+                    MediaPlayer.BYPASS_METADATA_FILTER);
+
+            // Add Play action as default
+            long playbackActions = PlaybackState.ACTION_PLAY;
+            if (data != null) {
+                if (!data.has(Metadata.PAUSE_AVAILABLE)
+                        || data.getBoolean(Metadata.PAUSE_AVAILABLE)) {
+                    playbackActions |= PlaybackState.ACTION_PAUSE;
+                }
+                if (!data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
+                        || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
+                    playbackActions |= PlaybackState.ACTION_REWIND;
+                }
+                if (!data.has(Metadata.SEEK_FORWARD_AVAILABLE)
+                        || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
+                    playbackActions |= PlaybackState.ACTION_FAST_FORWARD;
+                }
+                if (!data.has(Metadata.SEEK_AVAILABLE)
+                        || data.getBoolean(Metadata.SEEK_AVAILABLE)) {
+                    playbackActions |= PlaybackState.ACTION_SEEK_TO;
+                }
+            } else {
+                playbackActions |= (PlaybackState.ACTION_PAUSE |
+                        PlaybackState.ACTION_REWIND | PlaybackState.ACTION_FAST_FORWARD |
+                        PlaybackState.ACTION_SEEK_TO);
+            }
+            mStateBuilder = new PlaybackState.Builder();
+            mStateBuilder.setActions(playbackActions);
+            mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_SHOW_SUBTITLE, null, -1);
+            mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_HIDE_SUBTITLE, null, -1);
+        }
+        mStateBuilder.setState(getCorrespondingPlaybackState(),
+                mInstance.getCurrentPosition(), 1.0f);
+        mStateBuilder.setBufferedPosition(
+                (long) (mCurrentBufferPercentage / 100.0) * mInstance.getDuration());
+
+        // Set PlaybackState for MediaSession
+        if (mMediaSession != null) {
+            PlaybackState state = mStateBuilder.build();
+            mMediaSession.setPlaybackState(state);
+        }
+    }
+
+    private int getCorrespondingPlaybackState() {
+        switch (mCurrentState) {
+            case STATE_ERROR:
+                return PlaybackState.STATE_ERROR;
+            case STATE_IDLE:
+                return PlaybackState.STATE_NONE;
+            case STATE_PREPARING:
+                return PlaybackState.STATE_CONNECTING;
+            case STATE_PREPARED:
+                return PlaybackState.STATE_STOPPED;
+            case STATE_PLAYING:
+                return PlaybackState.STATE_PLAYING;
+            case STATE_PAUSED:
+                return PlaybackState.STATE_PAUSED;
+            case STATE_PLAYBACK_COMPLETED:
+                return PlaybackState.STATE_STOPPED;
+            default:
+                return -1;
+        }
+    }
+
+    private void toggleMediaControlViewVisibility() {
+        if (mMediaControlView.isShowing()) {
+            mMediaControlView.hide();
+        } else {
+            mMediaControlView.show();
+        }
+    }
+
+    private void applySpeed() {
+        PlaybackParams params = mMediaPlayer.getPlaybackParams().allowDefaults();
+        if (mSpeed != params.getSpeed()) {
+            try {
+                params.setSpeed(mSpeed);
+                mMediaPlayer.setPlaybackParams(params);
+                mFallbackSpeed = mSpeed;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "PlaybackParams has unsupported value: " + e);
+                // TODO: should revise this part after integrating with MP2.
+                // If mSpeed had an illegal value for speed rate, system will determine best
+                // handling (see PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT).
+                // Note: The pre-MP2 returns 0.0f when it is paused. In this case, VideoView2 will
+                // use mFallbackSpeed instead.
+                float fallbackSpeed = mMediaPlayer.getPlaybackParams().allowDefaults().getSpeed();
+                if (fallbackSpeed > 0.0f) {
+                    mFallbackSpeed = fallbackSpeed;
+                }
+                mSpeed = mFallbackSpeed;
+            }
+        }
+    }
+
+    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
+            new MediaPlayer.OnVideoSizeChangedListener() {
+                public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+                    if (DEBUG) {
+                        Log.d(TAG, "OnVideoSizeChanged(): size: " + width + "/" + height);
+                    }
+                    mVideoWidth = mp.getVideoWidth();
+                    mVideoHeight = mp.getVideoHeight();
+                    if (DEBUG) {
+                        Log.d(TAG, "OnVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/"
+                                + mVideoHeight);
+                    }
+
+                    if (mVideoWidth != 0 && mVideoHeight != 0) {
+                        mInstance.requestLayout();
+                    }
+                }
+            };
+
+    MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
+        public void onPrepared(MediaPlayer mp) {
+            if (DEBUG) {
+                Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState
+                        + ", mTargetState=" + mTargetState);
+            }
+            mCurrentState = STATE_PREPARED;
+            if (mOnPreparedListener != null) {
+                mOnPreparedListener.onPrepared();
+            }
+            if (mMediaControlView != null) {
+                mMediaControlView.setEnabled(true);
+            }
+            mVideoWidth = mp.getVideoWidth();
+            mVideoHeight = mp.getVideoHeight();
+
+            // mSeekWhenPrepared may be changed after seekTo() call
+            int seekToPosition = mSeekWhenPrepared;
+            if (seekToPosition != 0) {
+                mInstance.seekTo(seekToPosition);
+            }
+
+            // Create and set playback state for MediaControlView2
+            updatePlaybackState();
+
+            // Get and set duration value as MediaMetadata for MediaControlView2
+            MediaMetadata.Builder builder = new MediaMetadata.Builder();
+            builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mInstance.getDuration());
+            if (mMediaSession != null) {
+                mMediaSession.setMetadata(builder.build());
+            }
+
+            if (mVideoWidth != 0 && mVideoHeight != 0) {
+                if (mVideoWidth != mSurfaceWidth || mVideoHeight != mSurfaceHeight) {
+                    if (DEBUG) {
+                        Log.i(TAG, "OnPreparedListener() : ");
+                        Log.i(TAG, " video size: " + mVideoWidth + "/" + mVideoHeight);
+                        Log.i(TAG, " surface size: " + mSurfaceWidth + "/" + mSurfaceHeight);
+                        Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
+                                + mInstance.getMeasuredHeight());
+                        Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
+                                + mInstance.getHeight());
+                    }
+
+                    // TODO: It seems like that overriding onMeasure() is needed like legacy code.
+                    mSurfaceWidth = mVideoWidth;
+                    mSurfaceHeight = mVideoHeight;
+                    mInstance.requestLayout();
+                }
+                if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
+                    // We didn't actually change the size (it was already at the size
+                    // we need), so we won't get a "surface changed" callback, so
+                    // start the video here instead of in the callback.
+                    if (needToStart()) {
+                        mInstance.start();
+                        if (mMediaControlView != null) {
+                            mMediaControlView.show();
+                        }
+                    } else if (!mInstance.isPlaying() && (seekToPosition != 0
+                            || mInstance.getCurrentPosition() > 0)) {
+                        if (mMediaControlView != null) {
+                            // Show the media controls when we're paused into a video and
+                            // make them stick.
+                            mMediaControlView.show(0);
+                        }
+                    }
+                }
+            } else {
+                // We don't know the video size yet, but should start anyway.
+                // The video size might be reported to us later.
+                if (needToStart()) {
+                    mInstance.start();
+                }
+            }
+        }
+    };
+
+    private MediaPlayer.OnCompletionListener mCompletionListener =
+            new MediaPlayer.OnCompletionListener() {
+                public void onCompletion(MediaPlayer mp) {
+                    mCurrentState = STATE_PLAYBACK_COMPLETED;
+                    mTargetState = STATE_PLAYBACK_COMPLETED;
+                    updatePlaybackState();
+
+                    if (mMediaControlView != null) {
+                        mMediaControlView.hide();
+                    }
+                    if (mOnCompletionListener != null) {
+                        mOnCompletionListener.onCompletion();
+                    }
+                    if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+                        mAudioManager.abandonAudioFocus(null);
+                    }
+                }
+            };
+
+    private MediaPlayer.OnInfoListener mInfoListener =
+            new MediaPlayer.OnInfoListener() {
+                public boolean onInfo(MediaPlayer mp, int what, int extra) {
+                    if (mOnInfoListener != null) {
+                        mOnInfoListener.onInfo(what, extra);
+                    }
+                    return true;
+                }
+            };
+
+    private MediaPlayer.OnErrorListener mErrorListener =
+            new MediaPlayer.OnErrorListener() {
+                public boolean onError(MediaPlayer mp, int frameworkErr, int implErr) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
+                    }
+                    mCurrentState = STATE_ERROR;
+                    mTargetState = STATE_ERROR;
+                    updatePlaybackState();
+
+                    if (mMediaControlView != null) {
+                        mMediaControlView.hide();
+                    }
+
+                    /* If an error handler has been supplied, use it and finish. */
+                    if (mOnErrorListener != null) {
+                        if (mOnErrorListener.onError(frameworkErr, implErr)) {
+                            return true;
+                        }
+                    }
+
+                    /* Otherwise, pop up an error dialog so the user knows that
+                     * something bad has happened. Only try and pop up the dialog
+                     * if we're attached to a window. When we're going away and no
+                     * longer have a window, don't bother showing the user an error.
+                    */
+                    if (mInstance.getWindowToken() != null) {
+                        int messageId;
+
+                        if (frameworkErr
+                                == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
+                            messageId = R.string.VideoView2_error_text_invalid_progressive_playback;
+                        } else {
+                            messageId = R.string.VideoView2_error_text_unknown;
+                        }
+
+                        Resources res = ApiHelper.getLibResources();
+                        new AlertDialog.Builder(mInstance.getContext())
+                                .setMessage(res.getString(messageId))
+                                .setPositiveButton(res.getString(R.string.VideoView2_error_button),
+                                        new DialogInterface.OnClickListener() {
+                                            public void onClick(DialogInterface dialog,
+                                                                int whichButton) {
+                                                /* If we get here, there is no onError listener, so
+                                                * at least inform them that the video is over.
+                                                */
+                                                if (mOnCompletionListener != null) {
+                                                    mOnCompletionListener.onCompletion();
+                                                }
+                                            }
+                                        })
+                                .setCancelable(false)
+                                .show();
+                    }
+                    return true;
+                }
+            };
+
+    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
+            new MediaPlayer.OnBufferingUpdateListener() {
+                public void onBufferingUpdate(MediaPlayer mp, int percent) {
+                    mCurrentBufferPercentage = percent;
+                    updatePlaybackState();
+                }
+            };
+
+    private class MediaSessionCallback extends MediaSession.Callback {
+        @Override
+        public void onCommand(String command, Bundle args, ResultReceiver receiver) {
+            switch (command) {
+                case MediaControlView2Impl.ACTION_SHOW_SUBTITLE:
+                    mInstance.showSubtitle();
+                    break;
+                case MediaControlView2Impl.ACTION_HIDE_SUBTITLE:
+                    mInstance.hideSubtitle();
+                    break;
+            }
+        }
+
+        @Override
+        public void onPlay() {
+            mInstance.start();
+        }
+
+        @Override
+        public void onPause() {
+            mInstance.pause();
+        }
+
+        @Override
+        public void onSeekTo(long pos) {
+            mInstance.seekTo((int) pos);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java b/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
new file mode 100644
index 0000000..2a5eb94
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
@@ -0,0 +1,65 @@
+/*
+ * 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.widget;
+
+import android.annotation.NonNull;
+import android.media.MediaPlayer;
+import android.view.View;
+
+interface VideoViewInterface {
+    /**
+     * Assigns the view's surface to the given MediaPlayer instance.
+     *
+     * @param mp MediaPlayer
+     * @return true if the surface is successfully assigned, false if not. It will fail to assign
+     *         if any of MediaPlayer or surface is unavailable.
+     */
+    boolean assignSurfaceToMediaPlayer(MediaPlayer mp);
+    void setSurfaceListener(SurfaceListener l);
+    int getViewType();
+    void setMediaPlayer(MediaPlayer mp);
+
+    /**
+     * Takes over oldView. It means that the MediaPlayer will start rendering on this view.
+     * The visibility of oldView will be set as {@link View.GONE}. If the view doesn't have a
+     * MediaPlayer instance or its surface is not available, the actual execution is deferred until
+     * a MediaPlayer instance is set by {@link #setMediaPlayer} or its surface becomes available.
+     * {@link SurfaceListener.onSurfaceTakeOverDone} will be called when the actual execution is
+     * done.
+     *
+     * @param oldView The view that MediaPlayer is currently rendering on.
+     */
+    void takeOver(@NonNull VideoViewInterface oldView);
+
+    /**
+     * Indicates if the view's surface is available.
+     *
+     * @return true if the surface is available.
+     */
+    boolean hasAvailableSurface();
+
+    /**
+     * An instance of VideoViewInterface calls these surface notification methods accordingly if
+     * a listener has been registered via {@link #setSurfaceListener(SurfaceListener)}.
+     */
+    interface SurfaceListener {
+        void onSurfaceCreated(View view, int width, int height);
+        void onSurfaceDestroyed(View view);
+        void onSurfaceChanged(View view, int width, int height);
+        void onSurfaceTakeOverDone(VideoViewInterface view);
+    }
+}
diff --git a/packages/MediaUpdate/Android.mk b/packages/MediaUpdate/Android.mk
deleted file mode 100644
index e757098..0000000
--- a/packages/MediaUpdate/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# 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_PACKAGE_NAME := MediaUpdate
-LOCAL_MODULE_OWNER := google
-LOCAL_PRIVILEGED_MODULE := true
-
-# TODO: create a separate key for this package.
-LOCAL_CERTIFICATE := platform
-
-include $(BUILD_PACKAGE)
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 3c975c3..2a2f6fc 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -262,6 +262,7 @@
                                              audio_config_base_t *config,
                                              const AudioClient& client,
                                              audio_port_handle_t *deviceId,
+                                             audio_session_t *sessionId,
                                              const sp<MmapStreamCallback>& callback,
                                              sp<MmapStreamInterface>& interface,
                                              audio_port_handle_t *handle)
@@ -274,7 +275,8 @@
     status_t ret = NO_INIT;
     if (af != 0) {
         ret = af->openMmapStream(
-                direction, attr, config, client, deviceId, callback, interface, handle);
+                direction, attr, config, client, deviceId,
+                sessionId, callback, interface, handle);
     }
     return ret;
 }
@@ -284,6 +286,7 @@
                                       audio_config_base_t *config,
                                       const AudioClient& client,
                                       audio_port_handle_t *deviceId,
+                                      audio_session_t *sessionId,
                                       const sp<MmapStreamCallback>& callback,
                                       sp<MmapStreamInterface>& interface,
                                       audio_port_handle_t *handle)
@@ -292,8 +295,10 @@
     if (ret != NO_ERROR) {
         return ret;
     }
-
-    audio_session_t sessionId = (audio_session_t) newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION);
+    audio_session_t actualSessionId = *sessionId;
+    if (actualSessionId == AUDIO_SESSION_ALLOCATE) {
+        actualSessionId = (audio_session_t) newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION);
+    }
     audio_stream_type_t streamType = AUDIO_STREAM_DEFAULT;
     audio_io_handle_t io = AUDIO_IO_HANDLE_NONE;
     audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
@@ -303,7 +308,7 @@
         fullConfig.channel_mask = config->channel_mask;
         fullConfig.format = config->format;
         ret = AudioSystem::getOutputForAttr(attr, &io,
-                                            sessionId,
+                                            actualSessionId,
                                             &streamType, client.clientUid,
                                             &fullConfig,
                                             (audio_output_flags_t)(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ |
@@ -311,7 +316,7 @@
                                             deviceId, &portId);
     } else {
         ret = AudioSystem::getInputForAttr(attr, &io,
-                                              sessionId,
+                                              actualSessionId,
                                               client.clientPid,
                                               client.clientUid,
                                               config,
@@ -326,13 +331,14 @@
     sp<MmapThread> thread = mMmapThreads.valueFor(io);
     if (thread != 0) {
         interface = new MmapThreadHandle(thread);
-        thread->configure(attr, streamType, sessionId, callback, *deviceId, portId);
+        thread->configure(attr, streamType, actualSessionId, callback, *deviceId, portId);
         *handle = portId;
+        *sessionId = actualSessionId;
     } else {
         if (direction == MmapStreamInterface::DIRECTION_OUTPUT) {
-            AudioSystem::releaseOutput(io, streamType, sessionId);
+            AudioSystem::releaseOutput(io, streamType, actualSessionId);
         } else {
-            AudioSystem::releaseInput(io, sessionId);
+            AudioSystem::releaseInput(io, actualSessionId);
         }
         ret = NO_INIT;
     }
@@ -2597,6 +2603,7 @@
 
     for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
         sp<PlaybackThread> t = mPlaybackThreads.valueAt(i);
+        Mutex::Autolock _l(t->mLock);
         for (size_t j = 0; j < t->mEffectChains.size(); j++) {
             sp<EffectChain> ec = t->mEffectChains[j];
             if (ec->sessionId() > AUDIO_SESSION_OUTPUT_MIX) {
@@ -2606,6 +2613,7 @@
     }
     for (size_t i = 0; i < mRecordThreads.size(); i++) {
         sp<RecordThread> t = mRecordThreads.valueAt(i);
+        Mutex::Autolock _l(t->mLock);
         for (size_t j = 0; j < t->mEffectChains.size(); j++) {
             sp<EffectChain> ec = t->mEffectChains[j];
             chains.push(ec);
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index bc73ffd..5a64f0b 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -271,6 +271,7 @@
                             audio_config_base_t *config,
                             const AudioClient& client,
                             audio_port_handle_t *deviceId,
+                            audio_session_t *sessionId,
                             const sp<MmapStreamCallback>& callback,
                             sp<MmapStreamInterface>& interface,
                             audio_port_handle_t *handle);
diff --git a/services/audioflinger/Configuration.h b/services/audioflinger/Configuration.h
index 6e0f2b6..ede8e3f 100644
--- a/services/audioflinger/Configuration.h
+++ b/services/audioflinger/Configuration.h
@@ -47,6 +47,9 @@
 #ifdef FLOAT_EFFECT_CHAIN
 // define FLOAT_AUX to process aux effect buffers in float (FLOAT_EFFECT_CHAIN must be defined)
 #define FLOAT_AUX
+
+// define MULTICHANNEL_EFFECT_CHAIN to allow multichannel effects (FLOAT_EFFECT_CHAIN defined)
+#define MULTICHANNEL_EFFECT_CHAIN
 #endif
 
 #endif // ANDROID_AUDIOFLINGER_CONFIGURATION_H
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index dda9d20..b13e551 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -26,6 +26,7 @@
 #include <system/audio_effects/effect_aec.h>
 #include <system/audio_effects/effect_ns.h>
 #include <system/audio_effects/effect_visualizer.h>
+#include <audio_utils/channels.h>
 #include <audio_utils/primitives.h>
 #include <media/AudioEffect.h>
 #include <media/audiohal/EffectHalInterface.h>
@@ -292,7 +293,6 @@
         return;
     }
 
-    // TODO: Implement multichannel effects; here outChannelCount == FCC_2 == 2
     const uint32_t inChannelCount =
             audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
     const uint32_t outChannelCount =
@@ -342,6 +342,7 @@
         if (isProcessImplemented()) {
             if (auxType) {
                 // We overwrite the aux input buffer here and clear after processing.
+                // aux input is always mono.
 #ifdef FLOAT_EFFECT_CHAIN
                 if (mSupportsFloat) {
 #ifndef FLOAT_AUX
@@ -371,6 +372,28 @@
                 }
             }
 #ifdef FLOAT_EFFECT_CHAIN
+            sp<EffectBufferHalInterface> inBuffer = mInBuffer;
+            sp<EffectBufferHalInterface> outBuffer = mOutBuffer;
+
+            if (!auxType && mInChannelCountRequested != inChannelCount) {
+                adjust_channels(
+                        inBuffer->audioBuffer()->f32, mInChannelCountRequested,
+                        mInConversionBuffer->audioBuffer()->f32, inChannelCount,
+                        sizeof(float),
+                        sizeof(float)
+                        * mInChannelCountRequested * mConfig.inputCfg.buffer.frameCount);
+                inBuffer = mInConversionBuffer;
+            }
+            if (mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE
+                    && mOutChannelCountRequested != outChannelCount) {
+                adjust_selected_channels(
+                        outBuffer->audioBuffer()->f32, mOutChannelCountRequested,
+                        mOutConversionBuffer->audioBuffer()->f32, outChannelCount,
+                        sizeof(float),
+                        sizeof(float)
+                        * mOutChannelCountRequested * mConfig.outputCfg.buffer.frameCount);
+                outBuffer = mOutConversionBuffer;
+            }
             if (!mSupportsFloat) { // convert input to int16_t as effect doesn't support float.
                 if (!auxType) {
                     if (mInConversionBuffer.get() == nullptr) {
@@ -379,8 +402,9 @@
                     }
                     memcpy_to_i16_from_float(
                             mInConversionBuffer->audioBuffer()->s16,
-                            mInBuffer->audioBuffer()->f32,
+                            inBuffer->audioBuffer()->f32,
                             inChannelCount * mConfig.inputCfg.buffer.frameCount);
+                    inBuffer = mInConversionBuffer;
                 }
                 if (mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) {
                     if (mOutConversionBuffer.get() == nullptr) {
@@ -389,21 +413,30 @@
                     }
                     memcpy_to_i16_from_float(
                             mOutConversionBuffer->audioBuffer()->s16,
-                            mOutBuffer->audioBuffer()->f32,
+                            outBuffer->audioBuffer()->f32,
                             outChannelCount * mConfig.outputCfg.buffer.frameCount);
+                    outBuffer = mOutConversionBuffer;
                 }
             }
 #endif
-
             ret = mEffectInterface->process();
-
 #ifdef FLOAT_EFFECT_CHAIN
             if (!mSupportsFloat) { // convert output int16_t back to float.
+                sp<EffectBufferHalInterface> target =
+                        mOutChannelCountRequested != outChannelCount
+                        ? mOutConversionBuffer : mOutBuffer;
+
                 memcpy_to_float_from_i16(
-                        mOutBuffer->audioBuffer()->f32,
+                        target->audioBuffer()->f32,
                         mOutConversionBuffer->audioBuffer()->s16,
                         outChannelCount * mConfig.outputCfg.buffer.frameCount);
             }
+            if (mOutChannelCountRequested != outChannelCount) {
+                adjust_selected_channels(mOutConversionBuffer->audioBuffer()->f32, outChannelCount,
+                        mOutBuffer->audioBuffer()->f32, mOutChannelCountRequested,
+                        sizeof(float),
+                        sizeof(float) * outChannelCount * mConfig.outputCfg.buffer.frameCount);
+            }
 #endif
         } else {
 #ifdef FLOAT_EFFECT_CHAIN
@@ -476,15 +509,28 @@
     }
 
     // TODO: handle configuration of effects replacing track process
+    // TODO: handle configuration of input (record) SW effects above the HAL,
+    // similar to output EFFECT_FLAG_TYPE_INSERT/REPLACE,
+    // in which case input channel masks should be used here.
     channelMask = thread->channelMask();
+    mConfig.inputCfg.channels = channelMask;
     mConfig.outputCfg.channels = channelMask;
 
     if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
-        mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_MONO;
-        mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
-        ALOGV("Overriding auxiliary effect input as MONO and output as STEREO");
+        if (mConfig.inputCfg.channels != AUDIO_CHANNEL_OUT_MONO) {
+            mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_MONO;
+            ALOGV("Overriding auxiliary effect input channels %#x as MONO",
+                    mConfig.inputCfg.channels);
+        }
+#ifndef MULTICHANNEL_EFFECT_CHAIN
+        if (mConfig.outputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) {
+            mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+            ALOGV("Overriding auxiliary effect output channels %#x as STEREO",
+                    mConfig.outputCfg.channels);
+        }
+#endif
     } else {
-        mConfig.inputCfg.channels = channelMask;
+#ifndef MULTICHANNEL_EFFECT_CHAIN
         // TODO: Update this logic when multichannel effects are implemented.
         // For offloaded tracks consider mono output as stereo for proper effect initialization
         if (channelMask == AUDIO_CHANNEL_OUT_MONO) {
@@ -492,7 +538,12 @@
             mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
             ALOGV("Overriding effect input and output as STEREO");
         }
+#endif
     }
+    mInChannelCountRequested =
+            audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
+    mOutChannelCountRequested =
+            audio_channel_count_from_out_mask(mConfig.outputCfg.channels);
 
     mConfig.inputCfg.format = EFFECT_BUFFER_FORMAT;
     mConfig.outputCfg.format = EFFECT_BUFFER_FORMAT;
@@ -530,28 +581,58 @@
     status_t cmdStatus;
     size = sizeof(int);
     status = mEffectInterface->command(EFFECT_CMD_SET_CONFIG,
-                                       sizeof(effect_config_t),
+                                       sizeof(mConfig),
                                        &mConfig,
                                        &size,
                                        &cmdStatus);
     if (status == NO_ERROR) {
         status = cmdStatus;
-#ifdef FLOAT_EFFECT_CHAIN
-        mSupportsFloat = true;
-#endif
     }
-#ifdef FLOAT_EFFECT_CHAIN
-    else {
-        ALOGV("EFFECT_CMD_SET_CONFIG failed with float format, retry with int16_t.");
-        mConfig.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
-        mConfig.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
+
+#ifdef MULTICHANNEL_EFFECT_CHAIN
+    if (status != NO_ERROR &&
+            (mConfig.inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO
+                    || mConfig.outputCfg.channels != AUDIO_CHANNEL_OUT_STEREO)) {
+        // Older effects may require exact STEREO position mask.
+        if (mConfig.inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) {
+            ALOGV("Overriding effect input channels %#x as STEREO", mConfig.inputCfg.channels);
+            mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+        }
+        if (mConfig.outputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) {
+            ALOGV("Overriding effect output channels %#x as STEREO", mConfig.outputCfg.channels);
+            mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+        }
+        size = sizeof(int);
         status = mEffectInterface->command(EFFECT_CMD_SET_CONFIG,
-                                           sizeof(effect_config_t),
+                                           sizeof(mConfig),
                                            &mConfig,
                                            &size,
                                            &cmdStatus);
         if (status == NO_ERROR) {
             status = cmdStatus;
+        }
+    }
+#endif
+
+#ifdef FLOAT_EFFECT_CHAIN
+    if (status == NO_ERROR) {
+        mSupportsFloat = true;
+    }
+
+    if (status != NO_ERROR) {
+        ALOGV("EFFECT_CMD_SET_CONFIG failed with float format, retry with int16_t.");
+        mConfig.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
+        mConfig.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
+        size = sizeof(int);
+        status = mEffectInterface->command(EFFECT_CMD_SET_CONFIG,
+                                           sizeof(mConfig),
+                                           &mConfig,
+                                           &size,
+                                           &cmdStatus);
+        if (status == NO_ERROR) {
+            status = cmdStatus;
+        }
+        if (status == NO_ERROR) {
             mSupportsFloat = false;
             ALOGVV("config worked with 16 bit");
         } else {
@@ -929,11 +1010,15 @@
     // the original buffer) when the output buffer is identical to the input buffer,
     // but we don't optimize for it here.
     const bool auxType = (mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY;
-    if (!auxType && !mSupportsFloat && mInBuffer.get() != nullptr) {
+    const uint32_t inChannelCount =
+            audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
+    const bool formatMismatch = !mSupportsFloat || mInChannelCountRequested != inChannelCount;
+    if (!auxType && formatMismatch && mInBuffer.get() != nullptr) {
         // we need to translate - create hidl shared buffer and intercept
         const size_t inFrameCount = mConfig.inputCfg.buffer.frameCount;
-        const int inChannels = audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
-        const size_t size = inChannels * inFrameCount * sizeof(int16_t);
+        // Use FCC_2 in case mInChannelCountRequested is mono and the effect is stereo.
+        const uint32_t inChannels = std::max((uint32_t)FCC_2, mInChannelCountRequested);
+        const size_t size = inChannels * inFrameCount * std::max(sizeof(int16_t), sizeof(float));
 
         ALOGV("%s: setInBuffer updating for inChannels:%d inFrameCount:%zu total size:%zu",
                 __func__, inChannels, inFrameCount, size);
@@ -970,10 +1055,14 @@
 #ifdef FLOAT_EFFECT_CHAIN
     // Note: Any effect that does not accumulate does not need mOutConversionBuffer and
     // can do in-place conversion from int16_t to float.  We don't optimize here.
-    if (!mSupportsFloat && mOutBuffer.get() != nullptr) {
+    const uint32_t outChannelCount =
+            audio_channel_count_from_out_mask(mConfig.outputCfg.channels);
+    const bool formatMismatch = !mSupportsFloat || mOutChannelCountRequested != outChannelCount;
+    if (formatMismatch && mOutBuffer.get() != nullptr) {
         const size_t outFrameCount = mConfig.outputCfg.buffer.frameCount;
-        const int outChannels = audio_channel_count_from_out_mask(mConfig.outputCfg.channels);
-        const size_t size = outChannels * outFrameCount * sizeof(int16_t);
+        // Use FCC_2 in case mOutChannelCountRequested is mono and the effect is stereo.
+        const uint32_t outChannels = std::max((uint32_t)FCC_2, mOutChannelCountRequested);
+        const size_t size = outChannels * outFrameCount * std::max(sizeof(int16_t), sizeof(float));
 
         ALOGV("%s: setOutBuffer updating for outChannels:%d outFrameCount:%zu total size:%zu",
                 __func__, outChannels, outFrameCount, size);
@@ -1279,12 +1368,9 @@
 
 void AudioFlinger::EffectModule::dump(int fd, const Vector<String16>& args __unused)
 {
-    const size_t SIZE = 256;
-    char buffer[SIZE];
     String8 result;
 
-    snprintf(buffer, SIZE, "\tEffect ID %d:\n", mId);
-    result.append(buffer);
+    result.appendFormat("\tEffect ID %d:\n", mId);
 
     bool locked = AudioFlinger::dumpTryLock(mLock);
     // failed to lock - AudioFlinger is probably deadlocked
@@ -1293,51 +1379,46 @@
     }
 
     result.append("\t\tSession Status State Engine:\n");
-    snprintf(buffer, SIZE, "\t\t%05d   %03d    %03d   %p\n",
+    result.appendFormat("\t\t%05d   %03d    %03d   %p\n",
             mSessionId, mStatus, mState, mEffectInterface.get());
-    result.append(buffer);
 
     result.append("\t\tDescriptor:\n");
     char uuidStr[64];
     AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
-    snprintf(buffer, SIZE, "\t\t- UUID: %s\n", uuidStr);
-    result.append(buffer);
+    result.appendFormat("\t\t- UUID: %s\n", uuidStr);
     AudioEffect::guidToString(&mDescriptor.type, uuidStr, sizeof(uuidStr));
-    snprintf(buffer, SIZE, "\t\t- TYPE: %s\n", uuidStr);
-    result.append(buffer);
-    snprintf(buffer, SIZE, "\t\t- apiVersion: %08X\n\t\t- flags: %08X (%s)\n",
+    result.appendFormat("\t\t- TYPE: %s\n", uuidStr);
+    result.appendFormat("\t\t- apiVersion: %08X\n\t\t- flags: %08X (%s)\n",
             mDescriptor.apiVersion,
             mDescriptor.flags,
             effectFlagsToString(mDescriptor.flags).string());
-    result.append(buffer);
-    snprintf(buffer, SIZE, "\t\t- name: %s\n",
+    result.appendFormat("\t\t- name: %s\n",
             mDescriptor.name);
-    result.append(buffer);
-    snprintf(buffer, SIZE, "\t\t- implementor: %s\n",
+
+    result.appendFormat("\t\t- implementor: %s\n",
             mDescriptor.implementor);
-    result.append(buffer);
+
+    result.appendFormat("\t\t- data: %s\n", mSupportsFloat ? "float" : "int16");
 
     result.append("\t\t- Input configuration:\n");
-    result.append("\t\t\tFrames  Smp rate Channels Format Buffer\n");
-    snprintf(buffer, SIZE, "\t\t\t%05zu   %05d    %08x %6d (%s) %p\n",
+    result.append("\t\t\tBuffer     Frames  Smp rate Channels Format\n");
+    result.appendFormat("\t\t\t%p %05zu   %05d    %08x %6d (%s)\n",
+            mConfig.inputCfg.buffer.raw,
             mConfig.inputCfg.buffer.frameCount,
             mConfig.inputCfg.samplingRate,
             mConfig.inputCfg.channels,
             mConfig.inputCfg.format,
-            formatToString((audio_format_t)mConfig.inputCfg.format).c_str(),
-            mConfig.inputCfg.buffer.raw);
-    result.append(buffer);
+            formatToString((audio_format_t)mConfig.inputCfg.format).c_str());
 
     result.append("\t\t- Output configuration:\n");
     result.append("\t\t\tBuffer     Frames  Smp rate Channels Format\n");
-    snprintf(buffer, SIZE, "\t\t\t%p %05zu   %05d    %08x %d (%s)\n",
+    result.appendFormat("\t\t\t%p %05zu   %05d    %08x %6d (%s)\n",
             mConfig.outputCfg.buffer.raw,
             mConfig.outputCfg.buffer.frameCount,
             mConfig.outputCfg.samplingRate,
             mConfig.outputCfg.channels,
             mConfig.outputCfg.format,
             formatToString((audio_format_t)mConfig.outputCfg.format).c_str());
-    result.append(buffer);
 
 #ifdef FLOAT_EFFECT_CHAIN
 
@@ -1349,13 +1430,13 @@
             dumpInOutBuffer(false /* isInput */, mOutConversionBuffer).c_str());
 #endif
 
-    snprintf(buffer, SIZE, "\t\t%zu Clients:\n", mHandles.size());
-    result.append(buffer);
+    result.appendFormat("\t\t%zu Clients:\n", mHandles.size());
     result.append("\t\t\t  Pid Priority Ctrl Locked client server\n");
+    char buffer[256];
     for (size_t i = 0; i < mHandles.size(); ++i) {
         EffectHandle *handle = mHandles[i];
         if (handle != NULL && !handle->disconnected()) {
-            handle->dumpToBuffer(buffer, SIZE);
+            handle->dumpToBuffer(buffer, sizeof(buffer));
             result.append(buffer);
         }
     }
@@ -1821,14 +1902,8 @@
     if (mInBuffer == NULL) {
         return;
     }
-    // TODO: This will change in the future, depending on multichannel
-    // and sample format changes for effects.
-    // Currently effects processing is only available for stereo, AUDIO_FORMAT_PCM_16_BIT
-    // (4 bytes frame size)
-
     const size_t frameSize =
-            audio_bytes_per_sample(EFFECT_BUFFER_FORMAT)
-            * std::min((uint32_t)FCC_2, thread->channelCount());
+            audio_bytes_per_sample(EFFECT_BUFFER_FORMAT) * thread->channelCount();
 
     memset(mInBuffer->audioBuffer()->raw, 0, thread->frameCount() * frameSize);
     mInBuffer->commit();
diff --git a/services/audioflinger/Effects.h b/services/audioflinger/Effects.h
index eea3208..2327bb9 100644
--- a/services/audioflinger/Effects.h
+++ b/services/audioflinger/Effects.h
@@ -173,6 +173,8 @@
     bool    mSupportsFloat;         // effect supports float processing
     sp<EffectBufferHalInterface> mInConversionBuffer;  // Buffers for HAL conversion if needed.
     sp<EffectBufferHalInterface> mOutConversionBuffer;
+    uint32_t mInChannelCountRequested;
+    uint32_t mOutChannelCountRequested;
 #endif
 };
 
diff --git a/services/audioflinger/FastThread.cpp b/services/audioflinger/FastThread.cpp
index 85865b7..dc15487 100644
--- a/services/audioflinger/FastThread.cpp
+++ b/services/audioflinger/FastThread.cpp
@@ -297,7 +297,8 @@
                     size_t i = mBounds & (mDumpState->mSamplingN - 1);
                     mBounds = (mBounds & 0xFFFF0000) | ((mBounds + 1) & 0xFFFF);
                     if (mFull) {
-                        mBounds += 0x10000;
+                        //mBounds += 0x10000;
+                        __builtin_add_overflow(mBounds, 0x10000, &mBounds);
                     } else if (!(mBounds & (mDumpState->mSamplingN - 1))) {
                         mFull = true;
                     }
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3bb5803..3a41ac8 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1176,6 +1176,7 @@
 
     switch (mType) {
     case MIXER: {
+#ifndef MULTICHANNEL_EFFECT_CHAIN
         // Reject any effect on mixer multichannel sinks.
         // TODO: fix both format and multichannel issues with effects.
         if (mChannelCount != FCC_2) {
@@ -1183,6 +1184,7 @@
                     " thread %s", desc->name, mChannelCount, mThreadName);
             return BAD_VALUE;
         }
+#endif
         audio_output_flags_t flags = mOutput->flags;
         if (hasFastMixer() || (flags & AUDIO_OUTPUT_FLAG_FAST)) {
             if (sessionId == AUDIO_SESSION_OUTPUT_MIX) {
@@ -1229,6 +1231,7 @@
                 desc->name, mThreadName);
         return BAD_VALUE;
     case DUPLICATING:
+#ifndef MULTICHANNEL_EFFECT_CHAIN
         // Reject any effect on mixer multichannel sinks.
         // TODO: fix both format and multichannel issues with effects.
         if (mChannelCount != FCC_2) {
@@ -1236,6 +1239,7 @@
                     " on DUPLICATING thread %s", desc->name, mChannelCount, mThreadName);
             return BAD_VALUE;
         }
+#endif
         if ((sessionId == AUDIO_SESSION_OUTPUT_STAGE) || (sessionId == AUDIO_SESSION_OUTPUT_MIX)) {
             ALOGW("checkEffectCompatibility_l(): global effect %s on DUPLICATING"
                     " thread %s", desc->name, mThreadName);
@@ -4301,10 +4305,16 @@
                     // because we're about to decrement the last sp<> on those tracks.
                     block = FastMixerStateQueue::BLOCK_UNTIL_ACKED;
                 } else {
-                    LOG_ALWAYS_FATAL("fast track %d should have been active; "
+                    // ALOGW rather than LOG_ALWAYS_FATAL because it seems there are cases where an
+                    // AudioTrack may start (which may not be with a start() but with a write()
+                    // after underrun) and immediately paused or released.  In that case the
+                    // FastTrack state hasn't had time to update.
+                    // TODO Remove the ALOGW when this theory is confirmed.
+                    ALOGW("fast track %d should have been active; "
                             "mState=%d, mTrackMask=%#x, recentUnderruns=%u, isShared=%d",
                             j, track->mState, state->mTrackMask, recentUnderruns,
                             track->sharedBuffer() != 0);
+                    // Since the FastMixer state already has the track inactive, do nothing here.
                 }
                 tracksToRemove->add(track);
                 // Avoids a misleading display in dumpsys
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioPort.h b/services/audiopolicy/common/managerdefinitions/include/AudioPort.h
index d520937..caf3c02 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioPort.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioPort.h
@@ -84,12 +84,7 @@
     bool hasDynamicAudioProfile() const { return mProfiles.hasDynamicProfile(); }
 
     // searches for an exact match
-    status_t checkExactAudioProfile(uint32_t samplingRate,
-                                    audio_channel_mask_t channelMask,
-                                    audio_format_t format) const
-    {
-        return mProfiles.checkExactProfile(samplingRate, channelMask, format);
-    }
+    virtual status_t checkExactAudioProfile(const struct audio_port_config *config) const;
 
     // searches for a compatible match, currently implemented for input
     // parameters are input|output, returned value is the best match.
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp
index d6ea698..094ff65 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp
@@ -137,6 +137,26 @@
     }
 }
 
+status_t AudioPort::checkExactAudioProfile(const struct audio_port_config *config) const
+{
+    status_t status = NO_ERROR;
+    auto config_mask = config->config_mask;
+    if (config_mask & AUDIO_PORT_CONFIG_GAIN) {
+        config_mask &= ~AUDIO_PORT_CONFIG_GAIN;
+        status = checkGain(&config->gain, config->gain.index);
+        if (status != NO_ERROR) {
+            return status;
+        }
+    }
+    if (config_mask != 0) {
+        // TODO should we check sample_rate / channel_mask / format separately?
+        status = mProfiles.checkExactProfile(config->sample_rate,
+                                             config->channel_mask,
+                                             config->format);
+    }
+    return status;
+}
+
 void AudioPort::pickSamplingRate(uint32_t &pickedRate,const SampleRateVector &samplingRates) const
 {
     pickedRate = 0;
@@ -388,9 +408,7 @@
         status = NO_INIT;
         goto exit;
     }
-    status = audioport->checkExactAudioProfile(config->sample_rate,
-                                               config->channel_mask,
-                                               config->format);
+    status = audioport->checkExactAudioProfile(config);
     if (status != NO_ERROR) {
         goto exit;
     }
@@ -404,10 +422,6 @@
         mFormat = config->format;
     }
     if (config->config_mask & AUDIO_PORT_CONFIG_GAIN) {
-        status = audioport->checkGain(&config->gain, config->gain.index);
-        if (status != NO_ERROR) {
-            goto exit;
-        }
         mGain = config->gain;
     }
 
diff --git a/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp b/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp
index e8980b5..69dd06b 100644
--- a/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/IOProfile.cpp
@@ -71,7 +71,13 @@
             return false;
         }
     } else {
-        if (checkExactAudioProfile(samplingRate, channelMask, format) != NO_ERROR) {
+        const struct audio_port_config config = {
+            .config_mask = AUDIO_PORT_CONFIG_ALL & ~AUDIO_PORT_CONFIG_GAIN,
+            .sample_rate = samplingRate,
+            .channel_mask = channelMask,
+            .format = format,
+        };
+        if (checkExactAudioProfile(&config) != NO_ERROR) {
             return false;
         }
     }
diff --git a/services/camera/libcameraservice/Android.mk b/services/camera/libcameraservice/Android.mk
index aeaca48..7b86180 100644
--- a/services/camera/libcameraservice/Android.mk
+++ b/services/camera/libcameraservice/Android.mk
@@ -60,6 +60,7 @@
 LOCAL_SHARED_LIBRARIES:= \
     libui \
     liblog \
+    libutilscallstack \
     libutils \
     libbinder \
     libcutils \
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index ae3bbc1..2ff200d 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -643,6 +643,14 @@
             dprintf(fd, "  API2 camera characteristics:\n");
             info2.dump(fd, /*verbosity*/ 2, /*indentation*/ 4);
         }
+
+        dprintf(fd, "== Camera HAL device %s (v%d.%d) dumpState: ==\n", device->mName.c_str(),
+                device->mVersion.get_major(), device->mVersion.get_minor());
+        res = device->dumpState(fd);
+        if (res != OK) {
+            dprintf(fd, "   <Error dumping device %s state: %s (%d)>\n",
+                    device->mName.c_str(), strerror(-res), res);
+        }
     }
     return OK;
 }
@@ -908,6 +916,17 @@
     return OK;
 }
 
+status_t CameraProviderManager::ProviderInfo::DeviceInfo1::dumpState(int fd) const {
+    native_handle_t* handle = native_handle_create(1,0);
+    handle->data[0] = fd;
+    hardware::Return<Status> s = mInterface->dumpState(handle);
+    native_handle_delete(handle);
+    if (!s.isOk()) {
+        return INVALID_OPERATION;
+    }
+    return mapToStatusT(s);
+}
+
 CameraProviderManager::ProviderInfo::DeviceInfo3::DeviceInfo3(const std::string& name,
         const metadata_vendor_id_t tagId, const std::string &id,
         uint16_t minorVersion,
@@ -1011,6 +1030,17 @@
     return isBackwardCompatible;
 }
 
+status_t CameraProviderManager::ProviderInfo::DeviceInfo3::dumpState(int fd) const {
+    native_handle_t* handle = native_handle_create(1,0);
+    handle->data[0] = fd;
+    auto ret = mInterface->dumpState(handle);
+    native_handle_delete(handle);
+    if (!ret.isOk()) {
+        return INVALID_OPERATION;
+    }
+    return OK;
+}
+
 status_t CameraProviderManager::ProviderInfo::DeviceInfo3::getCameraCharacteristics(
         CameraMetadata *characteristics) const {
     if (characteristics == nullptr) return BAD_VALUE;
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.h b/services/camera/libcameraservice/common/CameraProviderManager.h
index e82282f..0f1f07b 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.h
+++ b/services/camera/libcameraservice/common/CameraProviderManager.h
@@ -293,6 +293,7 @@
             virtual status_t setTorchMode(bool enabled) = 0;
             virtual status_t getCameraInfo(hardware::CameraInfo *info) const = 0;
             virtual bool isAPI1Compatible() const = 0;
+            virtual status_t dumpState(int fd) const = 0;
             virtual status_t getCameraCharacteristics(CameraMetadata *characteristics) const {
                 (void) characteristics;
                 return INVALID_OPERATION;
@@ -326,6 +327,7 @@
             virtual status_t getCameraInfo(hardware::CameraInfo *info) const override;
             //In case of Device1Info assume that we are always API1 compatible
             virtual bool isAPI1Compatible() const override { return true; }
+            virtual status_t dumpState(int fd) const override;
             DeviceInfo1(const std::string& name, const metadata_vendor_id_t tagId,
                     const std::string &id, uint16_t minorVersion,
                     const hardware::camera::common::V1_0::CameraResourceCost& resourceCost,
@@ -343,6 +345,7 @@
             virtual status_t setTorchMode(bool enabled) override;
             virtual status_t getCameraInfo(hardware::CameraInfo *info) const override;
             virtual bool isAPI1Compatible() const override;
+            virtual status_t dumpState(int fd) const override;
             virtual status_t getCameraCharacteristics(
                     CameraMetadata *characteristics) const override;
 
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 7f42b1b..2954b3b 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -389,7 +389,7 @@
     nsecs_t ts_since = 0;
     String16 helpOption("-help");
     String16 onlyOption("-only");
-    AString only;
+    std::string only;
     int n = args.size();
 
     for (int i = 0; i < n; i++) {
@@ -553,7 +553,7 @@
                 if (only != NULL && strcmp(only, (*it)->getKey()) != 0) {
                     ALOGV("Told to omit '%s'", (*it)->getKey());
                 }
-                AString distilled = (*it)->dumpSummary(slot, only);
+                std::string distilled = (*it)->dumpSummary(slot, only);
                 result.append(distilled.c_str());
             }
         }
@@ -605,7 +605,7 @@
                 ALOGV("Omit '%s', it's not '%s'", (*it)->getKey().c_str(), only);
                 continue;
             }
-            AString entry = (*it)->toString(mDumpProto);
+            std::string entry = (*it)->toString(mDumpProto);
             result.appendFormat("%5d: %s\n", slot, entry.c_str());
             slot++;
         }
@@ -746,7 +746,7 @@
     }
 }
 
-static AString allowedKeys[] =
+static std::string allowedKeys[] =
 {
     "codec",
     "extractor"
@@ -760,7 +760,7 @@
     // untrusted uids can only send us a limited set of keys
     if (isTrusted == false) {
         // restrict to a specific set of keys
-        AString key = item->getKey();
+        std::string key = item->getKey();
 
         size_t i;
         for(i = 0; i < nAllowedKeys; i++) {
@@ -854,7 +854,7 @@
             return setPkgInfo(item, uid, setName, setVersion);
         }
     } else {
-        AString pkg;
+        std::string pkg;
         std::string installer = "";
         int64_t versionCode = 0;
 
@@ -896,7 +896,7 @@
             }
 
             // strip any leading "shared:" strings that came back
-            if (pkg.startsWith("shared:")) {
+            if (pkg.compare(0, 7, "shared:") == 0) {
                 pkg.erase(0, 7);
             }
 
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index fce7d08..1287835 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -136,8 +136,8 @@
     // mapping uids to package names
     struct UidToPkgMap {
         uid_t uid;
-        AString pkg;
-        AString installer;
+        std::string pkg;
+        std::string installer;
         int64_t versionCode;
         nsecs_t expiration;
     };
diff --git a/services/mediaanalytics/MetricsSummarizer.cpp b/services/mediaanalytics/MetricsSummarizer.cpp
index 93fe0ec..e7c26e3 100644
--- a/services/mediaanalytics/MetricsSummarizer.cpp
+++ b/services/mediaanalytics/MetricsSummarizer.cpp
@@ -19,6 +19,7 @@
 
 #include <stdlib.h>
 #include <stdint.h>
+#include <string>
 #include <inttypes.h>
 
 #include <utils/threads.h>
@@ -87,21 +88,21 @@
 {
     if (mKey == NULL)
         return true;
-    AString itemKey = item.getKey();
+    std::string itemKey = item.getKey();
     if (strcmp(mKey, itemKey.c_str()) != 0) {
         return false;
     }
     return true;
 }
 
-AString MetricsSummarizer::dumpSummary(int &slot)
+std::string MetricsSummarizer::dumpSummary(int &slot)
 {
     return dumpSummary(slot, NULL);
 }
 
-AString MetricsSummarizer::dumpSummary(int &slot, const char *only)
+std::string MetricsSummarizer::dumpSummary(int &slot, const char *only)
 {
-    AString value = "";
+    std::string value;
 
     List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
     if (it != mSummaries->end()) {
@@ -110,7 +111,7 @@
             if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
                 continue;
             }
-            AString entry = (*it)->toString();
+            std::string entry = (*it)->toString();
             snprintf(buf, sizeof(buf), "%5d: ", slot);
             value.append(buf);
             value.append(entry.c_str());
diff --git a/services/mediaanalytics/MetricsSummarizer.h b/services/mediaanalytics/MetricsSummarizer.h
index a9f0786..a16c7bc 100644
--- a/services/mediaanalytics/MetricsSummarizer.h
+++ b/services/mediaanalytics/MetricsSummarizer.h
@@ -18,10 +18,10 @@
 #ifndef ANDROID_METRICSSUMMARIZER_H
 #define ANDROID_METRICSSUMMARIZER_H
 
+#include <string>
 #include <utils/threads.h>
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
-#include <utils/String8.h>
 #include <utils/List.h>
 
 #include <media/IMediaAnalyticsService.h>
@@ -49,8 +49,8 @@
     virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
 
     // dump the summarized records (for dumpsys)
-    AString dumpSummary(int &slot);
-    AString dumpSummary(int &slot, const char *only);
+    std::string dumpSummary(int &slot);
+    std::string dumpSummary(int &slot, const char *only);
 
     void setIgnorables(const char **);
     const char **getIgnorables();
diff --git a/services/mediacodec/Android.mk b/services/mediacodec/Android.mk
index ca31691..8e5b260 100644
--- a/services/mediacodec/Android.mk
+++ b/services/mediacodec/Android.mk
@@ -2,7 +2,11 @@
 
 # service executable
 include $(CLEAR_VARS)
+# seccomp is not required for coverage build.
+ifneq ($(NATIVE_COVERAGE),true)
 LOCAL_REQUIRED_MODULES_arm := mediacodec.policy
+LOCAL_REQUIRED_MODULES_x86 := mediacodec.policy
+endif
 LOCAL_SRC_FILES := main_codecservice.cpp
 LOCAL_SHARED_LIBRARIES := \
     libmedia_omx \
@@ -28,7 +32,7 @@
 include $(BUILD_EXECUTABLE)
 
 # service seccomp policy
-ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), arm arm64))
+ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), x86 x86_64 arm arm64))
 include $(CLEAR_VARS)
 LOCAL_MODULE := mediacodec.policy
 LOCAL_MODULE_CLASS := ETC
diff --git a/services/mediacodec/seccomp_policy/mediacodec-x86.policy b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
new file mode 100644
index 0000000..dc2c04f
--- /dev/null
+++ b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
@@ -0,0 +1,69 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+read: 1
+mprotect: 1
+prctl: 1
+openat: 1
+getuid32: 1
+writev: 1
+ioctl: 1
+close: 1
+mmap2: 1
+fstat64: 1
+madvise: 1
+fstatat64: 1
+futex: 1
+munmap: 1
+faccessat: 1
+_llseek: 1
+lseek: 1
+clone: 1
+sigaltstack: 1
+setpriority: 1
+restart_syscall: 1
+exit: 1
+exit_group: 1
+rt_sigreturn: 1
+ugetrlimit: 1
+readlinkat: 1
+_llseek: 1
+fstatfs64: 1
+pread64: 1
+mremap: 1
+dup: 1
+set_tid_address: 1
+write: 1
+nanosleep: 1
+
+# for attaching to debuggerd on process crash
+socketcall: 1
+sigaction: 1
+tgkill: 1
+rt_sigprocmask: 1
+fcntl64: 1
+rt_tgsigqueueinfo: 1
+geteuid32: 1
+getgid32: 1
+getegid32: 1
+getgroups32: 1
+getdents64: 1
+pipe2: 1
+ppoll: 1
+
+# Required by AddressSanitizer
+gettid: 1
+sched_yield: 1
+getpid: 1
+gettid: 1
diff --git a/services/mediaextractor/Android.mk b/services/mediaextractor/Android.mk
index cd086f9..4980316 100644
--- a/services/mediaextractor/Android.mk
+++ b/services/mediaextractor/Android.mk
@@ -2,8 +2,11 @@
 
 # service library
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := MediaExtractorService.cpp
 LOCAL_CFLAGS := -Wall -Werror
+LOCAL_SRC_FILES := \
+    MediaExtractorService.cpp \
+    MediaExtractorUpdateService.cpp \
+
 LOCAL_SHARED_LIBRARIES := libmedia libstagefright libbinder libutils liblog
 LOCAL_MODULE:= libmediaextractorservice
 include $(BUILD_SHARED_LIBRARY)
@@ -18,16 +21,7 @@
 
 # extractor libraries
 LOCAL_REQUIRED_MODULES := \
-    libaacextractor \
-    libamrextractor \
-    libflacextractor \
-    libmidiextractor \
-    libmkvextractor \
-    libmp3extractor \
-    libmp4extractor \
-    libmpeg2extractor \
-    liboggextractor \
-    libwavextractor \
+    MediaComponents \
 
 LOCAL_SRC_FILES := main_extractorservice.cpp
 LOCAL_SHARED_LIBRARIES := libmedia libmediaextractorservice libbinder libutils \
diff --git a/services/mediaextractor/MediaExtractorService.cpp b/services/mediaextractor/MediaExtractorService.cpp
index f09d7cf..f0f44f5 100644
--- a/services/mediaextractor/MediaExtractorService.cpp
+++ b/services/mediaextractor/MediaExtractorService.cpp
@@ -36,16 +36,15 @@
 
     sp<DataSource> localSource = CreateDataSourceFromIDataSource(remoteSource);
 
-    sp<MediaExtractor> extractor = MediaExtractorFactory::CreateFromService(localSource, mime);
+    sp<IMediaExtractor> extractor = MediaExtractorFactory::CreateFromService(localSource, mime);
 
     ALOGV("extractor service created %p (%s)",
             extractor.get(),
             extractor == nullptr ? "" : extractor->name());
 
     if (extractor != nullptr) {
-        sp<IMediaExtractor> ret = CreateIMediaExtractorFromMediaExtractor(extractor);
-        registerMediaExtractor(ret, localSource, mime);
-        return ret;
+        registerMediaExtractor(extractor, localSource, mime);
+        return extractor;
     }
     return nullptr;
 }
@@ -57,7 +56,7 @@
 }
 
 status_t MediaExtractorService::dump(int fd, const Vector<String16>& args) {
-    return dumpExtractors(fd, args);
+    return MediaExtractorFactory::dump(fd, args) || dumpExtractors(fd, args);
 }
 
 status_t MediaExtractorService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
diff --git a/services/mediaextractor/MediaExtractorUpdateService.cpp b/services/mediaextractor/MediaExtractorUpdateService.cpp
new file mode 100644
index 0000000..473a698
--- /dev/null
+++ b/services/mediaextractor/MediaExtractorUpdateService.cpp
@@ -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.
+ */
+
+#define LOG_TAG "MediaExtractorUpdateService"
+#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <media/stagefright/MediaExtractorFactory.h>
+
+#include "MediaExtractorUpdateService.h"
+
+namespace android {
+namespace media {
+
+binder::Status MediaExtractorUpdateService::loadPlugins(const ::std::string& apkPath) {
+    ALOGV("loadPlugins %s", apkPath.c_str());
+    MediaExtractorFactory::LoadPlugins(apkPath);
+    return binder::Status::ok();
+}
+
+}   // namespace media
+}   // namespace android
diff --git a/services/mediaextractor/MediaExtractorUpdateService.h b/services/mediaextractor/MediaExtractorUpdateService.h
new file mode 100644
index 0000000..4115f6d
--- /dev/null
+++ b/services/mediaextractor/MediaExtractorUpdateService.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_EXTRACTOR_UPDATE_SERVICE_H
+#define ANDROID_MEDIA_EXTRACTOR_UPDATE_SERVICE_H
+
+#include <binder/BinderService.h>
+#include <android/media/BnMediaExtractorUpdateService.h>
+
+namespace android {
+namespace media {
+
+class MediaExtractorUpdateService
+    : public BinderService<MediaExtractorUpdateService>, public BnMediaExtractorUpdateService
+{
+    friend class BinderService<MediaExtractorUpdateService>;
+public:
+    MediaExtractorUpdateService() : BnMediaExtractorUpdateService() { }
+    virtual ~MediaExtractorUpdateService() { }
+    static const char* getServiceName() { return "media.extractor.update"; }
+    binder::Status loadPlugins(const ::std::string& apkPath);
+};
+
+}   // namespace media
+}   // namespace android
+
+#endif  // ANDROID_MEDIA_EXTRACTOR_UPDATE_SERVICE_H
diff --git a/services/mediaextractor/main_extractorservice.cpp b/services/mediaextractor/main_extractorservice.cpp
index 6a5320d..0dc5e29 100644
--- a/services/mediaextractor/main_extractorservice.cpp
+++ b/services/mediaextractor/main_extractorservice.cpp
@@ -30,6 +30,7 @@
 // from LOCAL_C_INCLUDES
 #include "IcuUtils.h"
 #include "MediaExtractorService.h"
+#include "MediaExtractorUpdateService.h"
 #include "MediaUtils.h"
 #include "minijail.h"
 
@@ -63,6 +64,16 @@
     sp<ProcessState> proc(ProcessState::self());
     sp<IServiceManager> sm = defaultServiceManager();
     MediaExtractorService::instantiate();
+
+    // TODO: Uncomment below once sepolicy change is landed.
+    /*
+    char value[PROPERTY_VALUE_MAX];
+    property_get("ro.build.type", value, "unknown");
+    if (strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0) {
+        media::MediaExtractorUpdateService::instantiate();
+    }
+    */
+
     ProcessState::self()->startThreadPool();
     IPCThreadState::self()->joinThreadPool();
 }
diff --git a/services/oboeservice/AAudioEndpointManager.cpp b/services/oboeservice/AAudioEndpointManager.cpp
index b0c3771..7b8d817 100644
--- a/services/oboeservice/AAudioEndpointManager.cpp
+++ b/services/oboeservice/AAudioEndpointManager.cpp
@@ -102,8 +102,8 @@
         }
     }
 
-    ALOGV("findExclusiveEndpoint_l(), found %p for device = %d",
-          endpoint.get(), configuration.getDeviceId());
+    ALOGV("findExclusiveEndpoint_l(), found %p for device = %d, sessionId = %d",
+          endpoint.get(), configuration.getDeviceId(), configuration.getSessionId());
     return endpoint;
 }
 
@@ -118,8 +118,8 @@
         }
     }
 
-    ALOGV("findSharedEndpoint_l(), found %p for device = %d",
-          endpoint.get(), configuration.getDeviceId());
+    ALOGV("findSharedEndpoint_l(), found %p for device = %d, sessionId = %d",
+          endpoint.get(), configuration.getDeviceId(), configuration.getSessionId());
     return endpoint;
 }
 
@@ -151,19 +151,17 @@
         return nullptr;
     } else {
         sp<AAudioServiceEndpointMMAP> endpointMMap = new AAudioServiceEndpointMMAP();
-        ALOGD("openEndpoint(),created MMAP %p", endpointMMap.get());
+        ALOGD("openExclusiveEndpoint(), no match so try to open MMAP %p for dev %d",
+              endpointMMap.get(), configuration.getDeviceId());
         endpoint = endpointMMap;
 
         aaudio_result_t result = endpoint->open(request);
         if (result != AAUDIO_OK) {
-            ALOGE("openEndpoint(), open failed");
+            ALOGE("openExclusiveEndpoint(), open failed");
             endpoint.clear();
         } else {
             mExclusiveStreams.push_back(endpointMMap);
         }
-
-        ALOGD("openEndpoint(), created %p for device = %d",
-              endpoint.get(), configuration.getDeviceId());
     }
 
     if (endpoint.get() != nullptr) {
@@ -209,7 +207,7 @@
                 mSharedStreams.push_back(endpoint);
             }
         }
-        ALOGD("openSharedEndpoint(), created %p for device = %d, dir = %d",
+        ALOGD("openSharedEndpoint(), created %p, requested device = %d, dir = %d",
               endpoint.get(), configuration.getDeviceId(), (int)direction);
         IPCThreadState::self()->restoreCallingIdentity(token);
     }
diff --git a/services/oboeservice/AAudioEndpointManager.h b/services/oboeservice/AAudioEndpointManager.h
index 32c8454..f6aeb5a 100644
--- a/services/oboeservice/AAudioEndpointManager.h
+++ b/services/oboeservice/AAudioEndpointManager.h
@@ -47,7 +47,7 @@
     std::string dump() const;
 
     /**
-     * Find a service endpoint for the given deviceId and direction.
+     * Find a service endpoint for the given deviceId, sessionId and direction.
      * If an endpoint does not already exist then try to create one.
      *
      * @param audioService
diff --git a/services/oboeservice/AAudioServiceEndpoint.cpp b/services/oboeservice/AAudioServiceEndpoint.cpp
index f917675..33439fc 100644
--- a/services/oboeservice/AAudioServiceEndpoint.cpp
+++ b/services/oboeservice/AAudioServiceEndpoint.cpp
@@ -55,11 +55,16 @@
 
     result << "    Direction:            " << ((getDirection() == AAUDIO_DIRECTION_OUTPUT)
                                    ? "OUTPUT" : "INPUT") << "\n";
-    result << "    Sample Rate:          " << getSampleRate() << "\n";
-    result << "    Frames Per Burst:     " << mFramesPerBurst << "\n";
-    result << "    Reference Count:      " << mOpenCount << "\n";
     result << "    Requested Device Id:  " << mRequestedDeviceId << "\n";
     result << "    Device Id:            " << getDeviceId() << "\n";
+    result << "    Sample Rate:          " << getSampleRate() << "\n";
+    result << "    Channel Count:        " << getSamplesPerFrame() << "\n";
+    result << "    Frames Per Burst:     " << mFramesPerBurst << "\n";
+    result << "    Usage:                " << getUsage() << "\n";
+    result << "    ContentType:          " << getContentType() << "\n";
+    result << "    InputPreset:          " << getInputPreset() << "\n";
+    result << "    Reference Count:      " << mOpenCount << "\n";
+    result << "    Session Id:           " << getSessionId() << "\n";
     result << "    Connected:            " << mConnected.load() << "\n";
     result << "    Registered Streams:" << "\n";
     result << AAudioServiceStreamShared::dumpHeader() << "\n";
@@ -109,6 +114,10 @@
         configuration.getDeviceId() != getDeviceId()) {
         return false;
     }
+    if (configuration.getSessionId() != AAUDIO_SESSION_ID_ALLOCATE &&
+        configuration.getSessionId() != getSessionId()) {
+        return false;
+    }
     if (configuration.getSampleRate() != AAUDIO_UNSPECIFIED &&
         configuration.getSampleRate() != getSampleRate()) {
         return false;
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.cpp b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
index a61994d..db01c88 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.cpp
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
@@ -72,13 +72,6 @@
 
 aaudio_result_t AAudioServiceEndpointMMAP::open(const aaudio::AAudioStreamRequest &request) {
     aaudio_result_t result = AAUDIO_OK;
-    const audio_attributes_t attributes = {
-            .content_type = AUDIO_CONTENT_TYPE_MUSIC,
-            .usage = AUDIO_USAGE_MEDIA,
-            .source = AUDIO_SOURCE_VOICE_RECOGNITION,
-            .flags = AUDIO_FLAG_LOW_LATENCY,
-            .tags = ""
-    };
     audio_config_base_t config;
     audio_port_handle_t deviceId;
 
@@ -87,6 +80,27 @@
 
     copyFrom(request.getConstantConfiguration());
 
+    aaudio_direction_t direction = getDirection();
+
+    const audio_content_type_t contentType =
+            AAudioConvert_contentTypeToInternal(getContentType());
+    const audio_usage_t usage = (direction == AAUDIO_DIRECTION_OUTPUT)
+            ? AAudioConvert_usageToInternal(getUsage())
+            : AUDIO_USAGE_UNKNOWN;
+    const audio_source_t source = (direction == AAUDIO_DIRECTION_INPUT)
+            ? AAudioConvert_inputPresetToAudioSource(getInputPreset())
+            : AUDIO_SOURCE_DEFAULT;
+
+    const audio_attributes_t attributes = {
+            .content_type = contentType,
+            .usage = usage,
+            .source = source,
+            .flags = AUDIO_FLAG_LOW_LATENCY,
+            .tags = ""
+    };
+    ALOGV("open() MMAP attributes.usage = %d, content_type = %d, source = %d",
+          attributes.usage, attributes.content_type, attributes.source);
+
     mMmapClient.clientUid = request.getUserId();
     mMmapClient.clientPid = request.getProcessId();
     mMmapClient.packageName.setTo(String16(""));
@@ -108,7 +122,6 @@
 
     int32_t aaudioSamplesPerFrame = getSamplesPerFrame();
 
-    aaudio_direction_t direction = getDirection();
     if (direction == AAUDIO_DIRECTION_OUTPUT) {
         config.channel_mask = (aaudioSamplesPerFrame == AAUDIO_UNSPECIFIED)
                               ? AUDIO_CHANNEL_OUT_STEREO
@@ -131,12 +144,16 @@
             ? MmapStreamInterface::DIRECTION_OUTPUT
             : MmapStreamInterface::DIRECTION_INPUT;
 
+    aaudio_session_id_t requestedSessionId = getSessionId();
+    audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
+
     // Open HAL stream. Set mMmapStream
     status_t status = MmapStreamInterface::openMmapStream(streamDirection,
                                                           &attributes,
                                                           &config,
                                                           mMmapClient,
                                                           &deviceId,
+                                                          &sessionId,
                                                           this, // callback
                                                           mMmapStream,
                                                           &mPortHandle);
@@ -152,6 +169,17 @@
     }
     setDeviceId(deviceId);
 
+    if (sessionId == AUDIO_SESSION_ALLOCATE) {
+        ALOGW("open() - openMmapStream() failed to set sessionId");
+    }
+
+    aaudio_session_id_t actualSessionId =
+            (requestedSessionId == AAUDIO_SESSION_ID_NONE)
+            ? AAUDIO_SESSION_ID_NONE
+            : (aaudio_session_id_t) sessionId;
+    setSessionId(actualSessionId);
+    ALOGD("open() deviceId = %d, sessionId = %d", getDeviceId(), getSessionId());
+
     // Create MMAP/NOIRQ buffer.
     int32_t minSizeFrames = getBufferCapacity();
     if (minSizeFrames <= 0) { // zero will get rejected
diff --git a/services/oboeservice/AAudioServiceEndpointShared.cpp b/services/oboeservice/AAudioServiceEndpointShared.cpp
index 6af9e7e..584efe5 100644
--- a/services/oboeservice/AAudioServiceEndpointShared.cpp
+++ b/services/oboeservice/AAudioServiceEndpointShared.cpp
@@ -60,18 +60,16 @@
     aaudio_result_t result = AAUDIO_OK;
     const AAudioStreamConfiguration &configuration = request.getConstantConfiguration();
 
+    copyFrom(configuration);
     mRequestedDeviceId = configuration.getDeviceId();
-    setDirection(configuration.getDirection());
 
     AudioStreamBuilder builder;
+    builder.copyFrom(configuration);
+
     builder.setSharingMode(AAUDIO_SHARING_MODE_EXCLUSIVE);
     // Don't fall back to SHARED because that would cause recursion.
     builder.setSharingModeMatchRequired(true);
-    builder.setDeviceId(mRequestedDeviceId);
-    builder.setFormat(configuration.getFormat());
-    builder.setSampleRate(configuration.getSampleRate());
-    builder.setSamplesPerFrame(configuration.getSamplesPerFrame());
-    builder.setDirection(configuration.getDirection());
+
     builder.setBufferCapacity(DEFAULT_BUFFER_CAPACITY);
 
     result = mStreamInternal->open(builder);
@@ -79,6 +77,8 @@
     setSampleRate(mStreamInternal->getSampleRate());
     setSamplesPerFrame(mStreamInternal->getSamplesPerFrame());
     setDeviceId(mStreamInternal->getDeviceId());
+    setSessionId(mStreamInternal->getSessionId());
+    ALOGD("open() deviceId = %d, sessionId = %d", getDeviceId(), getSessionId());
     mFramesPerBurst = mStreamInternal->getFramesPerBurst();
 
     return result;
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index 53d2860..864fc35 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -42,7 +42,7 @@
 
 AAudioServiceStreamBase::AAudioServiceStreamBase(AAudioService &audioService)
         : mUpMessageQueue(nullptr)
-        , mTimestampThread()
+        , mTimestampThread("AATime")
         , mAtomicTimestamp()
         , mAudioService(audioService) {
     mMmapClient.clientUid = -1;
diff --git a/services/oboeservice/AAudioThread.cpp b/services/oboeservice/AAudioThread.cpp
index fbb0da4..ed7895b 100644
--- a/services/oboeservice/AAudioThread.cpp
+++ b/services/oboeservice/AAudioThread.cpp
@@ -27,12 +27,26 @@
 
 using namespace aaudio;
 
+std::atomic<uint32_t> AAudioThread::mNextThreadIndex{1};
 
-AAudioThread::AAudioThread()
-    : mRunnable(nullptr)
-    , mHasThread(false) {
+AAudioThread::AAudioThread(const char *prefix) {
+    setup(prefix);
+}
+
+AAudioThread::AAudioThread() {
+    setup("AAudio");
+}
+
+void AAudioThread::setup(const char *prefix) {
     // mThread is a pthread_t of unknown size so we need memset().
     memset(&mThread, 0, sizeof(mThread));
+
+    // Name the thread with an increasing index, "prefix_#", for debugging.
+    uint32_t index = mNextThreadIndex++;
+    // Wrap the index so that we do not hit the 16 char limit
+    // and to avoid hard-to-read large numbers.
+    index = index % 100000; // arbitrary
+    snprintf(mName, sizeof(mName), "%s_%u", prefix, index);
 }
 
 void AAudioThread::dispatch() {
@@ -64,6 +78,8 @@
         ALOGE("start() - pthread_create() returned %d %s", err, strerror(err));
         return AAudioConvert_androidToAAudioResult(-err);
     } else {
+        int err = pthread_setname_np(mThread, mName);
+        ALOGW_IF((err != 0), "Could not set name of AAudioThread. err = %d", err);
         mHasThread = true;
         return AAUDIO_OK;
     }
diff --git a/services/oboeservice/AAudioThread.h b/services/oboeservice/AAudioThread.h
index 02f1459..ffc9b7b 100644
--- a/services/oboeservice/AAudioThread.h
+++ b/services/oboeservice/AAudioThread.h
@@ -43,7 +43,9 @@
 {
 public:
     AAudioThread();
-    AAudioThread(Runnable *runnable);
+
+    AAudioThread(const char *prefix);
+
     virtual ~AAudioThread() = default;
 
     /**
@@ -66,10 +68,15 @@
     void dispatch(); // called internally from 'C' thread wrapper
 
 private:
-    Runnable    *mRunnable;
-    bool         mHasThread;
+
+    void setup(const char *prefix);
+
+    Runnable    *mRunnable = nullptr;
+    bool         mHasThread = false;
     pthread_t    mThread; // initialized in constructor
 
+    static std::atomic<uint32_t> mNextThreadIndex;
+    char         mName[16]; // max length for a pthread_name
 };
 
 } /* namespace aaudio */