Merge "Use MediaRouteButton in MediaControlView2Impl"
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/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/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..c6b3f4d 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 {
@@ -287,51 +286,10 @@
virtual ~MetaData();
private:
- struct 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 Rect {
- int32_t mLeft, mTop, mRight, mBottom;
- };
-
- KeyedVector<uint32_t, typed_data> mItems;
-
- // MetaData &operator=(const MetaData &);
+ 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/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
new file mode 100644
index 0000000..1a27056
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -0,0 +1,461 @@
+/*
+ * 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() {
+ 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();
+ });
+ }
+
+ @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);
+ }
+ 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) {
+ 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();
+ }
+ }
+ }
+
+ private 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;
+ }
+
+ 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));
+ }
+ }
+
+ // 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/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
new file mode 100644
index 0000000..09d7adc
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -0,0 +1,424 @@
+/*
+ * 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 SessionCallback mCallback;
+ 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());
+ mCallback = callback;
+ mSessionStub = new MediaSession2Stub(this);
+ // 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;
+ }
+
+ SessionCallback getCallback() {
+ return mCallback;
+ }
+
+ 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;
+ }
+ }
+}
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..bae02e5
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -0,0 +1,309 @@
+/*
+ * 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.MediaSession2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+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.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;
+
+// TODO(jaewan): Add a hook for media apps to log which app requested specific command.
+// TODO(jaewan): Add a way to block specific command from a specific app. Also add supported
+// command per apps.
+public class MediaSession2Stub extends IMediaSession2.Stub {
+ private static final String TAG = "MediaSession2Stub";
+ private static final boolean DEBUG = true; // TODO(jaewan): Rename.
+
+ private final Object mLock = new Object();
+ private final CommandHandler mCommandHandler;
+ private final WeakReference<MediaSession2Impl> mSession;
+ private final Context mContext;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
+
+ public MediaSession2Stub(MediaSession2Impl session) {
+ mSession = new WeakReference<>(session);
+ mContext = session.getContext();
+ mCommandHandler = new CommandHandler(session.getHandler().getLooper());
+ }
+
+ 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);
+ }
+
+ @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) controllerInfo.getProvider()).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 impl =
+ ((ControllerInfoImpl) controllerInfo.getProvider());
+ impl.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) controllerInfo.getProvider()).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) list.get(i).getProvider())
+ .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?
+ }
+ }
+ }
+
+ private class CommandHandler extends Handler {
+ public static final int MSG_CONNECT = 1000;
+ public static final int MSG_COMMAND = 1001;
+
+ 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 = session.getCallback().onConnect(request);
+ // Don't reject connection for the request from trusted app.
+ // Otherwise server will fail to retrieve session's information to dispatch
+ // media keys to.
+ boolean accept = allowedCommands != null || request.isTrusted();
+ ControllerInfoImpl impl = (ControllerInfoImpl) request.getProvider();
+ if (accept) {
+ synchronized (mLock) {
+ mControllers.put(impl.getId(), request);
+ }
+ }
+ 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 = session.getCallback().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;
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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..6a4760d
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -0,0 +1,175 @@
+/*
+ * 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 static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID;
+import static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_ID;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+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;
+
+// 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 NotificationChannel mDefaultNotificationChannel;
+ private MediaSession2 mSession;
+
+ public MediaSessionService2Impl(MediaSessionService2 instance) {
+ if (DEBUG) {
+ Log.d(TAG, "MediaSessionService2Impl(" + instance + ")");
+ }
+ mInstance = instance;
+ }
+
+ @Override
+ public MediaSession2 getSession_impl() {
+ synchronized (mLock) {
+ return mSession;
+ }
+ }
+
+ @Override
+ public MediaNotification onUpdateNotification_impl(PlaybackState state) {
+ return createDefaultNotification(state);
+ }
+
+ // TODO(jaewan): Remove this for framework release.
+ private MediaNotification createDefaultNotification(PlaybackState state) {
+ // TODO(jaewan): Place better notification here.
+ if (mDefaultNotificationChannel == null) {
+ mDefaultNotificationChannel = new NotificationChannel(
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
+ }
+ Notification notification = new Notification.Builder(
+ mInstance, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(mInstance.getPackageName())
+ .setContentText("Playback state: " + state.getState())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
+ }
+
+
+ @Override
+ public void onCreate_impl() {
+ mNotificationManager = (NotificationManager) mInstance.getSystemService(
+ NOTIFICATION_SERVICE);
+ mStartSelfIntent = new Intent(mInstance, mInstance.getClass());
+
+ Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+ serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+ 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"
+ + MediaSessionService2.SERVICE_INTERFACE);
+ } 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);
+ }
+ }
+
+ 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) {
+ mediaNotification = createDefaultNotification(state);
+ }
+ 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..cfef77f 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -16,9 +16,21 @@
package com.android.media.update;
+import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.media.MediaController2;
+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.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 +39,14 @@
import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import com.android.media.MediaController2Impl;
+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 +55,33 @@
}
@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 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 MediaControlView2Provider createMediaControlView2(
MediaControlView2 instance, ViewProvider superProvider) {
return new MediaControlView2Impl(instance, superProvider);
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index f60e0cb..a814d5e 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -231,6 +231,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();
}
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..57534f1 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,18 @@
// Protected or private methods
///////////////////////////////////////////////////
+ private void attachMediaControlView() {
+ // TODO: change this so that the CC button appears only where there is a subtitle track.
+ // mMediaControlView.showCCButton();
+
+ // Get MediaController from MediaSession and set it inside MediaControlView
+ mMediaControlView.setController(mMediaSession.getController());
+
+ 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 +673,6 @@
mAudioManager.abandonAudioFocus(null);
}
}
- mSurfaceWidth = 0;
- mSurfaceHeight = 0;
mVideoWidth = 0;
mVideoHeight = 0;
}
@@ -797,8 +808,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 +827,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..fe16583
--- /dev/null
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?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.session.MediaSessionService2" />
+ </intent-filter>
+ <meta-data android:name="android.media.session" android:value="TestSession" />
+ </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/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
new file mode 100644
index 0000000..38d34cc
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -0,0 +1,452 @@
+/*
+ * 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";
+
+ private MediaSession2 mSession;
+ private MediaController2Wrapper mController;
+ private 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();
+ });
+ MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+ assertNotNull(controller);
+ controller.waitForConnect(false);
+ controller.waitForDisconnect(true);
+ }
+
+ @Test
+ public void testControllerCallback_releaseSession() throws InterruptedException {
+ sHandler.postAndSync(() -> {
+ mSession.setPlayer(null);
+ });
+ mController.waitForDisconnect(true);
+ }
+
+ @Test
+ public void testControllerCallback_release() throws InterruptedException {
+ mController.release();
+ mController.waitForDisconnect(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();
+ }
+
+ @Ignore
+ @Test
+ public void testConnectToService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+
+ 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);
+ }
+
+ @Ignore
+ @Test
+ public void testRelease_sessionService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ 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(MockMediaSessionService2.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);
+ });
+ mController.waitForDisconnect(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..f7224ea
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -0,0 +1,266 @@
+/*
+ * 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();
+ });
+ MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+ assertNotNull(controller);
+ controller.waitForConnect(false);
+ controller.waitForDisconnect(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..ab842c4
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -0,0 +1,148 @@
+/*
+ * 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.MediaSession2.CommandGroup;
+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<>();
+
+ @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();
+ }
+ }
+
+ MediaController2Wrapper createController(SessionToken token) throws InterruptedException {
+ return createController(token, true, null);
+ }
+
+ MediaController2Wrapper createController(@NonNull SessionToken token, boolean waitForConnect,
+ @Nullable TestControllerCallback callback)
+ throws InterruptedException {
+ if (callback == null) {
+ callback = new TestControllerCallback();
+ }
+ MediaController2Wrapper controller = new MediaController2Wrapper(mContext, token, callback);
+ mControllers.add(controller);
+ if (waitForConnect) {
+ controller.waitForConnect(true);
+ }
+ return controller;
+ }
+
+ public static class TestControllerCallback extends MediaController2.ControllerCallback {
+ public final CountDownLatch connectLatch = new CountDownLatch(1);
+ public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+
+ @CallSuper
+ @Override
+ public void onConnected(CommandGroup commands) {
+ super.onConnected(commands);
+ connectLatch.countDown();
+ }
+
+ @CallSuper
+ @Override
+ public void onDisconnected() {
+ super.onDisconnected();
+ disconnectLatch.countDown();
+ }
+ }
+
+ public class MediaController2Wrapper extends MediaController2 {
+ private final TestControllerCallback mCallback;
+
+ public MediaController2Wrapper(@NonNull Context context, @NonNull SessionToken token,
+ @NonNull TestControllerCallback callback) {
+ super(context, token, callback, sHandlerExecutor);
+ mCallback = callback;
+ }
+
+ public void waitForConnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(mCallback.connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(mCallback.connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ public void waitForDisconnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(mCallback.disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(mCallback.disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+}
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..0ee37b1
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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;
+ 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;
+ }
+ }
+ assertTrue(foundTestSessionService);
+ }
+
+ @Test
+ public void testGetAllSessionTokens() throws InterruptedException {
+ boolean foundTestSession = false;
+ boolean foundTestSessionService = 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;
+ default:
+ fail("Unexpected session " + token + " exists in the package");
+ }
+ }
+ assertTrue(foundTestSession);
+ assertTrue(foundTestSessionService);
+ }
+
+ // 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/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
new file mode 100644
index 0000000..e4a7485
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -0,0 +1,72 @@
+/*
+ * 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.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.TestUtils.SyncHandler;
+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";
+ public MediaSession2 mSession;
+
+ @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();
+ }
+
+ @Override
+ public void onDestroy() {
+ TestServiceRegistry.getInstance().cleanUp();
+ super.onDestroy();
+ }
+
+ 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..0cca12c
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/TestUtils.java
@@ -0,0 +1,86 @@
+/*
+ * 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.Handler;
+
+import android.os.Looper;
+
+import java.util.List;
+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();
+ }
+
+ 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;
+ }
+
+ /**
+ * 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/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