Snap for 4562879 from d403c2a4600f4b1fa7307a40bca35e0b1e8a0f76 to pi-release

Change-Id: I048ef2456de8143c8a31924813250a469d8193bf
diff --git a/cmds/stagefright/audioloop.cpp b/cmds/stagefright/audioloop.cpp
index 5500d24..fc24646 100644
--- a/cmds/stagefright/audioloop.cpp
+++ b/cmds/stagefright/audioloop.cpp
@@ -33,7 +33,6 @@
 #include <media/stagefright/AudioSource.h>
 #include <media/stagefright/MediaCodecSource.h>
 #include <media/stagefright/MediaDefs.h>
-#include <media/stagefright/MetaData.h>
 #include <media/stagefright/SimpleDecodingSource.h>
 #include "SineSource.h"
 
diff --git a/drm/libmediadrm/Android.bp b/drm/libmediadrm/Android.bp
index 0c14201..2e8da4b 100644
--- a/drm/libmediadrm/Android.bp
+++ b/drm/libmediadrm/Android.bp
@@ -2,7 +2,9 @@
 // libmediadrm
 //
 
-cc_library_shared {
+// TODO: change it back to cc_library_shared when MediaPlayer2 switches to
+// using NdkMediaDrm, instead of MediaDrm.java.
+cc_library {
     name: "libmediadrm",
 
 
@@ -34,6 +36,7 @@
         "libstagefright_foundation",
         "libutils",
         "android.hardware.drm@1.0",
+        "android.hardware.drm@1.1",
         "libhidlallocatorutils",
         "libhidlbase",
         "libhidltransport",
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp
index 31344c3..c4b197c 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -37,17 +37,15 @@
 #include <media/stagefright/foundation/hexdump.h>
 #include <media/stagefright/MediaErrors.h>
 
-using ::android::hardware::drm::V1_0::EventType;
-using ::android::hardware::drm::V1_0::IDrmFactory;
-using ::android::hardware::drm::V1_0::IDrmPlugin;
-using ::android::hardware::drm::V1_0::KeyedVector;
-using ::android::hardware::drm::V1_0::KeyRequestType;
-using ::android::hardware::drm::V1_0::KeyStatus;
-using ::android::hardware::drm::V1_0::KeyStatusType;
-using ::android::hardware::drm::V1_0::KeyType;
-using ::android::hardware::drm::V1_0::KeyValue;
-using ::android::hardware::drm::V1_0::SecureStop;
-using ::android::hardware::drm::V1_0::Status;
+using drm::V1_0::KeyedVector;
+using drm::V1_0::KeyRequestType;
+using drm::V1_0::KeyStatusType;
+using drm::V1_0::KeyType;
+using drm::V1_0::KeyValue;
+using drm::V1_1::HdcpLevel;;
+using drm::V1_0::SecureStop;
+using drm::V1_1::SecurityLevel;
+using drm::V1_0::Status;
 using ::android::hardware::hidl_array;
 using ::android::hardware::hidl_string;
 using ::android::hardware::hidl_vec;
@@ -58,6 +56,8 @@
 
 namespace android {
 
+#define INIT_CHECK() {if (mInitCheck != OK) return mInitCheck;}
+
 static inline int getCallingPid() {
     return IPCThreadState::self()->getCallingPid();
 }
@@ -89,6 +89,42 @@
     return hidl_string(string.string());
 }
 
+static DrmPlugin::SecurityLevel toSecurityLevel(SecurityLevel level) {
+    switch(level) {
+    case SecurityLevel::SW_SECURE_CRYPTO:
+        return DrmPlugin::kSecurityLevelSwSecureCrypto;
+    case SecurityLevel::SW_SECURE_DECODE:
+        return DrmPlugin::kSecurityLevelSwSecureDecode;
+    case SecurityLevel::HW_SECURE_CRYPTO:
+        return DrmPlugin::kSecurityLevelHwSecureCrypto;
+    case SecurityLevel::HW_SECURE_DECODE:
+        return DrmPlugin::kSecurityLevelHwSecureDecode;
+    case SecurityLevel::HW_SECURE_ALL:
+        return DrmPlugin::kSecurityLevelHwSecureAll;
+    default:
+        return DrmPlugin::kSecurityLevelUnknown;
+    }
+}
+
+static DrmPlugin::HdcpLevel toHdcpLevel(HdcpLevel level) {
+    switch(level) {
+    case HdcpLevel::HDCP_NONE:
+        return DrmPlugin::kHdcpNone;
+    case HdcpLevel::HDCP_V1:
+        return DrmPlugin::kHdcpV1;
+    case HdcpLevel::HDCP_V2:
+        return DrmPlugin::kHdcpV2;
+    case HdcpLevel::HDCP_V2_1:
+        return DrmPlugin::kHdcpV2_1;
+    case HdcpLevel::HDCP_V2_2:
+        return DrmPlugin::kHdcpV2_2;
+    case HdcpLevel::HDCP_NO_OUTPUT:
+        return DrmPlugin::kHdcpNoOutput;
+    default:
+        return DrmPlugin::kHdcpLevelUnknown;
+    }
+}
+
 
 static ::KeyedVector toHidlKeyedVector(const KeyedVector<String8, String8>&
         keyedVector) {
@@ -407,6 +443,7 @@
     for (size_t i = 0; i < mFactories.size(); i++) {
         if (mFactories[i]->isCryptoSchemeSupported(uuid)) {
             mPlugin = makeDrmPlugin(mFactories[i], uuid, appPackageName);
+            mPluginV1_1 = drm::V1_1::IDrmPlugin::castFrom(mPlugin);
         }
     }
 
@@ -425,9 +462,7 @@
 
 status_t DrmHal::destroyPlugin() {
     Mutex::Autolock autoLock(mLock);
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     closeOpenSessions();
     reportMetrics();
@@ -445,10 +480,7 @@
 
 status_t DrmHal::openSession(Vector<uint8_t> &sessionId) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     status_t  err = UNKNOWN_ERROR;
 
@@ -491,10 +523,7 @@
 
 status_t DrmHal::closeSession(Vector<uint8_t> const &sessionId) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     Return<Status> status = mPlugin->closeSession(toHidlVec(sessionId));
     if (status.isOk()) {
@@ -519,10 +548,7 @@
         String8> const &optionalParameters, Vector<uint8_t> &request,
         String8 &defaultUrl, DrmPlugin::KeyRequestType *keyRequestType) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -574,10 +600,7 @@
 status_t DrmHal::provideKeyResponse(Vector<uint8_t> const &sessionId,
         Vector<uint8_t> const &response, Vector<uint8_t> &keySetId) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -598,10 +621,7 @@
 
 status_t DrmHal::removeKeys(Vector<uint8_t> const &keySetId) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     return toStatusT(mPlugin->removeKeys(toHidlVec(keySetId)));
 }
@@ -609,10 +629,7 @@
 status_t DrmHal::restoreKeys(Vector<uint8_t> const &sessionId,
         Vector<uint8_t> const &keySetId) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -623,10 +640,7 @@
 status_t DrmHal::queryKeyStatus(Vector<uint8_t> const &sessionId,
         KeyedVector<String8, String8> &infoMap) const {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -650,10 +664,7 @@
         String8 const &certAuthority, Vector<uint8_t> &request,
         String8 &defaultUrl) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     status_t err = UNKNOWN_ERROR;
 
@@ -675,10 +686,7 @@
 status_t DrmHal::provideProvisionResponse(Vector<uint8_t> const &response,
         Vector<uint8_t> &certificate, Vector<uint8_t> &wrappedKey) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     status_t err = UNKNOWN_ERROR;
 
@@ -698,10 +706,7 @@
 
 status_t DrmHal::getSecureStops(List<Vector<uint8_t>> &secureStops) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     status_t err = UNKNOWN_ERROR;
 
@@ -720,10 +725,7 @@
 
 status_t DrmHal::getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     status_t err = UNKNOWN_ERROR;
 
@@ -741,24 +743,141 @@
 
 status_t DrmHal::releaseSecureStops(Vector<uint8_t> const &ssRelease) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     return toStatusT(mPlugin->releaseSecureStop(toHidlVec(ssRelease)));
 }
 
 status_t DrmHal::releaseAllSecureStops() {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     return toStatusT(mPlugin->releaseAllSecureStops());
 }
 
+status_t DrmHal::getHdcpLevels(DrmPlugin::HdcpLevel *connected,
+            DrmPlugin::HdcpLevel *max) const {
+    Mutex::Autolock autoLock(mLock);
+    INIT_CHECK();
+
+    if (connected == NULL || max == NULL) {
+        return BAD_VALUE;
+    }
+    status_t err = UNKNOWN_ERROR;
+
+    if (mPluginV1_1 == NULL) {
+        return ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    *connected = DrmPlugin::kHdcpLevelUnknown;
+    *max = DrmPlugin::kHdcpLevelUnknown;
+
+    Return<void> hResult = mPluginV1_1->getHdcpLevels(
+            [&](Status status, const HdcpLevel& hConnected, const HdcpLevel& hMax) {
+                if (status == Status::OK) {
+                    *connected = toHdcpLevel(hConnected);
+                    *max = toHdcpLevel(hMax);
+                }
+                err = toStatusT(status);
+            }
+    );
+
+    return hResult.isOk() ? err : DEAD_OBJECT;
+}
+
+status_t DrmHal::getNumberOfSessions(uint32_t *open, uint32_t *max) const {
+    Mutex::Autolock autoLock(mLock);
+    INIT_CHECK();
+
+    if (open == NULL || max == NULL) {
+        return BAD_VALUE;
+    }
+    status_t err = UNKNOWN_ERROR;
+
+    *open = 0;
+    *max = 0;
+
+    if (mPluginV1_1 == NULL) {
+        return ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    Return<void> hResult = mPluginV1_1->getNumberOfSessions(
+            [&](Status status, uint32_t hOpen, uint32_t hMax) {
+                if (status == Status::OK) {
+                    *open = hOpen;
+                    *max = hMax;
+                }
+                err = toStatusT(status);
+            }
+    );
+
+    return hResult.isOk() ? err : DEAD_OBJECT;
+}
+
+status_t DrmHal::getSecurityLevel(Vector<uint8_t> const &sessionId,
+        DrmPlugin::SecurityLevel *level) const {
+    Mutex::Autolock autoLock(mLock);
+    INIT_CHECK();
+
+    if (level == NULL) {
+        return BAD_VALUE;
+    }
+    status_t err = UNKNOWN_ERROR;
+
+    if (mPluginV1_1 == NULL) {
+        return ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    *level = DrmPlugin::kSecurityLevelUnknown;
+
+    Return<void> hResult = mPluginV1_1->getSecurityLevel(toHidlVec(sessionId),
+            [&](Status status, SecurityLevel hLevel) {
+                if (status == Status::OK) {
+                    *level = toSecurityLevel(hLevel);
+                }
+                err = toStatusT(status);
+            }
+    );
+
+    return hResult.isOk() ? err : DEAD_OBJECT;
+}
+
+status_t DrmHal::setSecurityLevel(Vector<uint8_t> const &sessionId,
+        const DrmPlugin::SecurityLevel& level) {
+    Mutex::Autolock autoLock(mLock);
+    INIT_CHECK();
+
+    if (mPluginV1_1 == NULL) {
+        return ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    SecurityLevel hSecurityLevel;
+
+    switch(level) {
+    case DrmPlugin::kSecurityLevelSwSecureCrypto:
+        hSecurityLevel = SecurityLevel::SW_SECURE_CRYPTO;
+        break;
+    case DrmPlugin::kSecurityLevelSwSecureDecode:
+        hSecurityLevel = SecurityLevel::SW_SECURE_DECODE;
+        break;
+    case DrmPlugin::kSecurityLevelHwSecureCrypto:
+        hSecurityLevel = SecurityLevel::HW_SECURE_CRYPTO;
+        break;
+    case DrmPlugin::kSecurityLevelHwSecureDecode:
+        hSecurityLevel = SecurityLevel::HW_SECURE_DECODE;
+        break;
+    case DrmPlugin::kSecurityLevelHwSecureAll:
+        hSecurityLevel = SecurityLevel::HW_SECURE_ALL;
+        break;
+    default:
+        return ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    Status status = mPluginV1_1->setSecurityLevel(toHidlVec(sessionId),
+            hSecurityLevel);
+    return toStatusT(status);
+}
+
 status_t DrmHal::getPropertyString(String8 const &name, String8 &value ) const {
     Mutex::Autolock autoLock(mLock);
     return getPropertyStringInternal(name, value);
@@ -767,10 +886,7 @@
 status_t DrmHal::getPropertyStringInternal(String8 const &name, String8 &value) const {
     // This function is internal to the class and should only be called while
     // mLock is already held.
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     status_t err = UNKNOWN_ERROR;
 
@@ -794,10 +910,7 @@
 status_t DrmHal::getPropertyByteArrayInternal(String8 const &name, Vector<uint8_t> &value ) const {
     // This function is internal to the class and should only be called while
     // mLock is already held.
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     status_t err = UNKNOWN_ERROR;
 
@@ -815,12 +928,9 @@
 
 status_t DrmHal::setPropertyString(String8 const &name, String8 const &value ) const {
     Mutex::Autolock autoLock(mLock);
+    INIT_CHECK();
 
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
-
-    Status status =  mPlugin->setPropertyString(toHidlString(name),
+    Status status = mPlugin->setPropertyString(toHidlString(name),
             toHidlString(value));
     return toStatusT(status);
 }
@@ -828,10 +938,7 @@
 status_t DrmHal::setPropertyByteArray(String8 const &name,
                                    Vector<uint8_t> const &value ) const {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     Status status = mPlugin->setPropertyByteArray(toHidlString(name),
             toHidlVec(value));
@@ -847,10 +954,7 @@
 status_t DrmHal::setCipherAlgorithm(Vector<uint8_t> const &sessionId,
                                  String8 const &algorithm) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -862,10 +966,7 @@
 status_t DrmHal::setMacAlgorithm(Vector<uint8_t> const &sessionId,
                               String8 const &algorithm) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -878,10 +979,7 @@
         Vector<uint8_t> const &keyId, Vector<uint8_t> const &input,
         Vector<uint8_t> const &iv, Vector<uint8_t> &output) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -904,10 +1002,7 @@
         Vector<uint8_t> const &keyId, Vector<uint8_t> const &input,
         Vector<uint8_t> const &iv, Vector<uint8_t> &output) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -930,10 +1025,7 @@
         Vector<uint8_t> const &keyId, Vector<uint8_t> const &message,
         Vector<uint8_t> &signature) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -956,10 +1048,7 @@
         Vector<uint8_t> const &keyId, Vector<uint8_t> const &message,
         Vector<uint8_t> const &signature, bool &match) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     DrmSessionManager::Instance()->useSession(sessionId);
 
@@ -984,10 +1073,7 @@
         String8 const &algorithm, Vector<uint8_t> const &message,
         Vector<uint8_t> const &wrappedKey, Vector<uint8_t> &signature) {
     Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
+    INIT_CHECK();
 
     if (!checkPermission("android.permission.ACCESS_DRM_CERTIFICATES")) {
         return -EPERM;
diff --git a/drm/libmediadrm/IDrm.cpp b/drm/libmediadrm/IDrm.cpp
index d157be7..e7417cc 100644
--- a/drm/libmediadrm/IDrm.cpp
+++ b/drm/libmediadrm/IDrm.cpp
@@ -56,7 +56,11 @@
     VERIFY,
     SET_LISTENER,
     GET_SECURE_STOP,
-    RELEASE_ALL_SECURE_STOPS
+    RELEASE_ALL_SECURE_STOPS,
+    GET_HDCP_LEVELS,
+    GET_NUMBER_OF_SESSIONS,
+    GET_SECURITY_LEVEL,
+    SET_SECURITY_LEVEL,
 };
 
 struct BpDrm : public BpInterface<IDrm> {
@@ -351,6 +355,82 @@
         return reply.readInt32();
     }
 
+    virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connected,
+            DrmPlugin::HdcpLevel *max) const {
+        Parcel data, reply;
+
+        if (connected == NULL || max == NULL) {
+            return BAD_VALUE;
+        }
+
+        data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+        status_t status = remote()->transact(GET_HDCP_LEVELS, data, &reply);
+        if (status != OK) {
+            return status;
+        }
+
+        *connected = static_cast<DrmPlugin::HdcpLevel>(reply.readInt32());
+        *max = static_cast<DrmPlugin::HdcpLevel>(reply.readInt32());
+        return reply.readInt32();
+    }
+
+    virtual status_t getNumberOfSessions(uint32_t *open, uint32_t *max) const {
+        Parcel data, reply;
+
+        if (open == NULL || max == NULL) {
+            return BAD_VALUE;
+        }
+
+        data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+        status_t status = remote()->transact(GET_NUMBER_OF_SESSIONS, data, &reply);
+        if (status != OK) {
+            return status;
+        }
+
+        *open = reply.readInt32();
+        *max = reply.readInt32();
+        return reply.readInt32();
+    }
+
+    virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId,
+            DrmPlugin::SecurityLevel *level) const {
+        Parcel data, reply;
+
+        if (level == NULL) {
+            return BAD_VALUE;
+        }
+
+        data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+        writeVector(data, sessionId);
+        status_t status = remote()->transact(GET_SECURITY_LEVEL, data, &reply);
+        if (status != OK) {
+            return status;
+        }
+
+        *level = static_cast<DrmPlugin::SecurityLevel>(reply.readInt32());
+        return reply.readInt32();
+    }
+
+    virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId,
+            const DrmPlugin::SecurityLevel& level) {
+        Parcel data, reply;
+
+        data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+        writeVector(data, sessionId);
+        data.writeInt32(static_cast<uint32_t>(level));
+
+        status_t status = remote()->transact(SET_SECURITY_LEVEL, data, &reply);
+        if (status != OK) {
+            return status;
+        }
+
+        return reply.readInt32();
+    }
+
     virtual status_t getPropertyByteArray(String8 const &name, Vector<uint8_t> &value) const {
         Parcel data, reply;
         data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
@@ -801,6 +881,53 @@
             return OK;
         }
 
+        case GET_HDCP_LEVELS:
+        {
+            CHECK_INTERFACE(IDrm, data, reply);
+            DrmPlugin::HdcpLevel connected = DrmPlugin::kHdcpLevelUnknown;
+            DrmPlugin::HdcpLevel max = DrmPlugin::kHdcpLevelUnknown;
+            status_t result = getHdcpLevels(&connected, &max);
+            reply->writeInt32(connected);
+            reply->writeInt32(max);
+            reply->writeInt32(result);
+            return OK;
+        }
+
+        case GET_NUMBER_OF_SESSIONS:
+        {
+            CHECK_INTERFACE(IDrm, data, reply);
+            uint32_t open = 0, max = 0;
+            status_t result = getNumberOfSessions(&open, &max);
+            reply->writeInt32(open);
+            reply->writeInt32(max);
+            reply->writeInt32(result);
+            return OK;
+        }
+
+        case GET_SECURITY_LEVEL:
+        {
+            CHECK_INTERFACE(IDrm, data, reply);
+            Vector<uint8_t> sessionId;
+            readVector(data, sessionId);
+            DrmPlugin::SecurityLevel level = DrmPlugin::kSecurityLevelUnknown;
+            status_t result = getSecurityLevel(sessionId, &level);
+            reply->writeInt32(level);
+            reply->writeInt32(result);
+            return OK;
+        }
+
+        case SET_SECURITY_LEVEL:
+        {
+            CHECK_INTERFACE(IDrm, data, reply);
+            Vector<uint8_t> sessionId;
+            readVector(data, sessionId);
+            DrmPlugin::SecurityLevel level =
+                    static_cast<DrmPlugin::SecurityLevel>(data.readInt32());
+            status_t result = setSecurityLevel(sessionId, level);
+            reply->writeInt32(result);
+            return OK;
+        }
+
         case GET_PROPERTY_STRING:
         {
             CHECK_INTERFACE(IDrm, data, reply);
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/DrmPlugin.h
index 62bc86f..4fa42e5 100644
--- a/drm/mediadrm/plugins/clearkey/DrmPlugin.h
+++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.h
@@ -133,6 +133,35 @@
         return android::ERROR_DRM_CANNOT_HANDLE;
     }
 
+    virtual status_t getHdcpLevels(HdcpLevel *connectedLevel,
+            HdcpLevel *maxLevel) const {
+        UNUSED(connectedLevel);
+        UNUSED(maxLevel);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+
+    virtual status_t getNumberOfSessions(uint32_t *currentSessions,
+            uint32_t *maxSessions) const {
+        UNUSED(currentSessions);
+        UNUSED(maxSessions);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId,
+            SecurityLevel *level) const {
+        UNUSED(sessionId);
+        UNUSED(level);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId,
+            const SecurityLevel& level) {
+        UNUSED(sessionId);
+        UNUSED(level);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
     virtual status_t getPropertyString(
             const String8& name, String8& value) const;
 
diff --git a/media/extractors/mp4/SampleIterator.cpp b/media/extractors/mp4/SampleIterator.cpp
index 78cc691..c194397 100644
--- a/media/extractors/mp4/SampleIterator.cpp
+++ b/media/extractors/mp4/SampleIterator.cpp
@@ -244,13 +244,14 @@
     switch (mTable->mSampleSizeFieldSize) {
         case 32:
         {
+            uint32_t x;
             if (mTable->mDataSource->readAt(
                         mTable->mSampleSizeOffset + 12 + 4 * sampleIndex,
-                        size, sizeof(*size)) < (ssize_t)sizeof(*size)) {
+                        &x, sizeof(x)) < (ssize_t)sizeof(x)) {
                 return ERROR_IO;
             }
 
-            *size = ntohl(*size);
+            *size = ntohl(x);
             break;
         }
 
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.cpp b/media/libaaudio/src/core/AAudioStreamParameters.cpp
index 9645ea8..d56701b 100644
--- a/media/libaaudio/src/core/AAudioStreamParameters.cpp
+++ b/media/libaaudio/src/core/AAudioStreamParameters.cpp
@@ -17,7 +17,7 @@
 
 #define LOG_TAG "AAudioStreamParameters"
 #include <utils/Log.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
 
 #include "AAudioStreamParameters.h"
 
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index 0c59f6d..d3a2ae9 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -23,7 +23,7 @@
 #include <sys/types.h>
 
 #include <utils/Errors.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
 
 #include "aaudio/AAudio.h"
 
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index fd7400a..28684da 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -146,7 +146,7 @@
     ],
 }
 
-cc_library_shared {
+cc_library {
     name: "libmedia",
 
     srcs: [
@@ -249,7 +249,7 @@
     },
 }
 
-cc_library_shared {
+cc_library {
     name: "libmedia_player2_util",
 
     srcs: [
@@ -259,6 +259,7 @@
         "IMediaExtractorService.cpp",
         "IMediaSource.cpp",
         "IStreamSource.cpp",
+        "MediaCodecBuffer.cpp",
         "MediaUtils.cpp",
         "Metadata.cpp",
         "NdkWrapper.cpp",
@@ -267,7 +268,6 @@
     shared_libs: [
         "libbinder",
         "libcutils",
-        "libgui",
         "liblog",
         "libmediaextractor",
         "libmediandk",
@@ -287,9 +287,6 @@
     ],
 
     static_libs: [
-        "libc_malloc_debug_backtrace",  // for memory heap analysis
-
-        "libstagefright_nuplayer2",
         "libstagefright_rtsp",
         "libstagefright_timedtext",
     ],
@@ -316,16 +313,14 @@
     },
 }
 
-cc_library_shared {
+cc_library {
     name: "libmedia_player2",
 
     srcs: [
-        "AudioParameter.cpp",
         "JAudioTrack.cpp",
         "MediaPlayer2Factory.cpp",
         "MediaPlayer2Manager.cpp",
         "TestPlayerStub.cpp",
-        "TypeConverter.cpp",
         "mediaplayer2.cpp",
     ],
 
@@ -366,8 +361,7 @@
     ],
 
     static_libs: [
-        "libc_malloc_debug_backtrace",  // for memory heap analysis
-
+        "libmedia_helper",
         "libstagefright_nuplayer2",
         "libstagefright_rtsp",
         "libstagefright_timedtext",
diff --git a/media/libmedia/MediaPlayer2Factory.cpp b/media/libmedia/MediaPlayer2Factory.cpp
index d6aab70..df567ce 100644
--- a/media/libmedia/MediaPlayer2Factory.cpp
+++ b/media/libmedia/MediaPlayer2Factory.cpp
@@ -22,7 +22,6 @@
 #include <cutils/properties.h>
 #include <media/DataSource.h>
 #include <media/MediaPlayer2Engine.h>
-#include <media/stagefright/FileSource.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <utils/Errors.h>
 #include <utils/misc.h>
diff --git a/media/libmedia/MediaPlayer2Manager.cpp b/media/libmedia/MediaPlayer2Manager.cpp
index 720c1e3..c119750 100644
--- a/media/libmedia/MediaPlayer2Manager.cpp
+++ b/media/libmedia/MediaPlayer2Manager.cpp
@@ -64,6 +64,7 @@
 
 #include <memunreachable/memunreachable.h>
 #include <system/audio.h>
+#include <system/window.h>
 
 #include <private/android_filesystem_config.h>
 
@@ -470,8 +471,9 @@
         if (unreachableMemory) {
             result.append("\nDumping unreachable memory:\n");
             // TODO - should limit be an argument parameter?
-            std::string s = GetUnreachableMemoryString(true /* contents */, 10000 /* limit */);
-            result.append(s.c_str(), s.size());
+            // TODO: enable GetUnreachableMemoryString if it's part of stable API
+            //std::string s = GetUnreachableMemoryString(true /* contents */, 10000 /* limit */);
+            //result.append(s.c_str(), s.size());
         }
     }
     write(fd, result.string(), result.size());
@@ -738,8 +740,8 @@
 
 void MediaPlayer2Manager::Client::disconnectNativeWindow_l() {
     if (mConnectedWindow != NULL && mConnectedWindow->getANativeWindow() != NULL) {
-        status_t err = nativeWindowDisconnect(
-                mConnectedWindow->getANativeWindow(), "disconnectNativeWindow");
+        status_t err = native_window_api_disconnect(
+                mConnectedWindow->getANativeWindow(), NATIVE_WINDOW_API_MEDIA);
 
         if (err != OK) {
             ALOGW("nativeWindowDisconnect returned an error: %s (%d)",
@@ -763,7 +765,8 @@
             && mConnectedWindow->getANativeWindow() == nww->getANativeWindow()) {
             return OK;
         }
-        status_t err = nativeWindowConnect(nww->getANativeWindow(), "setVideoSurfaceTexture");
+        status_t err = native_window_api_connect(
+                nww->getANativeWindow(), NATIVE_WINDOW_API_MEDIA);
 
         if (err != OK) {
             ALOGE("setVideoSurfaceTexture failed: %d", err);
@@ -792,8 +795,8 @@
         mLock.unlock();
     } else if (nww != NULL) {
         mLock.unlock();
-        status_t err = nativeWindowDisconnect(
-                nww->getANativeWindow(), "disconnectNativeWindow");
+        status_t err = native_window_api_disconnect(
+                nww->getANativeWindow(), NATIVE_WINDOW_API_MEDIA);
 
         if (err != OK) {
             ALOGW("nativeWindowDisconnect returned an error: %s (%d)",
diff --git a/media/libmedia/TypeConverter.cpp b/media/libmedia/TypeConverter.cpp
index ae232b5..4cadeb1 100644
--- a/media/libmedia/TypeConverter.cpp
+++ b/media/libmedia/TypeConverter.cpp
@@ -117,7 +117,7 @@
     MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_VOIP_RX),
     // FIXME: this cast will be removed when the flag will be
     // declared in types.hal for audio HAL V4.0 and auto imported to audio-base.h
-    MAKE_STRING_FROM_ENUM((audio_output_flags_t)AUDIO_OUTPUT_FLAG_INCALL_MUSIC),
+    {"AUDIO_OUTPUT_FLAG_INCALL_MUSIC", static_cast<audio_output_flags_t>(AUDIO_OUTPUT_FLAG_INCALL_MUSIC)},
     TERMINATOR
 };
 
diff --git a/media/libmedia/include/media/DrmHal.h b/media/libmedia/include/media/DrmHal.h
index 55fbce9..1a0553e 100644
--- a/media/libmedia/include/media/DrmHal.h
+++ b/media/libmedia/include/media/DrmHal.h
@@ -19,6 +19,7 @@
 #define DRM_HAL_H_
 
 #include <android/hardware/drm/1.0/IDrmPlugin.h>
+#include <android/hardware/drm/1.1/IDrmPlugin.h>
 #include <android/hardware/drm/1.0/IDrmPluginListener.h>
 #include <android/hardware/drm/1.0/IDrmFactory.h>
 
@@ -27,11 +28,12 @@
 #include <media/MediaAnalyticsItem.h>
 #include <utils/threads.h>
 
-using ::android::hardware::drm::V1_0::EventType;
-using ::android::hardware::drm::V1_0::IDrmFactory;
-using ::android::hardware::drm::V1_0::IDrmPlugin;
-using ::android::hardware::drm::V1_0::IDrmPluginListener;
-using ::android::hardware::drm::V1_0::KeyStatus;
+namespace drm = ::android::hardware::drm;
+using drm::V1_0::EventType;
+using drm::V1_0::IDrmFactory;
+using drm::V1_0::IDrmPlugin;
+using drm::V1_0::IDrmPluginListener;
+using drm::V1_0::KeyStatus;
 using ::android::hardware::hidl_vec;
 using ::android::hardware::Return;
 using ::android::hardware::Void;
@@ -99,6 +101,15 @@
     virtual status_t releaseSecureStops(Vector<uint8_t> const &ssRelease);
     virtual status_t releaseAllSecureStops();
 
+    virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connectedLevel,
+            DrmPlugin::HdcpLevel *maxLevel) const;
+    virtual status_t getNumberOfSessions(uint32_t *currentSessions,
+            uint32_t *maxSessions) const;
+    virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId,
+            DrmPlugin::SecurityLevel *level) const;
+    virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId,
+            const DrmPlugin::SecurityLevel& level);
+
     virtual status_t getPropertyString(String8 const &name, String8 &value ) const;
     virtual status_t getPropertyByteArray(String8 const &name,
                                           Vector<uint8_t> &value ) const;
@@ -167,6 +178,7 @@
 
     const Vector<sp<IDrmFactory>> mFactories;
     sp<IDrmPlugin> mPlugin;
+    sp<drm::V1_1::IDrmPlugin> mPluginV1_1;
 
     Vector<Vector<uint8_t>> mOpenSessions;
     void closeOpenSessions();
diff --git a/media/libmedia/include/media/IDrm.h b/media/libmedia/include/media/IDrm.h
index ce0360b..9266f99 100644
--- a/media/libmedia/include/media/IDrm.h
+++ b/media/libmedia/include/media/IDrm.h
@@ -79,6 +79,16 @@
     virtual status_t releaseSecureStops(Vector<uint8_t> const &ssRelease) = 0;
     virtual status_t releaseAllSecureStops() = 0;
 
+    virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connectedLevel,
+            DrmPlugin::HdcpLevel *maxLevel)
+            const = 0;
+    virtual status_t getNumberOfSessions(uint32_t *currentSessions,
+            uint32_t *maxSessions) const = 0;
+    virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId,
+            DrmPlugin::SecurityLevel *level) const = 0;
+    virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId,
+            const DrmPlugin::SecurityLevel& level) = 0;
+
     virtual status_t getPropertyString(String8 const &name, String8 &value) const = 0;
     virtual status_t getPropertyByteArray(String8 const &name,
                                           Vector<uint8_t> &value) const = 0;
diff --git a/media/libmedia/include/media/IMediaSource.h b/media/libmedia/include/media/IMediaSource.h
index 493742e..dabe231 100644
--- a/media/libmedia/include/media/IMediaSource.h
+++ b/media/libmedia/include/media/IMediaSource.h
@@ -28,7 +28,6 @@
 
 namespace android {
 
-class MetaData;
 class MediaBufferGroup;
 
 class IMediaSource : public IInterface {
diff --git a/media/libmedia/nuplayer2/Android.bp b/media/libmedia/nuplayer2/Android.bp
index d609ba0..700c840 100644
--- a/media/libmedia/nuplayer2/Android.bp
+++ b/media/libmedia/nuplayer2/Android.bp
@@ -22,7 +22,6 @@
     ],
 
     include_dirs: [
-        "frameworks/av/media/libmedia/include",
         "frameworks/av/media/libstagefright",
         "frameworks/av/media/libstagefright/httplive",
         "frameworks/av/media/libstagefright/include",
@@ -54,6 +53,10 @@
         "libpowermanager",
     ],
 
+    static_libs: [
+        "libmedia_helper",
+    ],
+
     name: "libstagefright_nuplayer2",
 
     tags: ["eng"],
diff --git a/media/libmedia/nuplayer2/GenericSource.cpp b/media/libmedia/nuplayer2/GenericSource.cpp
index 011691a..c0b81fb 100644
--- a/media/libmedia/nuplayer2/GenericSource.cpp
+++ b/media/libmedia/nuplayer2/GenericSource.cpp
@@ -33,7 +33,6 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/DataSourceFactory.h>
-#include <media/stagefright/FileSource.h>
 #include <media/stagefright/InterfaceUtils.h>
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaClock.h>
diff --git a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
index 25d41f3..715d6fc 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
@@ -40,6 +40,7 @@
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/SurfaceUtils.h>
 
+#include <system/window.h>
 #include "ATSParser.h"
 
 namespace android {
@@ -241,22 +242,25 @@
                 //
                 // at this point MediaPlayer2Manager::client has already connected to the
                 // surface, which MediaCodec does not expect
-                err = nativeWindowDisconnect(nww->getANativeWindow(), "kWhatSetVideoSurface(nww)");
+                err = native_window_api_disconnect(nww->getANativeWindow(),
+                                                   NATIVE_WINDOW_API_MEDIA);
                 if (err == OK) {
                     err = mCodec->setOutputSurface(nww);
                     ALOGI_IF(err, "codec setOutputSurface returned: %d", err);
                     if (err == OK) {
                         // reconnect to the old surface as MPS::Client will expect to
                         // be able to disconnect from it.
-                        (void)nativeWindowConnect(mNativeWindow->getANativeWindow(),
-                                                  "kWhatSetVideoSurface(mNativeWindow)");
+                        (void)native_window_api_connect(mNativeWindow->getANativeWindow(),
+                                                        NATIVE_WINDOW_API_MEDIA);
+
                         mNativeWindow = nww;
                     }
                 }
                 if (err != OK) {
                     // reconnect to the new surface on error as MPS::Client will expect to
                     // be able to disconnect from it.
-                    (void)nativeWindowConnect(nww->getANativeWindow(), "kWhatSetVideoSurface(err)");
+                    (void)native_window_api_connect(nww->getANativeWindow(),
+                                                    NATIVE_WINDOW_API_MEDIA);
                 }
             }
 
@@ -326,7 +330,8 @@
     status_t err;
     if (mNativeWindow != NULL && mNativeWindow->getANativeWindow() != NULL) {
         // disconnect from surface as MediaCodec will reconnect
-        err = nativeWindowDisconnect(mNativeWindow->getANativeWindow(), "onConfigure");
+        err = native_window_api_disconnect(mNativeWindow->getANativeWindow(),
+                                           NATIVE_WINDOW_API_MEDIA);
         // We treat this as a warning, as this is a preparatory step.
         // Codec will try to connect to the surface, which is where
         // any error signaling will occur.
@@ -540,7 +545,8 @@
 
         if (mNativeWindow != NULL && mNativeWindow->getANativeWindow() != NULL) {
             // reconnect to surface as MediaCodec disconnected from it
-            status_t error = nativeWindowConnect(mNativeWindow->getANativeWindow(), "onShutdown");
+            status_t error = native_window_api_connect(mNativeWindow->getANativeWindow(),
+                                                       NATIVE_WINDOW_API_MEDIA);
             ALOGW_IF(error != NO_ERROR,
                     "[%s] failed to connect to native window, error=%d",
                     mComponentName.c_str(), error);
diff --git a/media/libmedia/nuplayer2/NuPlayer2Drm.h b/media/libmedia/nuplayer2/NuPlayer2Drm.h
index e762ccc..f9c8711 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Drm.h
+++ b/media/libmedia/nuplayer2/NuPlayer2Drm.h
@@ -18,7 +18,6 @@
 #define NUPLAYER2_DRM_H_
 
 #include <binder/Parcel.h>
-#include <media/stagefright/MetaData.h> // for CryptInfo
 
 
 namespace android {
diff --git a/media/libmedia/nuplayer2/NuPlayer2Renderer.cpp b/media/libmedia/nuplayer2/NuPlayer2Renderer.cpp
index 71f5dce..1a9f246 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Renderer.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Renderer.cpp
@@ -27,7 +27,6 @@
 #include <media/stagefright/foundation/AUtils.h>
 #include <media/stagefright/MediaClock.h>
 #include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MetaData.h>
 #include <media/stagefright/Utils.h>
 #include <media/stagefright/VideoFrameScheduler.h>
 #include <media/MediaCodecBuffer.h>
diff --git a/media/libmediaextractor/Android.bp b/media/libmediaextractor/Android.bp
index dcdb320..18573db 100644
--- a/media/libmediaextractor/Android.bp
+++ b/media/libmediaextractor/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
     name: "libmediaextractor",
 
     include_dirs: [
diff --git a/media/libmediaextractor/include/media/MediaSource.h b/media/libmediaextractor/include/media/MediaSource.h
index 98b136b..25d691d 100644
--- a/media/libmediaextractor/include/media/MediaSource.h
+++ b/media/libmediaextractor/include/media/MediaSource.h
@@ -30,7 +30,6 @@
 namespace android {
 
 class MediaBuffer;
-class MetaData;
 class IMediaSource;
 
 struct MediaSource : public virtual RefBase {
diff --git a/media/libmediametrics/Android.bp b/media/libmediametrics/Android.bp
index 15dac59..07e124b 100644
--- a/media/libmediametrics/Android.bp
+++ b/media/libmediametrics/Android.bp
@@ -1,4 +1,6 @@
-cc_library_shared {
+// TODO: change it back to cc_library_shared when there is a way to
+// expose media metrics as stable API.
+cc_library {
     name: "libmediametrics",
 
     srcs: [
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index c3d9c24..01f73a1 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -128,7 +128,6 @@
         "libstagefright_timedtext",
         "libvpx",
         "libwebm",
-        "libstagefright_mpeg2support",
         "libstagefright_esds",
         "libstagefright_id3",
         "libFLAC",
@@ -173,7 +172,7 @@
     },
 }
 
-cc_library_shared {
+cc_library {
     name: "libstagefright_player2",
 
     srcs: [
@@ -190,7 +189,6 @@
         "NuCachedSource2.cpp",
         "RemoteMediaExtractor.cpp",
         "RemoteMediaSource.cpp",
-        "SurfaceUtils.cpp",
         "Utils.cpp",
         "VideoFrameScheduler.cpp",
         "http/MediaHTTP.cpp",
@@ -202,7 +200,6 @@
         "libdrmframework",
         "libgui",
         "liblog",
-        "libmedia_omx",
         "libmedia_player2_util",
         "libaudioclient",
         "libmediaextractor",
@@ -213,15 +210,15 @@
         "libutils",
         "libmedia_helper",
         "libstagefright_foundation",
-        "libdl",
         "libziparchive",
     ],
 
     static_libs: [
         "libstagefright_esds",
-        "libstagefright_id3",
-        "libstagefright_mpeg2support",
-        "libstagefright_timedtext",
+    ],
+
+    header_libs:[
+        "media_plugin_headers",
     ],
 
     export_shared_lib_headers: [
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 77d9ce4..559b108 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -51,7 +51,6 @@
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MediaFilter.h>
-#include <media/stagefright/MetaData.h>
 #include <media/stagefright/OMXClient.h>
 #include <media/stagefright/PersistentSurface.h>
 #include <media/stagefright/SurfaceUtils.h>
diff --git a/media/libstagefright/MediaExtractorFactory.cpp b/media/libstagefright/MediaExtractorFactory.cpp
index 078c646..472c137 100644
--- a/media/libstagefright/MediaExtractorFactory.cpp
+++ b/media/libstagefright/MediaExtractorFactory.cpp
@@ -26,7 +26,6 @@
 #include <media/stagefright/FileSource.h>
 #include <media/stagefright/InterfaceUtils.h>
 #include <media/stagefright/MediaExtractorFactory.h>
-#include <media/stagefright/MetaData.h>
 #include <media/IMediaExtractor.h>
 #include <media/IMediaExtractorService.h>
 #include <cutils/properties.h>
diff --git a/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp b/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
index 32fdbd3..379d41e 100644
--- a/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
+++ b/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
@@ -26,7 +26,6 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MetaData.h>
 #include <OMX_IndexExt.h>
 #include <OMX_VideoExt.h>
 
diff --git a/media/libstagefright/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index df3e280..4dfe5e5 100644
--- a/media/libstagefright/foundation/Android.bp
+++ b/media/libstagefright/foundation/Android.bp
@@ -4,7 +4,7 @@
     vendor_available: true,
 }
 
-cc_library_shared {
+cc_library {
     name: "libstagefright_foundation",
     vendor_available: true,
     vndk: {
diff --git a/media/libstagefright/foundation/MetaData.cpp b/media/libstagefright/foundation/MetaData.cpp
index a8965f0..2415c61 100644
--- a/media/libstagefright/foundation/MetaData.cpp
+++ b/media/libstagefright/foundation/MetaData.cpp
@@ -17,6 +17,7 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MetaData"
 #include <inttypes.h>
+#include <utils/KeyedVector.h>
 #include <utils/Log.h>
 
 #include <stdlib.h>
@@ -29,30 +30,81 @@
 
 namespace android {
 
-MetaData::MetaData() {
+struct MetaData::typed_data {
+    typed_data();
+    ~typed_data();
+
+    typed_data(const MetaData::typed_data &);
+    typed_data &operator=(const MetaData::typed_data &);
+
+    void clear();
+    void setData(uint32_t type, const void *data, size_t size);
+    void getData(uint32_t *type, const void **data, size_t *size) const;
+    // may include hexdump of binary data if verbose=true
+    String8 asString(bool verbose) const;
+
+private:
+    uint32_t mType;
+    size_t mSize;
+
+    union {
+        void *ext_data;
+        float reservoir;
+    } u;
+
+    bool usesReservoir() const {
+        return mSize <= sizeof(u.reservoir);
+    }
+
+    void *allocateStorage(size_t size);
+    void freeStorage();
+
+    void *storage() {
+        return usesReservoir() ? &u.reservoir : u.ext_data;
+    }
+
+    const void *storage() const {
+        return usesReservoir() ? &u.reservoir : u.ext_data;
+    }
+};
+
+struct MetaData::Rect {
+    int32_t mLeft, mTop, mRight, mBottom;
+};
+
+
+struct MetaData::MetaDataInternal {
+    KeyedVector<uint32_t, MetaData::typed_data> mItems;
+};
+
+
+MetaData::MetaData()
+    : mInternalData(new MetaDataInternal()) {
 }
 
 MetaData::MetaData(const MetaData &from)
     : RefBase(),
-      mItems(from.mItems) {
+      mInternalData(new MetaDataInternal()) {
+    mInternalData->mItems = from.mInternalData->mItems;
 }
 
 MetaData::~MetaData() {
     clear();
+    delete mInternalData;
 }
 
 void MetaData::clear() {
-    mItems.clear();
+    mInternalData->mItems.clear();
 }
 
 bool MetaData::remove(uint32_t key) {
-    ssize_t i = mItems.indexOfKey(key);
+    ssize_t i = mInternalData->mItems.indexOfKey(key);
 
     if (i < 0) {
         return false;
     }
 
-    mItems.removeItemsAt(i);
+    mInternalData->mItems.removeItemsAt(i);
 
     return true;
 }
@@ -192,15 +244,15 @@
         uint32_t key, uint32_t type, const void *data, size_t size) {
     bool overwrote_existing = true;
 
-    ssize_t i = mItems.indexOfKey(key);
+    ssize_t i = mInternalData->mItems.indexOfKey(key);
     if (i < 0) {
         typed_data item;
-        i = mItems.add(key, item);
+        i = mInternalData->mItems.add(key, item);
 
         overwrote_existing = false;
     }
 
-    typed_data &item = mItems.editValueAt(i);
+    typed_data &item = mInternalData->mItems.editValueAt(i);
 
     item.setData(type, data, size);
 
@@ -209,13 +261,13 @@
 
 bool MetaData::findData(uint32_t key, uint32_t *type,
                         const void **data, size_t *size) const {
-    ssize_t i = mItems.indexOfKey(key);
+    ssize_t i = mInternalData->mItems.indexOfKey(key);
 
     if (i < 0) {
         return false;
     }
 
-    const typed_data &item = mItems.valueAt(i);
+    const typed_data &item = mInternalData->mItems.valueAt(i);
 
     item.getData(type, data, size);
 
@@ -223,7 +275,7 @@
 }
 
 bool MetaData::hasData(uint32_t key) const {
-    ssize_t i = mItems.indexOfKey(key);
+    ssize_t i = mInternalData->mItems.indexOfKey(key);
 
     if (i < 0) {
         return false;
@@ -369,11 +421,11 @@
 
 String8 MetaData::toString() const {
     String8 s;
-    for (int i = mItems.size(); --i >= 0;) {
-        int32_t key = mItems.keyAt(i);
+    for (int i = mInternalData->mItems.size(); --i >= 0;) {
+        int32_t key = mInternalData->mItems.keyAt(i);
         char cc[5];
         MakeFourCCString(key, cc);
-        const typed_data &item = mItems.valueAt(i);
+        const typed_data &item = mInternalData->mItems.valueAt(i);
         s.appendFormat("%s: %s", cc, item.asString(false).string());
         if (i != 0) {
             s.append(", ");
@@ -382,25 +434,25 @@
     return s;
 }
 void MetaData::dumpToLog() const {
-    for (int i = mItems.size(); --i >= 0;) {
-        int32_t key = mItems.keyAt(i);
+    for (int i = mInternalData->mItems.size(); --i >= 0;) {
+        int32_t key = mInternalData->mItems.keyAt(i);
         char cc[5];
         MakeFourCCString(key, cc);
-        const typed_data &item = mItems.valueAt(i);
+        const typed_data &item = mInternalData->mItems.valueAt(i);
         ALOGI("%s: %s", cc, item.asString(true /* verbose */).string());
     }
 }
 
 status_t MetaData::writeToParcel(Parcel &parcel) {
     status_t ret;
-    size_t numItems = mItems.size();
+    size_t numItems = mInternalData->mItems.size();
     ret = parcel.writeUint32(uint32_t(numItems));
     if (ret) {
         return ret;
     }
     for (size_t i = 0; i < numItems; i++) {
-        int32_t key = mItems.keyAt(i);
-        const typed_data &item = mItems.valueAt(i);
+        int32_t key = mInternalData->mItems.keyAt(i);
+        const typed_data &item = mInternalData->mItems.valueAt(i);
         uint32_t type;
         const void *data;
         size_t size;
diff --git a/media/libstagefright/gbs/Android.bp b/media/libstagefright/gbs/Android.bp
new file mode 100644
index 0000000..a53b7b7
--- /dev/null
+++ b/media/libstagefright/gbs/Android.bp
@@ -0,0 +1,58 @@
+cc_library_shared {
+    name: "libstagefright_gbs",
+    vendor_available: true,
+    vndk: {
+        enabled: true,
+    },
+
+    srcs: [
+        "FrameDropper.cpp",
+        "GraphicBufferSource.cpp",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    header_libs: [
+        "media_plugin_headers",
+    ],
+
+    export_header_lib_headers: [
+        "media_plugin_headers",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libutils",
+        "liblog",
+        "libui",
+        "libgui",
+        "libcutils",
+        "libstagefright_foundation",
+        "libnativewindow", // TODO(b/62923479): use header library
+    ],
+
+    export_shared_lib_headers: [
+        "libstagefright_foundation",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-Wno-unused-parameter",
+        "-Wno-documentation",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+}
diff --git a/media/libstagefright/omx/FrameDropper.cpp b/media/libstagefright/gbs/FrameDropper.cpp
similarity index 97%
rename from media/libstagefright/omx/FrameDropper.cpp
rename to media/libstagefright/gbs/FrameDropper.cpp
index 0c50c58..9f0b8cc 100644
--- a/media/libstagefright/omx/FrameDropper.cpp
+++ b/media/libstagefright/gbs/FrameDropper.cpp
@@ -18,7 +18,7 @@
 #define LOG_TAG "FrameDropper"
 #include <utils/Log.h>
 
-#include <media/stagefright/omx/FrameDropper.h>
+#include <media/stagefright/gbs/FrameDropper.h>
 #include <media/stagefright/foundation/ADebug.h>
 
 namespace android {
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/gbs/GraphicBufferSource.cpp
similarity index 96%
rename from media/libstagefright/omx/GraphicBufferSource.cpp
rename to media/libstagefright/gbs/GraphicBufferSource.cpp
index f331dbb..139c916 100644
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ b/media/libstagefright/gbs/GraphicBufferSource.cpp
@@ -22,9 +22,8 @@
 
 #define STRINGIFY_ENUMS // for asString in HardwareAPI.h/VideoAPI.h
 
-#include <media/stagefright/omx/GraphicBufferSource.h>
-#include <media/stagefright/omx/FrameDropper.h>
-#include <media/stagefright/omx/OMXUtils.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/gbs/FrameDropper.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/foundation/ColorUtils.h>
@@ -34,9 +33,6 @@
 #include <ui/GraphicBuffer.h>
 #include <gui/BufferItem.h>
 #include <media/hardware/HardwareAPI.h>
-#include <media/openmax/OMX_Component.h>
-#include <media/openmax/OMX_IndexExt.h>
-#include <media/OMXBuffer.h>
 
 #include <inttypes.h>
 
@@ -361,9 +357,9 @@
     }
 }
 
-Status GraphicBufferSource::onOmxExecuting() {
+Status GraphicBufferSource::start() {
     Mutex::Autolock autoLock(mMutex);
-    ALOGV("--> executing; available=%zu, submittable=%zd",
+    ALOGV("--> start; available=%zu, submittable=%zd",
             mAvailableBuffers.size(), mFreeCodecBuffers.size());
     CHECK(!mExecuting);
     mExecuting = true;
@@ -411,8 +407,8 @@
     return Status::ok();
 }
 
-Status GraphicBufferSource::onOmxIdle() {
-    ALOGV("omxIdle");
+Status GraphicBufferSource::stop() {
+    ALOGV("stop");
 
     Mutex::Autolock autoLock(mMutex);
 
@@ -424,7 +420,7 @@
     return Status::ok();
 }
 
-Status GraphicBufferSource::onOmxLoaded(){
+Status GraphicBufferSource::release(){
     Mutex::Autolock autoLock(mMutex);
     if (mLooper != NULL) {
         mLooper->unregisterHandler(mReflector->id());
@@ -434,7 +430,7 @@
         mLooper.clear();
     }
 
-    ALOGV("--> loaded; available=%zu+%d eos=%d eosSent=%d acquired=%d",
+    ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
             mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
             mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
 
@@ -442,7 +438,7 @@
     mFreeCodecBuffers.clear();
     mSubmittedCodecBuffers.clear();
     mLatestBuffer.mBuffer.reset();
-    mOMXNode.clear();
+    mComponent.clear();
     mExecuting = false;
 
     return Status::ok();
@@ -537,7 +533,8 @@
     mLastDataspace = dataspace;
 
     if (ColorUtils::convertDataSpaceToV0(dataspace)) {
-        mOMXNode->dispatchDataSpaceChanged(mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
+        mComponent->dispatchDataSpaceChanged(
+                mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
     }
 }
 
@@ -631,7 +628,7 @@
                 default:
                     TRESPASS_DBG("Unknown action type");
                     // return true here because we did consume an available buffer, so the
-                    // loop in onOmxExecuting will eventually terminate even if we hit this.
+                    // loop in start will eventually terminate even if we hit this.
                     return false;
             }
         }
@@ -799,7 +796,7 @@
 
 status_t GraphicBufferSource::submitBuffer_l(const VideoBuffer &item) {
     CHECK(!mFreeCodecBuffers.empty());
-    IOMX::buffer_id codecBufferId = *mFreeCodecBuffers.begin();
+    uint32_t codecBufferId = *mFreeCodecBuffers.begin();
 
     ALOGV("submitBuffer_l [slot=%d, bufferId=%d]", item.mBuffer->getSlot(), codecBufferId);
 
@@ -815,15 +812,14 @@
     }
 
     std::shared_ptr<AcquiredBuffer> buffer = item.mBuffer;
-    // use a GraphicBuffer for now as OMXNodeInstance is using GraphicBuffers to hold references
+    // use a GraphicBuffer for now as component is using GraphicBuffers to hold references
     // and it requires this graphic buffer to be able to hold its reference
     // and thus we would need to create a new GraphicBuffer from an ANWBuffer separate from the
     // acquired GraphicBuffer.
     // TODO: this can be reworked globally to use ANWBuffer references
     sp<GraphicBuffer> graphicBuffer = buffer->getGraphicBuffer();
-    status_t err = mOMXNode->emptyBuffer(
-            codecBufferId, OMX_BUFFERFLAG_ENDOFFRAME, graphicBuffer, codecTimeUs,
-            buffer->getAcquireFenceFd());
+    status_t err = mComponent->submitBuffer(
+            codecBufferId, graphicBuffer, codecTimeUs, buffer->getAcquireFenceFd());
 
     if (err != OK) {
         ALOGW("WARNING: emptyGraphicBuffer failed: 0x%x", err);
@@ -849,11 +845,10 @@
         ALOGV("submitEndOfInputStream_l: no codec buffers available");
         return;
     }
-    IOMX::buffer_id codecBufferId = *mFreeCodecBuffers.begin();
+    uint32_t codecBufferId = *mFreeCodecBuffers.begin();
 
     // We reject any additional incoming graphic buffers. There is no acquired buffer used for EOS
-    status_t err = mOMXNode->emptyBuffer(
-            codecBufferId, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS);
+    status_t err = mComponent->submitEos(codecBufferId);
     if (err != OK) {
         ALOGW("emptyDirectBuffer EOS failed: 0x%x", err);
     } else {
@@ -959,7 +954,7 @@
 
 bool GraphicBufferSource::areWeDiscardingAvailableBuffers_l() {
     return mEndOfStreamSent // already sent EOS to codec
-            || mOMXNode == nullptr // there is no codec connected
+            || mComponent == nullptr // there is no codec connected
             || (mSuspended && mActionQueue.empty()) // we are suspended and not waiting for
                                                     // any further action
             || !mExecuting;
@@ -970,7 +965,7 @@
         // This should only be possible if a new buffer was queued after
         // EOS was signaled, i.e. the app is misbehaving.
         ALOGW("onFrameAvailable: EOS is sent, ignoring frame");
-    } else if (mOMXNode == NULL || (mSuspended && mActionQueue.empty())) {
+    } else if (mComponent == NULL || (mSuspended && mActionQueue.empty())) {
         // FIXME: if we are suspended but have a resume queued we will stop repeating the last
         // frame. Is that the desired behavior?
         ALOGV("onFrameAvailable: suspended, ignoring frame");
@@ -1064,13 +1059,13 @@
 }
 
 status_t GraphicBufferSource::configure(
-        const sp<IOmxNodeWrapper>& omxNode,
+        const sp<ComponentWrapper>& component,
         int32_t dataSpace,
         int32_t bufferCount,
         uint32_t frameWidth,
         uint32_t frameHeight,
         uint32_t consumerUsage) {
-    if (omxNode == NULL) {
+    if (component == NULL) {
         return BAD_VALUE;
     }
 
@@ -1088,7 +1083,7 @@
 
     {
         Mutex::Autolock autoLock(mMutex);
-        mOMXNode = omxNode;
+        mComponent = component;
 
         err = mConsumer->setDefaultBufferSize(frameWidth, frameHeight);
         if (err != NO_ERROR) {
@@ -1320,7 +1315,7 @@
     // Set the end-of-stream flag.  If no frames are pending from the
     // BufferQueue, and a codec buffer is available, and we're executing,
     // and there is no stop timestamp, we initiate the EOS from here.
-    // Otherwise, we'll let codecBufferEmptied() (or omxExecuting) do it.
+    // Otherwise, we'll let codecBufferEmptied() (or start) do it.
     //
     // Note: if there are no pending frames and all codec buffers are
     // available, we *must* submit the EOS from here or we'll just
diff --git a/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h b/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h
new file mode 100644
index 0000000..e27829b
--- /dev/null
+++ b/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COMPONENT_WRAPPER_H_
+#define COMPONENT_WRAPPER_H_
+
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <ui/GraphicBuffer.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct ComponentWrapper : public RefBase {
+    virtual status_t submitBuffer(
+            int32_t bufferId, const sp<GraphicBuffer> &buffer = nullptr,
+            int64_t timestamp = 0, int fenceFd = -1) = 0;
+    virtual status_t submitEos(int32_t bufferId) = 0;
+    virtual void dispatchDataSpaceChanged(
+            int32_t dataSpace, int32_t aspects, int32_t pixelFormat) = 0;
+};
+
+}  // namespace android
+
+#endif  // COMPONENT_WRAPPER_H_
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/FrameDropper.h b/media/libstagefright/gbs/include/media/stagefright/gbs/FrameDropper.h
similarity index 100%
rename from media/libstagefright/omx/include/media/stagefright/omx/FrameDropper.h
rename to media/libstagefright/gbs/include/media/stagefright/gbs/FrameDropper.h
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h b/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
similarity index 93%
rename from media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h
rename to media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
index 84fee6f..89f6cf8 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h
+++ b/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
@@ -18,21 +18,16 @@
 
 #define GRAPHIC_BUFFER_SOURCE_H_
 
-#include <gui/IGraphicBufferProducer.h>
+#include <binder/Status.h>
 #include <gui/BufferQueue.h>
+#include <gui/IGraphicBufferProducer.h>
 #include <utils/RefBase.h>
 
 #include <media/hardware/VideoAPI.h>
-#include <media/IOMX.h>
-#include <media/OMXFenceParcelable.h>
 #include <media/stagefright/foundation/ABase.h>
 #include <media/stagefright/foundation/AHandlerReflector.h>
 #include <media/stagefright/foundation/ALooper.h>
-
-#include <android/BnGraphicBufferSource.h>
-#include <android/BnOMXBufferSource.h>
-
-#include "IOmxNodeWrapper.h"
+#include <media/stagefright/gbs/ComponentWrapper.h>
 
 namespace android {
 
@@ -41,7 +36,7 @@
 struct FrameDropper;
 
 /*
- * This class is used to feed OMX codecs from a Surface via BufferQueue or
+ * This class is used to feed codecs from a Surface via BufferQueue or
  * HW producer.
  *
  * Instances of the class don't run on a dedicated thread.  Instead,
@@ -49,7 +44,7 @@
  *
  *  - Availability of a new frame of data from the BufferQueue (notified
  *    via the onFrameAvailable callback).
- *  - The return of a codec buffer (via OnEmptyBufferDone).
+ *  - The return of a codec buffer.
  *  - Application signaling end-of-stream.
  *  - Transition to or from "executing" state.
  *
@@ -91,39 +86,37 @@
         return mProducer;
     }
 
-    // OmxBufferSource interface
-    // ------------------------------
-
-    // This is called when OMX transitions to OMX_StateExecuting, which means
+    // This is called when component transitions to running state, which means
     // we can start handing it buffers.  If we already have buffers of data
     // sitting in the BufferQueue, this will send them to the codec.
-    Status onOmxExecuting();
+    Status start();
 
-    // This is called when OMX transitions to OMX_StateIdle, indicating that
+    // This is called when component transitions to stopped, indicating that
     // the codec is meant to return all buffers back to the client for them
     // to be freed. Do NOT submit any more buffers to the component.
-    Status onOmxIdle();
+    Status stop();
 
-    // This is called when OMX transitions to OMX_StateLoaded, indicating that
+    // This is called when component transitions to released, indicating that
     // we are shutting down.
-    Status onOmxLoaded();
+    Status release();
 
     // A "codec buffer", i.e. a buffer that can be used to pass data into
     // the encoder, has been allocated.  (This call does not call back into
-    // OMXNodeInstance.)
+    // component.)
     Status onInputBufferAdded(int32_t bufferId);
 
-    // Called from OnEmptyBufferDone.  If we have a BQ buffer available,
-    // fill it with a new frame of data; otherwise, just mark it as available.
+    // Called when encoder is no longer using the buffer.  If we have a BQ
+    // buffer available, fill it with a new frame of data; otherwise, just mark
+    // it as available.
     Status onInputBufferEmptied(int32_t bufferId, int fenceFd);
 
     // IGraphicBufferSource interface
     // ------------------------------
 
-    // Configure the buffer source to be used with an OMX node with the default
+    // Configure the buffer source to be used with a component with the default
     // data space.
     status_t configure(
-        const sp<IOmxNodeWrapper> &omxNode,
+        const sp<ComponentWrapper> &component,
         int32_t dataSpace,
         int32_t bufferCount,
         uint32_t frameWidth,
@@ -335,10 +328,10 @@
     // called when the data space of the input buffer changes
     void onDataspaceChanged_l(android_dataspace dataspace, android_pixel_format pixelFormat);
 
-    // Pointer back to the Omx node that created us.  We send buffers here.
-    sp<IOmxNodeWrapper> mOMXNode;
+    // Pointer back to the component that created us.  We send buffers here.
+    sp<ComponentWrapper> mComponent;
 
-    // Set by omxExecuting() / omxIdling().
+    // Set by start() / stop().
     bool mExecuting;
 
     bool mSuspended;
diff --git a/media/libstagefright/gbs/tests/Android.bp b/media/libstagefright/gbs/tests/Android.bp
new file mode 100644
index 0000000..1463e73
--- /dev/null
+++ b/media/libstagefright/gbs/tests/Android.bp
@@ -0,0 +1,15 @@
+cc_test {
+    name: "FrameDropper_test",
+
+    srcs: ["FrameDropper_test.cpp"],
+
+    shared_libs: [
+        "libstagefright_gbs",
+        "libutils",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
diff --git a/media/libstagefright/omx/tests/FrameDropper_test.cpp b/media/libstagefright/gbs/tests/FrameDropper_test.cpp
similarity index 98%
rename from media/libstagefright/omx/tests/FrameDropper_test.cpp
rename to media/libstagefright/gbs/tests/FrameDropper_test.cpp
index a925da6..54a84d3 100644
--- a/media/libstagefright/omx/tests/FrameDropper_test.cpp
+++ b/media/libstagefright/gbs/tests/FrameDropper_test.cpp
@@ -20,7 +20,7 @@
 
 #include <gtest/gtest.h>
 
-#include <media/stagefright/omx/FrameDropper.h>
+#include <media/stagefright/gbs/FrameDropper.h>
 #include <media/stagefright/foundation/ADebug.h>
 
 namespace android {
diff --git a/media/libstagefright/httplive/Android.bp b/media/libstagefright/httplive/Android.bp
index ac113b8..de560b6 100644
--- a/media/libstagefright/httplive/Android.bp
+++ b/media/libstagefright/httplive/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
     name: "libstagefright_httplive",
 
     srcs: [
diff --git a/media/libstagefright/include/media/stagefright/AACWriter.h b/media/libstagefright/include/media/stagefright/AACWriter.h
index aa60a19..7c63ddd 100644
--- a/media/libstagefright/include/media/stagefright/AACWriter.h
+++ b/media/libstagefright/include/media/stagefright/AACWriter.h
@@ -24,7 +24,6 @@
 namespace android {
 
 struct MediaSource;
-class MetaData;
 
 struct AACWriter : public MediaWriter {
     AACWriter(int fd);
diff --git a/media/libstagefright/include/media/stagefright/AMRWriter.h b/media/libstagefright/include/media/stagefright/AMRWriter.h
index 7d2c879..2ea2f78 100644
--- a/media/libstagefright/include/media/stagefright/AMRWriter.h
+++ b/media/libstagefright/include/media/stagefright/AMRWriter.h
@@ -25,8 +25,6 @@
 
 namespace android {
 
-class MetaData;
-
 struct AMRWriter : public MediaWriter {
     AMRWriter(int fd);
 
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 5d2c120..2a062cc 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -30,7 +30,6 @@
 
 struct AMessage;
 class MediaBuffer;
-class MetaData;
 struct ABuffer;
 
 class MPEG4Writer : public MediaWriter {
diff --git a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
index 3051406..3041181 100644
--- a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
+++ b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
@@ -25,7 +25,6 @@
 namespace android {
 
 class MediaBuffer;
-class MetaData;
 
 class MediaBufferGroup : public MediaBufferObserver {
 public:
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecSource.h b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
index bc0653d..eec115e 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecSource.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
@@ -30,7 +30,6 @@
 struct AReplyToken;
 class IGraphicBufferProducer;
 struct MediaCodec;
-class MetaData;
 
 struct MediaCodecSource : public MediaSource,
                           public MediaBufferObserver {
diff --git a/media/libstagefright/include/media/stagefright/MediaWriter.h b/media/libstagefright/include/media/stagefright/MediaWriter.h
index c4bba0e..2c12a87 100644
--- a/media/libstagefright/include/media/stagefright/MediaWriter.h
+++ b/media/libstagefright/include/media/stagefright/MediaWriter.h
@@ -24,8 +24,6 @@
 
 namespace android {
 
-class MetaData;
-
 struct MediaWriter : public RefBase {
     MediaWriter()
         : mMaxFileSizeLimitBytes(0),
diff --git a/media/libstagefright/include/media/stagefright/MetaData.h b/media/libstagefright/include/media/stagefright/MetaData.h
index 3438c56..5959e86 100644
--- a/media/libstagefright/include/media/stagefright/MetaData.h
+++ b/media/libstagefright/include/media/stagefright/MetaData.h
@@ -24,7 +24,6 @@
 
 #include <binder/Parcel.h>
 #include <utils/RefBase.h>
-#include <utils/KeyedVector.h>
 #include <utils/String8.h>
 
 namespace android {
@@ -229,7 +228,7 @@
     kTypeD263        = 'd263',
 };
 
-class MetaData : public RefBase {
+class MetaData final : public RefBase {
 public:
     MetaData();
     MetaData(const MetaData &from);
@@ -279,59 +278,22 @@
     String8 toString() const;
     void dumpToLog() const;
 
-    status_t writeToParcel(Parcel &parcel);
-    status_t updateFromParcel(const Parcel &parcel);
-    static sp<MetaData> createFromParcel(const Parcel &parcel);
-
 protected:
     virtual ~MetaData();
 
 private:
-    struct typed_data {
-        typed_data();
-        ~typed_data();
+    friend class BpMediaSource;
+    friend class BnMediaSource;
+    friend class BpMediaExtractor;
+    friend class BnMediaExtractor;
 
-        typed_data(const MetaData::typed_data &);
-        typed_data &operator=(const MetaData::typed_data &);
-
-        void clear();
-        void setData(uint32_t type, const void *data, size_t size);
-        void getData(uint32_t *type, const void **data, size_t *size) const;
-        // may include hexdump of binary data if verbose=true
-        String8 asString(bool verbose) const;
-
-    private:
-        uint32_t mType;
-        size_t mSize;
-
-        union {
-            void *ext_data;
-            float reservoir;
-        } u;
-
-        bool usesReservoir() const {
-            return mSize <= sizeof(u.reservoir);
-        }
-
-        void *allocateStorage(size_t size);
-        void freeStorage();
-
-        void *storage() {
-            return usesReservoir() ? &u.reservoir : u.ext_data;
-        }
-
-        const void *storage() const {
-            return usesReservoir() ? &u.reservoir : u.ext_data;
-        }
-    };
-
-    struct Rect {
-        int32_t mLeft, mTop, mRight, mBottom;
-    };
-
-    KeyedVector<uint32_t, typed_data> mItems;
-
-    // MetaData &operator=(const MetaData &);
+    status_t writeToParcel(Parcel &parcel);
+    status_t updateFromParcel(const Parcel &parcel);
+    static sp<MetaData> createFromParcel(const Parcel &parcel);
+    struct typed_data;
+    struct Rect;
+    struct MetaDataInternal;
+    MetaDataInternal *mInternalData;
 };
 
 }  // namespace android
diff --git a/media/libstagefright/include/media/stagefright/Utils.h b/media/libstagefright/include/media/stagefright/Utils.h
index 7d4a611..6a28e0b 100644
--- a/media/libstagefright/include/media/stagefright/Utils.h
+++ b/media/libstagefright/include/media/stagefright/Utils.h
@@ -28,7 +28,6 @@
 
 namespace android {
 
-class MetaData;
 struct AMessage;
 status_t convertMetaDataToMessage(
         const sp<MetaData> &meta, sp<AMessage> *format);
diff --git a/media/libstagefright/omx/1.0/Omx.cpp b/media/libstagefright/omx/1.0/Omx.cpp
index fe50656..4e2d398 100644
--- a/media/libstagefright/omx/1.0/Omx.cpp
+++ b/media/libstagefright/omx/1.0/Omx.cpp
@@ -24,7 +24,7 @@
 
 #include <media/stagefright/omx/OMXUtils.h>
 #include <media/stagefright/omx/OMXMaster.h>
-#include <media/stagefright/omx/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
 
 #include <media/stagefright/omx/1.0/WOmxNode.h>
 #include <media/stagefright/omx/1.0/WOmxObserver.h>
@@ -148,7 +148,7 @@
 Return<void> Omx::createInputSurface(createInputSurface_cb _hidl_cb) {
     sp<::android::IGraphicBufferProducer> bufferProducer;
 
-    sp<GraphicBufferSource> graphicBufferSource = new GraphicBufferSource();
+    sp<OmxGraphicBufferSource> graphicBufferSource = new OmxGraphicBufferSource();
     status_t err = graphicBufferSource->initCheck();
     if (err != OK) {
         LOG(ERROR) << "Failed to create persistent input surface: "
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
index 3201c32..ed272bb 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
@@ -79,9 +79,9 @@
 };
 
 struct TWGraphicBufferSource::TWOmxBufferSource : public IOmxBufferSource {
-    sp<GraphicBufferSource> mSource;
+    sp<OmxGraphicBufferSource> mSource;
 
-    TWOmxBufferSource(const sp<GraphicBufferSource> &source): mSource(source) {
+    TWOmxBufferSource(const sp<OmxGraphicBufferSource> &source): mSource(source) {
     }
 
     Return<void> onOmxExecuting() override {
@@ -115,7 +115,7 @@
 
 // TWGraphicBufferSource
 TWGraphicBufferSource::TWGraphicBufferSource(
-        sp<GraphicBufferSource> const& base) :
+        sp<OmxGraphicBufferSource> const& base) :
     mBase(base),
     mOmxBufferSource(new TWOmxBufferSource(base)) {
 }
diff --git a/media/libstagefright/omx/Android.bp b/media/libstagefright/omx/Android.bp
index 8539864..306a8eb 100644
--- a/media/libstagefright/omx/Android.bp
+++ b/media/libstagefright/omx/Android.bp
@@ -6,12 +6,11 @@
     },
 
     srcs: [
-        "FrameDropper.cpp",
-        "GraphicBufferSource.cpp",
         "BWGraphicBufferSource.cpp",
         "OMXMaster.cpp",
         "OMXNodeInstance.cpp",
         "OMXUtils.cpp",
+        "OmxGraphicBufferSource.cpp",
         "SimpleSoftOMXComponent.cpp",
         "SoftOMXComponent.cpp",
         "SoftOMXPlugin.cpp",
@@ -49,6 +48,7 @@
         "libgui",
         "libcutils",
         "libstagefright_foundation",
+        "libstagefright_gbs",
         "libstagefright_xmlparser",
         "libdl",
         "libhidlbase",
diff --git a/media/libstagefright/omx/BWGraphicBufferSource.cpp b/media/libstagefright/omx/BWGraphicBufferSource.cpp
index 94ef598..fa30a46 100644
--- a/media/libstagefright/omx/BWGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/BWGraphicBufferSource.cpp
@@ -55,9 +55,9 @@
 };
 
 struct BWGraphicBufferSource::BWOMXBufferSource : public BnOMXBufferSource {
-    sp<GraphicBufferSource> mSource;
+    sp<OmxGraphicBufferSource> mSource;
 
-    BWOMXBufferSource(const sp<GraphicBufferSource> &source): mSource(source) {
+    BWOMXBufferSource(const sp<OmxGraphicBufferSource> &source): mSource(source) {
     }
 
     Status onOmxExecuting() override {
@@ -83,7 +83,7 @@
 };
 
 BWGraphicBufferSource::BWGraphicBufferSource(
-        sp<GraphicBufferSource> const& base) :
+        sp<OmxGraphicBufferSource> const& base) :
     mBase(base),
     mOMXBufferSource(new BWOMXBufferSource(base)) {
 }
diff --git a/media/libstagefright/omx/OmxGraphicBufferSource.cpp b/media/libstagefright/omx/OmxGraphicBufferSource.cpp
new file mode 100644
index 0000000..83feac8
--- /dev/null
+++ b/media/libstagefright/omx/OmxGraphicBufferSource.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "OmxGraphicBufferSource"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <media/stagefright/gbs/ComponentWrapper.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
+
+namespace android {
+
+namespace {
+
+class OmxComponentWrapper : public ComponentWrapper {
+public:
+    explicit OmxComponentWrapper(const sp<IOmxNodeWrapper> &node)
+        : mOmxNode(node) {}
+    virtual ~OmxComponentWrapper() = default;
+
+    status_t submitBuffer(
+            int32_t bufferId, const sp<GraphicBuffer> &buffer,
+            int64_t timestamp, int fenceFd) override {
+        return mOmxNode->emptyBuffer(
+                bufferId, OMX_BUFFERFLAG_ENDOFFRAME, buffer, timestamp, fenceFd);
+    }
+
+    status_t submitEos(int32_t bufferId) override {
+        return mOmxNode->emptyBuffer(bufferId, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS);
+    }
+
+    void dispatchDataSpaceChanged(
+            int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
+        mOmxNode->dispatchDataSpaceChanged(dataSpace, aspects, pixelFormat);
+    }
+
+private:
+    sp<IOmxNodeWrapper> mOmxNode;
+
+    DISALLOW_EVIL_CONSTRUCTORS(OmxComponentWrapper);
+};
+
+}  // namespace
+
+Status OmxGraphicBufferSource::onOmxExecuting() {
+    return start();
+}
+
+Status OmxGraphicBufferSource::onOmxIdle() {
+    return stop();
+}
+
+Status OmxGraphicBufferSource::onOmxLoaded(){
+    return release();
+}
+
+status_t OmxGraphicBufferSource::configure(
+        const sp<IOmxNodeWrapper>& omxNode,
+        int32_t dataSpace,
+        int32_t bufferCount,
+        uint32_t frameWidth,
+        uint32_t frameHeight,
+        uint32_t consumerUsage) {
+    if (omxNode == NULL) {
+        return BAD_VALUE;
+    }
+
+    return GraphicBufferSource::configure(
+            new OmxComponentWrapper(omxNode), dataSpace, bufferCount,
+            frameWidth, frameHeight, consumerUsage);
+}
+
+}  // namespace android
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
index b9f22ab..4e56c98 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
@@ -28,7 +28,7 @@
 
 #include <android/BnGraphicBufferSource.h>
 
-#include <media/stagefright/omx/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
 
 namespace android {
 namespace hardware {
@@ -37,7 +37,7 @@
 namespace V1_0 {
 namespace implementation {
 
-using ::android::GraphicBufferSource;
+using ::android::OmxGraphicBufferSource;
 using ::android::hardware::graphics::common::V1_0::Dataspace;
 using ::android::hardware::media::omx::V1_0::ColorAspects;
 using ::android::hardware::media::omx::V1_0::IGraphicBufferSource;
@@ -69,10 +69,10 @@
 struct TWGraphicBufferSource : public TGraphicBufferSource {
     struct TWOmxNodeWrapper;
     struct TWOmxBufferSource;
-    sp<GraphicBufferSource> mBase;
+    sp<OmxGraphicBufferSource> mBase;
     sp<IOmxBufferSource> mOmxBufferSource;
 
-    TWGraphicBufferSource(sp<GraphicBufferSource> const& base);
+    TWGraphicBufferSource(sp<OmxGraphicBufferSource> const& base);
     Return<Status> configure(
             const sp<IOmxNode>& omxNode, Dataspace dataspace) override;
     Return<Status> setSuspend(bool suspend, int64_t timeUs) override;
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
index 0f78eb6..0efff22 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
@@ -23,14 +23,14 @@
 #include <android/BnOMXBufferSource.h>
 #include <media/IOMX.h>
 
-#include "GraphicBufferSource.h"
+#include "OmxGraphicBufferSource.h"
 #include "IOmxNodeWrapper.h"
 
 namespace android {
 
 using ::android::binder::Status;
 using ::android::BnGraphicBufferSource;
-using ::android::GraphicBufferSource;
+using ::android::OmxGraphicBufferSource;
 using ::android::IOMXNode;
 using ::android::sp;
 
@@ -38,10 +38,10 @@
     struct BWOMXBufferSource;
     struct BWOmxNodeWrapper;
 
-    sp<GraphicBufferSource> mBase;
+    sp<OmxGraphicBufferSource> mBase;
     sp<IOMXBufferSource> mOMXBufferSource;
 
-    BWGraphicBufferSource(sp<GraphicBufferSource> const &base);
+    BWGraphicBufferSource(sp<OmxGraphicBufferSource> const &base);
 
     Status configure(
             const sp<IOMXNode>& omxNode, int32_t dataSpace) override;
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h
new file mode 100644
index 0000000..4b0f3d2
--- /dev/null
+++ b/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OMX_GRAPHIC_BUFFER_SOURCE_H_
+
+#define OMX_GRAPHIC_BUFFER_SOURCE_H_
+
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/foundation/ABase.h>
+
+#include <android/BnGraphicBufferSource.h>
+#include <android/BnOMXBufferSource.h>
+
+#include "IOmxNodeWrapper.h"
+
+namespace android {
+
+using ::android::binder::Status;
+
+/*
+ * This class is used to feed OMX codecs from a Surface via BufferQueue or
+ * HW producer.
+ *
+ * See media/stagefright/gbs/GraphicBufferSource.h for documentation.
+ */
+class OmxGraphicBufferSource : public GraphicBufferSource {
+public:
+    OmxGraphicBufferSource() = default;
+    virtual ~OmxGraphicBufferSource() = default;
+
+    // OmxBufferSource interface
+    // ------------------------------
+
+    // This is called when OMX transitions to OMX_StateExecuting, which means
+    // we can start handing it buffers.  If we already have buffers of data
+    // sitting in the BufferQueue, this will send them to the codec.
+    Status onOmxExecuting();
+
+    // This is called when OMX transitions to OMX_StateIdle, indicating that
+    // the codec is meant to return all buffers back to the client for them
+    // to be freed. Do NOT submit any more buffers to the component.
+    Status onOmxIdle();
+
+    // This is called when OMX transitions to OMX_StateLoaded, indicating that
+    // we are shutting down.
+    Status onOmxLoaded();
+
+    // Rest of the interface in GraphicBufferSource.
+
+    // IGraphicBufferSource interface
+    // ------------------------------
+
+    // Configure the buffer source to be used with an OMX node with the default
+    // data space.
+    status_t configure(
+        const sp<IOmxNodeWrapper> &omxNode,
+        int32_t dataSpace,
+        int32_t bufferCount,
+        uint32_t frameWidth,
+        uint32_t frameHeight,
+        uint32_t consumerUsage);
+
+    // Rest of the interface in GraphicBufferSource.
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(OmxGraphicBufferSource);
+};
+
+}  // namespace android
+
+#endif  // OMX_GRAPHIC_BUFFER_SOURCE_H_
diff --git a/media/libstagefright/omx/tests/Android.bp b/media/libstagefright/omx/tests/Android.bp
index 999d9d4..3b521ab 100644
--- a/media/libstagefright/omx/tests/Android.bp
+++ b/media/libstagefright/omx/tests/Android.bp
@@ -34,21 +34,3 @@
 
     compile_multilib: "32",
 }
-
-cc_test {
-    name: "FrameDropper_test",
-
-    srcs: ["FrameDropper_test.cpp"],
-
-    shared_libs: [
-        "libstagefright_omx",
-        "libutils",
-    ],
-
-    include_dirs: ["frameworks/av/media/libstagefright/omx"],
-
-    cflags: [
-        "-Werror",
-        "-Wall",
-    ],
-}
diff --git a/media/libstagefright/rtsp/AMPEG2TSAssembler.h b/media/libstagefright/rtsp/AMPEG2TSAssembler.h
index f39c2b5..c987b5b 100644
--- a/media/libstagefright/rtsp/AMPEG2TSAssembler.h
+++ b/media/libstagefright/rtsp/AMPEG2TSAssembler.h
@@ -24,7 +24,6 @@
 
 struct AMessage;
 struct AString;
-class MetaData;
 
 struct AMPEG2TSAssembler : public ARTPAssembler {
     AMPEG2TSAssembler(
diff --git a/media/libstagefright/rtsp/rtp_test.cpp b/media/libstagefright/rtsp/rtp_test.cpp
index 98a8fb4..4590699 100644
--- a/media/libstagefright/rtsp/rtp_test.cpp
+++ b/media/libstagefright/rtsp/rtp_test.cpp
@@ -25,7 +25,6 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/ALooper.h>
 #include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/MetaData.h>
 #include <media/stagefright/SimpleDecodingSource.h>
 
 #include "ARTPSession.h"
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index a9025c0..b86876b 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -25,7 +25,6 @@
 #include <utils/StrongPointer.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/MetaData.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_util_Binder.h>
 
diff --git a/media/ndk/NdkMediaMuxer.cpp b/media/ndk/NdkMediaMuxer.cpp
index 80a4391..dffc4d7 100644
--- a/media/ndk/NdkMediaMuxer.cpp
+++ b/media/ndk/NdkMediaMuxer.cpp
@@ -27,7 +27,6 @@
 #include <utils/StrongPointer.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/MetaData.h>
 #include <media/stagefright/MediaMuxer.h>
 #include <media/IMediaHTTPService.h>
 #include <android_runtime/AndroidRuntime.h>
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index f2bc6d0..d6dae5b 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_library_shared {
+cc_library {
     name: "libmediautils",
 
     srcs: [
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index bb3dc99..61d73a0 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -56,3 +56,5 @@
 LOCAL_USE_AAPT2 := true
 
 include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/MediaComponents/res/drawable/ic_cast.xml b/packages/MediaComponents/res/drawable/ic_cast.xml
deleted file mode 100644
index ac22a4b..0000000
--- a/packages/MediaComponents/res/drawable/ic_cast.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index ff4f12a..069fbdd 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -49,11 +49,10 @@
             android:text="North by Northwest"
             android:textColor="#FFFFFFFF" />
 
-        <ImageButton
-            android:id="@+id/cast"
+        <view class="com.android.support.mediarouter.app.MediaRouteButton" android:id="@+id/cast"
             android:layout_alignParentEnd="true"
             android:layout_centerVertical="true"
-            style="@style/TitleBarButton.MediaRouteButton"/>
+            style="@style/TitleBarButton" />
 
     </RelativeLayout>
 
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index d31b41d..8363dd2 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -34,11 +34,6 @@
         <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>
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
new file mode 100644
index 0000000..b517b4a
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media;
+
+import android.content.Context;
+import android.media.IMediaSession2;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.SessionToken;
+import android.media.update.MediaBrowser2Provider;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+public class MediaBrowser2Impl extends MediaController2Impl implements MediaBrowser2Provider {
+    private final String TAG = "MediaBrowser2";
+    private final boolean DEBUG = true; // TODO(jaewan): change.
+
+    private final MediaBrowser2 mInstance;
+    private final MediaBrowser2.BrowserCallback mCallback;
+
+    public MediaBrowser2Impl(MediaBrowser2 instance, Context context, SessionToken token,
+            BrowserCallback callback, Executor executor) {
+        super(instance, context, token, callback, executor);
+        mInstance = instance;
+        mCallback = callback;
+    }
+
+    @Override
+    public void getBrowserRoot_impl(Bundle rootHints) {
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.getBrowserRoot(getControllerStub(), rootHints);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+    }
+
+    public void onGetRootResult(
+            final Bundle rootHints, final String rootMediaId, final Bundle rootExtra) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onGetRootResult(rootHints, rootMediaId, rootExtra);
+        });
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
new file mode 100644
index 0000000..7a01b97
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.IMediaSession2;
+import android.media.IMediaSession2Callback;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaController2;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaPlayerBase;
+import android.media.MediaSessionService2;
+import android.media.SessionToken;
+import android.media.session.PlaybackState;
+import android.media.update.MediaController2Provider;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.annotation.GuardedBy;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaController2Impl implements MediaController2Provider {
+    private static final String TAG = "MediaController2";
+    private static final boolean DEBUG = true; // TODO(jaewan): Change
+
+    private final MediaController2 mInstance;
+
+    /**
+     * Flag used by MediaController2Record to filter playback callback.
+     */
+    static final int CALLBACK_FLAG_PLAYBACK = 0x1;
+
+    static final int REQUEST_CODE_ALL = 0;
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final MediaSession2CallbackStub mSessionCallbackStub;
+    private final SessionToken mToken;
+    private final ControllerCallback mCallback;
+    private final Executor mCallbackExecutor;
+    private final IBinder.DeathRecipient mDeathRecipient;
+
+    @GuardedBy("mLock")
+    private final List<PlaybackListenerHolder> mPlaybackListeners = new ArrayList<>();
+    @GuardedBy("mLock")
+    private SessionServiceConnection mServiceConnection;
+    @GuardedBy("mLock")
+    private boolean mIsReleased;
+
+    // Assignment should be used with the lock hold, but should be used without a lock to prevent
+    // potential deadlock.
+    // Postfix -Binder is added to explicitly show that it's potentially remote process call.
+    // Technically -Interface is more correct, but it may misread that it's interface (vs class)
+    // so let's keep this postfix until we find better postfix.
+    @GuardedBy("mLock")
+    private volatile IMediaSession2 mSessionBinder;
+
+    // TODO(jaewan): Require session activeness changed listener, because controller can be
+    //               available when the session's player is null.
+    public MediaController2Impl(MediaController2 instance, Context context, SessionToken token,
+            ControllerCallback callback, Executor executor) {
+        mInstance = instance;
+
+        if (context == null) {
+            throw new IllegalArgumentException("context shouldn't be null");
+        }
+        if (token == null) {
+            throw new IllegalArgumentException("token shouldn't be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback shouldn't be null");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException("executor shouldn't be null");
+        }
+        mContext = context;
+        mSessionCallbackStub = new MediaSession2CallbackStub(this);
+        mToken = token;
+        mCallback = callback;
+        mCallbackExecutor = executor;
+        mDeathRecipient = () -> {
+            mInstance.release();
+        };
+
+        mSessionBinder = null;
+
+        if (token.getSessionBinder() == null) {
+            mServiceConnection = new SessionServiceConnection();
+            connectToService();
+        } else {
+            mServiceConnection = null;
+            connectToSession(token.getSessionBinder());
+        }
+    }
+
+    // Should be only called by constructor.
+    private void connectToService() {
+        // Service. Needs to get fresh binder whenever connection is needed.
+        final Intent intent = new Intent(MediaSessionService2.SERVICE_INTERFACE);
+        intent.setClassName(mToken.getPackageName(), mToken.getServiceName());
+
+        // Use bindService() instead of startForegroundService() to start session service for three
+        // reasons.
+        // 1. Prevent session service owner's stopSelf() from destroying service.
+        //    With the startForegroundService(), service's call of stopSelf() will trigger immediate
+        //    onDestroy() calls on the main thread even when onConnect() is running in another
+        //    thread.
+        // 2. Minimize APIs for developers to take care about.
+        //    With bindService(), developers only need to take care about Service.onBind()
+        //    but Service.onStartCommand() should be also taken care about with the
+        //    startForegroundService().
+        // 3. Future support for UI-less playback
+        //    If a service wants to keep running, it should be either foreground service or
+        //    bounded service. But there had been request for the feature for system apps
+        //    and using bindService() will be better fit with it.
+        // TODO(jaewan): Use bindServiceAsUser()??
+        boolean result = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        if (!result) {
+            Log.w(TAG, "bind to " + mToken + " failed");
+        } else if (DEBUG) {
+            Log.d(TAG, "bind to " + mToken + " success");
+        }
+    }
+
+    private void connectToSession(IMediaSession2 sessionBinder) {
+        try {
+            sessionBinder.connect(mContext.getPackageName(), mSessionCallbackStub);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to call connection request. Framework will retry"
+                    + " automatically");
+        }
+    }
+
+    @Override
+    public void release_impl() {
+        if (DEBUG) {
+            Log.d(TAG, "release from " + mToken);
+        }
+        final IMediaSession2 binder;
+        synchronized (mLock) {
+            if (mIsReleased) {
+                // Prevent re-enterance from the ControllerCallback.onDisconnected()
+                return;
+            }
+            mIsReleased = true;
+            if (mServiceConnection != null) {
+                mContext.unbindService(mServiceConnection);
+                mServiceConnection = null;
+            }
+            mPlaybackListeners.clear();
+            binder = mSessionBinder;
+            mSessionBinder = null;
+            mSessionCallbackStub.destroy();
+        }
+        if (binder != null) {
+            try {
+                binder.asBinder().unlinkToDeath(mDeathRecipient, 0);
+                binder.release(mSessionCallbackStub);
+            } catch (RemoteException e) {
+                // No-op.
+            }
+        }
+        mCallbackExecutor.execute(() -> {
+            mCallback.onDisconnected();
+        });
+    }
+
+    IMediaSession2 getSessionBinder() {
+        return mSessionBinder;
+    }
+
+    MediaSession2CallbackStub getControllerStub() {
+        return mSessionCallbackStub;
+    }
+
+    Executor getCallbackExecutor() {
+        return mCallbackExecutor;
+    }
+
+    @Override
+    public SessionToken getSessionToken_impl() {
+        return mToken;
+    }
+
+    @Override
+    public boolean isConnected_impl() {
+        final IMediaSession2 binder = mSessionBinder;
+        return binder != null;
+    }
+
+    @Override
+    public void play_impl() {
+        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_START);
+    }
+
+    @Override
+    public void pause_impl() {
+        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE);
+    }
+
+    @Override
+    public void stop_impl() {
+        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_STOP);
+    }
+
+    @Override
+    public void skipToPrevious_impl() {
+        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
+    }
+
+    @Override
+    public void skipToNext_impl() {
+        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
+    }
+
+    private void sendCommand(int code) {
+        // TODO(jaewan): optimization) Cache Command objects?
+        Command command = new Command(code);
+        // TODO(jaewan): Check if the command is in the allowed group.
+
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                binder.sendCommand(mSessionCallbackStub, command.toBundle(), null);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+    }
+
+    @Override
+    public PlaybackState getPlaybackState_impl() {
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                return binder.getPlaybackState();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+        // TODO(jaewan): What to return for error case?
+        return null;
+    }
+
+    @Override
+    public void addPlaybackListener_impl(
+            MediaPlayerBase.PlaybackListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener shouldn't be null");
+        }
+        if (handler == null) {
+            throw new IllegalArgumentException("handler shouldn't be null");
+        }
+        boolean registerCallback;
+        synchronized (mLock) {
+            if (PlaybackListenerHolder.contains(mPlaybackListeners, listener)) {
+                throw new IllegalArgumentException("listener is already added. Ignoring.");
+            }
+            registerCallback = mPlaybackListeners.isEmpty();
+            mPlaybackListeners.add(new PlaybackListenerHolder(listener, handler));
+        }
+        if (registerCallback) {
+            registerCallbackForPlaybackNotLocked();
+        }
+    }
+
+    @Override
+    public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener shouldn't be null");
+        }
+        boolean unregisterCallback;
+        synchronized (mLock) {
+            int idx = PlaybackListenerHolder.indexOf(mPlaybackListeners, listener);
+            if (idx >= 0) {
+                mPlaybackListeners.get(idx).removeCallbacksAndMessages(null);
+                mPlaybackListeners.remove(idx);
+            }
+            unregisterCallback = mPlaybackListeners.isEmpty();
+        }
+        if (unregisterCallback) {
+            final IMediaSession2 binder = mSessionBinder;
+            if (binder != null) {
+                // Lazy unregister
+                try {
+                    binder.unregisterCallback(mSessionCallbackStub, CALLBACK_FLAG_PLAYBACK);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        }
+    }
+
+    ///////////////////////////////////////////////////
+    // Protected or private methods
+    ///////////////////////////////////////////////////
+    // Should be used without a lock to prevent potential deadlock.
+    private void registerCallbackForPlaybackNotLocked() {
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                binder.registerCallback(mSessionCallbackStub,
+                        CALLBACK_FLAG_PLAYBACK, REQUEST_CODE_ALL);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        }
+    }
+
+    private void pushPlaybackStateChanges(final PlaybackState state) {
+        synchronized (mLock) {
+            for (int i = 0; i < mPlaybackListeners.size(); i++) {
+                mPlaybackListeners.get(i).postPlaybackChange(state);
+            }
+        }
+    }
+
+    // Called when the result for connecting to the session was delivered.
+    // Should be used without a lock to prevent potential deadlock.
+    private void onConnectionChangedNotLocked(IMediaSession2 sessionBinder,
+            CommandGroup commandGroup) {
+        if (DEBUG) {
+            Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder
+                    + ", commands=" + commandGroup);
+        }
+        boolean release = false;
+        try {
+            if (sessionBinder == null || commandGroup == null) {
+                // Connection rejected.
+                release = true;
+                return;
+            }
+            boolean registerCallbackForPlaybackNeeded;
+            synchronized (mLock) {
+                if (mIsReleased) {
+                    return;
+                }
+                if (mSessionBinder != null) {
+                    Log.e(TAG, "Cannot be notified about the connection result many times."
+                            + " Probably a bug or malicious app.");
+                    release = true;
+                    return;
+                }
+                mSessionBinder = sessionBinder;
+                try {
+                    // Implementation for the local binder is no-op,
+                    // so can be used without worrying about deadlock.
+                    mSessionBinder.asBinder().linkToDeath(mDeathRecipient, 0);
+                } catch (RemoteException e) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Session died too early.", e);
+                    }
+                    release = true;
+                    return;
+                }
+                registerCallbackForPlaybackNeeded = !mPlaybackListeners.isEmpty();
+            }
+            // TODO(jaewan): Keep commands to prevents illegal API calls.
+            mCallbackExecutor.execute(() -> {
+                mCallback.onConnected(commandGroup);
+            });
+            if (registerCallbackForPlaybackNeeded) {
+                registerCallbackForPlaybackNotLocked();
+            }
+        } finally {
+            if (release) {
+                // Trick to call release() without holding the lock, to prevent potential deadlock
+                // with the developer's custom lock within the ControllerCallback.onDisconnected().
+                mInstance.release();
+            }
+        }
+    }
+
+    // TODO(jaewan): Pull out this from the controller2, and rename it to the MediaController2Stub
+    //               or MediaBrowser2Stub.
+    static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
+        private final WeakReference<MediaController2Impl> mController;
+
+        private MediaSession2CallbackStub(MediaController2Impl controller) {
+            mController = new WeakReference<>(controller);
+        }
+
+        private MediaController2Impl getController() throws IllegalStateException {
+            final MediaController2Impl controller = mController.get();
+            if (controller == null) {
+                throw new IllegalStateException("Controller is released");
+            }
+            return controller;
+        }
+
+        // TODO(jaewan): Refactor code to get rid of these pattern.
+        private MediaBrowser2Impl getBrowser() throws IllegalStateException {
+            final MediaController2Impl controller = getController();
+            if (controller instanceof MediaBrowser2Impl) {
+                return (MediaBrowser2Impl) controller;
+            }
+            return null;
+        }
+
+        public void destroy() {
+            mController.clear();
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) throws RuntimeException {
+            final MediaController2Impl controller = getController();
+            controller.pushPlaybackStateChanges(state);
+        }
+
+        @Override
+        public void onConnectionChanged(IMediaSession2 sessionBinder, Bundle commandGroup)
+                throws RuntimeException {
+            final MediaController2Impl controller;
+            try {
+                controller = getController();
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+                return;
+            }
+            controller.onConnectionChangedNotLocked(
+                    sessionBinder, CommandGroup.fromBundle(commandGroup));
+        }
+
+        @Override
+        public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra)
+                throws RuntimeException {
+            final MediaBrowser2Impl browser;
+            try {
+                browser = getBrowser();
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+                return;
+            }
+            browser.onGetRootResult(rootHints, rootMediaId, rootExtra);
+        }
+    }
+
+    // This will be called on the main thread.
+    private class SessionServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            // Note that it's always main-thread.
+            if (DEBUG) {
+                Log.d(TAG, "onServiceConnected " + name + " " + this);
+            }
+            // Sanity check
+            if (!mToken.getPackageName().equals(name.getPackageName())) {
+                Log.wtf(TAG, name + " was connected, but expected pkg="
+                        + mToken.getPackageName() + " with id=" + mToken.getId());
+                return;
+            }
+            final IMediaSession2 sessionBinder = IMediaSession2.Stub.asInterface(service);
+            connectToSession(sessionBinder);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            // Temporal lose of the binding because of the service crash. System will automatically
+            // rebind, so just no-op.
+            // TODO(jaewan): Really? Either disconnect cleanly or
+            if (DEBUG) {
+                Log.w(TAG, "Session service " + name + " is disconnected.");
+            }
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            // Permanent lose of the binding because of the service package update or removed.
+            // This SessionServiceRecord will be removed accordingly, but forget session binder here
+            // for sure.
+            mInstance.release();
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
new file mode 100644
index 0000000..430ab4c
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media;
+
+import android.content.Intent;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaSession2;
+import android.media.MediaSessionService2;
+import android.media.update.MediaLibraryService2Provider;
+
+public class MediaLibraryService2Impl extends MediaSessionService2Impl implements
+        MediaLibraryService2Provider {
+    private final MediaSessionService2 mInstance;
+    private MediaLibrarySession mLibrarySession;
+
+    public MediaLibraryService2Impl(MediaLibraryService2 instance) {
+        super(instance);
+        mInstance = instance;
+    }
+
+    @Override
+    public void onCreate_impl() {
+        super.onCreate_impl();
+
+        // Effectively final
+        MediaSession2 session = getSession();
+        if (!(session instanceof MediaLibrarySession)) {
+            throw new RuntimeException("Expected MediaLibrarySession, but returned MediaSession2");
+        }
+        mLibrarySession = (MediaLibrarySession) getSession();
+    }
+
+    @Override
+    Intent createServiceIntent() {
+        Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+        serviceIntent.setAction(MediaLibraryService2.SERVICE_INTERFACE);
+        return serviceIntent;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
new file mode 100644
index 0000000..2cb562f
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media;
+
+import android.Manifest.permission;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.IMediaSession2Callback;
+import android.media.MediaController2;
+import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.SessionToken;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.media.update.MediaSession2Provider;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.util.Log;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MediaSession2Impl implements MediaSession2Provider {
+    private static final String TAG = "MediaSession2";
+    private static final boolean DEBUG = true;//Log.isLoggable(TAG, Log.DEBUG);
+
+    private final MediaSession2 mInstance;
+
+    private final Context mContext;
+    private final String mId;
+    private final Handler mHandler;
+    private final MediaSession2Stub mSessionStub;
+    private final SessionToken mSessionToken;
+
+    private MediaPlayerBase mPlayer;
+
+    private final List<PlaybackListenerHolder> mListeners = new ArrayList<>();
+    private MyPlaybackListener mListener;
+    private MediaSession2 instance;
+
+    /**
+     * Can be only called by the {@link Builder#build()}.
+     *
+     * @param instance
+     * @param context
+     * @param player
+     * @param id
+     * @param callback
+     */
+    public MediaSession2Impl(MediaSession2 instance, Context context, MediaPlayerBase player,
+            String id, SessionCallback callback) {
+        mInstance = instance;
+
+        // Argument checks are done by builder already.
+        // Initialize finals first.
+        mContext = context;
+        mId = id;
+        mHandler = new Handler(Looper.myLooper());
+        mSessionStub = new MediaSession2Stub(this, callback);
+        // Ask server to create session token for following reasons.
+        //   1. Make session ID unique per package.
+        //      Server can only know if the package has another process and has another session
+        //      with the same id. Let server check this.
+        //      Note that 'ID is unique per package' is important for controller to distinguish
+        //      a session in another package.
+        //   2. Easier to know the type of session.
+        //      Session created here can be the session service token. In order distinguish,
+        //      we need to iterate AndroidManifest.xml but it's already done by the server.
+        //      Let server to create token with the type.
+        MediaSessionManager manager =
+                (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+        mSessionToken = manager.createSessionToken(mContext.getPackageName(), mId, mSessionStub);
+        if (mSessionToken == null) {
+            throw new IllegalStateException("Session with the same id is already used by"
+                    + " another process. Use MediaController2 instead.");
+        }
+
+        setPlayerInternal(player);
+    }
+
+    // TODO(jaewan): Add explicit release() and do not remove session object with the
+    //               setPlayer(null). Token can be available when player is null, and
+    //               controller can also attach to session.
+    @Override
+    public void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException {
+        ensureCallingThread();
+        // TODO(jaewan): Remove this when we don't inherits MediaPlayerBase.
+        if (player instanceof MediaSession2 || player instanceof MediaController2) {
+            throw new IllegalArgumentException("player doesn't accept MediaSession2 nor"
+                    + " MediaController2");
+        }
+        if (player != null && mPlayer == player) {
+            // Player didn't changed. No-op.
+            return;
+        }
+        setPlayerInternal(player);
+    }
+
+    private void setPlayerInternal(MediaPlayerBase player) {
+        mHandler.removeCallbacksAndMessages(null);
+        if (mPlayer == null && player != null) {
+            if (DEBUG) {
+                Log.d(TAG, "session is ready to use, id=" + mId);
+            }
+        } else if (mPlayer != null && player == null) {
+            if (DEBUG) {
+                Log.d(TAG, "session is now unavailable, id=" + mId);
+            }
+            if (mSessionStub != null) {
+                // Invalidate previously published session stub.
+                mSessionStub.destroyNotLocked();
+            }
+        }
+        if (mPlayer != null && mListener != null) {
+            // This might not work for a poorly implemented player.
+            mPlayer.removePlaybackListener(mListener);
+        }
+        if (player != null) {
+            mListener = new MyPlaybackListener(this, player);
+            player.addPlaybackListener(mListener, mHandler);
+            notifyPlaybackStateChanged(player.getPlaybackState());
+        }
+        mPlayer = player;
+    }
+
+    @Override
+    public MediaPlayerBase getPlayer_impl() {
+        return getPlayer();
+    }
+
+    // TODO(jaewan): Change this to @NonNull
+    @Override
+    public SessionToken getToken_impl() {
+        return mSessionToken;
+    }
+
+    @Override
+    public List<ControllerInfo> getConnectedControllers_impl() {
+        return mSessionStub.getControllers();
+    }
+
+    @Override
+    public void play_impl() {
+        ensureCallingThread();
+        ensurePlayer();
+        mPlayer.play();
+    }
+
+    @Override
+    public void pause_impl() {
+        ensureCallingThread();
+        ensurePlayer();
+        mPlayer.pause();
+    }
+
+    @Override
+    public void stop_impl() {
+        ensureCallingThread();
+        ensurePlayer();
+        mPlayer.stop();
+    }
+
+    @Override
+    public void skipToPrevious_impl() {
+        ensureCallingThread();
+        ensurePlayer();
+        mPlayer.skipToPrevious();
+    }
+
+    @Override
+    public void skipToNext_impl() {
+        ensureCallingThread();
+        ensurePlayer();
+        mPlayer.skipToNext();
+    }
+
+    @Override
+    public PlaybackState getPlaybackState_impl() {
+        ensureCallingThread();
+        ensurePlayer();
+        return mPlayer.getPlaybackState();
+    }
+
+    @Override
+    public void addPlaybackListener_impl(
+            MediaPlayerBase.PlaybackListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener shouldn't be null");
+        }
+        if (handler == null) {
+            throw new IllegalArgumentException("handler shouldn't be null");
+        }
+        ensureCallingThread();
+        if (PlaybackListenerHolder.contains(mListeners, listener)) {
+            Log.w(TAG, "listener is already added. Ignoring.");
+            return;
+        }
+        mListeners.add(new PlaybackListenerHolder(listener, handler));
+    }
+
+    @Override
+    public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener shouldn't be null");
+        }
+        ensureCallingThread();
+        int idx = PlaybackListenerHolder.indexOf(mListeners, listener);
+        if (idx >= 0) {
+            mListeners.get(idx).removeCallbacksAndMessages(null);
+            mListeners.remove(idx);
+        }
+    }
+
+    ///////////////////////////////////////////////////
+    // Protected or private methods
+    ///////////////////////////////////////////////////
+
+    // Enforces developers to call all the methods on the initially given thread
+    // because calls from the MediaController2 will be run on the thread.
+    // TODO(jaewan): Should we allow calls from the multiple thread?
+    //               I prefer this way because allowing multiple thread may case tricky issue like
+    //               b/63446360. If the {@link #setPlayer()} with {@code null} can be called from
+    //               another thread, transport controls can be called after that.
+    //               That's basically the developer's mistake, but they cannot understand what's
+    //               happening behind until we tell them so.
+    //               If enforcing callling thread doesn't look good, we can alternatively pick
+    //               1. Allow calls from random threads for all methods.
+    //               2. Allow calls from random threads for all methods, except for the
+    //                  {@link #setPlayer()}.
+    // TODO(jaewan): Should we pend command instead of exception?
+    private void ensureCallingThread() {
+        if (mHandler.getLooper() != Looper.myLooper()) {
+            throw new IllegalStateException("Run this on the given thread");
+        }
+    }
+
+    private void ensurePlayer() {
+        // TODO(jaewan): Should we pend command instead? Follow the decision from MP2.
+        //               Alternatively we can add a API like setAcceptsPendingCommands(boolean).
+        if (mPlayer == null) {
+            throw new IllegalStateException("Player isn't set");
+        }
+    }
+
+    Handler getHandler() {
+        return mHandler;
+    }
+
+    private void notifyPlaybackStateChanged(PlaybackState state) {
+        // Notify to listeners added directly to this session
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).postPlaybackChange(state);
+        }
+        // Notify to controllers as well.
+        mSessionStub.notifyPlaybackStateChangedNotLocked(state);
+    }
+
+    Context getContext() {
+        return mContext;
+    }
+
+    MediaSession2 getInstance() {
+        return mInstance;
+    }
+
+    MediaPlayerBase getPlayer() {
+        return mPlayer;
+    }
+
+    private static class MyPlaybackListener implements MediaPlayerBase.PlaybackListener {
+        private final WeakReference<MediaSession2Impl> mSession;
+        private final MediaPlayerBase mPlayer;
+
+        private MyPlaybackListener(MediaSession2Impl session, MediaPlayerBase player) {
+            mSession = new WeakReference<>(session);
+            mPlayer = player;
+        }
+
+        @Override
+        public void onPlaybackChanged(PlaybackState state) {
+            MediaSession2Impl session = mSession.get();
+            if (session == null || session.getHandler().getLooper() != Looper.myLooper()
+                    || mPlayer != session.mInstance.getPlayer()) {
+                Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
+                        new IllegalStateException());
+                return;
+            }
+            session.notifyPlaybackStateChanged(state);
+        }
+    }
+
+    public static class ControllerInfoImpl implements ControllerInfoProvider {
+        private final ControllerInfo mInstance;
+        private final int mUid;
+        private final String mPackageName;
+        private final boolean mIsTrusted;
+        private final IMediaSession2Callback mControllerBinder;
+
+        // Flag to indicate which callbacks should be returned for the controller binder.
+        // Either 0 or combination of {@link #CALLBACK_FLAG_PLAYBACK},
+        // {@link #CALLBACK_FLAG_SESSION_ACTIVENESS}
+        private int mFlag;
+
+        public ControllerInfoImpl(ControllerInfo instance, Context context, int uid,
+                int pid, String packageName, IMediaSession2Callback callback) {
+            mInstance = instance;
+            mUid = uid;
+            mPackageName = packageName;
+
+            // TODO(jaewan): Remove this workaround
+            if ("com.android.server.media".equals(packageName)) {
+                mIsTrusted = true;
+            } else if (context.checkPermission(permission.MEDIA_CONTENT_CONTROL, pid, uid) ==
+                    PackageManager.PERMISSION_GRANTED) {
+                mIsTrusted = true;
+            } else {
+                // TODO(jaewan): Also consider enabled notification listener.
+                mIsTrusted = false;
+                // System apps may bind across the user so uid can be differ.
+                // Skip sanity check for the system app.
+                try {
+                    int uidForPackage = context.getPackageManager().getPackageUid(packageName, 0);
+                    if (uid != uidForPackage) {
+                        throw new IllegalArgumentException("Illegal call from uid=" + uid +
+                                ", pkg=" + packageName + ". Expected uid" + uidForPackage);
+                    }
+                } catch (NameNotFoundException e) {
+                    // Rethrow exception with different name because binder methods only accept
+                    // RemoteException.
+                    throw new IllegalArgumentException(e);
+                }
+            }
+            mControllerBinder = callback;
+        }
+
+        @Override
+        public String getPackageName_impl() {
+            return mPackageName;
+        }
+
+        @Override
+        public int getUid_impl() {
+            return mUid;
+        }
+
+        @Override
+        public boolean isTrusted_impl() {
+            return mIsTrusted;
+        }
+
+        @Override
+        public int hashCode_impl() {
+            return mControllerBinder.hashCode();
+        }
+
+        @Override
+        public boolean equals_impl(ControllerInfoProvider obj) {
+            return equals(obj);
+        }
+
+        @Override
+        public int hashCode() {
+            return mControllerBinder.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof ControllerInfoImpl)) {
+                return false;
+            }
+            ControllerInfoImpl other = (ControllerInfoImpl) obj;
+            return mControllerBinder.asBinder().equals(other.mControllerBinder.asBinder());
+        }
+
+        public ControllerInfo getInstance() {
+            return mInstance;
+        }
+
+        public IBinder getId() {
+            return mControllerBinder.asBinder();
+        }
+
+        public IMediaSession2Callback getControllerBinder() {
+            return mControllerBinder;
+        }
+
+        public boolean containsFlag(int flag) {
+            return (mFlag & flag) != 0;
+        }
+
+        public void addFlag(int flag) {
+            mFlag |= flag;
+        }
+
+        public void removeFlag(int flag) {
+            mFlag &= ~flag;
+        }
+
+        public static ControllerInfoImpl from(ControllerInfo controller) {
+            return (ControllerInfoImpl) controller.getProvider();
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
new file mode 100644
index 0000000..8fb72c4
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media;
+
+import static com.android.media.MediaController2Impl.CALLBACK_FLAG_PLAYBACK;
+
+import android.content.Context;
+import android.media.IMediaSession2;
+import android.media.IMediaSession2Callback;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.session.PlaybackState;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.service.media.MediaBrowserService.BrowserRoot;
+import android.support.annotation.GuardedBy;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.media.MediaSession2Impl.ControllerInfoImpl;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MediaSession2Stub extends IMediaSession2.Stub {
+    private static final String TAG = "MediaSession2Stub";
+    private static final boolean DEBUG = true; // TODO(jaewan): Rename.
+
+    private final Object mLock = new Object();
+    private final CommandHandler mCommandHandler;
+    private final WeakReference<MediaSession2Impl> mSession;
+    private final Context mContext;
+    private final SessionCallback mSessionCallback;
+    private final MediaLibrarySessionCallback mLibraryCallback;
+
+    @GuardedBy("mLock")
+    private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
+
+    public MediaSession2Stub(MediaSession2Impl session, SessionCallback callback) {
+        mSession = new WeakReference<>(session);
+        mContext = session.getContext();
+        // TODO(jaewan): Should be executor from the session builder
+        mCommandHandler = new CommandHandler(session.getHandler().getLooper());
+        mSessionCallback = callback;
+        mLibraryCallback = (callback instanceof MediaLibrarySessionCallback)
+                ? (MediaLibrarySessionCallback) callback : null;
+    }
+
+    public void destroyNotLocked() {
+        final List<ControllerInfo> list;
+        synchronized (mLock) {
+            mSession.clear();
+            mCommandHandler.removeCallbacksAndMessages(null);
+            list = getControllers();
+            mControllers.clear();
+        }
+        for (int i = 0; i < list.size(); i++) {
+            IMediaSession2Callback callbackBinder =
+                    ((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
+            try {
+                // Should be used without a lock hold to prevent potential deadlock.
+                callbackBinder.onConnectionChanged(null, null);
+            } catch (RemoteException e) {
+                // Controller is gone. Should be fine because we're destroying.
+            }
+        }
+    }
+
+    private MediaSession2Impl getSession() throws IllegalStateException {
+        final MediaSession2Impl session = mSession.get();
+        if (session == null) {
+            throw new IllegalStateException("Session is died");
+        }
+        return session;
+    }
+
+    @Override
+    public void connect(String callingPackage, IMediaSession2Callback callback) {
+        if (callback == null) {
+            // Requesting connect without callback to receive result.
+            return;
+        }
+        ControllerInfo request = new ControllerInfo(mContext,
+                Binder.getCallingUid(), Binder.getCallingPid(), callingPackage, callback);
+        mCommandHandler.postConnect(request);
+    }
+
+    @Override
+    public void release(IMediaSession2Callback caller) throws RemoteException {
+        synchronized (mLock) {
+            ControllerInfo controllerInfo = mControllers.remove(caller.asBinder());
+            if (DEBUG) {
+                Log.d(TAG, "releasing " + controllerInfo);
+            }
+        }
+    }
+
+    @Override
+    public void sendCommand(IMediaSession2Callback caller, Bundle command, Bundle args)
+            throws RuntimeException {
+        ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        mCommandHandler.postCommand(controller, Command.fromBundle(command), args);
+    }
+
+    @Override
+    public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
+            throws RuntimeException {
+        if (mLibraryCallback == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Session cannot hand getBrowserRoot()");
+            }
+            return;
+        }
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "getBrowerRoot from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        mCommandHandler.postOnGetRoot(controller, rootHints);
+    }
+
+    @Deprecated
+    @Override
+    public PlaybackState getPlaybackState() throws RemoteException {
+        MediaSession2Impl session = getSession();
+        // TODO(jaewan): Check if mPlayer.getPlaybackState() is safe here.
+        return session.getInstance().getPlayer().getPlaybackState();
+    }
+
+    @Deprecated
+    @Override
+    public void registerCallback(final IMediaSession2Callback callbackBinder,
+            final int callbackFlag, final int requestCode) throws RemoteException {
+        // TODO(jaewan): Call onCommand() here. To do so, you should pend message.
+        synchronized (mLock) {
+            ControllerInfo controllerInfo = getController(callbackBinder);
+            if (controllerInfo == null) {
+                return;
+            }
+            ControllerInfoImpl.from(controllerInfo).addFlag(callbackFlag);
+        }
+    }
+
+    @Deprecated
+    @Override
+    public void unregisterCallback(IMediaSession2Callback callbackBinder, int callbackFlag)
+            throws RemoteException {
+        // TODO(jaewan): Call onCommand() here. To do so, you should pend message.
+        synchronized (mLock) {
+            ControllerInfo controllerInfo = getController(callbackBinder);
+            if (controllerInfo == null) {
+                return;
+            }
+            ControllerInfoImpl.from(controllerInfo).removeFlag(callbackFlag);
+        }
+    }
+
+    private ControllerInfo getController(IMediaSession2Callback caller) {
+        synchronized (mLock) {
+            return mControllers.get(caller.asBinder());
+        }
+    }
+
+    public List<ControllerInfo> getControllers() {
+        ArrayList<ControllerInfo> controllers = new ArrayList<>();
+        synchronized (mLock) {
+            for (int i = 0; i < mControllers.size(); i++) {
+                controllers.add(mControllers.valueAt(i));
+            }
+        }
+        return controllers;
+    }
+
+    public List<ControllerInfo> getControllersWithFlag(int flag) {
+        ArrayList<ControllerInfo> controllers = new ArrayList<>();
+        synchronized (mLock) {
+            for (int i = 0; i < mControllers.size(); i++) {
+                ControllerInfo controllerInfo = mControllers.valueAt(i);
+                if (ControllerInfoImpl.from(controllerInfo).containsFlag(flag)) {
+                    controllers.add(controllerInfo);
+                }
+            }
+        }
+        return controllers;
+    }
+
+    // Should be used without a lock to prevent potential deadlock.
+    public void notifyPlaybackStateChangedNotLocked(PlaybackState state) {
+        final List<ControllerInfo> list = getControllersWithFlag(CALLBACK_FLAG_PLAYBACK);
+        for (int i = 0; i < list.size(); i++) {
+            IMediaSession2Callback callbackBinder =
+                    ControllerInfoImpl.from(list.get(i)).getControllerBinder();
+            try {
+                callbackBinder.onPlaybackStateChanged(state);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Controller is gone", e);
+                // TODO(jaewan): What to do when the controller is gone?
+            }
+        }
+    }
+
+    // TODO(jaewan): Remove this. We should use Executor given by the session builder.
+    private class CommandHandler extends Handler {
+        public static final int MSG_CONNECT = 1000;
+        public static final int MSG_COMMAND = 1001;
+        public static final int MSG_ON_GET_ROOT = 2000;
+
+        public CommandHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final MediaSession2Impl session = MediaSession2Stub.this.mSession.get();
+            if (session == null || session.getPlayer() == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case MSG_CONNECT: {
+                    ControllerInfo request = (ControllerInfo) msg.obj;
+                    CommandGroup allowedCommands = mSessionCallback.onConnect(request);
+                    // Don't reject connection for the request from trusted app.
+                    // Otherwise server will fail to retrieve session's information to dispatch
+                    // media keys to.
+                    boolean accept = allowedCommands != null || request.isTrusted();
+                    ControllerInfoImpl impl = ControllerInfoImpl.from(request);
+                    if (accept) {
+                        synchronized (mLock) {
+                            mControllers.put(impl.getId(), request);
+                        }
+                        if (allowedCommands == null) {
+                            // For trusted apps, send non-null allowed commands to keep connection.
+                            allowedCommands = new CommandGroup();
+                        }
+                    }
+                    if (DEBUG) {
+                        Log.d(TAG, "onConnectResult, request=" + request
+                                + " accept=" + accept);
+                    }
+                    try {
+                        impl.getControllerBinder().onConnectionChanged(
+                                accept ? MediaSession2Stub.this : null,
+                                allowedCommands == null ? null : allowedCommands.toBundle());
+                    } catch (RemoteException e) {
+                        // Controller may be died prematurely.
+                    }
+                    break;
+                }
+                case MSG_COMMAND: {
+                    CommandParam param = (CommandParam) msg.obj;
+                    Command command = param.command;
+                    boolean accepted = mSessionCallback.onCommandRequest(
+                            param.controller, command);
+                    if (!accepted) {
+                        // Don't run rejected command.
+                        if (DEBUG) {
+                            Log.d(TAG, "Command " + command + " from "
+                                    + param.controller + " was rejected by " + session);
+                        }
+                        return;
+                    }
+
+                    switch (param.command.getCommandCode()) {
+                        case MediaSession2.COMMAND_CODE_PLAYBACK_START:
+                            session.getInstance().play();
+                            break;
+                        case MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE:
+                            session.getInstance().pause();
+                            break;
+                        case MediaSession2.COMMAND_CODE_PLAYBACK_STOP:
+                            session.getInstance().stop();
+                            break;
+                        case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
+                            session.getInstance().skipToPrevious();
+                            break;
+                        case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
+                            session.getInstance().skipToNext();
+                            break;
+                        default:
+                            // TODO(jaewan): Handle custom command.
+                    }
+                    break;
+                }
+                case MSG_ON_GET_ROOT: {
+                    final CommandParam param = (CommandParam) msg.obj;
+                    final ControllerInfoImpl controller = ControllerInfoImpl.from(param.controller);
+                    BrowserRoot root = mLibraryCallback.onGetRoot(param.controller, param.args);
+                    try {
+                        controller.getControllerBinder().onGetRootResult(param.args,
+                                root == null ? null : root.getRootId(),
+                                root == null ? null : root.getExtras());
+                    } catch (RemoteException e) {
+                        // Controller may be died prematurely.
+                        // TODO(jaewan): Handle this.
+                    }
+                    break;
+                }
+            }
+        }
+
+        public void postConnect(ControllerInfo request) {
+            obtainMessage(MSG_CONNECT, request).sendToTarget();
+        }
+
+        public void postCommand(ControllerInfo controller, Command command, Bundle args) {
+            CommandParam param = new CommandParam(controller, command, args);
+            obtainMessage(MSG_COMMAND, param).sendToTarget();
+        }
+
+        public void postOnGetRoot(ControllerInfo controller, Bundle rootHints) {
+            CommandParam param = new CommandParam(controller, null, rootHints);
+            obtainMessage(MSG_ON_GET_ROOT, param).sendToTarget();
+        }
+    }
+
+    private static class CommandParam {
+        public final ControllerInfo controller;
+        public final Command command;
+        public final Bundle args;
+
+        private CommandParam(ControllerInfo controller, Command command, Bundle args) {
+            this.controller = controller;
+            this.command = command;
+            this.args = args;
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
new file mode 100644
index 0000000..773a06f
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media;
+
+import static android.content.Context.NOTIFICATION_SERVICE;
+
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaSession2;
+import android.media.MediaSessionService2;
+import android.media.MediaSessionService2.MediaNotification;
+import android.media.session.PlaybackState;
+import android.media.update.MediaSessionService2Provider;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.annotation.GuardedBy;
+import android.util.Log;
+
+// TODO(jaewan): Need a test for session service itself.
+public class MediaSessionService2Impl implements MediaSessionService2Provider {
+
+    private static final String TAG = "MPSessionService"; // to meet 23 char limit in Log tag
+    private static final boolean DEBUG = true; // TODO(jaewan): Change this.
+
+    private final MediaSessionService2 mInstance;
+    private final PlaybackListener mListener = new SessionServicePlaybackListener();
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private NotificationManager mNotificationManager;
+    @GuardedBy("mLock")
+    private Intent mStartSelfIntent;
+
+    private boolean mIsRunningForeground;
+    private MediaSession2 mSession;
+
+    public MediaSessionService2Impl(MediaSessionService2 instance) {
+        if (DEBUG) {
+            Log.d(TAG, "MediaSessionService2Impl(" + instance + ")");
+        }
+        mInstance = instance;
+    }
+
+    @Override
+    public MediaSession2 getSession_impl() {
+        return getSession();
+    }
+
+    MediaSession2 getSession() {
+        synchronized (mLock) {
+            return mSession;
+        }
+    }
+
+    @Override
+    public MediaNotification onUpdateNotification_impl(PlaybackState state) {
+        // Provide default notification UI later.
+        return null;
+    }
+
+    @Override
+    public void onCreate_impl() {
+        mNotificationManager = (NotificationManager) mInstance.getSystemService(
+                NOTIFICATION_SERVICE);
+        mStartSelfIntent = new Intent(mInstance, mInstance.getClass());
+
+        Intent serviceIntent = createServiceIntent();
+        ResolveInfo resolveInfo = mInstance.getPackageManager()
+                .resolveService(serviceIntent, PackageManager.GET_META_DATA);
+        String id;
+        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+            throw new IllegalArgumentException("service " + mInstance + " doesn't implement"
+                    + serviceIntent.getAction());
+        } else if (resolveInfo.serviceInfo.metaData == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Failed to resolve ID for " + mInstance + ". Using empty id");
+            }
+            id = "";
+        } else {
+            id = resolveInfo.serviceInfo.metaData.getString(
+                    MediaSessionService2.SERVICE_META_DATA, "");
+        }
+        mSession = mInstance.onCreateSession(id);
+        if (mSession == null || !id.equals(mSession.getToken().getId())) {
+            throw new RuntimeException("Expected session with id " + id + ", but got " + mSession);
+        }
+        // TODO(jaewan): Uncomment here.
+        // mSession.addPlaybackListener(mListener, mSession.getExecutor());
+    }
+
+    Intent createServiceIntent() {
+        Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+        serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+        return serviceIntent;
+    }
+
+    public IBinder onBind_impl(Intent intent) {
+        if (MediaSessionService2.SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mSession.getToken().getSessionBinder().asBinder();
+        }
+        return null;
+    }
+
+    private void updateNotification(PlaybackState state) {
+        MediaNotification mediaNotification = mInstance.onUpdateNotification(state);
+        if (mediaNotification == null) {
+            return;
+        }
+        switch((int) state.getState()) {
+            case PlaybackState.STATE_PLAYING:
+                if (!mIsRunningForeground) {
+                    mIsRunningForeground = true;
+                    mInstance.startForegroundService(mStartSelfIntent);
+                    mInstance.startForeground(mediaNotification.id, mediaNotification.notification);
+                    return;
+                }
+                break;
+            case PlaybackState.STATE_STOPPED:
+                if (mIsRunningForeground) {
+                    mIsRunningForeground = false;
+                    mInstance.stopForeground(true);
+                    return;
+                }
+                break;
+        }
+        mNotificationManager.notify(mediaNotification.id, mediaNotification.notification);
+    }
+
+    private class SessionServicePlaybackListener implements PlaybackListener {
+        @Override
+        public void onPlaybackChanged(PlaybackState state) {
+            if (state == null) {
+                Log.w(TAG, "Ignoring null playback state");
+                return;
+            }
+            MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
+            if (impl.getHandler().getLooper() != Looper.myLooper()) {
+                Log.w(TAG, "Ignoring " + state + ". Expected " + impl.getHandler().getLooper()
+                        + " but " + Looper.myLooper());
+                return;
+            }
+            updateNotification(state);
+        }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java b/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
new file mode 100644
index 0000000..4d06463
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media;
+
+import android.media.MediaPlayerBase;
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.NonNull;
+import java.util.List;
+
+/**
+ * Holds {@link android.media.MediaPlayerBase.PlaybackListener} with the {@link Handler}.
+ */
+public class PlaybackListenerHolder extends Handler {
+    private static final int ON_PLAYBACK_CHANGED = 1;
+
+    public final MediaPlayerBase.PlaybackListener listener;
+
+    public PlaybackListenerHolder(
+            @NonNull MediaPlayerBase.PlaybackListener listener, @NonNull Handler handler) {
+        super(handler.getLooper());
+        this.listener = listener;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case ON_PLAYBACK_CHANGED:
+                listener.onPlaybackChanged((PlaybackState) msg.obj);
+                break;
+        }
+    }
+
+    public void postPlaybackChange(PlaybackState state) {
+        obtainMessage(ON_PLAYBACK_CHANGED, state).sendToTarget();
+    }
+
+    /**
+     * Returns {@code true} if the given list contains a {@link PlaybackListenerHolder} that holds
+     * the given listener.
+     *
+     * @param list list to check
+     * @param listener listener to check
+     * @return {@code true} if the given list contains listener. {@code false} otherwise.
+     */
+    public static <Holder extends PlaybackListenerHolder> boolean contains(
+            @NonNull List<Holder> list, PlaybackListener listener) {
+        return indexOf(list, listener) >= 0;
+    }
+
+    /**
+     * Returns the index of the {@link PlaybackListenerHolder} that contains the given listener.
+     *
+     * @param list list to check
+     * @param listener listener to check
+     * @return {@code index} of item if the given list contains listener. {@code -1} otherwise.
+     */
+    public static <Holder extends PlaybackListenerHolder> int indexOf(
+            @NonNull List<Holder> list, PlaybackListener listener) {
+        for (int i = 0; i < list.size(); i++) {
+            if (list.get(i).listener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 633a342..07b565e 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -16,9 +16,25 @@
 
 package com.android.media.update;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaController2;
+import android.media.MediaLibraryService2;
+import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.MediaSessionService2;
+import android.media.IMediaSession2Callback;
+import android.media.SessionToken;
+import android.media.update.MediaBrowser2Provider;
 import android.media.update.MediaControlView2Provider;
+import android.media.update.MediaController2Provider;
+import android.media.update.MediaSession2Provider;
+import android.media.update.MediaSessionService2Provider;
 import android.media.update.VideoView2Provider;
 import android.media.update.StaticProvider;
 import android.media.update.ViewProvider;
@@ -27,9 +43,16 @@
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
 
+import com.android.media.MediaBrowser2Impl;
+import com.android.media.MediaController2Impl;
+import com.android.media.MediaLibraryService2Impl;
+import com.android.media.MediaSession2Impl;
+import com.android.media.MediaSessionService2Impl;
 import com.android.widget.MediaControlView2Impl;
 import com.android.widget.VideoView2Impl;
 
+import java.util.concurrent.Executor;
+
 public class ApiFactory implements StaticProvider {
     public static Object initialize(Resources libResources, Theme libTheme)
             throws ReflectiveOperationException {
@@ -38,6 +61,45 @@
     }
 
     @Override
+    public MediaController2Provider createMediaController2(
+            MediaController2 instance, Context context, SessionToken token,
+            MediaController2.ControllerCallback callback, Executor executor) {
+        return new MediaController2Impl(instance, context, token, callback, executor);
+    }
+
+    @Override
+    public MediaBrowser2Provider createMediaBrowser2(MediaBrowser2 instance, Context context,
+            SessionToken token, BrowserCallback callback, Executor executor) {
+        return new MediaBrowser2Impl(instance, context, token, callback, executor);
+    }
+
+    @Override
+    public MediaSession2Provider createMediaSession2(MediaSession2 instance, Context context,
+            MediaPlayerBase player, String id, SessionCallback callback) {
+        return new MediaSession2Impl(instance, context, player, id, callback);
+    }
+
+    @Override
+    public MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
+            ControllerInfo instance, Context context, int uid, int pid, String packageName,
+            IMediaSession2Callback callback) {
+        return new MediaSession2Impl.ControllerInfoImpl(
+                instance, context, uid, pid, packageName, callback);
+    }
+
+    @Override
+    public MediaSessionService2Provider createMediaSessionService2(
+            MediaSessionService2 instance) {
+        return new MediaSessionService2Impl(instance);
+    }
+
+    @Override
+    public MediaSessionService2Provider createMediaLibraryService2(
+            MediaLibraryService2 instance) {
+        return new MediaLibraryService2Impl(instance);
+    }
+
+    @Override
     public MediaControlView2Provider createMediaControlView2(
             MediaControlView2 instance, ViewProvider superProvider) {
         return new MediaControlView2Impl(instance, superProvider);
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index 26f858c..b0ca1bd 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -16,8 +16,16 @@
 
 package com.android.media.update;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.support.mediarouter.app.MediaRouteButton;
 
 public class ApiHelper {
     private static ApiHelper sInstance;
@@ -46,4 +54,31 @@
     public static Resources.Theme getLibTheme() {
         return sInstance.mLibTheme;
     }
+
+    public static LayoutInflater getLayoutInflater(Context context) {
+        LayoutInflater layoutInflater = LayoutInflater.from(context).cloneInContext(
+                new ContextThemeWrapper(context, getLibTheme()));
+        layoutInflater.setFactory2(new LayoutInflater.Factory2() {
+            @Override
+            public View onCreateView(
+                    View parent, String name, Context context, AttributeSet attrs) {
+                if (MediaRouteButton.class.getCanonicalName().equals(name)) {
+                    return new MediaRouteButton(context, attrs);
+                }
+                return null;
+            }
+
+            @Override
+            public View onCreateView(String name, Context context, AttributeSet attrs) {
+                return onCreateView(null, name, context, attrs);
+            }
+        });
+        return layoutInflater;
+    }
+
+    public static View inflateLibLayout(Context context, int libResId) {
+        try (XmlResourceParser parser = getLibResources().getLayout(libResId)) {
+            return getLayoutInflater(context).inflate(parser, null);
+        }
+    }
 }
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
index 68e8d3a..65fc88c 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.AnimationDrawable;
@@ -36,6 +37,7 @@
 import android.view.SoundEffectConstants;
 import android.view.View;
 
+import com.android.media.update.ApiHelper;
 import com.android.media.update.R;
 import com.android.support.mediarouter.media.MediaRouteSelector;
 import com.android.support.mediarouter.media.MediaRouter;
@@ -128,8 +130,11 @@
         mRouter = MediaRouter.getInstance(context);
         mCallback = new MediaRouterCallback();
 
-        TypedArray a = context.obtainStyledAttributes(attrs,
+        Resources.Theme theme = ApiHelper.getLibResources().newTheme();
+        theme.applyStyle(MediaRouterThemeHelper.getRouterThemeId(context), true);
+        TypedArray a = theme.obtainStyledAttributes(attrs,
                 R.styleable.MediaRouteButton, defStyleAttr, 0);
+
         mButtonTint = a.getColorStateList(R.styleable.MediaRouteButton_mediaRouteButtonTint);
         mMinWidth = a.getDimensionPixelSize(
                 R.styleable.MediaRouteButton_android_minWidth, 0);
@@ -290,8 +295,9 @@
      * button when the button is long pressed.
      */
     void setCheatSheetEnabled(boolean enable) {
-        TooltipCompat.setTooltipText(this,
-                enable ? getContext().getString(R.string.mr_button_content_description) : null);
+        TooltipCompat.setTooltipText(this, enable
+                ? ApiHelper.getLibResources().getString(R.string.mr_button_content_description)
+                : null);
     }
 
     @Override
@@ -533,7 +539,7 @@
         } else {
             resId = R.string.mr_cast_button_disconnected;
         }
-        setContentDescription(getContext().getString(resId));
+        setContentDescription(ApiHelper.getLibResources().getString(resId));
     }
 
     private final class MediaRouterCallback extends MediaRouter.Callback {
@@ -590,7 +596,7 @@
 
         @Override
         protected Drawable doInBackground(Void... params) {
-            return getContext().getResources().getDrawable(mResId);
+            return ApiHelper.getLibResources().getDrawable(mResId);
         }
 
         @Override
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
index b4b49df..7440130 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
@@ -196,7 +196,7 @@
         return value.data;
     }
 
-    private static int getRouterThemeId(Context context) {
+    static int getRouterThemeId(Context context) {
         int themeId;
         if (isLightTheme(context)) {
             if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
index f5e1e61..33d92b4 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
@@ -27,6 +27,7 @@
 import android.support.annotation.RequiresApi;
 import android.view.Display;
 
+import com.android.media.update.ApiHelper;
 import com.android.media.update.R;
 
 import java.util.ArrayList;
@@ -266,7 +267,7 @@
             mCallbackObj = createCallbackObj();
             mVolumeCallbackObj = createVolumeCallbackObj();
 
-            Resources r = context.getResources();
+            Resources r = ApiHelper.getLibResources();
             mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
                     mRouterObj, r.getString(R.string.mr_user_route_category_name), false);
 
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index 317a45f..43dcd5f 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -161,13 +161,6 @@
     }
 
     @Override
-    public void showCCButton_impl() {
-        if (mCCButton != null) {
-            mCCButton.setVisibility(View.VISIBLE);
-        }
-    }
-
-    @Override
     public boolean isPlaying_impl() {
         if (mPlaybackState != null) {
             return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
@@ -231,6 +224,15 @@
     }
 
     @Override
+    public void onAttachedToWindow_impl() {
+        mSuperProvider.onAttachedToWindow_impl();
+    }
+    @Override
+    public void onDetachedFromWindow_impl() {
+        mSuperProvider.onDetachedFromWindow_impl();
+    }
+
+    @Override
     public CharSequence getAccessibilityClassName_impl() {
         return MediaControlView2.class.getName();
     }
@@ -339,11 +341,8 @@
      * @hide This doesn't work as advertised
      */
     protected View makeControllerView() {
-        View root = LayoutInflater.from(mInstance.getContext()).inflate(
-                R.layout.media_controller, null);
-
+        View root = ApiHelper.inflateLibLayout(mInstance.getContext(), R.layout.media_controller);
         initControllerView(root);
-
         return root;
     }
 
diff --git a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
index 800be8e..8577123 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
@@ -162,57 +162,22 @@
         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;
+            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;
+            // for compatibility, we adjust size based on aspect ratio
+            if (videoWidth * height < width * videoHeight) {
                 width = height * videoWidth / videoHeight;
-                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
-                    // couldn't match aspect ratio within the constraints
-                    width = widthSpecSize;
+                if (DEBUG) {
+                    Log.d(TAG, "image too wide, correcting. width: " + width);
                 }
-            } 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 if (videoWidth * height > width * videoHeight) {
+                height = width * videoHeight / videoWidth;
+                if (DEBUG) {
+                    Log.d(TAG, "image too tall, correcting. height: " + height);
                 }
             }
         } else {
diff --git a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
index f240301..69a4ebe 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
@@ -173,57 +173,22 @@
         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;
+            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;
+            // for compatibility, we adjust size based on aspect ratio
+            if (videoWidth * height < width * videoHeight) {
                 width = height * videoWidth / videoHeight;
-                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
-                    // couldn't match aspect ratio within the constraints
-                    width = widthSpecSize;
+                if (DEBUG) {
+                    Log.d(TAG, "image too wide, correcting. width: " + width);
                 }
-            } 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 if (videoWidth * height > width * videoHeight) {
+                height = width * videoHeight / videoWidth;
+                if (DEBUG) {
+                    Log.d(TAG, "image too tall, correcting. height: " + height);
                 }
             }
         } else {
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 19a41de..309dbd1 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -98,8 +98,6 @@
     private int mCurrentBufferPercentage;
     private int mSeekWhenPrepared;  // recording the seek position while preparing
 
-    private int mSurfaceWidth;
-    private int mSurfaceHeight;
     private int mVideoWidth;
     private int mVideoHeight;
 
@@ -120,8 +118,6 @@
 
         mVideoWidth = 0;
         mVideoHeight = 0;
-        mSurfaceWidth = 0;
-        mSurfaceHeight = 0;
         mSpeed = 1.0f;
         mFallbackSpeed = mSpeed;
 
@@ -137,7 +133,8 @@
         mTextureView = new VideoTextureView(mInstance.getContext());
         mSurfaceView = new VideoSurfaceView(mInstance.getContext());
         LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
-                LayoutParams.WRAP_CONTENT);
+                LayoutParams.MATCH_PARENT);
+        params.gravity = Gravity.CENTER;
         mTextureView.setLayoutParams(params);
         mSurfaceView.setLayoutParams(params);
         mTextureView.setSurfaceListener(this);
@@ -158,16 +155,12 @@
         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",
+                "http://schemas.android.com/apk/com.android.media.update",
                 "enableControlView", true);
         if (enableControlView) {
-            setMediaControlView2_impl(new MediaControlView2(mInstance.getContext()));
+            mMediaControlView = new MediaControlView2(mInstance.getContext());
         }
     }
 
@@ -175,15 +168,9 @@
     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);
+        if (mInstance.isAttachedToWindow()) {
+            attachMediaControlView();
+        }
     }
 
     @Override
@@ -415,6 +402,25 @@
     }
 
     @Override
+    public void onAttachedToWindow_impl() {
+        mSuperProvider.onAttachedToWindow_impl();
+
+        // Create MediaSession
+        mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
+        mMediaSession.setCallback(new MediaSessionCallback());
+
+        attachMediaControlView();
+    }
+
+    @Override
+    public void onDetachedFromWindow_impl() {
+        mSuperProvider.onDetachedFromWindow_impl();
+        mMediaSession.release();
+        mMediaSession = null;
+    }
+
+
+    @Override
     public CharSequence getAccessibilityClassName_impl() {
         return VideoView2.class.getName();
     }
@@ -509,9 +515,6 @@
                     + ", mTargetState=" + mTargetState + ", width/height: " + width + "/" + height
                     + ", " + view.toString());
         }
-        mSurfaceWidth = width;
-        mSurfaceHeight = height;
-
         if (needToStart()) {
             mInstance.start();
         }
@@ -535,8 +538,6 @@
             Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height
                     + ", " + view.toString());
         }
-        mSurfaceWidth = width;
-        mSurfaceHeight = height;
     }
 
     @Override
@@ -557,6 +558,15 @@
     // Protected or private methods
     ///////////////////////////////////////////////////
 
+    private void attachMediaControlView() {
+        // Get MediaController from MediaSession and set it inside MediaControlView
+        mMediaControlView.setController(mMediaSession.getController());
+
+        LayoutParams params =
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+        mInstance.addView(mMediaControlView, params);
+    }
+
     private boolean isInPlaybackState() {
         return (mMediaPlayer != null
                 && mCurrentState != STATE_ERROR
@@ -660,8 +670,6 @@
                 mAudioManager.abandonAudioFocus(null);
             }
         }
-        mSurfaceWidth = 0;
-        mSurfaceHeight = 0;
         mVideoWidth = 0;
         mVideoHeight = 0;
     }
@@ -797,8 +805,8 @@
             if (mMediaControlView != null) {
                 mMediaControlView.setEnabled(true);
             }
-            mVideoWidth = mp.getVideoWidth();
-            mVideoHeight = mp.getVideoHeight();
+            int videoWidth = mp.getVideoWidth();
+            int videoHeight = mp.getVideoHeight();
 
             // mSeekWhenPrepared may be changed after seekTo() call
             int seekToPosition = mSeekWhenPrepared;
@@ -816,39 +824,32 @@
                 mMediaSession.setMetadata(builder.build());
             }
 
-            if (mVideoWidth != 0 && mVideoHeight != 0) {
-                if (mVideoWidth != mSurfaceWidth || mVideoHeight != mSurfaceHeight) {
+            if (videoWidth != 0 && videoHeight != 0) {
+                if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
                     if (DEBUG) {
                         Log.i(TAG, "OnPreparedListener() : ");
-                        Log.i(TAG, " video size: " + mVideoWidth + "/" + mVideoHeight);
-                        Log.i(TAG, " surface size: " + mSurfaceWidth + "/" + mSurfaceHeight);
+                        Log.i(TAG, " video size: " + videoWidth + "/" + videoHeight);
                         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;
+                    mVideoWidth = videoWidth;
+                    mVideoHeight = videoHeight;
                     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);
-                        }
+                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 {
diff --git a/packages/MediaComponents/test/Android.mk b/packages/MediaComponents/test/Android.mk
new file mode 100644
index 0000000..8703b9f
--- /dev/null
+++ b/packages/MediaComponents/test/Android.mk
@@ -0,0 +1,28 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# TODO(jaewan): Copy this to the CTS as well
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-target-minus-junit4 \
+    compatibility-device-util
+
+LOCAL_PACKAGE_NAME := MediaComponentsTest
+include $(BUILD_PACKAGE)
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
new file mode 100644
index 0000000..30bac87
--- /dev/null
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.media.test">
+
+    <application android:label="Media API Test">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="android.widget2.VideoView2TestActivity"
+                  android:configChanges="keyboardHidden|orientation|screenSize"
+                  android:label="VideoView2TestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+          </activity>
+
+        <!-- Keep the test services synced together with the TestUtils.java -->
+        <service android:name="android.media.MockMediaSessionService2">
+            <intent-filter>
+                <action android:name="android.media.MediaSessionService2" />
+            </intent-filter>
+            <meta-data android:name="android.media.session" android:value="TestSession" />
+        </service>
+        <!-- Keep the test services synced together with the MockMediaLibraryService -->
+        <service android:name="android.media.MockMediaLibraryService2">
+            <intent-filter>
+                <action android:name="android.media.MediaLibraryService2" />
+            </intent-filter>
+            <meta-data android:name="android.media.session" android:value="TestBrowser" />
+        </service>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.media.test"
+        android:label="Media API test" />
+
+</manifest>
diff --git a/packages/MediaComponents/test/runtest.sh b/packages/MediaComponents/test/runtest.sh
new file mode 100644
index 0000000..5c0ef51
--- /dev/null
+++ b/packages/MediaComponents/test/runtest.sh
@@ -0,0 +1,189 @@
+#!/bin/bash
+# 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.
+
+# Usage '. runtest.sh'
+
+function _runtest_mediacomponent_usage() {
+  echo 'runtest-MediaComponents [option]: Run MediaComponents test'
+  echo '     -h|--help: This help'
+  echo '     --skip: Skip build. Just rerun-tests.'
+  echo '     --min: Only rebuild test apk and updatable library.'
+  echo '     -s [device_id]: Specify a device name to run test against.'
+  echo '                     You can define ${ADBHOST} instead.'
+  echo '     -r [count]: Repeat tests for given count. It will stop when fails.'
+  echo '     --ignore: Keep repeating tests even when it fails.'
+  echo '     -t [test]: Only run the specific test. Can be either a class or a method.'
+}
+
+function runtest-MediaComponents() {
+  # Edit here if you want to support other tests.
+  # List up libs and apks in the media_api needed for tests, and place test target at the last.
+  local TEST_PACKAGE_DIR=("frameworks/av/packages/MediaComponents/test")
+  local BUILD_TARGETS=("MediaComponents" "MediaComponentsTest")
+  local INSTALL_TARGETS=("MediaComponentsTest")
+  local TEST_RUNNER="android.support.test.runner.AndroidJUnitRunner"
+  local DEPENDENCIES=("mockito-target-minus-junit4" "android-support-test" "compatibility-device-util")
+
+  if [[ -z "${ANDROID_BUILD_TOP}" ]]; then
+    echo "Needs to lunch a target first"
+    return
+  fi
+
+  local old_path=${OLDPWD}
+  while true; do
+    local OPTION_SKIP="false"
+    local OPTION_MIN="false"
+    local OPTION_REPEAT_COUNT="1"
+    local OPTION_IGNORE="false"
+    local OPTION_TEST_TARGET=""
+    local adbhost_local
+    while (( "$#" )); do
+      case "${1}" in
+        -h|--help)
+          _runtest_mediacomponent_usage
+          return
+          ;;
+        --skip)
+          OPTION_SKIP="true"
+          ;;
+        --min)
+          OPTION_MIN="true"
+          ;;
+        -s)
+          shift
+          adbhost_local=${1}
+          ;;
+        -r)
+          shift
+          OPTION_REPEAT_COUNT="${1}"
+          ;;
+        --ignore)
+          OPTION_IGNORE="true"
+          ;;
+        -t)
+          shift
+          OPTION_TEST_TARGET="${1}"
+      esac
+      shift
+    done
+
+    # Build adb command.
+    local adb
+    if [[ -z "${adbhost_local}" ]]; then
+      adbhost_local=${ADBHOST}
+    fi
+    if [[ -z "${adbhost_local}" ]]; then
+      local device_count=$(adb devices | sed '/^[[:space:]]*$/d' | wc -l)
+      if [[ "${device_count}" != "2" ]]; then
+        echo "Too many devices. Specify a device." && break
+      fi
+      adb="adb"
+    else
+      adb="adb -s ${adbhost_local}"
+    fi
+
+    local target_dir="${ANDROID_BUILD_TOP}/${TEST_PACKAGE_DIR}"
+    local TEST_PACKAGE=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\.]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
+
+    if [[ "${OPTION_SKIP}" != "true" ]]; then
+      # Build dependencies if needed.
+      local dependency
+      local build_dependency=""
+      for dependency in ${DEPENDENCIES[@]}; do
+        if [[ "${dependency}" == "out/"* ]]; then
+          if [[ ! -f ${ANDROID_BUILD_TOP}/${dependency} ]]; then
+            build_dependency="true"
+            break
+          fi
+        else
+          if [[ "$(find ${OUT} -name ${dependency}_intermediates | wc -l)" == "0" ]]; then
+            build_dependency="true"
+            break
+          fi
+        fi
+      done
+      if [[ "${build_dependency}" == "true" ]]; then
+        echo "Building dependencies. Will only print stderr."
+        m ${DEPENDENCIES[@]} -j > /dev/null
+      fi
+
+      # Build test apk and required apk.
+      local build_targets="${BUILD_TARGETS[@]}"
+      if [[ "${OPTION_MIN}" != "true" ]]; then
+        build_targets="${build_targets} droid"
+      fi
+      m ${build_targets} -j || break
+
+      ${adb} root
+      ${adb} remount
+      ${adb} shell stop
+      ${adb} sync
+      ${adb} shell start
+      ${adb} wait-for-device || break
+      # Ensure package manager is loaded.
+      sleep 5
+
+      # Install apks
+      local install_failed="false"
+      for target in ${INSTALL_TARGETS[@]}; do
+        echo "${target}"
+        local target_dir=$(mgrep -l -e '^LOCAL_PACKAGE_NAME.*'"${target}$")
+        if [[ -z ${target_dir} ]]; then
+          continue
+        fi
+        target_dir=$(dirname ${target_dir})
+        local package=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\._]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
+        local apk_path=$(find ${OUT} -name ${target}.apk)
+        if [[ -z "${apk_path}" ]]; then
+          echo "Cannot locate ${target}.apk" && break
+        fi
+        echo "Installing ${target}.apk. path=${apk_path}"
+        ${adb} install -r ${apk_path}
+        if [[ "${?}" != "0" ]]; then
+          install_failed="true"
+          break
+        fi
+      done
+      if [[ "${install_failed}" == "true" ]]; then
+        echo "Failed to install. Test wouldn't run."
+        break
+      fi
+    fi
+
+    local test_target=""
+    if [[ -n "${OPTION_TEST_TARGET}" ]]; then
+      test_target="-e class ${OPTION_TEST_TARGET}"
+    fi
+
+    local i
+    local tmpfile=$(tempfile)
+    for ((i=1; i <= ${OPTION_REPEAT_COUNT}; i++)); do
+      echo "Run test ${i}/${OPTION_REPEAT_COUNT}"
+      ${adb} shell am instrument ${test_target} -w ${TEST_PACKAGE}/${TEST_RUNNER} >& ${tmpfile}
+      cat ${tmpfile}
+      if [[ "${OPTION_IGNORE}" != "true" ]]; then
+        if [[ -n "$(grep ${tmpfile} -e 'FAILURE\|crashed')" ]]; then
+          # am instrument doesn't return error code so need to grep result message instead
+          break
+        fi
+      fi
+    done
+    rm ${tmpfile}
+    break
+  done
+}
+
+echo "Following functions are added to your environment:"
+_runtest_mediacomponent_usage
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
new file mode 100644
index 0000000..fe8aeb9
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link MediaBrowser2}.
+ * <p>
+ * This test inherits {@link MediaController2Test} to ensure that inherited APIs from
+ * {@link MediaController2} works cleanly.
+ */
+// TODO(jaewan): Implement host-side test so browser and service can run in different processes.
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaBrowser2Test extends MediaController2Test {
+    private static final String TAG = "MediaBrowser2Test";
+
+    @Override
+    TestControllerInterface onCreateController(@NonNull SessionToken token,
+            @NonNull TestControllerCallbackInterface callback) {
+        return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
+    }
+
+    @Test
+    public void testGetBrowserRoot() throws InterruptedException {
+        final Bundle param = new Bundle();
+        param.putString(TAG, TAG);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+            @Override
+            public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+                assertTrue(TestUtils.equals(param, rootHints));
+                assertEquals(MockMediaLibraryService2.ROOT_ID, rootMediaId);
+                assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRA, rootExtra));
+                latch.countDown();
+            }
+        };
+
+        final SessionToken token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser =
+                (MediaBrowser2) createController(token,true, callback);
+        browser.getBrowserRoot(param);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    public static class TestBrowserCallback extends BrowserCallback
+            implements WaitForConnectionInterface {
+        private final TestControllerCallbackInterface mCallbackProxy;
+        public final CountDownLatch connectLatch = new CountDownLatch(1);
+        public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+
+        TestBrowserCallback(TestControllerCallbackInterface callbackProxy) {
+            mCallbackProxy = callbackProxy;
+        }
+
+        @CallSuper
+        @Override
+        public void onConnected(CommandGroup commands) {
+            super.onConnected(commands);
+            connectLatch.countDown();
+        }
+
+        @CallSuper
+        @Override
+        public void onDisconnected() {
+            super.onDisconnected();
+            disconnectLatch.countDown();
+        }
+
+        @Override
+        public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+            mCallbackProxy.onGetRootResult(rootHints, rootMediaId, rootExtra);
+        }
+
+        @Override
+        public void waitForConnect(boolean expect) throws InterruptedException {
+            if (expect) {
+                assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            } else {
+                assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            }
+        }
+
+        @Override
+        public void waitForDisconnect(boolean expect) throws InterruptedException {
+            if (expect) {
+                assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            } else {
+                assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            }
+        }
+    }
+
+    public class TestMediaBrowser extends MediaBrowser2 implements TestControllerInterface {
+        private final BrowserCallback mCallback;
+
+        public TestMediaBrowser(@NonNull Context context, @NonNull SessionToken token,
+                @NonNull ControllerCallback callback) {
+            super(context, token, (BrowserCallback) callback, sHandlerExecutor);
+            mCallback = (BrowserCallback) callback;
+        }
+
+        @Override
+        public BrowserCallback getCallback() {
+            return mCallback;
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
new file mode 100644
index 0000000..88ca106
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.TestUtils.SyncHandler;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.media.TestUtils.createPlaybackState;
+import static org.junit.Assert.*;
+
+/**
+ * Tests {@link MediaController2}.
+ */
+// TODO(jaewan): Implement host-side test so controller and session can run in different processes.
+// TODO(jaewan): Fix flaky failure -- see MediaController2Impl.getController()
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@FlakyTest
+public class MediaController2Test extends MediaSession2TestBase {
+    private static final String TAG = "MediaController2Test";
+
+    MediaSession2 mSession;
+    MediaController2 mController;
+    MockPlayer mPlayer;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        // Create this test specific MediaSession2 to use our own Handler.
+        sHandler.postAndSync(()->{
+            mPlayer = new MockPlayer(1);
+            mSession = new MediaSession2.Builder(mContext, mPlayer).setId(TAG).build();
+        });
+
+        mController = createController(mSession.getToken());
+        TestServiceRegistry.getInstance().setHandler(sHandler);
+    }
+
+    @After
+    @Override
+    public void cleanUp() throws Exception {
+        super.cleanUp();
+        sHandler.postAndSync(() -> {
+            if (mSession != null) {
+                mSession.setPlayer(null);
+            }
+        });
+        TestServiceRegistry.getInstance().cleanUp();
+    }
+
+    @Test
+    public void testPlay() throws InterruptedException {
+        mController.play();
+        try {
+            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(mPlayer.mPlayCalled);
+    }
+
+    @Test
+    public void testPause() throws InterruptedException {
+        mController.pause();
+        try {
+            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(mPlayer.mPauseCalled);
+    }
+
+
+    @Test
+    public void testSkipToPrevious() throws InterruptedException {
+        mController.skipToPrevious();
+        try {
+            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(mPlayer.mSkipToPreviousCalled);
+    }
+
+    @Test
+    public void testSkipToNext() throws InterruptedException {
+        mController.skipToNext();
+        try {
+            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(mPlayer.mSkipToNextCalled);
+    }
+
+    @Test
+    public void testStop() throws InterruptedException {
+        mController.stop();
+        try {
+            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(mPlayer.mStopCalled);
+    }
+
+    @Test
+    public void testGetPackageName() {
+        assertEquals(mContext.getPackageName(), mController.getSessionToken().getPackageName());
+    }
+
+    @Test
+    public void testGetPlaybackState() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final MediaPlayerBase.PlaybackListener listener = (state) -> {
+            assertEquals(PlaybackState.STATE_BUFFERING, state.getState());
+            latch.countDown();
+        };
+        assertNull(mController.getPlaybackState());
+        mController.addPlaybackListener(listener, sHandler);
+
+        mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_BUFFERING));
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertEquals(PlaybackState.STATE_BUFFERING, mController.getPlaybackState().getState());
+    }
+
+    @Test
+    public void testAddPlaybackListener() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(2);
+        final MediaPlayerBase.PlaybackListener listener = (state) -> {
+            switch ((int) latch.getCount()) {
+                case 2:
+                    assertEquals(PlaybackState.STATE_PLAYING, state.getState());
+                    break;
+                case 1:
+                    assertEquals(PlaybackState.STATE_PAUSED, state.getState());
+                    break;
+            }
+            latch.countDown();
+        };
+
+        mController.addPlaybackListener(listener, sHandler);
+        sHandler.postAndSync(()->{
+            mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
+            mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
+        });
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testRemovePlaybackListener() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final MediaPlayerBase.PlaybackListener listener = (state) -> {
+            fail();
+            latch.countDown();
+        };
+        mController.addPlaybackListener(listener, sHandler);
+        mController.removePlaybackListener(listener);
+        mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
+        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testControllerCallback_onConnected() throws InterruptedException {
+        // createController() uses controller callback to wait until the controller becomes
+        // available.
+        MediaController2 controller = createController(mSession.getToken());
+        assertNotNull(controller);
+    }
+
+    @Test
+    public void testControllerCallback_sessionRejects() throws InterruptedException {
+        final MediaSession2.SessionCallback sessionCallback = new SessionCallback() {
+            @Override
+            public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+                return null;
+            }
+        };
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+            mSession = new MediaSession2.Builder(mContext, mPlayer)
+                    .setSessionCallback(sessionCallback).build();
+        });
+        MediaController2 controller =
+                createController(mSession.getToken(), false, null);
+        assertNotNull(controller);
+        waitForConnect(controller, false);
+        waitForDisconnect(controller, true);
+    }
+
+    @Test
+    public void testControllerCallback_releaseSession() throws InterruptedException {
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+        });
+        waitForDisconnect(mController, true);
+    }
+
+    @Test
+    public void testControllerCallback_release() throws InterruptedException {
+        mController.release();
+        waitForDisconnect(mController, true);
+    }
+
+    @Test
+    public void testIsConnected() throws InterruptedException {
+        assertTrue(mController.isConnected());
+        sHandler.postAndSync(()->{
+            mSession.setPlayer(null);
+        });
+        // postAndSync() to wait until the disconnection is propagated.
+        sHandler.postAndSync(()->{
+            assertFalse(mController.isConnected());
+        });
+    }
+
+    /**
+     * Test potential deadlock for calls between controller and session.
+     */
+    @Test
+    public void testDeadlock() throws InterruptedException {
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+            mSession = null;
+        });
+
+        // Two more threads are needed not to block test thread nor test wide thread (sHandler).
+        final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
+        final HandlerThread testThread = new HandlerThread("testDeadlock_test");
+        sessionThread.start();
+        testThread.start();
+        final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
+        final Handler testHandler = new Handler(testThread.getLooper());
+        final CountDownLatch latch = new CountDownLatch(1);
+        try {
+            final MockPlayer player = new MockPlayer(0);
+            sessionHandler.postAndSync(() -> {
+                mSession = new MediaSession2.Builder(mContext, mPlayer)
+                        .setId("testDeadlock").build();
+            });
+            final MediaController2 controller = createController(mSession.getToken());
+            testHandler.post(() -> {
+                controller.addPlaybackListener((state) -> {
+                    // no-op. Just to set a binder call path from session to controller.
+                }, sessionHandler);
+                final PlaybackState state = createPlaybackState(PlaybackState.STATE_ERROR);
+                for (int i = 0; i < 100; i++) {
+                    // triggers call from session to controller.
+                    player.notifyPlaybackState(state);
+                    // triggers call from controller to session.
+                    controller.play();
+
+                    // Repeat above
+                    player.notifyPlaybackState(state);
+                    controller.pause();
+                    player.notifyPlaybackState(state);
+                    controller.stop();
+                    player.notifyPlaybackState(state);
+                    controller.skipToNext();
+                    player.notifyPlaybackState(state);
+                    controller.skipToPrevious();
+                }
+                // This may hang if deadlock happens.
+                latch.countDown();
+            });
+            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            if (mSession != null) {
+                sessionHandler.postAndSync(() -> {
+                    // Clean up here because sessionHandler will be removed afterwards.
+                    mSession.setPlayer(null);
+                    mSession = null;
+                });
+            }
+            if (sessionThread != null) {
+                sessionThread.quitSafely();
+            }
+            if (testThread != null) {
+                testThread.quitSafely();
+            }
+        }
+    }
+
+    @Ignore
+    @Test
+    public void testGetServiceToken() {
+        SessionToken token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID);
+        assertNotNull(token);
+        assertEquals(mContext.getPackageName(), token.getPackageName());
+        assertEquals(MockMediaSessionService2.ID, token.getId());
+        assertNull(token.getSessionBinder());
+        assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
+    }
+
+    private void connectToService(SessionToken token) throws InterruptedException {
+        mController = createController(token);
+        mSession = TestServiceRegistry.getInstance().getServiceInstance().getSession();
+        mPlayer = (MockPlayer) mSession.getPlayer();
+    }
+
+    // TODO(jaewan): Reenable when session manager detects app installs
+    @Ignore
+    @Test
+    public void testConnectToService_sessionService() throws InterruptedException {
+        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+        testConnectToService();
+    }
+
+    // TODO(jaewan): Reenable when session manager detects app installs
+    @Ignore
+    @Test
+    public void testConnectToService_libraryService() throws InterruptedException {
+        connectToService(TestUtils.getServiceToken(mContext, MockMediaLibraryService2.ID));
+        testConnectToService();
+    }
+
+    public void testConnectToService() throws InterruptedException {
+        TestServiceRegistry serviceInfo = TestServiceRegistry.getInstance();
+        ControllerInfo info = serviceInfo.getOnConnectControllerInfo();
+        assertEquals(mContext.getPackageName(), info.getPackageName());
+        assertEquals(Process.myUid(), info.getUid());
+        assertFalse(info.isTrusted());
+
+        // Test command from controller to session service
+        mController.play();
+        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertTrue(mPlayer.mPlayCalled);
+
+        // Test command from session service to controller
+        final CountDownLatch latch = new CountDownLatch(1);
+        mController.addPlaybackListener((state) -> {
+            assertNotNull(state);
+            assertEquals(PlaybackState.STATE_REWINDING, state.getState());
+            latch.countDown();
+        }, sHandler);
+        mPlayer.notifyPlaybackState(
+                TestUtils.createPlaybackState(PlaybackState.STATE_REWINDING));
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testControllerAfterSessionIsGone_session() throws InterruptedException {
+        testControllerAfterSessionIsGone(mSession.getToken().getId());
+    }
+
+    @Ignore
+    @Test
+    public void testControllerAfterSessionIsGone_sessionService() throws InterruptedException {
+        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+        testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
+    }
+
+    @Test
+    public void testRelease_beforeConnected() throws InterruptedException {
+        MediaController2 controller =
+                createController(mSession.getToken(), false, null);
+        controller.release();
+    }
+
+    @Test
+    public void testRelease_twice() throws InterruptedException {
+        mController.release();
+        mController.release();
+    }
+
+    @Test
+    public void testRelease_session() throws InterruptedException {
+        final String id = mSession.getToken().getId();
+        mController.release();
+        // Release is done immediately for session.
+        testNoInteraction();
+
+        // Test whether the controller is notified about later release of the session or
+        // re-creation.
+        testControllerAfterSessionIsGone(id);
+    }
+
+    // TODO(jaewan): Reenable when session manager detects app installs
+    @Ignore
+    @Test
+    public void testRelease_sessionService() throws InterruptedException {
+        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+        testReleaseFromService();
+    }
+
+    // TODO(jaewan): Reenable when session manager detects app installs
+    @Ignore
+    @Test
+    public void testRelease_libraryService() throws InterruptedException {
+        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+        testReleaseFromService();
+    }
+
+    private void testReleaseFromService() throws InterruptedException {
+        final String id = mController.getSessionToken().getId();
+        final CountDownLatch latch = new CountDownLatch(1);
+        TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
+            if (service == null) {
+                // Destroying..
+                latch.countDown();
+            }
+        });
+        mController.release();
+        // Wait until release triggers onDestroy() of the session service.
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertNull(TestServiceRegistry.getInstance().getServiceInstance());
+        testNoInteraction();
+
+        // Test whether the controller is notified about later release of the session or
+        // re-creation.
+        testControllerAfterSessionIsGone(id);
+    }
+
+    private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
+        sHandler.postAndSync(() -> {
+            // TODO(jaewan): Use Session.release later when we add the API.
+            mSession.setPlayer(null);
+        });
+        waitForDisconnect(mController, true);
+        testNoInteraction();
+
+        // Test with the newly created session.
+        sHandler.postAndSync(() -> {
+            // Recreated session has different session stub, so previously created controller
+            // shouldn't be available.
+            mSession = new MediaSession2.Builder(mContext, mPlayer).setId(id).build();
+        });
+        testNoInteraction();
+    }
+
+    private void testNoInteraction() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final PlaybackListener playbackListener = (state) -> {
+            fail("Controller shouldn't be notified about change in session after the release.");
+            latch.countDown();
+        };
+        mController.addPlaybackListener(playbackListener, sHandler);
+        mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
+        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        mController.removePlaybackListener(playbackListener);
+    }
+
+    // TODO(jaewan): Add  test for service connect rejection, when we differentiate session
+    //               active/inactive and connection accept/refuse
+}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
new file mode 100644
index 0000000..49687a7
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.session.PlaybackState;
+import android.os.Process;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.media.TestUtils.createPlaybackState;
+import static org.junit.Assert.*;
+
+/**
+ * Tests {@link MediaSession2}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaSession2Test extends MediaSession2TestBase {
+    private static final String TAG = "MediaSession2Test";
+
+    private MediaSession2 mSession;
+    private MockPlayer mPlayer;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        sHandler.postAndSync(() -> {
+            mPlayer = new MockPlayer(0);
+            mSession = new MediaSession2.Builder(mContext, mPlayer).build();
+        });
+    }
+
+    @After
+    @Override
+    public void cleanUp() throws Exception {
+        super.cleanUp();
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+        });
+    }
+
+    @Test
+    public void testBuilder() throws Exception {
+        try {
+            MediaSession2.Builder builder = new Builder(mContext, null);
+            fail("null player shouldn't be allowed");
+        } catch (IllegalArgumentException e) {
+            // expected. pass-through
+        }
+        MediaSession2.Builder builder = new Builder(mContext, mPlayer);
+        try {
+            builder.setId(null);
+            fail("null id shouldn't be allowed");
+        } catch (IllegalArgumentException e) {
+            // expected. pass-through
+        }
+    }
+
+    @Test
+    public void testSetPlayer() throws Exception {
+        sHandler.postAndSync(() -> {
+            MockPlayer player = new MockPlayer(0);
+            // Test if setPlayer doesn't crash with various situations.
+            mSession.setPlayer(mPlayer);
+            mSession.setPlayer(player);
+            mSession.setPlayer(null);
+        });
+    }
+
+    @Test
+    public void testPlay() throws Exception {
+        sHandler.postAndSync(() -> {
+            mSession.play();
+            assertTrue(mPlayer.mPlayCalled);
+        });
+    }
+
+    @Test
+    public void testPause() throws Exception {
+        sHandler.postAndSync(() -> {
+            mSession.pause();
+            assertTrue(mPlayer.mPauseCalled);
+        });
+    }
+
+    @Test
+    public void testStop() throws Exception {
+        sHandler.postAndSync(() -> {
+            mSession.stop();
+            assertTrue(mPlayer.mStopCalled);
+        });
+    }
+
+    @Test
+    public void testSkipToNext() throws Exception {
+        sHandler.postAndSync(() -> {
+            mSession.skipToNext();
+            assertTrue(mPlayer.mSkipToNextCalled);
+        });
+    }
+
+    @Test
+    public void testSkipToPrevious() throws Exception {
+        sHandler.postAndSync(() -> {
+            mSession.skipToPrevious();
+            assertTrue(mPlayer.mSkipToPreviousCalled);
+        });
+    }
+
+    @Test
+    public void testPlaybackStateChangedListener() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(2);
+        final MockPlayer player = new MockPlayer(0);
+        final PlaybackListener listener = (state) -> {
+            assertEquals(sHandler.getLooper(), Looper.myLooper());
+            assertNotNull(state);
+            switch ((int) latch.getCount()) {
+                case 2:
+                    assertEquals(PlaybackState.STATE_PLAYING, state.getState());
+                    break;
+                case 1:
+                    assertEquals(PlaybackState.STATE_PAUSED, state.getState());
+                    break;
+                case 0:
+                    fail();
+            }
+            latch.countDown();
+        };
+        player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
+        sHandler.postAndSync(() -> {
+            mSession.addPlaybackListener(listener, sHandler);
+            // When the player is set, listeners will be notified about the player's current state.
+            mSession.setPlayer(player);
+        });
+        player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testBadPlayer() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(3); // expected call + 1
+        final BadPlayer player = new BadPlayer(0);
+        sHandler.postAndSync(() -> {
+            mSession.addPlaybackListener((state) -> {
+                // This will be called for every setPlayer() calls, but no more.
+                assertNull(state);
+                latch.countDown();
+            }, sHandler);
+            mSession.setPlayer(player);
+            mSession.setPlayer(mPlayer);
+        });
+        player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
+        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    private static class BadPlayer extends MockPlayer {
+        public BadPlayer(int count) {
+            super(count);
+        }
+
+        @Override
+        public void removePlaybackListener(@NonNull PlaybackListener listener) {
+            // No-op. This bad player will keep push notification to the listener that is previously
+            // registered by session.setPlayer().
+        }
+    }
+
+    @Test
+    public void testOnCommandCallback() throws InterruptedException {
+        final MockOnCommandCallback callback = new MockOnCommandCallback();
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+            mPlayer = new MockPlayer(1);
+            mSession = new MediaSession2.Builder(mContext, mPlayer)
+                    .setSessionCallback(callback).build();
+        });
+        MediaController2 controller = createController(mSession.getToken());
+        controller.pause();
+        assertFalse(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertFalse(mPlayer.mPauseCalled);
+        assertEquals(1, callback.commands.size());
+        assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE,
+                (long) callback.commands.get(0).getCommandCode());
+        controller.skipToNext();
+        assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertTrue(mPlayer.mSkipToNextCalled);
+        assertFalse(mPlayer.mPauseCalled);
+        assertEquals(2, callback.commands.size());
+        assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM,
+                (long) callback.commands.get(1).getCommandCode());
+    }
+
+    @Test
+    public void testOnConnectCallback() throws InterruptedException {
+        final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+            mSession = new MediaSession2.Builder(mContext, mPlayer)
+                    .setSessionCallback(sessionCallback).build();
+        });
+        MediaController2 controller =
+                createController(mSession.getToken(), false, null);
+        assertNotNull(controller);
+        waitForConnect(controller, false);
+        waitForDisconnect(controller, true);
+    }
+
+    public class MockOnConnectCallback extends SessionCallback {
+        @Override
+        public MediaSession2.CommandGroup onConnect(ControllerInfo controllerInfo) {
+            if (Process.myUid() != controllerInfo.getUid()) {
+                return null;
+            }
+            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
+            assertEquals(Process.myUid(), controllerInfo.getUid());
+            assertFalse(controllerInfo.isTrusted());
+            // Reject all
+            return null;
+        }
+    }
+
+    public class MockOnCommandCallback extends SessionCallback {
+        public final ArrayList<MediaSession2.Command> commands = new ArrayList<>();
+
+        @Override
+        public boolean onCommandRequest(ControllerInfo controllerInfo, MediaSession2.Command command) {
+            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
+            assertEquals(Process.myUid(), controllerInfo.getUid());
+            assertFalse(controllerInfo.isTrusted());
+            commands.add(command);
+            if (command.getCommandCode() == MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE) {
+                return false;
+            }
+            return true;
+        }
+    }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
new file mode 100644
index 0000000..cc77a50
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
+import android.os.HandlerThread;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/**
+ * Base class for session test.
+ */
+abstract class MediaSession2TestBase {
+    // Expected success
+    static final int WAIT_TIME_MS = 1000;
+
+    // Expected timeout
+    static final int TIMEOUT_MS = 500;
+
+    static TestUtils.SyncHandler sHandler;
+    static Executor sHandlerExecutor;
+
+    Context mContext;
+    private List<MediaController2> mControllers = new ArrayList<>();
+
+    interface TestControllerInterface {
+        ControllerCallback getCallback();
+    }
+
+    interface TestControllerCallbackInterface {
+        // Currently empty. Add methods in ControllerCallback/BrowserCallback that you want to test.
+
+        // Browser specific callbacks
+        default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
+    }
+
+    interface WaitForConnectionInterface {
+        void waitForConnect(boolean expect) throws InterruptedException;
+        void waitForDisconnect(boolean expect) throws InterruptedException;
+    }
+
+    @BeforeClass
+    public static void setUpThread() {
+        if (sHandler == null) {
+            HandlerThread handlerThread = new HandlerThread("MediaSession2TestBase");
+            handlerThread.start();
+            sHandler = new TestUtils.SyncHandler(handlerThread.getLooper());
+            sHandlerExecutor = (runnable) -> {
+                sHandler.post(runnable);
+            };
+        }
+    }
+
+    @AfterClass
+    public static void cleanUpThread() {
+        if (sHandler != null) {
+            sHandler.getLooper().quitSafely();
+            sHandler = null;
+            sHandlerExecutor = null;
+        }
+    }
+
+    @CallSuper
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @CallSuper
+    public void cleanUp() throws Exception {
+        for (int i = 0; i < mControllers.size(); i++) {
+            mControllers.get(i).release();
+        }
+    }
+
+    final MediaController2 createController(SessionToken token) throws InterruptedException {
+        return createController(token, true, null);
+    }
+
+    final MediaController2 createController(@NonNull SessionToken token,
+            boolean waitForConnect, @Nullable TestControllerCallbackInterface callback)
+            throws InterruptedException {
+        TestControllerInterface instance = onCreateController(token, callback);
+        if (!(instance instanceof MediaController2)) {
+            throw new RuntimeException("Test has a bug. Expected MediaController2 but returned "
+                    + instance);
+        }
+        MediaController2 controller = (MediaController2) instance;
+        mControllers.add(controller);
+        if (waitForConnect) {
+            waitForConnect(controller, true);
+        }
+        return controller;
+    }
+
+    private static WaitForConnectionInterface getWaitForConnectionInterface(
+            MediaController2 controller) {
+        if (!(controller instanceof TestControllerInterface)) {
+            throw new RuntimeException("Test has a bug. Expected controller implemented"
+                    + " TestControllerInterface but got " + controller);
+        }
+        ControllerCallback callback = ((TestControllerInterface) controller).getCallback();
+        if (!(callback instanceof WaitForConnectionInterface)) {
+            throw new RuntimeException("Test has a bug. Expected controller with callback "
+                    + " implemented WaitForConnectionInterface but got " + controller);
+        }
+        return (WaitForConnectionInterface) callback;
+    }
+
+    public static void waitForConnect(MediaController2 controller, boolean expected)
+            throws InterruptedException {
+        getWaitForConnectionInterface(controller).waitForConnect(expected);
+    }
+
+    public static void waitForDisconnect(MediaController2 controller, boolean expected)
+            throws InterruptedException {
+        getWaitForConnectionInterface(controller).waitForDisconnect(expected);
+    }
+
+    TestControllerInterface onCreateController(@NonNull SessionToken token,
+            @NonNull TestControllerCallbackInterface callback) {
+        return new TestMediaController(mContext, token, new TestControllerCallback(callback));
+    }
+
+    public static class TestControllerCallback extends MediaController2.ControllerCallback
+            implements WaitForConnectionInterface {
+        public final TestControllerCallbackInterface mCallbackProxy;
+        public final CountDownLatch connectLatch = new CountDownLatch(1);
+        public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+
+        TestControllerCallback(TestControllerCallbackInterface callbackProxy) {
+            mCallbackProxy = callbackProxy;
+        }
+
+        @CallSuper
+        @Override
+        public void onConnected(CommandGroup commands) {
+            super.onConnected(commands);
+            connectLatch.countDown();
+        }
+
+        @CallSuper
+        @Override
+        public void onDisconnected() {
+            super.onDisconnected();
+            disconnectLatch.countDown();
+        }
+
+        @Override
+        public void waitForConnect(boolean expect) throws InterruptedException {
+            if (expect) {
+                assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            } else {
+                assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            }
+        }
+
+        @Override
+        public void waitForDisconnect(boolean expect) throws InterruptedException {
+            if (expect) {
+                assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            } else {
+                assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            }
+        }
+    }
+
+    public class TestMediaController extends MediaController2 implements TestControllerInterface {
+        private final ControllerCallback mCallback;
+
+        public TestMediaController(@NonNull Context context, @NonNull SessionToken token,
+                @NonNull ControllerCallback callback) {
+            super(context, token, callback, sHandlerExecutor);
+            mCallback = callback;
+        }
+
+        @Override
+        public ControllerCallback getCallback() {
+            return mCallback;
+        }
+    }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
new file mode 100644
index 0000000..df45063
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Context;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static android.media.TestUtils.createPlaybackState;
+import static org.junit.Assert.*;
+
+/**
+ * Tests {@link MediaSessionManager} with {@link MediaSession2} specific APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Ignore
+// TODO(jaewan): Reenable test when the media session service detects newly installed sesison
+//               service app.
+public class MediaSessionManager_MediaSession2 extends MediaSession2TestBase {
+    private static final String TAG = "MediaSessionManager_MediaSession2";
+
+    private MediaSessionManager mManager;
+    private MediaSession2 mSession;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+
+        // Specify TAG here so {@link MediaSession2.getInstance()} doesn't complaint about
+        // per test thread differs across the {@link MediaSession2} with the same TAG.
+        final MockPlayer player = new MockPlayer(1);
+        sHandler.postAndSync(() -> {
+            mSession = new MediaSession2.Builder(mContext, player).setId(TAG).build();
+        });
+        ensureChangeInSession();
+    }
+
+    @After
+    @Override
+    public void cleanUp() throws Exception {
+        super.cleanUp();
+        sHandler.removeCallbacksAndMessages(null);
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+        });
+    }
+
+    // TODO(jaewan): Make this host-side test to see per-user behavior.
+    @Test
+    public void testGetMediaSession2Tokens_hasMediaController() throws InterruptedException {
+        final MockPlayer player = (MockPlayer) mSession.getPlayer();
+        player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_STOPPED));
+
+        MediaController2 controller = null;
+        List<SessionToken> tokens = mManager.getActiveSessionTokens();
+        assertNotNull(tokens);
+        for (int i = 0; i < tokens.size(); i++) {
+            SessionToken token = tokens.get(i);
+            if (mContext.getPackageName().equals(token.getPackageName())
+                    && TAG.equals(token.getId())) {
+                assertNotNull(token.getSessionBinder());
+                assertNull(controller);
+                controller = createController(token);
+            }
+        }
+        assertNotNull(controller);
+
+        // Test if the found controller is correct one.
+        assertEquals(PlaybackState.STATE_STOPPED, controller.getPlaybackState().getState());
+        controller.play();
+
+        assertTrue(player.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertTrue(player.mPlayCalled);
+    }
+
+    /**
+     * Test if server recognizes session even if session refuses the connection from server.
+     *
+     * @throws InterruptedException
+     */
+    @Test
+    public void testGetSessionTokens_sessionRejected() throws InterruptedException {
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+            mSession = new MediaSession2.Builder(mContext, new MockPlayer(0)).setId(TAG)
+                    .setSessionCallback(new SessionCallback() {
+                        @Override
+                        public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+                            // Reject all connection request.
+                            return null;
+                        }
+                    }).build();
+        });
+        ensureChangeInSession();
+
+        boolean foundSession = false;
+        List<SessionToken> tokens = mManager.getActiveSessionTokens();
+        assertNotNull(tokens);
+        for (int i = 0; i < tokens.size(); i++) {
+            SessionToken token = tokens.get(i);
+            if (mContext.getPackageName().equals(token.getPackageName())
+                    && TAG.equals(token.getId())) {
+                assertFalse(foundSession);
+                foundSession = true;
+            }
+        }
+        assertTrue(foundSession);
+    }
+
+    @Test
+    public void testGetMediaSession2Tokens_playerRemoved() throws InterruptedException {
+        // Release
+        sHandler.postAndSync(() -> {
+            mSession.setPlayer(null);
+        });
+        ensureChangeInSession();
+
+        // When the mSession's player becomes null, it should lose binder connection between server.
+        // So server will forget the session.
+        List<SessionToken> tokens = mManager.getActiveSessionTokens();
+        for (int i = 0; i < tokens.size(); i++) {
+            SessionToken token = tokens.get(i);
+            assertFalse(mContext.getPackageName().equals(token.getPackageName())
+                    && TAG.equals(token.getId()));
+        }
+    }
+
+    @Test
+    public void testGetMediaSessionService2Token() throws InterruptedException {
+        boolean foundTestSessionService = false;
+        boolean foundTestLibraryService = false;
+        List<SessionToken> tokens = mManager.getSessionServiceTokens();
+        for (int i = 0; i < tokens.size(); i++) {
+            SessionToken token = tokens.get(i);
+            if (mContext.getPackageName().equals(token.getPackageName())
+                    && MockMediaSessionService2.ID.equals(token.getId())) {
+                assertFalse(foundTestSessionService);
+                assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
+                assertNull(token.getSessionBinder());
+                foundTestSessionService = true;
+            } else if (mContext.getPackageName().equals(token.getPackageName())
+                    && MockMediaLibraryService2.ID.equals(token.getId())) {
+                assertFalse(foundTestLibraryService);
+                assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
+                assertNull(token.getSessionBinder());
+                foundTestLibraryService = true;
+            }
+        }
+        assertTrue(foundTestSessionService);
+        assertTrue(foundTestLibraryService);
+    }
+
+    @Test
+    public void testGetAllSessionTokens() throws InterruptedException {
+        boolean foundTestSession = false;
+        boolean foundTestSessionService = false;
+        boolean foundTestLibraryService = false;
+        List<SessionToken> tokens = mManager.getAllSessionTokens();
+        for (int i = 0; i < tokens.size(); i++) {
+            SessionToken token = tokens.get(i);
+            if (!mContext.getPackageName().equals(token.getPackageName())) {
+                continue;
+            }
+            switch (token.getId()) {
+                case TAG:
+                    assertFalse(foundTestSession);
+                    foundTestSession = true;
+                    break;
+                case MockMediaSessionService2.ID:
+                    assertFalse(foundTestSessionService);
+                    foundTestSessionService = true;
+                    assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
+                    break;
+                case MockMediaLibraryService2.ID:
+                    assertFalse(foundTestLibraryService);
+                    assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
+                    foundTestLibraryService = true;
+                    break;
+                default:
+                    fail("Unexpected session " + token + " exists in the package");
+            }
+        }
+        assertTrue(foundTestSession);
+        assertTrue(foundTestSessionService);
+        assertTrue(foundTestLibraryService);
+    }
+
+    // Ensures if the session creation/release is notified to the server.
+    private void ensureChangeInSession() throws InterruptedException {
+        // TODO(jaewan): Wait by listener.
+        Thread.sleep(WAIT_TIME_MS);
+    }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
new file mode 100644
index 0000000..7a16127
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.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 android.media;
+
+import static junit.framework.Assert.fail;
+
+import android.content.Context;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.TestUtils.SyncHandler;
+import android.os.Bundle;
+import android.os.Process;
+import android.service.media.MediaBrowserService.BrowserRoot;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Mock implementation of {@link MediaLibraryService2} for testing.
+ */
+public class MockMediaLibraryService2 extends MediaLibraryService2 {
+    // Keep in sync with the AndroidManifest.xml
+    public static final String ID = "TestLibrary";
+
+    public static final String ROOT_ID = "rootId";
+    public static final Bundle EXTRA = new Bundle();
+    static {
+        EXTRA.putString(ROOT_ID, ROOT_ID);
+    }
+    @GuardedBy("MockMediaLibraryService2.class")
+    private static SessionToken sToken;
+
+    private MediaLibrarySession mSession;
+
+    @Override
+    public MediaLibrarySession onCreateSession(String sessionId) {
+        final MockPlayer player = new MockPlayer(1);
+        SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+        try {
+            handler.postAndSync(() -> {
+                TestLibrarySessionCallback callback = new TestLibrarySessionCallback();
+                mSession = new MediaLibrarySessionBuilder(
+                        MockMediaLibraryService2.this, player, callback)
+                        .setId(sessionId).build();
+            });
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+        return mSession;
+    }
+
+    @Override
+    public void onDestroy() {
+        TestServiceRegistry.getInstance().cleanUp();
+        super.onDestroy();
+    }
+
+    public static SessionToken getToken(Context context) {
+        synchronized (MockMediaLibraryService2.class) {
+            if (sToken == null) {
+                sToken = new SessionToken(SessionToken.TYPE_LIBRARY_SERVICE,
+                        context.getPackageName(), ID,
+                        MockMediaLibraryService2.class.getName(), null);
+            }
+            return sToken;
+        }
+    }
+
+    private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
+        @Override
+        public CommandGroup onConnect(ControllerInfo controller) {
+            if (Process.myUid() != controller.getUid()) {
+                // It's system app wants to listen changes. Ignore.
+                return super.onConnect(controller);
+            }
+            TestServiceRegistry.getInstance().setServiceInstance(
+                    MockMediaLibraryService2.this, controller);
+            return super.onConnect(controller);
+        }
+
+        @Override
+        public BrowserRoot onGetRoot(ControllerInfo controller, Bundle rootHints) {
+            return new BrowserRoot(ROOT_ID, EXTRA);
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
new file mode 100644
index 0000000..9cf4911
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static junit.framework.Assert.fail;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.TestUtils.SyncHandler;
+import android.media.session.PlaybackState;
+import android.os.Process;
+
+/**
+ * Mock implementation of {@link android.media.MediaSessionService2} for testing.
+ */
+public class MockMediaSessionService2 extends MediaSessionService2 {
+    // Keep in sync with the AndroidManifest.xml
+    public static final String ID = "TestSession";
+
+    private static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
+    private static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
+
+    private NotificationChannel mDefaultNotificationChannel;
+    private MediaSession2 mSession;
+    private NotificationManager mNotificationManager;
+
+    @Override
+    public MediaSession2 onCreateSession(String sessionId) {
+        final MockPlayer player = new MockPlayer(1);
+        SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+        try {
+            handler.postAndSync(() -> {
+                mSession = new MediaSession2.Builder(MockMediaSessionService2.this, player)
+                        .setId(sessionId).setSessionCallback(new MySessionCallback()).build();
+            });
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+        return mSession;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    @Override
+    public void onDestroy() {
+        TestServiceRegistry.getInstance().cleanUp();
+        super.onDestroy();
+    }
+
+    @Override
+    public MediaNotification onUpdateNotification(PlaybackState state) {
+        if (mDefaultNotificationChannel == null) {
+            mDefaultNotificationChannel = new NotificationChannel(
+                    DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+                    DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+                    NotificationManager.IMPORTANCE_DEFAULT);
+            mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
+        }
+        Notification notification = new Notification.Builder(
+                this, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
+                .setContentTitle(getPackageName())
+                .setContentText("Playback state: " + state.getState())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+        return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
+    }
+
+    private class MySessionCallback extends SessionCallback {
+        @Override
+        public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+            if (Process.myUid() != controller.getUid()) {
+                // It's system app wants to listen changes. Ignore.
+                return super.onConnect(controller);
+            }
+            TestServiceRegistry.getInstance().setServiceInstance(
+                    MockMediaSessionService2.this, controller);
+            return super.onConnect(controller);
+        }
+    }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
new file mode 100644
index 0000000..b0d7a69
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockPlayer.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A mock implementation of {@link MediaPlayerBase} for testing.
+ */
+public class MockPlayer extends MediaPlayerBase {
+    public final CountDownLatch mCountDownLatch;
+
+    public boolean mPlayCalled;
+    public boolean mPauseCalled;
+    public boolean mStopCalled;
+    public boolean mSkipToPreviousCalled;
+    public boolean mSkipToNextCalled;
+    public List<PlaybackListenerHolder> mListeners = new ArrayList<>();
+    private PlaybackState mLastPlaybackState;
+
+    public MockPlayer(int count) {
+        mCountDownLatch = (count > 0) ? new CountDownLatch(count) : null;
+    }
+
+    @Override
+    public void play() {
+        mPlayCalled = true;
+        if (mCountDownLatch != null) {
+            mCountDownLatch.countDown();
+        }
+    }
+
+    @Override
+    public void pause() {
+        mPauseCalled = true;
+        if (mCountDownLatch != null) {
+            mCountDownLatch.countDown();
+        }
+    }
+
+    @Override
+    public void stop() {
+        mStopCalled = true;
+        if (mCountDownLatch != null) {
+            mCountDownLatch.countDown();
+        }
+    }
+
+    @Override
+    public void skipToPrevious() {
+        mSkipToPreviousCalled = true;
+        if (mCountDownLatch != null) {
+            mCountDownLatch.countDown();
+        }
+    }
+
+    @Override
+    public void skipToNext() {
+        mSkipToNextCalled = true;
+        if (mCountDownLatch != null) {
+            mCountDownLatch.countDown();
+        }
+    }
+
+    @Nullable
+    @Override
+    public PlaybackState getPlaybackState() {
+        return mLastPlaybackState;
+    }
+
+    @Override
+    public void addPlaybackListener(
+            @NonNull PlaybackListener listener, @NonNull Handler handler) {
+        mListeners.add(new PlaybackListenerHolder(listener, handler));
+    }
+
+    @Override
+    public void removePlaybackListener(@NonNull PlaybackListener listener) {
+        int index = PlaybackListenerHolder.indexOf(mListeners, listener);
+        if (index >= 0) {
+            mListeners.remove(index);
+        }
+    }
+
+    public void notifyPlaybackState(final PlaybackState state) {
+        mLastPlaybackState = state;
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).postPlaybackChange(state);
+        }
+    }
+}
diff --git a/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java b/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
new file mode 100644
index 0000000..b0b87de
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Holds {@link PlaybackListener} with the {@link Handler}.
+ */
+public class PlaybackListenerHolder extends Handler {
+    private static final int ON_PLAYBACK_CHANGED = 1;
+
+    public final PlaybackListener listener;
+
+    public PlaybackListenerHolder(
+            @NonNull PlaybackListener listener, @NonNull Handler handler) {
+        super(handler.getLooper());
+        this.listener = listener;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case ON_PLAYBACK_CHANGED:
+                listener.onPlaybackChanged((PlaybackState) msg.obj);
+                break;
+        }
+    }
+
+    public void postPlaybackChange(PlaybackState state) {
+        obtainMessage(ON_PLAYBACK_CHANGED, state).sendToTarget();
+    }
+
+    /**
+     * Returns {@code true} if the given list contains a {@link PlaybackListenerHolder} that holds
+     * the given listener.
+     *
+     * @param list list to check
+     * @param listener listener to check
+     * @return {@code true} if the given list contains listener. {@code false} otherwise.
+     */
+    public static <Holder extends PlaybackListenerHolder> boolean contains(
+            @NonNull List<Holder> list, PlaybackListener listener) {
+        return indexOf(list, listener) >= 0;
+    }
+
+    /**
+     * Returns the index of the {@link PlaybackListenerHolder} that contains the given listener.
+     *
+     * @param list list to check
+     * @param listener listener to check
+     * @return {@code index} of item if the given list contains listener. {@code -1} otherwise.
+     */
+    public static <Holder extends PlaybackListenerHolder> int indexOf(
+            @NonNull List<Holder> list, PlaybackListener listener) {
+        for (int i = 0; i < list.size(); i++) {
+            if (list.get(i).listener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
new file mode 100644
index 0000000..378a6c4
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static org.junit.Assert.fail;
+
+import android.media.MediaSession2.ControllerInfo;
+import android.media.TestUtils.SyncHandler;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.GuardedBy;
+
+/**
+ * Keeps the instance of currently running {@link MockMediaSessionService2}. And also provides
+ * a way to control them in one place.
+ * <p>
+ * It only support only one service at a time.
+ */
+public class TestServiceRegistry {
+    public interface ServiceInstanceChangedCallback {
+        void OnServiceInstanceChanged(MediaSessionService2 service);
+    }
+
+    @GuardedBy("TestServiceRegistry.class")
+    private static TestServiceRegistry sInstance;
+    @GuardedBy("TestServiceRegistry.class")
+    private MediaSessionService2 mService;
+    @GuardedBy("TestServiceRegistry.class")
+    private SyncHandler mHandler;
+    @GuardedBy("TestServiceRegistry.class")
+    private ControllerInfo mOnConnectControllerInfo;
+    @GuardedBy("TestServiceRegistry.class")
+    private ServiceInstanceChangedCallback mCallback;
+
+    public static TestServiceRegistry getInstance() {
+        synchronized (TestServiceRegistry.class) {
+            if (sInstance == null) {
+                sInstance = new TestServiceRegistry();
+            }
+            return sInstance;
+        }
+    }
+
+    public void setHandler(Handler handler) {
+        synchronized (TestServiceRegistry.class) {
+            mHandler = new SyncHandler(handler.getLooper());
+        }
+    }
+
+    public void setServiceInstanceChangedCallback(ServiceInstanceChangedCallback callback) {
+        synchronized (TestServiceRegistry.class) {
+            mCallback = callback;
+        }
+    }
+
+    public Handler getHandler() {
+        synchronized (TestServiceRegistry.class) {
+            return mHandler;
+        }
+    }
+
+    public void setServiceInstance(MediaSessionService2 service, ControllerInfo controller) {
+        synchronized (TestServiceRegistry.class) {
+            if (mService != null) {
+                fail("Previous service instance is still running. Clean up manually to ensure"
+                        + " previoulsy running service doesn't break current test");
+            }
+            mService = service;
+            mOnConnectControllerInfo = controller;
+            if (mCallback != null) {
+                mCallback.OnServiceInstanceChanged(service);
+            }
+        }
+    }
+
+    public MediaSessionService2 getServiceInstance() {
+        synchronized (TestServiceRegistry.class) {
+            return mService;
+        }
+    }
+
+    public ControllerInfo getOnConnectControllerInfo() {
+        synchronized (TestServiceRegistry.class) {
+            return mOnConnectControllerInfo;
+        }
+    }
+
+
+    public void cleanUp() {
+        synchronized (TestServiceRegistry.class) {
+            final ServiceInstanceChangedCallback callback = mCallback;
+            if (mService != null) {
+                try {
+                    if (mHandler.getLooper() == Looper.myLooper()) {
+                        mService.getSession().setPlayer(null);
+                    } else {
+                        mHandler.postAndSync(() -> {
+                            mService.getSession().setPlayer(null);
+                        });
+                    }
+                } catch (InterruptedException e) {
+                    // No-op. Service containing session will die, but shouldn't be a huge issue.
+                }
+                // stopSelf() would not kill service while the binder connection established by
+                // bindService() exists, and setPlayer(null) above will do the job instead.
+                // So stopSelf() isn't really needed, but just for sure.
+                mService.stopSelf();
+                mService = null;
+            }
+            if (mHandler != null) {
+                mHandler.removeCallbacksAndMessages(null);
+            }
+            mCallback = null;
+            mOnConnectControllerInfo = null;
+
+            if (callback != null) {
+                callback.OnServiceInstanceChanged(null);
+            }
+        }
+    }
+}
diff --git a/packages/MediaComponents/test/src/android/media/TestUtils.java b/packages/MediaComponents/test/src/android/media/TestUtils.java
new file mode 100644
index 0000000..1372f01
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/TestUtils.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Context;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.os.Looper;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Utilities for tests.
+ */
+public final class TestUtils {
+    private static final int WAIT_TIME_MS = 1000;
+    private static final int WAIT_SERVICE_TIME_MS = 5000;
+
+    /**
+     * Creates a {@link android.media.session.PlaybackState} with the given state.
+     *
+     * @param state one of the PlaybackState.STATE_xxx.
+     * @return a PlaybackState
+     */
+    public static PlaybackState createPlaybackState(int state) {
+        return new PlaybackState.Builder().setState(state, 0, 1.0f).build();
+    }
+
+    /**
+     * Finds the session with id in this test package.
+     *
+     * @param context
+     * @param id
+     * @return
+     */
+    // TODO(jaewan): Currently not working.
+    public static SessionToken getServiceToken(Context context, String id) {
+        MediaSessionManager manager =
+                (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+        List<SessionToken> tokens = manager.getSessionServiceTokens();
+        for (int i = 0; i < tokens.size(); i++) {
+            SessionToken token = tokens.get(i);
+            if (context.getPackageName().equals(token.getPackageName())
+                    && id.equals(token.getId())) {
+                return token;
+            }
+        }
+        fail("Failed to find service");
+        return null;
+    }
+
+    /**
+     * Compares contents of two bundles.
+     *
+     * @param a a bundle
+     * @param b another bundle
+     * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
+     *     incorrect if any bundle contains a bundle.
+     */
+    public static boolean equals(Bundle a, Bundle b) {
+        if (a == b) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        if (!a.keySet().containsAll(b.keySet())
+                || !b.keySet().containsAll(a.keySet())) {
+            return false;
+        }
+        for (String key : a.keySet()) {
+            if (!Objects.equals(a.get(key), b.get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Handler that always waits until the Runnable finishes.
+     */
+    public static class SyncHandler extends Handler {
+        public SyncHandler(Looper looper) {
+            super(looper);
+        }
+
+        public void postAndSync(Runnable runnable) throws InterruptedException {
+            final CountDownLatch latch = new CountDownLatch(1);
+            if (getLooper() == Looper.myLooper()) {
+                runnable.run();
+            } else {
+                post(()->{
+                    runnable.run();
+                    latch.countDown();
+                });
+                assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            }
+        }
+    }
+}
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 2803ec1..cd2174d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -187,6 +187,15 @@
     bool isStreamActiveRemotely(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
 
     /**
+     * return whether a stream is playing, but not on a "remote" device.
+     * Override to change the definition of a local/remote playback.
+     * Used for instance by policy manager to alter the speaker playback ("speaker safe" behavior)
+     * when media plays or not locally.
+     * For the base implementation, "remotely" means playing during screen mirroring.
+     */
+    bool isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
+
+    /**
      * returns the A2DP output handle if it is open or 0 otherwise
      */
     audio_io_handle_t getA2dpOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index d5e8e1b..17fc272 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -579,6 +579,19 @@
     return false;
 }
 
+bool SwAudioOutputCollection::isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs) const
+{
+    nsecs_t sysTime = systemTime();
+    for (size_t i = 0; i < this->size(); i++) {
+        const sp<SwAudioOutputDescriptor> outputDesc = this->valueAt(i);
+        if (outputDesc->isStreamActive(stream, inPastMs, sysTime)
+                && ((outputDesc->device() & APM_AUDIO_OUT_DEVICE_REMOTE_ALL) == 0)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 bool SwAudioOutputCollection::isStreamActiveRemotely(audio_stream_type_t stream,
                                                    uint32_t inPastMs) const
 {
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 9bdb98c..5ec0475 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -238,18 +238,19 @@
     const SwAudioOutputCollection &outputs = mApmObserver->getOutputs();
 
     return getDeviceForStrategyInt(strategy, availableOutputDevices,
-                                   availableInputDevices, outputs);
+                                   availableInputDevices, outputs, (uint32_t)AUDIO_DEVICE_NONE);
 }
 
 
-
 audio_devices_t Engine::getDeviceForStrategyInt(routing_strategy strategy,
-                                                DeviceVector availableOutputDevices,
-                                                DeviceVector availableInputDevices,
-                                                const SwAudioOutputCollection &outputs) const
+        DeviceVector availableOutputDevices,
+        DeviceVector availableInputDevices,
+        const SwAudioOutputCollection &outputs,
+        uint32_t outputDeviceTypesToIgnore) const
 {
     uint32_t device = AUDIO_DEVICE_NONE;
-    uint32_t availableOutputDevicesType = availableOutputDevices.types();
+    uint32_t availableOutputDevicesType =
+            availableOutputDevices.types() & ~outputDeviceTypesToIgnore;
 
     switch (strategy) {
 
@@ -260,38 +261,24 @@
     case STRATEGY_SONIFICATION_RESPECTFUL:
         if (isInCall()) {
             device = getDeviceForStrategyInt(
-                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
-        } else if (outputs.isStreamActiveRemotely(AUDIO_STREAM_MUSIC,
-                SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)) {
-            // while media is playing on a remote device, use the the sonification behavior.
-            // Note that we test this usecase before testing if media is playing because
-            //   the isStreamActive() method only informs about the activity of a stream, not
-            //   if it's for local playback. Note also that we use the same delay between both tests
-            device = getDeviceForStrategyInt(
-                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
-            //user "safe" speaker if available instead of normal speaker to avoid triggering
-            //other acoustic safety mechanisms for notification
-            if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
-                    (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
-                device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
-                device &= ~AUDIO_DEVICE_OUT_SPEAKER;
-            }
-        } else if (outputs.isStreamActive(
-                                AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
-                    || outputs.isStreamActive(
-                            AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY))
-        {
-            // while media/a11y is playing (or has recently played), use the same device
-            device = getDeviceForStrategyInt(
-                    STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
         } else {
-            // when media is not playing anymore, fall back on the sonification behavior
-            device = getDeviceForStrategyInt(
-                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
-            //user "safe" speaker if available instead of normal speaker to avoid triggering
-            //other acoustic safety mechanisms for notification
-            if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
-                    (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
+            bool media_active_locally =
+                    outputs.isStreamActiveLocally(
+                            AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
+                    || outputs.isStreamActiveLocally(
+                            AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY);
+            // routing is same as media without the "remote" device
+            device = getDeviceForStrategyInt(STRATEGY_MEDIA,
+                    availableOutputDevices,
+                    availableInputDevices, outputs,
+                    AUDIO_DEVICE_OUT_REMOTE_SUBMIX | outputDeviceTypesToIgnore);
+            // if no media is playing on the device, check for mandatory use of "safe" speaker
+            // when media would have played on speaker, and the safe speaker path is available
+            if (!media_active_locally
+                    && (device & AUDIO_DEVICE_OUT_SPEAKER)
+                    && (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
                 device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
                 device &= ~AUDIO_DEVICE_OUT_SPEAKER;
             }
@@ -302,7 +289,8 @@
         if (!isInCall()) {
             // when off call, DTMF strategy follows the same rules as MEDIA strategy
             device = getDeviceForStrategyInt(
-                    STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
             break;
         }
         // when in call, DTMF and PHONE strategies follow the same rules
@@ -408,7 +396,8 @@
         // handleIncallSonification().
         if (isInCall()) {
             device = getDeviceForStrategyInt(
-                    STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
             break;
         }
         // FALL THROUGH
@@ -463,11 +452,13 @@
             if (outputs.isStreamActive(AUDIO_STREAM_RING) ||
                     outputs.isStreamActive(AUDIO_STREAM_ALARM)) {
                 return getDeviceForStrategyInt(
-                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
             }
             if (isInCall()) {
                 return getDeviceForStrategyInt(
-                        STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+                        STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+                        outputDeviceTypesToIgnore);
             }
         }
         // For other cases, STRATEGY_ACCESSIBILITY behaves like STRATEGY_MEDIA
@@ -486,7 +477,8 @@
         }
         if (isInCall() && (strategy == STRATEGY_MEDIA)) {
             device = getDeviceForStrategyInt(
-                    STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+                    STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+                    outputDeviceTypesToIgnore);
             break;
         }
         if ((device2 == AUDIO_DEVICE_NONE) &&
diff --git a/services/audiopolicy/enginedefault/src/Engine.h b/services/audiopolicy/enginedefault/src/Engine.h
index 57538c4..06186c1 100644
--- a/services/audiopolicy/enginedefault/src/Engine.h
+++ b/services/audiopolicy/enginedefault/src/Engine.h
@@ -126,9 +126,10 @@
     routing_strategy getStrategyForUsage(audio_usage_t usage);
     audio_devices_t getDeviceForStrategy(routing_strategy strategy) const;
     audio_devices_t getDeviceForStrategyInt(routing_strategy strategy,
-                                            DeviceVector availableOutputDevices,
-                                            DeviceVector availableInputDevices,
-                                            const SwAudioOutputCollection &outputs) const;
+            DeviceVector availableOutputDevices,
+            DeviceVector availableInputDevices,
+            const SwAudioOutputCollection &outputs,
+            uint32_t outputDeviceTypesToIgnore) const;
     audio_devices_t getDeviceForInputSource(audio_source_t inputSource) const;
     audio_mode_t mPhoneState;  /**< current phone state. */
 
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index c443b20..999e258 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -55,8 +55,6 @@
 #include "device3/Camera3SharedOutputStream.h"
 #include "CameraService.h"
 
-#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
-
 using namespace android::camera3;
 using namespace android::hardware::camera;
 using namespace android::hardware::camera::device::V3_2;
@@ -669,13 +667,15 @@
     }
 
     if (dumpTemplates) {
-        const char *templateNames[] = {
+        const char *templateNames[CAMERA3_TEMPLATE_COUNT] = {
             "TEMPLATE_PREVIEW",
             "TEMPLATE_STILL_CAPTURE",
             "TEMPLATE_VIDEO_RECORD",
             "TEMPLATE_VIDEO_SNAPSHOT",
             "TEMPLATE_ZERO_SHUTTER_LAG",
-            "TEMPLATE_MANUAL"
+            "TEMPLATE_MANUAL",
+            "TEMPLATE_MOTION_TRACKING_PREVIEW",
+            "TEMPALTE_MOTION_TRACKING_BEST"
         };
 
         for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; i++) {
@@ -3246,7 +3246,18 @@
             sp<ICameraDeviceSession> &session,
             std::shared_ptr<RequestMetadataQueue> queue) :
         mHidlSession(session),
-        mRequestMetadataQueue(queue) {}
+        mRequestMetadataQueue(queue) {
+    // Check with hardware service manager if we can downcast these interfaces
+    // Somewhat expensive, so cache the results at startup
+    auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
+    if (castResult_3_4.isOk()) {
+        mHidlSession_3_4 = castResult_3_4;
+    }
+    auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
+    if (castResult_3_3.isOk()) {
+        mHidlSession_3_3 = castResult_3_3;
+    }
+}
 
 Camera3Device::HalInterface::HalInterface() {}
 
@@ -3274,52 +3285,89 @@
     status_t res = OK;
 
     common::V1_0::Status status;
-    RequestTemplate id;
-    switch (templateId) {
-        case CAMERA3_TEMPLATE_PREVIEW:
-            id = RequestTemplate::PREVIEW;
-            break;
-        case CAMERA3_TEMPLATE_STILL_CAPTURE:
-            id = RequestTemplate::STILL_CAPTURE;
-            break;
-        case CAMERA3_TEMPLATE_VIDEO_RECORD:
-            id = RequestTemplate::VIDEO_RECORD;
-            break;
-        case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
-            id = RequestTemplate::VIDEO_SNAPSHOT;
-            break;
-        case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
-            id = RequestTemplate::ZERO_SHUTTER_LAG;
-            break;
-        case CAMERA3_TEMPLATE_MANUAL:
-            id = RequestTemplate::MANUAL;
-            break;
-        default:
-            // Unknown template ID
-            return BAD_VALUE;
-    }
-    auto err = mHidlSession->constructDefaultRequestSettings(id,
-            [&status, &requestTemplate]
+
+    auto requestCallback = [&status, &requestTemplate]
             (common::V1_0::Status s, const device::V3_2::CameraMetadata& request) {
-                status = s;
-                if (status == common::V1_0::Status::OK) {
-                    const camera_metadata *r =
-                            reinterpret_cast<const camera_metadata_t*>(request.data());
-                    size_t expectedSize = request.size();
-                    int ret = validate_camera_metadata_structure(r, &expectedSize);
-                    if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
-                        *requestTemplate = clone_camera_metadata(r);
-                        if (*requestTemplate == nullptr) {
-                            ALOGE("%s: Unable to clone camera metadata received from HAL",
-                                    __FUNCTION__);
-                            status = common::V1_0::Status::INTERNAL_ERROR;
-                        }
-                    } else {
-                        ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+            status = s;
+            if (status == common::V1_0::Status::OK) {
+                const camera_metadata *r =
+                        reinterpret_cast<const camera_metadata_t*>(request.data());
+                size_t expectedSize = request.size();
+                int ret = validate_camera_metadata_structure(r, &expectedSize);
+                if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
+                    *requestTemplate = clone_camera_metadata(r);
+                    if (*requestTemplate == nullptr) {
+                        ALOGE("%s: Unable to clone camera metadata received from HAL",
+                                __FUNCTION__);
                         status = common::V1_0::Status::INTERNAL_ERROR;
                     }
+                } else {
+                    ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+                    status = common::V1_0::Status::INTERNAL_ERROR;
                 }
-            });
+            }
+        };
+    hardware::Return<void> err;
+    if (mHidlSession_3_4 != nullptr) {
+        device::V3_4::RequestTemplate id;
+        switch (templateId) {
+            case CAMERA3_TEMPLATE_PREVIEW:
+                id = device::V3_4::RequestTemplate::PREVIEW;
+                break;
+            case CAMERA3_TEMPLATE_STILL_CAPTURE:
+                id = device::V3_4::RequestTemplate::STILL_CAPTURE;
+                break;
+            case CAMERA3_TEMPLATE_VIDEO_RECORD:
+                id = device::V3_4::RequestTemplate::VIDEO_RECORD;
+                break;
+            case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+                id = device::V3_4::RequestTemplate::VIDEO_SNAPSHOT;
+                break;
+            case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+                id = device::V3_4::RequestTemplate::ZERO_SHUTTER_LAG;
+                break;
+            case CAMERA3_TEMPLATE_MANUAL:
+                id = device::V3_4::RequestTemplate::MANUAL;
+                break;
+            case CAMERA3_TEMPLATE_MOTION_TRACKING_PREVIEW:
+                id = device::V3_4::RequestTemplate::MOTION_TRACKING_PREVIEW;
+                break;
+            case CAMERA3_TEMPLATE_MOTION_TRACKING_BEST:
+                id = device::V3_4::RequestTemplate::MOTION_TRACKING_BEST;
+                break;
+            default:
+                // Unknown template ID
+                return BAD_VALUE;
+        }
+        err = mHidlSession_3_4->constructDefaultRequestSettings_3_4(id, requestCallback);
+    } else {
+        RequestTemplate id;
+        switch (templateId) {
+            case CAMERA3_TEMPLATE_PREVIEW:
+                id = RequestTemplate::PREVIEW;
+                break;
+            case CAMERA3_TEMPLATE_STILL_CAPTURE:
+                id = RequestTemplate::STILL_CAPTURE;
+                break;
+            case CAMERA3_TEMPLATE_VIDEO_RECORD:
+                id = RequestTemplate::VIDEO_RECORD;
+                break;
+            case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+                id = RequestTemplate::VIDEO_SNAPSHOT;
+                break;
+            case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+                id = RequestTemplate::ZERO_SHUTTER_LAG;
+                break;
+            case CAMERA3_TEMPLATE_MANUAL:
+                id = RequestTemplate::MANUAL;
+                break;
+            default:
+                // Unknown template ID, or this HAL is too old to support it
+                return BAD_VALUE;
+        }
+        err = mHidlSession->constructDefaultRequestSettings(id, requestCallback);
+    }
+
     if (!err.isOk()) {
         ALOGE("%s: Transaction error: %s", __FUNCTION__, err.description().c_str());
         res = DEAD_OBJECT;
@@ -3411,24 +3459,11 @@
     common::V1_0::Status status;
 
     // See if we have v3.4 or v3.3 HAL
-    sp<device::V3_4::ICameraDeviceSession> hidlSession_3_4;
-    sp<device::V3_3::ICameraDeviceSession> hidlSession_3_3;
-    auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
-    if (castResult_3_4.isOk()) {
-        hidlSession_3_4 = castResult_3_4;
-    } else {
-        auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
-        if (castResult_3_3.isOk()) {
-            hidlSession_3_3 = castResult_3_3;
-        }
-    }
-
-    if (hidlSession_3_4 != nullptr) {
-        // We do; use v3.4 for the call, and construct a v3.4
-        // HalStreamConfiguration
+    if (mHidlSession_3_4 != nullptr) {
+        // We do; use v3.4 for the call
         ALOGV("%s: v3.4 device found", __FUNCTION__);
         device::V3_4::HalStreamConfiguration finalConfiguration3_4;
-        auto err = hidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
+        auto err = mHidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
             [&status, &finalConfiguration3_4]
             (common::V1_0::Status s, const device::V3_4::HalStreamConfiguration& halConfiguration) {
                 finalConfiguration3_4 = halConfiguration;
@@ -3442,10 +3477,10 @@
         for (size_t i = 0; i < finalConfiguration3_4.streams.size(); i++) {
             finalConfiguration.streams[i] = finalConfiguration3_4.streams[i].v3_3;
         }
-    } else if (hidlSession_3_3 != nullptr) {
+    } else if (mHidlSession_3_3 != nullptr) {
         // We do; use v3.3 for the call
         ALOGV("%s: v3.3 device found", __FUNCTION__);
-        auto err = hidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
+        auto err = mHidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
             [&status, &finalConfiguration]
             (common::V1_0::Status s, const device::V3_3::HalStreamConfiguration& halConfiguration) {
                 finalConfiguration = halConfiguration;
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 83f3f7a..bf2a577 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -31,6 +31,7 @@
 #include <android/hardware/camera/device/3.2/ICameraDevice.h>
 #include <android/hardware/camera/device/3.2/ICameraDeviceSession.h>
 #include <android/hardware/camera/device/3.3/ICameraDeviceSession.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
 #include <android/hardware/camera/device/3.2/ICameraDeviceCallback.h>
 #include <fmq/MessageQueue.h>
 #include <hardware/camera3.h>
@@ -297,7 +298,13 @@
         void getInflightBufferKeys(std::vector<std::pair<int32_t, int32_t>>* out);
 
       private:
+        // Always valid
         sp<hardware::camera::device::V3_2::ICameraDeviceSession> mHidlSession;
+        // Valid if ICameraDeviceSession is @3.3 or newer
+        sp<hardware::camera::device::V3_3::ICameraDeviceSession> mHidlSession_3_3;
+        // Valid if ICameraDeviceSession is @3.4 or newer
+        sp<hardware::camera::device::V3_4::ICameraDeviceSession> mHidlSession_3_4;
+
         std::shared_ptr<RequestMetadataQueue> mRequestMetadataQueue;
 
         std::mutex mInflightLock;
diff --git a/services/mediaanalytics/Android.mk b/services/mediaanalytics/Android.mk
index 9e2813e..2eeb7fa 100644
--- a/services/mediaanalytics/Android.mk
+++ b/services/mediaanalytics/Android.mk
@@ -6,11 +6,6 @@
 
 LOCAL_SRC_FILES:= \
     main_mediametrics.cpp              \
-    MetricsSummarizerCodec.cpp         \
-    MetricsSummarizerExtractor.cpp     \
-    MetricsSummarizerPlayer.cpp        \
-    MetricsSummarizerRecorder.cpp      \
-    MetricsSummarizer.cpp              \
     MediaAnalyticsService.cpp
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 2954b3b..7f52802 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -74,26 +74,11 @@
 
 #include "MediaAnalyticsService.h"
 
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerCodec.h"
-#include "MetricsSummarizerExtractor.h"
-#include "MetricsSummarizerPlayer.h"
-#include "MetricsSummarizerRecorder.h"
-
-
 namespace android {
 
     using namespace android::base;
     using namespace android::content::pm;
 
-
-
-// summarized records
-// up to 36 sets, each covering an hour -- so at least 1.5 days
-// (will be longer if there are hours without any media action)
-static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
-static const int kMaxRecordSets = 36;
-
 // individual records kept in memory: age or count
 // age: <= 36 hours (1.5 days)
 // count: hard limit of # records
@@ -108,57 +93,9 @@
             String16(kServiceName), new MediaAnalyticsService());
 }
 
-// handle sets of summarizers
-MediaAnalyticsService::SummarizerSet::SummarizerSet() {
-    mSummarizers = new List<MetricsSummarizer *>();
-}
-
-MediaAnalyticsService::SummarizerSet::~SummarizerSet() {
-    // empty the list
-    List<MetricsSummarizer *> *l = mSummarizers;
-    while (l->size() > 0) {
-        MetricsSummarizer *summarizer = *(l->begin());
-        l->erase(l->begin());
-        delete summarizer;
-    }
-}
-
-void MediaAnalyticsService::newSummarizerSet() {
-    ALOGD("MediaAnalyticsService::newSummarizerSet");
-    MediaAnalyticsService::SummarizerSet *set = new MediaAnalyticsService::SummarizerSet();
-    nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
-    set->setStarted(now);
-
-    set->appendSummarizer(new MetricsSummarizerExtractor("extractor"));
-    set->appendSummarizer(new MetricsSummarizerCodec("codec"));
-    set->appendSummarizer(new MetricsSummarizerPlayer("nuplayer"));
-    set->appendSummarizer(new MetricsSummarizerRecorder("recorder"));
-
-    // ALWAYS at the end, since it catches everything
-    set->appendSummarizer(new MetricsSummarizer(NULL));
-
-    // inject this set at the BACK of the list.
-    mSummarizerSets->push_back(set);
-    mCurrentSet = set;
-
-    // limit the # that we have
-    if (mMaxRecordSets > 0) {
-        List<SummarizerSet *> *l = mSummarizerSets;
-        while (l->size() > (size_t) mMaxRecordSets) {
-            ALOGD("Deleting oldest record set....");
-            MediaAnalyticsService::SummarizerSet *oset = *(l->begin());
-            l->erase(l->begin());
-            delete oset;
-            mSetsDiscarded++;
-        }
-    }
-}
-
 MediaAnalyticsService::MediaAnalyticsService()
         : mMaxRecords(kMaxRecords),
           mMaxRecordAgeNs(kMaxRecordAgeNs),
-          mMaxRecordSets(kMaxRecordSets),
-          mNewSetInterval(kNewSetIntervalNs),
           mDumpProto(MediaAnalyticsItem::PROTO_V1),
           mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1) {
 
@@ -167,9 +104,6 @@
     mOpen = new List<MediaAnalyticsItem *>();
     mFinalized = new List<MediaAnalyticsItem *>();
 
-    mSummarizerSets = new List<MediaAnalyticsService::SummarizerSet *>();
-    newSummarizerSet();
-
     mItemsSubmitted = 0;
     mItemsFinalized = 0;
     mItemsDiscarded = 0;
@@ -204,8 +138,6 @@
     }
     delete mFinalized;
     mFinalized = NULL;
-
-    // XXX: clean out the summaries
 }
 
 
@@ -315,13 +247,11 @@
                 oitem = NULL;
             } else {
                 oitem->setFinalized(true);
-                summarize(oitem);
                 saveItem(mFinalized, oitem, 0);
             }
             // new record could itself be marked finalized...
             id = item->getSessionID();
             if (finalizing) {
-                summarize(item);
                 saveItem(mFinalized, item, 0);
                 mItemsFinalized++;
             } else {
@@ -332,7 +262,6 @@
             oitem->merge(item);
             id = oitem->getSessionID();
             if (finalizing) {
-                summarize(oitem);
                 saveItem(mFinalized, oitem, 0);
                 mItemsFinalized++;
             }
@@ -350,7 +279,6 @@
                 delete item;
                 item = NULL;
             } else {
-                summarize(item);
                 saveItem(mFinalized, item, 0);
                 mItemsFinalized++;
             }
@@ -379,8 +307,6 @@
     }
 
     // crack any parameters
-    String16 summaryOption("-summary");
-    bool summary = false;
     String16 protoOption("-proto");
     int chosenProto = mDumpProtoDefault;
     String16 clearOption("-clear");
@@ -396,8 +322,6 @@
         String8 myarg(args[i]);
         if (args[i] == clearOption) {
             clear = true;
-        } else if (args[i] == summaryOption) {
-            summary = true;
         } else if (args[i] == protoOption) {
             i++;
             if (i < n) {
@@ -444,7 +368,6 @@
             result.append("Recognized parameters:\n");
             result.append("-help        this help message\n");
             result.append("-proto #     dump using protocol #");
-            result.append("-summary     show summary info\n");
             result.append("-clear       clears out saved records\n");
             result.append("-only X      process records for component X\n");
             result.append("-since X     include records since X\n");
@@ -464,12 +387,7 @@
 
     dumpHeaders(result, ts_since);
 
-    // want exactly 1, to avoid confusing folks that parse the output
-    if (summary) {
-        dumpSummaries(result, ts_since, only.c_str());
-    } else {
-        dumpRecent(result, ts_since, only.c_str());
-    }
+    dumpRecent(result, ts_since, only.c_str());
 
 
     if (clear) {
@@ -526,40 +444,6 @@
     }
 }
 
-// dump summary info
-void MediaAnalyticsService::dumpSummaries(String8 &result, nsecs_t ts_since, const char *only) {
-    const size_t SIZE = 512;
-    char buffer[SIZE];
-    int slot = 0;
-
-    snprintf(buffer, SIZE, "\nSummarized Metrics:\n");
-    result.append(buffer);
-
-    if (only != NULL && *only == '\0') {
-        only = NULL;
-    }
-
-    // have each of the distillers dump records
-    if (mSummarizerSets != NULL) {
-        List<SummarizerSet *>::iterator itSet = mSummarizerSets->begin();
-        for (; itSet != mSummarizerSets->end(); itSet++) {
-            nsecs_t when = (*itSet)->getStarted();
-            if (when < ts_since) {
-                continue;
-            }
-            List<MetricsSummarizer *> *list = (*itSet)->getSummarizers();
-            List<MetricsSummarizer *>::iterator it = list->begin();
-            for (; it != list->end(); it++) {
-                if (only != NULL && strcmp(only, (*it)->getKey()) != 0) {
-                    ALOGV("Told to omit '%s'", (*it)->getKey());
-                }
-                std::string distilled = (*it)->dumpSummary(slot, only);
-                result.append(distilled.c_str());
-            }
-        }
-    }
-}
-
 // the recent, detailed queues
 void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only) {
     const size_t SIZE = 512;
@@ -785,45 +669,6 @@
     return false;
 }
 
-// insert into the appropriate summarizer.
-// we make our own copy to save/summarize
-void MediaAnalyticsService::summarize(MediaAnalyticsItem *item) {
-
-    ALOGV("MediaAnalyticsService::summarize()");
-
-    if (item == NULL) {
-        return;
-    }
-
-    nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
-    if (mCurrentSet == NULL
-        || (mCurrentSet->getStarted() + mNewSetInterval < now)) {
-        newSummarizerSet();
-    }
-
-    if (mCurrentSet == NULL) {
-        return;
-    }
-
-    List<MetricsSummarizer *> *summarizers = mCurrentSet->getSummarizers();
-    List<MetricsSummarizer *>::iterator it = summarizers->begin();
-    for (; it != summarizers->end(); it++) {
-        if ((*it)->isMine(*item)) {
-            break;
-        }
-    }
-    if (it == summarizers->end()) {
-        ALOGD("no handler for type %s", item->getKey().c_str());
-        return;               // no handler
-    }
-
-    // invoke the summarizer. summarizer will make whatever copies
-    // it wants; the caller retains ownership of item.
-
-    (*it)->handleRecord(item);
-
-}
-
 // how long we hold package info before we re-fetch it
 #define PKG_EXPIRATION_NS (30*60*1000000000ll)   // 30 minutes, in nsecs
 
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index 1287835..484339c 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -28,9 +28,6 @@
 
 #include <media/IMediaAnalyticsService.h>
 
-#include "MetricsSummarizer.h"
-
-
 namespace android {
 
 class MediaAnalyticsService : public BnMediaAnalyticsService
@@ -89,36 +86,6 @@
     MediaAnalyticsItem *findItem(List<MediaAnalyticsItem *> *,
                                      MediaAnalyticsItem *, bool removeit);
 
-    // summarizers
-    void summarize(MediaAnalyticsItem *item);
-    class SummarizerSet {
-        nsecs_t mStarted;
-        List<MetricsSummarizer *> *mSummarizers;
-
-      public:
-        void appendSummarizer(MetricsSummarizer *s) {
-            if (s) {
-                mSummarizers->push_back(s);
-            }
-        };
-        nsecs_t getStarted() { return mStarted;}
-        void setStarted(nsecs_t started) {mStarted = started;}
-        List<MetricsSummarizer *> *getSummarizers() { return mSummarizers;}
-
-        SummarizerSet();
-        ~SummarizerSet();
-    };
-    void newSummarizerSet();
-    List<SummarizerSet *> *mSummarizerSets;
-    SummarizerSet *mCurrentSet;
-    List<MetricsSummarizer *> *getFirstSet() {
-        List<SummarizerSet *>::iterator first = mSummarizerSets->begin();
-        if (first != mSummarizerSets->end()) {
-            return (*first)->getSummarizers();
-        }
-        return NULL;
-    }
-
     void saveItem(MediaAnalyticsItem);
     void saveItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *, int);
     void deleteItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *);
diff --git a/services/mediaanalytics/MetricsSummarizer.cpp b/services/mediaanalytics/MetricsSummarizer.cpp
deleted file mode 100644
index e7c26e3..0000000
--- a/services/mediaanalytics/MetricsSummarizer.cpp
+++ /dev/null
@@ -1,348 +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.
- */
-
-#define LOG_TAG "MetricsSummarizer"
-#include <utils/Log.h>
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <string>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-#define DEBUG_SORT      0
-#define DEBUG_QUEUE     0
-
-
-MetricsSummarizer::MetricsSummarizer(const char *key)
-    : mIgnorables(NULL)
-{
-    ALOGV("MetricsSummarizer::MetricsSummarizer");
-
-    if (key == NULL) {
-        mKey = key;
-    } else {
-        mKey = strdup(key);
-    }
-
-    mSummaries = new List<MediaAnalyticsItem *>();
-}
-
-MetricsSummarizer::~MetricsSummarizer()
-{
-    ALOGV("MetricsSummarizer::~MetricsSummarizer");
-    if (mKey) {
-        free((void *)mKey);
-        mKey = NULL;
-    }
-
-    // clear the list of items we have saved
-    while (mSummaries->size() > 0) {
-        MediaAnalyticsItem * oitem = *(mSummaries->begin());
-        if (DEBUG_QUEUE) {
-            ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
-                oitem->getKey().c_str(), oitem->getSessionID(),
-                oitem->getTimestamp());
-        }
-        mSummaries->erase(mSummaries->begin());
-        delete oitem;
-    }
-}
-
-// so we know what summarizer we were using
-const char *MetricsSummarizer::getKey() {
-    const char *value = mKey;
-    if (value == NULL) {
-        value = "unknown";
-    }
-    return value;
-}
-
-// should the record be given to this summarizer
-bool MetricsSummarizer::isMine(MediaAnalyticsItem &item)
-{
-    if (mKey == NULL)
-        return true;
-    std::string itemKey = item.getKey();
-    if (strcmp(mKey, itemKey.c_str()) != 0) {
-        return false;
-    }
-    return true;
-}
-
-std::string MetricsSummarizer::dumpSummary(int &slot)
-{
-    return dumpSummary(slot, NULL);
-}
-
-std::string MetricsSummarizer::dumpSummary(int &slot, const char *only)
-{
-    std::string value;
-
-    List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
-    if (it != mSummaries->end()) {
-        char buf[16];   // enough for "#####: "
-        for (; it != mSummaries->end(); it++) {
-            if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
-                continue;
-            }
-            std::string entry = (*it)->toString();
-            snprintf(buf, sizeof(buf), "%5d: ", slot);
-            value.append(buf);
-            value.append(entry.c_str());
-            value.append("\n");
-            slot++;
-        }
-    }
-    return value;
-}
-
-void MetricsSummarizer::setIgnorables(const char **ignorables) {
-    mIgnorables = ignorables;
-}
-
-const char **MetricsSummarizer::getIgnorables() {
-    return mIgnorables;
-}
-
-void MetricsSummarizer::handleRecord(MediaAnalyticsItem *item) {
-
-    ALOGV("MetricsSummarizer::handleRecord() for %s",
-                item == NULL ? "<nothing>" : item->toString().c_str());
-
-    if (item == NULL) {
-        return;
-    }
-
-    List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
-    for (; it != mSummaries->end(); it++) {
-        bool good = sameAttributes((*it), item, getIgnorables());
-        ALOGV("Match against %s says %d", (*it)->toString().c_str(), good);
-        if (good)
-            break;
-    }
-    if (it == mSummaries->end()) {
-            ALOGV("save new record");
-            MediaAnalyticsItem *nitem = item->dup();
-            if (nitem == NULL) {
-                ALOGE("unable to save MediaMetrics record");
-            }
-            sortProps(nitem);
-            nitem->setInt32("aggregated",1);
-            mergeRecord(*nitem, *item);
-            mSummaries->push_back(nitem);
-    } else {
-            ALOGV("increment existing record");
-            (*it)->addInt32("aggregated",1);
-            mergeRecord(*(*it), *item);
-    }
-}
-
-void MetricsSummarizer::mergeRecord(MediaAnalyticsItem &/*have*/, MediaAnalyticsItem &/*item*/) {
-    // default is no further massaging.
-    ALOGV("MetricsSummarizer::mergeRecord() [default]");
-    return;
-}
-
-// keep some stats for things: sums, counts, standard deviation
-// the integer version -- all of these pieces are in 64 bits
-void MetricsSummarizer::minMaxVar64(MediaAnalyticsItem &summation, const char *key, int64_t value) {
-    if (key == NULL)
-        return;
-    int len = strlen(key) + 32;
-    char *tmpKey = (char *)malloc(len);
-
-    if (tmpKey == NULL) {
-        return;
-    }
-
-    // N - count of samples
-    snprintf(tmpKey, len, "%s.n", key);
-    summation.addInt64(tmpKey, 1);
-
-    // zero - count of samples that are zero
-    if (value == 0) {
-        snprintf(tmpKey, len, "%s.zero", key);
-        int64_t zero = 0;
-        (void) summation.getInt64(tmpKey,&zero);
-        zero++;
-        summation.setInt64(tmpKey, zero);
-    }
-
-    // min
-    snprintf(tmpKey, len, "%s.min", key);
-    int64_t min = value;
-    if (summation.getInt64(tmpKey,&min)) {
-        if (min > value) {
-            summation.setInt64(tmpKey, value);
-        }
-    } else {
-        summation.setInt64(tmpKey, value);
-    }
-
-    // max
-    snprintf(tmpKey, len, "%s.max", key);
-    int64_t max = value;
-    if (summation.getInt64(tmpKey,&max)) {
-        if (max < value) {
-            summation.setInt64(tmpKey, value);
-        }
-    } else {
-        summation.setInt64(tmpKey, value);
-    }
-
-    // components for mean, stddev;
-    // stddev = sqrt(1/4*(sumx2 - (2*sumx*sumx/n) + ((sumx/n)^2)))
-    // sum x
-    snprintf(tmpKey, len, "%s.sumX", key);
-    summation.addInt64(tmpKey, value);
-    // sum x^2
-    snprintf(tmpKey, len, "%s.sumX2", key);
-    summation.addInt64(tmpKey, value*value);
-
-
-    // last thing we do -- remove the base key from the summation
-    // record so we won't get confused about it having both individual
-    // and summary information in there.
-    summation.removeProp(key);
-
-    free(tmpKey);
-}
-
-
-//
-// Comparators
-//
-
-// testing that all of 'single' is in 'summ'
-// and that the values match.
-// 'summ' may have extra fields.
-// 'ignorable' is a set of things that we don't worry about matching up
-// (usually time- or count-based values we'll sum elsewhere)
-bool MetricsSummarizer::sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
-
-    if (single == NULL || summ == NULL) {
-        return false;
-    }
-    ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
-    ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
-
-    // keep different sources/users separate
-    if (single->mUid != summ->mUid) {
-        return false;
-    }
-
-    // this can be made better.
-    for(size_t i=0;i<single->mPropCount;i++) {
-        MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
-        const char *attrName = prop1->mName;
-
-        // is it something we should ignore
-        if (ignorable != NULL) {
-            const char **ig = ignorable;
-            for (;*ig; ig++) {
-                if (strcmp(*ig, attrName) == 0) {
-                    break;
-                }
-            }
-            if (*ig) {
-                ALOGV("we don't mind that it has attr '%s'", attrName);
-                continue;
-            }
-        }
-
-        MediaAnalyticsItem::Prop *prop2 = summ->findProp(attrName);
-        if (prop2 == NULL) {
-            ALOGV("summ doesn't have this attr");
-            return false;
-        }
-        if (prop1->mType != prop2->mType) {
-            ALOGV("mismatched attr types");
-            return false;
-        }
-        switch (prop1->mType) {
-            case MediaAnalyticsItem::kTypeInt32:
-                if (prop1->u.int32Value != prop2->u.int32Value) {
-                    ALOGV("mismatch values");
-                    return false;
-                }
-                break;
-            case MediaAnalyticsItem::kTypeInt64:
-                if (prop1->u.int64Value != prop2->u.int64Value) {
-                    ALOGV("mismatch values");
-                    return false;
-                }
-                break;
-            case MediaAnalyticsItem::kTypeDouble:
-                // XXX: watch out for floating point comparisons!
-                if (prop1->u.doubleValue != prop2->u.doubleValue) {
-                    ALOGV("mismatch values");
-                    return false;
-                }
-                break;
-            case MediaAnalyticsItem::kTypeCString:
-                if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0) {
-                    ALOGV("mismatch values");
-                    return false;
-                }
-                break;
-            case MediaAnalyticsItem::kTypeRate:
-                if (prop1->u.rate.count != prop2->u.rate.count) {
-                    ALOGV("mismatch values");
-                    return false;
-                }
-                if (prop1->u.rate.duration != prop2->u.rate.duration) {
-                    ALOGV("mismatch values");
-                    return false;
-                }
-                break;
-            default:
-                ALOGV("mismatch values in default type");
-                return false;
-        }
-    }
-
-    return true;
-}
-
-
-int MetricsSummarizer::PropSorter(const void *a, const void *b) {
-    MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
-    MediaAnalyticsItem::Prop *bi = (MediaAnalyticsItem::Prop *)b;
-    return strcmp(ai->mName, bi->mName);
-}
-
-// we sort in the summaries so that it looks pretty in the dumpsys
-void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
-    if (item->mPropCount != 0) {
-        qsort(item->mProps, item->mPropCount,
-              sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
-    }
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizer.h b/services/mediaanalytics/MetricsSummarizer.h
deleted file mode 100644
index a16c7bc..0000000
--- a/services/mediaanalytics/MetricsSummarizer.h
+++ /dev/null
@@ -1,81 +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.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZER_H
-#define ANDROID_METRICSSUMMARIZER_H
-
-#include <string>
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-
-namespace android {
-
-class MetricsSummarizer
-{
-
- public:
-
-    MetricsSummarizer(const char *key);
-    virtual ~MetricsSummarizer();
-
-    // show the key
-    const char * getKey();
-
-    // should the record be given to this summarizer
-    bool isMine(MediaAnalyticsItem &item);
-
-    // hand the record to this summarizer
-    void handleRecord(MediaAnalyticsItem *item);
-
-    virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
-
-    // dump the summarized records (for dumpsys)
-    std::string dumpSummary(int &slot);
-    std::string dumpSummary(int &slot, const char *only);
-
-    void setIgnorables(const char **);
-    const char **getIgnorables();
-
- protected:
-
-    // various comparators
-    // "do these records have same attributes and values in those attrs"
-    bool sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
-
-    void minMaxVar64(MediaAnalyticsItem &summ, const char *key, int64_t value);
-
-    static int PropSorter(const void *a, const void *b);
-    void sortProps(MediaAnalyticsItem *item);
-
- private:
-    const char *mKey;
-    const char **mIgnorables;
-    List<MediaAnalyticsItem *> *mSummaries;
-
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZER_H
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.cpp b/services/mediaanalytics/MetricsSummarizerCodec.cpp
deleted file mode 100644
index 6af3c9a..0000000
--- a/services/mediaanalytics/MetricsSummarizerCodec.cpp
+++ /dev/null
@@ -1,45 +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.
- */
-
-#define LOG_TAG "MetricsSummarizerCodec"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerCodec.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerCodec::MetricsSummarizerCodec(const char *key)
-    : MetricsSummarizer(key)
-{
-    ALOGV("MetricsSummarizerCodec::MetricsSummarizerCodec");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.h b/services/mediaanalytics/MetricsSummarizerCodec.h
deleted file mode 100644
index c01196f..0000000
--- a/services/mediaanalytics/MetricsSummarizerCodec.h
+++ /dev/null
@@ -1,47 +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.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZERCODEC_H
-#define ANDROID_METRICSSUMMARIZERCODEC_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerCodec : public MetricsSummarizer
-{
-
- public:
-
-    MetricsSummarizerCodec(const char *key);
-    virtual ~MetricsSummarizerCodec() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERCODEC_H
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.cpp b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
deleted file mode 100644
index 190f87d..0000000
--- a/services/mediaanalytics/MetricsSummarizerExtractor.cpp
+++ /dev/null
@@ -1,42 +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.
- */
-
-#define LOG_TAG "MetricsSummarizerExtractor"
-#include <utils/Log.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerExtractor.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerExtractor::MetricsSummarizerExtractor(const char *key)
-    : MetricsSummarizer(key)
-{
-    ALOGV("MetricsSummarizerExtractor::MetricsSummarizerExtractor");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.h b/services/mediaanalytics/MetricsSummarizerExtractor.h
deleted file mode 100644
index eee052b..0000000
--- a/services/mediaanalytics/MetricsSummarizerExtractor.h
+++ /dev/null
@@ -1,47 +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.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZEREXTRACTOR_H
-#define ANDROID_METRICSSUMMARIZEREXTRACTOR_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerExtractor : public MetricsSummarizer
-{
-
- public:
-
-    MetricsSummarizerExtractor(const char *key);
-    virtual ~MetricsSummarizerExtractor() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZEREXTRACTOR_H
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.cpp b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
deleted file mode 100644
index f882cb9..0000000
--- a/services/mediaanalytics/MetricsSummarizerPlayer.cpp
+++ /dev/null
@@ -1,93 +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.
- */
-
-#define LOG_TAG "MetricsSummarizerPlayer"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerPlayer.h"
-
-
-
-
-namespace android {
-
-static const char *player_ignorable[] = {
-    "android.media.mediaplayer.durationMs",
-    "android.media.mediaplayer.playingMs",
-    "android.media.mediaplayer.frames",
-    "android.media.mediaplayer.dropped",
-    0
-};
-
-MetricsSummarizerPlayer::MetricsSummarizerPlayer(const char *key)
-    : MetricsSummarizer(key)
-{
-    ALOGV("MetricsSummarizerPlayer::MetricsSummarizerPlayer");
-    setIgnorables(player_ignorable);
-}
-
-// NB: this is also called for the first time -- so summation == item
-// Not sure if we need a flag for that or not.
-// In this particular mergeRecord() code -- we're' ok for this.
-void MetricsSummarizerPlayer::mergeRecord(MediaAnalyticsItem &summation, MediaAnalyticsItem &item) {
-
-    ALOGV("MetricsSummarizerPlayer::mergeRecord()");
-
-
-    int64_t duration = 0;
-    if (item.getInt64("android.media.mediaplayer.durationMs", &duration)) {
-        ALOGV("found durationMs of %" PRId64, duration);
-        minMaxVar64(summation, "android.media.mediaplayer.durationMs", duration);
-    }
-
-    int64_t playing = 0;
-    if (item.getInt64("android.media.mediaplayer.playingMs", &playing)) {
-        ALOGV("found playingMs of %" PRId64, playing);
-    }
-    if (playing >= 0) {
-        minMaxVar64(summation,"android.media.mediaplayer.playingMs",playing);
-    }
-
-    int64_t frames = 0;
-    if (item.getInt64("android.media.mediaplayer.frames", &frames)) {
-        ALOGV("found framess of %" PRId64, frames);
-    }
-    if (frames >= 0) {
-        minMaxVar64(summation,"android.media.mediaplayer.frames",frames);
-    }
-
-    int64_t dropped = 0;
-    if (item.getInt64("android.media.mediaplayer.dropped", &dropped)) {
-        ALOGV("found dropped of %" PRId64, dropped);
-    }
-    if (dropped >= 0) {
-        minMaxVar64(summation,"android.media.mediaplayer.dropped",dropped);
-    }
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.h b/services/mediaanalytics/MetricsSummarizerPlayer.h
deleted file mode 100644
index ad1bf74..0000000
--- a/services/mediaanalytics/MetricsSummarizerPlayer.h
+++ /dev/null
@@ -1,49 +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.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZERPLAYER_H
-#define ANDROID_METRICSSUMMARIZERPLAYER_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerPlayer : public MetricsSummarizer
-{
-
- public:
-
-    MetricsSummarizerPlayer(const char *key);
-    virtual ~MetricsSummarizerPlayer() {};
-
-    virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERPLAYER_H
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.cpp b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
deleted file mode 100644
index c2919c3..0000000
--- a/services/mediaanalytics/MetricsSummarizerRecorder.cpp
+++ /dev/null
@@ -1,45 +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.
- */
-
-#define LOG_TAG "MetricsSummarizerRecorder"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerRecorder.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerRecorder::MetricsSummarizerRecorder(const char *key)
-    : MetricsSummarizer(key)
-{
-    ALOGV("MetricsSummarizerRecorder::MetricsSummarizerRecorder");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.h b/services/mediaanalytics/MetricsSummarizerRecorder.h
deleted file mode 100644
index 963baab..0000000
--- a/services/mediaanalytics/MetricsSummarizerRecorder.h
+++ /dev/null
@@ -1,47 +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.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZERRECORDER_H
-#define ANDROID_METRICSSUMMARIZERRECORDER_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerRecorder : public MetricsSummarizer
-{
-
- public:
-
-    MetricsSummarizerRecorder(const char *key);
-    virtual ~MetricsSummarizerRecorder() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERRECORDER_H
diff --git a/services/mediadrm/Android.mk b/services/mediadrm/Android.mk
index 2daa829..e870965 100644
--- a/services/mediadrm/Android.mk
+++ b/services/mediadrm/Android.mk
@@ -28,7 +28,8 @@
     libhidlbase \
     libhidlmemory \
     libhidltransport \
-    android.hardware.drm@1.0
+    android.hardware.drm@1.0 \
+    android.hardware.drm@1.1
 
 LOCAL_CFLAGS += -Wall -Wextra -Werror