/* * Copyright 2013, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaDrm-JNI" #include #include "android_media_MediaDrm.h" #include "android_media_MediaMetricsJNI.h" #include "android_os_Parcel.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" #include "android_os_Parcel.h" #include "jni.h" #include #include #include #include #include #include #include #include #include #include #include #include using ::android::os::PersistableBundle; namespace drm = ::android::hardware::drm; namespace android { #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ LOG_FATAL_IF(! (var), "Unable to find class %s", className); #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! (var), "Unable to find field %s", fieldName); #define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! (var), "Unable to find method %s", fieldName); #define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! (var), "Unable to find field %s", fieldName); #define GET_STATIC_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetStaticMethodID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! (var), "Unable to find static method %s", fieldName); #define GET_STATIC_OBJECT_FIELD(var, clazz, fieldId) \ var = env->GetStaticObjectField(clazz, fieldId); \ LOG_FATAL_IF(! (var), "Unable to find static object field %p", fieldId); struct RequestFields { jfieldID data; jfieldID defaultUrl; jfieldID requestType; }; struct ArrayListFields { jmethodID init; jmethodID add; }; struct HashmapFields { jmethodID init; jmethodID get; jmethodID put; jmethodID entrySet; }; struct SetFields { jmethodID iterator; }; struct IteratorFields { jmethodID next; jmethodID hasNext; }; struct EntryFields { jmethodID getKey; jmethodID getValue; }; struct EventTypes { jint kEventProvisionRequired; jint kEventKeyRequired; jint kEventKeyExpired; jint kEventVendorDefined; jint kEventSessionReclaimed; } gEventTypes; struct EventWhat { jint kWhatDrmEvent; jint kWhatExpirationUpdate; jint kWhatKeyStatusChange; jint kWhatSessionLostState; } gEventWhat; struct KeyTypes { jint kKeyTypeStreaming; jint kKeyTypeOffline; jint kKeyTypeRelease; } gKeyTypes; struct KeyRequestTypes { jint kKeyRequestTypeInitial; jint kKeyRequestTypeRenewal; jint kKeyRequestTypeRelease; jint kKeyRequestTypeNone; jint kKeyRequestTypeUpdate; } gKeyRequestTypes; struct CertificateTypes { jint kCertificateTypeNone; jint kCertificateTypeX509; } gCertificateTypes; struct CertificateFields { jfieldID wrappedPrivateKey; jfieldID certificateData; }; struct StateExceptionFields { jmethodID init; jclass classId; }; struct SessionExceptionFields { jmethodID init; jclass classId; jfieldID errorCode; }; struct SessionExceptionErrorCodes { jint kErrorUnknown; jint kResourceContention; } gSessionExceptionErrorCodes; struct HDCPLevels { jint kHdcpLevelUnknown; jint kHdcpNone; jint kHdcpV1; jint kHdcpV2; jint kHdcpV2_1; jint kHdcpV2_2; jint kHdcpV2_3; jint kHdcpNoOutput; } gHdcpLevels; struct SecurityLevels { jint kSecurityLevelUnknown; jint kSecurityLevelMax; jint kSecurityLevelSwSecureCrypto; jint kSecurityLevelSwSecureDecode; jint kSecurityLevelHwSecureCrypto; jint kSecurityLevelHwSecureDecode; jint kSecurityLevelHwSecureAll; } gSecurityLevels; struct OfflineLicenseState { jint kOfflineLicenseStateUsable; jint kOfflineLicenseStateReleased; jint kOfflineLicenseStateUnknown; } gOfflineLicenseStates; struct KeyStatusFields { jmethodID init; jclass classId; }; struct LogMessageFields { jmethodID init; jclass classId; }; struct fields_t { jfieldID context; jmethodID post_event; RequestFields keyRequest; RequestFields provisionRequest; ArrayListFields arraylist; HashmapFields hashmap; SetFields set; IteratorFields iterator; EntryFields entry; CertificateFields certificate; StateExceptionFields stateException; SessionExceptionFields sessionException; jclass certificateClassId; jclass hashmapClassId; jclass arraylistClassId; jclass stringClassId; jobject bundleCreator; jmethodID createFromParcelId; jclass parcelCreatorClassId; KeyStatusFields keyStatus; LogMessageFields logMessage; }; static fields_t gFields; namespace { jbyteArray hidlVectorToJByteArray(const hardware::hidl_vec &vector) { JNIEnv *env = AndroidRuntime::getJNIEnv(); size_t length = vector.size(); jbyteArray result = env->NewByteArray(length); if (result != NULL) { env->SetByteArrayRegion(result, 0, length, reinterpret_cast(vector.data())); } return result; } jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector &logs) { jclass clazz = gFields.arraylistClassId; jobject arrayList = env->NewObject(clazz, gFields.arraylist.init); clazz = gFields.logMessage.classId; for (auto log: logs) { jobject jLog = env->NewObject(clazz, gFields.logMessage.init, static_cast(log.timeMs), static_cast(log.priority), env->NewStringUTF(log.message.c_str())); env->CallBooleanMethod(arrayList, gFields.arraylist.add, jLog); } return arrayList; } } // namespace anonymous // ---------------------------------------------------------------------------- // ref-counted object for callbacks class JNIDrmListener: public DrmListener { public: JNIDrmListener(JNIEnv* env, jobject thiz, jobject weak_thiz); ~JNIDrmListener(); virtual void notify(DrmPlugin::EventType eventType, int extra, const ListenerArgs *arg = NULL); private: JNIDrmListener(); jclass mClass; // Reference to MediaDrm class jobject mObject; // Weak ref to MediaDrm Java object to call on }; JNIDrmListener::JNIDrmListener(JNIEnv* env, jobject thiz, jobject weak_thiz) { // Hold onto the MediaDrm class for use in calling the static method // that posts events to the application thread. jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { ALOGE("Can't find android/media/MediaDrm"); jniThrowException(env, "java/lang/Exception", "Can't find android/media/MediaDrm"); return; } mClass = (jclass)env->NewGlobalRef(clazz); // We use a weak reference so the MediaDrm object can be garbage collected. // The reference is only used as a proxy for callbacks. mObject = env->NewGlobalRef(weak_thiz); } JNIDrmListener::~JNIDrmListener() { // remove global references JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mObject); env->DeleteGlobalRef(mClass); } void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra, const ListenerArgs *args) { jint jwhat; jint jeventType = 0; // translate DrmPlugin event types into their java equivalents switch (eventType) { case DrmPlugin::kDrmPluginEventProvisionRequired: jwhat = gEventWhat.kWhatDrmEvent; jeventType = gEventTypes.kEventProvisionRequired; break; case DrmPlugin::kDrmPluginEventKeyNeeded: jwhat = gEventWhat.kWhatDrmEvent; jeventType = gEventTypes.kEventKeyRequired; break; case DrmPlugin::kDrmPluginEventKeyExpired: jwhat = gEventWhat.kWhatDrmEvent; jeventType = gEventTypes.kEventKeyExpired; break; case DrmPlugin::kDrmPluginEventVendorDefined: jwhat = gEventWhat.kWhatDrmEvent; jeventType = gEventTypes.kEventVendorDefined; break; case DrmPlugin::kDrmPluginEventSessionReclaimed: jwhat = gEventWhat.kWhatDrmEvent; jeventType = gEventTypes.kEventSessionReclaimed; break; case DrmPlugin::kDrmPluginEventExpirationUpdate: jwhat = gEventWhat.kWhatExpirationUpdate; break; case DrmPlugin::kDrmPluginEventKeysChange: jwhat = gEventWhat.kWhatKeyStatusChange; break; case DrmPlugin::kDrmPluginEventSessionLostState: jwhat = gEventWhat.kWhatSessionLostState; break; default: ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType); return; } JNIEnv *env = AndroidRuntime::getJNIEnv(); if (args) { env->CallStaticVoidMethod(mClass, gFields.post_event, mObject, jwhat, jeventType, extra, args->jSessionId, args->jData, args->jExpirationTime, args->jKeyStatusList, args->jHasNewUsableKey); } if (env->ExceptionCheck()) { ALOGW("An exception occurred while notifying an event."); LOGW_EX(env); env->ExceptionClear(); } } jint MediaErrorToJavaError(status_t err) { #define STATUS_CASE(status) \ case status: \ return J##status switch (err) { STATUS_CASE(ERROR_DRM_UNKNOWN); STATUS_CASE(ERROR_DRM_NO_LICENSE); STATUS_CASE(ERROR_DRM_LICENSE_EXPIRED); STATUS_CASE(ERROR_DRM_RESOURCE_BUSY); STATUS_CASE(ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION); STATUS_CASE(ERROR_DRM_SESSION_NOT_OPENED); STATUS_CASE(ERROR_DRM_CANNOT_HANDLE); STATUS_CASE(ERROR_DRM_INSUFFICIENT_SECURITY); STATUS_CASE(ERROR_DRM_FRAME_TOO_LARGE); STATUS_CASE(ERROR_DRM_SESSION_LOST_STATE); STATUS_CASE(ERROR_DRM_CERTIFICATE_MALFORMED); STATUS_CASE(ERROR_DRM_CERTIFICATE_MISSING); STATUS_CASE(ERROR_DRM_CRYPTO_LIBRARY); STATUS_CASE(ERROR_DRM_GENERIC_OEM); STATUS_CASE(ERROR_DRM_GENERIC_PLUGIN); STATUS_CASE(ERROR_DRM_INIT_DATA); STATUS_CASE(ERROR_DRM_KEY_NOT_LOADED); STATUS_CASE(ERROR_DRM_LICENSE_PARSE); STATUS_CASE(ERROR_DRM_LICENSE_POLICY); STATUS_CASE(ERROR_DRM_LICENSE_RELEASE); STATUS_CASE(ERROR_DRM_LICENSE_REQUEST_REJECTED); STATUS_CASE(ERROR_DRM_LICENSE_RESTORE); STATUS_CASE(ERROR_DRM_LICENSE_STATE); STATUS_CASE(ERROR_DRM_MEDIA_FRAMEWORK); STATUS_CASE(ERROR_DRM_PROVISIONING_CERTIFICATE); STATUS_CASE(ERROR_DRM_PROVISIONING_CONFIG); STATUS_CASE(ERROR_DRM_PROVISIONING_PARSE); STATUS_CASE(ERROR_DRM_PROVISIONING_REQUEST_REJECTED); STATUS_CASE(ERROR_DRM_PROVISIONING_RETRY); STATUS_CASE(ERROR_DRM_RESOURCE_CONTENTION); STATUS_CASE(ERROR_DRM_SECURE_STOP_RELEASE); STATUS_CASE(ERROR_DRM_STORAGE_READ); STATUS_CASE(ERROR_DRM_STORAGE_WRITE); STATUS_CASE(ERROR_DRM_ZERO_SUBSAMPLES); #undef STATUS_CASE } return static_cast(err); } static void throwStateException(JNIEnv *env, const char *msg, status_t err) { ALOGE("Illegal state exception: %s (%d)", msg, err); jint jerr = MediaErrorToJavaError(err); jobject exception = env->NewObject(gFields.stateException.classId, gFields.stateException.init, static_cast(jerr), env->NewStringUTF(msg)); env->Throw(static_cast(exception)); } static void throwSessionException(JNIEnv *env, const char *msg, status_t err) { ALOGE("Session exception: %s (%d)", msg, err); jint jErrorCode = 0; switch(err) { case ERROR_DRM_RESOURCE_CONTENTION: jErrorCode = gSessionExceptionErrorCodes.kResourceContention; break; default: break; } jobject exception = env->NewObject(gFields.sessionException.classId, gFields.sessionException.init, static_cast(err), env->NewStringUTF(msg)); env->SetIntField(exception, gFields.sessionException.errorCode, jErrorCode); env->Throw(static_cast(exception)); } static bool isSessionException(status_t err) { return err == ERROR_DRM_RESOURCE_CONTENTION; } static bool throwExceptionAsNecessary( JNIEnv *env, const sp &drm, status_t err, const char *msg = NULL) { std::string msgStr; if (drm != NULL && err != OK) { msgStr = DrmUtils::GetExceptionMessage(err, msg, drm); msg = msgStr.c_str(); } if (err == BAD_VALUE || err == ERROR_DRM_CANNOT_HANDLE) { jniThrowException(env, "java/lang/IllegalArgumentException", msg); return true; } else if (err == ERROR_UNSUPPORTED) { jniThrowException(env, "java/lang/UnsupportedOperationException", msg); return true; } else if (err == ERROR_DRM_NOT_PROVISIONED) { jniThrowException(env, "android/media/NotProvisionedException", msg); return true; } else if (err == ERROR_DRM_RESOURCE_BUSY) { jniThrowException(env, "android/media/ResourceBusyException", msg); return true; } else if (err == ERROR_DRM_DEVICE_REVOKED) { jniThrowException(env, "android/media/DeniedByServerException", msg); return true; } else if (err == DEAD_OBJECT) { jniThrowException(env, "android/media/MediaDrmResetException", msg); return true; } else if (isSessionException(err)) { throwSessionException(env, msg, err); return true; } else if (err != OK) { throwStateException(env, msg, err); return true; } return false; } static sp GetDrm(JNIEnv *env, jobject thiz) { JDrm *jdrm = (JDrm *)env->GetLongField(thiz, gFields.context); return jdrm ? jdrm->getDrm() : NULL; } JDrm::JDrm( JNIEnv *env, jobject thiz, const uint8_t uuid[16], const String8 &appPackageName) { mObject = env->NewWeakGlobalRef(thiz); mDrm = MakeDrm(uuid, appPackageName); if (mDrm != NULL) { mDrm->setListener(this); } } JDrm::~JDrm() { JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mObject); mObject = NULL; } // static sp JDrm::MakeDrm() { return DrmUtils::MakeDrm(); } // static sp JDrm::MakeDrm(const uint8_t uuid[16], const String8 &appPackageName) { sp drm = MakeDrm(); if (drm == NULL) { return NULL; } status_t err = drm->createPlugin(uuid, appPackageName); if (err != OK) { return NULL; } return drm; } status_t JDrm::setListener(const sp& listener) { Mutex::Autolock lock(mLock); mListener = listener; return OK; } void JDrm::notify(DrmPlugin::EventType eventType, int extra, const ListenerArgs *args) { sp listener; mLock.lock(); listener = mListener; mLock.unlock(); if (listener != NULL) { Mutex::Autolock lock(mNotifyLock); listener->notify(eventType, extra, args); } } void JDrm::sendEvent( DrmPlugin::EventType eventType, const hardware::hidl_vec &sessionId, const hardware::hidl_vec &data) { ListenerArgs args{ .jSessionId = hidlVectorToJByteArray(sessionId), .jData = hidlVectorToJByteArray(data), }; notify(eventType, 0, &args); } void JDrm::sendExpirationUpdate( const hardware::hidl_vec &sessionId, int64_t expiryTimeInMS) { ListenerArgs args{ .jSessionId = hidlVectorToJByteArray(sessionId), .jExpirationTime = expiryTimeInMS, }; notify(DrmPlugin::kDrmPluginEventExpirationUpdate, 0, &args); } void JDrm::sendKeysChange( const hardware::hidl_vec &sessionId, const std::vector &keyStatusList, bool hasNewUsableKey) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass clazz = gFields.arraylistClassId; jobject arrayList = env->NewObject(clazz, gFields.arraylist.init); clazz = gFields.keyStatus.classId; for (const auto &keyStatus : keyStatusList) { jbyteArray jKeyId(hidlVectorToJByteArray(keyStatus.keyId)); jint jStatusCode(keyStatus.type); jobject jKeyStatus = env->NewObject(clazz, gFields.keyStatus.init, jKeyId, jStatusCode); env->CallBooleanMethod(arrayList, gFields.arraylist.add, jKeyStatus); } ListenerArgs args{ .jSessionId = hidlVectorToJByteArray(sessionId), .jKeyStatusList = arrayList, .jHasNewUsableKey = hasNewUsableKey, }; notify(DrmPlugin::kDrmPluginEventKeysChange, 0, &args); } void JDrm::sendSessionLostState( const hardware::hidl_vec &sessionId) { ListenerArgs args{ .jSessionId = hidlVectorToJByteArray(sessionId), }; notify(DrmPlugin::kDrmPluginEventSessionLostState, 0, &args); } void JDrm::disconnect() { if (mDrm != NULL) { mDrm->destroyPlugin(); mDrm.clear(); } } // static status_t JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType, DrmPlugin::SecurityLevel securityLevel, bool *isSupported) { sp drm = MakeDrm(); if (drm == NULL) { return BAD_VALUE; } return drm->isCryptoSchemeSupported(uuid, mimeType, securityLevel, isSupported); } status_t JDrm::initCheck() const { return mDrm == NULL ? NO_INIT : OK; } // JNI conversion utilities static Vector JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray) { Vector vector; size_t length = env->GetArrayLength(byteArray); vector.insertAt((size_t)0, length); env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray()); return vector; } static jbyteArray VectorToJByteArray(JNIEnv *env, Vector const &vector) { size_t length = vector.size(); jbyteArray result = env->NewByteArray(length); if (result != NULL) { env->SetByteArrayRegion(result, 0, length, (jbyte *)vector.array()); } return result; } static String8 JStringToString8(JNIEnv *env, jstring const &jstr) { String8 result; const char *s = env->GetStringUTFChars(jstr, NULL); if (s) { result = s; env->ReleaseStringUTFChars(jstr, s); } return result; } /* import java.util.HashMap; import java.util.Set; import java.Map.Entry; import jav.util.Iterator; HashMap hm; Set> s = hm.entrySet(); Iterator i = s.iterator(); Entry e = s.next(); */ static KeyedVector HashMapToKeyedVector( JNIEnv *env, jobject &hashMap, bool* pIsOK) { jclass clazz = gFields.stringClassId; KeyedVector keyedVector; *pIsOK = true; jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet); if (entrySet) { jobject iterator = env->CallObjectMethod(entrySet, gFields.set.iterator); if (iterator) { jboolean hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext); while (hasNext) { jobject entry = env->CallObjectMethod(iterator, gFields.iterator.next); if (entry) { jobject obj = env->CallObjectMethod(entry, gFields.entry.getKey); if (obj == NULL || !env->IsInstanceOf(obj, clazz)) { jniThrowException(env, "java/lang/IllegalArgumentException", "HashMap key is not a String"); env->DeleteLocalRef(entry); *pIsOK = false; break; } jstring jkey = static_cast(obj); obj = env->CallObjectMethod(entry, gFields.entry.getValue); if (obj == NULL || !env->IsInstanceOf(obj, clazz)) { jniThrowException(env, "java/lang/IllegalArgumentException", "HashMap value is not a String"); env->DeleteLocalRef(entry); *pIsOK = false; break; } jstring jvalue = static_cast(obj); String8 key = JStringToString8(env, jkey); String8 value = JStringToString8(env, jvalue); keyedVector.add(key, value); env->DeleteLocalRef(jkey); env->DeleteLocalRef(jvalue); hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext); } env->DeleteLocalRef(entry); } env->DeleteLocalRef(iterator); } env->DeleteLocalRef(entrySet); } return keyedVector; } static jobject KeyedVectorToHashMap (JNIEnv *env, KeyedVector const &map) { jclass clazz = gFields.hashmapClassId; jobject hashMap = env->NewObject(clazz, gFields.hashmap.init); for (size_t i = 0; i < map.size(); ++i) { jstring jkey = env->NewStringUTF(map.keyAt(i).string()); jstring jvalue = env->NewStringUTF(map.valueAt(i).string()); env->CallObjectMethod(hashMap, gFields.hashmap.put, jkey, jvalue); env->DeleteLocalRef(jkey); env->DeleteLocalRef(jvalue); } return hashMap; } static jobject ListOfVectorsToArrayListOfByteArray(JNIEnv *env, List> list) { jclass clazz = gFields.arraylistClassId; jobject arrayList = env->NewObject(clazz, gFields.arraylist.init); List>::iterator iter = list.begin(); while (iter != list.end()) { jbyteArray byteArray = VectorToJByteArray(env, *iter); env->CallBooleanMethod(arrayList, gFields.arraylist.add, byteArray); env->DeleteLocalRef(byteArray); iter++; } return arrayList; } } // namespace android using namespace android; static sp setDrm( JNIEnv *env, jobject thiz, const sp &drm) { sp old = (JDrm *)env->GetLongField(thiz, gFields.context); if (drm != NULL) { drm->incStrong(thiz); } if (old != NULL) { old->decStrong(thiz); } env->SetLongField(thiz, gFields.context, reinterpret_cast(drm.get())); return old; } static bool CheckDrm(JNIEnv *env, const sp &drm) { if (drm == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "MediaDrm obj is null"); return false; } return true; } static bool CheckSession(JNIEnv *env, const sp &drm, jbyteArray const &jsessionId) { if (!CheckDrm(env, drm)) { return false; } if (jsessionId == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "sessionId is null"); return false; } return true; } static void android_media_MediaDrm_native_release(JNIEnv *env, jobject thiz) { sp drm = setDrm(env, thiz, NULL); if (drm != NULL) { drm->setListener(NULL); drm->disconnect(); } } static void android_media_MediaDrm_native_init(JNIEnv *env) { jclass clazz; FIND_CLASS(clazz, "android/media/MediaDrm"); GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "J"); GET_STATIC_METHOD_ID(gFields.post_event, clazz, "postEventFromNative", "(Ljava/lang/Object;III[B[BJLjava/util/List;Z)V"); jfieldID field; GET_STATIC_FIELD_ID(field, clazz, "EVENT_PROVISION_REQUIRED", "I"); gEventTypes.kEventProvisionRequired = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "EVENT_KEY_REQUIRED", "I"); gEventTypes.kEventKeyRequired = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "EVENT_KEY_EXPIRED", "I"); gEventTypes.kEventKeyExpired = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "EVENT_VENDOR_DEFINED", "I"); gEventTypes.kEventVendorDefined = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "EVENT_SESSION_RECLAIMED", "I"); gEventTypes.kEventSessionReclaimed = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "DRM_EVENT", "I"); gEventWhat.kWhatDrmEvent = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "EXPIRATION_UPDATE", "I"); gEventWhat.kWhatExpirationUpdate = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "KEY_STATUS_CHANGE", "I"); gEventWhat.kWhatKeyStatusChange = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "SESSION_LOST_STATE", "I"); gEventWhat.kWhatSessionLostState = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I"); gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I"); gKeyTypes.kKeyTypeOffline = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I"); gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_NONE", "I"); gCertificateTypes.kCertificateTypeNone = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_X509", "I"); gCertificateTypes.kCertificateTypeX509 = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "HDCP_LEVEL_UNKNOWN", "I"); gHdcpLevels.kHdcpLevelUnknown = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "HDCP_NONE", "I"); gHdcpLevels.kHdcpNone = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "HDCP_V1", "I"); gHdcpLevels.kHdcpV1 = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "HDCP_V2", "I"); gHdcpLevels.kHdcpV2 = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "HDCP_V2_1", "I"); gHdcpLevels.kHdcpV2_1 = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "HDCP_V2_2", "I"); gHdcpLevels.kHdcpV2_2 = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "HDCP_V2_3", "I"); gHdcpLevels.kHdcpV2_3 = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "HDCP_NO_DIGITAL_OUTPUT", "I"); gHdcpLevels.kHdcpNoOutput = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_UNKNOWN", "I"); gSecurityLevels.kSecurityLevelUnknown = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_SW_SECURE_CRYPTO", "I"); gSecurityLevels.kSecurityLevelSwSecureCrypto = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_SW_SECURE_DECODE", "I"); gSecurityLevels.kSecurityLevelSwSecureDecode = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_CRYPTO", "I"); gSecurityLevels.kSecurityLevelHwSecureCrypto = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_DECODE", "I"); gSecurityLevels.kSecurityLevelHwSecureDecode = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_ALL", "I"); gSecurityLevels.kSecurityLevelHwSecureAll = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "OFFLINE_LICENSE_STATE_USABLE", "I"); gOfflineLicenseStates.kOfflineLicenseStateUsable = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "OFFLINE_LICENSE_STATE_RELEASED", "I"); gOfflineLicenseStates.kOfflineLicenseStateReleased = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "OFFLINE_LICENSE_STATE_UNKNOWN", "I"); gOfflineLicenseStates.kOfflineLicenseStateUnknown = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_CRYPTO", "I"); jmethodID getMaxSecurityLevel; GET_STATIC_METHOD_ID(getMaxSecurityLevel, clazz, "getMaxSecurityLevel", "()I"); gSecurityLevels.kSecurityLevelMax = env->CallStaticIntMethod(clazz, getMaxSecurityLevel); FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest"); GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B"); GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;"); GET_FIELD_ID(gFields.keyRequest.requestType, clazz, "mRequestType", "I"); GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_INITIAL", "I"); gKeyRequestTypes.kKeyRequestTypeInitial = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RENEWAL", "I"); gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I"); gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_NONE", "I"); gKeyRequestTypes.kKeyRequestTypeNone = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_UPDATE", "I"); gKeyRequestTypes.kKeyRequestTypeUpdate = env->GetStaticIntField(clazz, field); FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest"); GET_FIELD_ID(gFields.provisionRequest.data, clazz, "mData", "[B"); GET_FIELD_ID(gFields.provisionRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;"); FIND_CLASS(clazz, "android/media/MediaDrm$Certificate"); GET_FIELD_ID(gFields.certificate.wrappedPrivateKey, clazz, "mWrappedKey", "[B"); GET_FIELD_ID(gFields.certificate.certificateData, clazz, "mCertificateData", "[B"); gFields.certificateClassId = static_cast(env->NewGlobalRef(clazz)); // Metrics-related fields and classes. FIND_CLASS(clazz, "android/os/PersistableBundle"); jfieldID bundleCreatorId; GET_STATIC_FIELD_ID(bundleCreatorId, clazz, "CREATOR", "Landroid/os/Parcelable$Creator;"); jobject bundleCreator; GET_STATIC_OBJECT_FIELD(bundleCreator, clazz, bundleCreatorId); gFields.bundleCreator = static_cast(env->NewGlobalRef(bundleCreator)); FIND_CLASS(clazz, "android/os/Parcelable$Creator"); GET_METHOD_ID(gFields.createFromParcelId, clazz, "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;"); gFields.parcelCreatorClassId = static_cast(env->NewGlobalRef(clazz)); FIND_CLASS(clazz, "java/util/ArrayList"); GET_METHOD_ID(gFields.arraylist.init, clazz, "", "()V"); GET_METHOD_ID(gFields.arraylist.add, clazz, "add", "(Ljava/lang/Object;)Z"); FIND_CLASS(clazz, "java/util/HashMap"); GET_METHOD_ID(gFields.hashmap.init, clazz, "", "()V"); GET_METHOD_ID(gFields.hashmap.get, clazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); GET_METHOD_ID(gFields.hashmap.put, clazz, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); GET_METHOD_ID(gFields.hashmap.entrySet, clazz, "entrySet", "()Ljava/util/Set;"); FIND_CLASS(clazz, "java/util/Set"); GET_METHOD_ID(gFields.set.iterator, clazz, "iterator", "()Ljava/util/Iterator;"); FIND_CLASS(clazz, "java/util/Iterator"); GET_METHOD_ID(gFields.iterator.next, clazz, "next", "()Ljava/lang/Object;"); GET_METHOD_ID(gFields.iterator.hasNext, clazz, "hasNext", "()Z"); FIND_CLASS(clazz, "java/util/Map$Entry"); GET_METHOD_ID(gFields.entry.getKey, clazz, "getKey", "()Ljava/lang/Object;"); GET_METHOD_ID(gFields.entry.getValue, clazz, "getValue", "()Ljava/lang/Object;"); FIND_CLASS(clazz, "java/util/HashMap"); gFields.hashmapClassId = static_cast(env->NewGlobalRef(clazz)); FIND_CLASS(clazz, "java/lang/String"); gFields.stringClassId = static_cast(env->NewGlobalRef(clazz)); FIND_CLASS(clazz, "java/util/ArrayList"); gFields.arraylistClassId = static_cast(env->NewGlobalRef(clazz)); FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException"); GET_METHOD_ID(gFields.stateException.init, clazz, "", "(ILjava/lang/String;)V"); gFields.stateException.classId = static_cast(env->NewGlobalRef(clazz)); FIND_CLASS(clazz, "android/media/MediaDrm$SessionException"); GET_METHOD_ID(gFields.sessionException.init, clazz, "", "(ILjava/lang/String;)V"); gFields.sessionException.classId = static_cast(env->NewGlobalRef(clazz)); GET_FIELD_ID(gFields.sessionException.errorCode, clazz, "mErrorCode", "I"); GET_STATIC_FIELD_ID(field, clazz, "ERROR_UNKNOWN", "I"); gSessionExceptionErrorCodes.kErrorUnknown = env->GetStaticIntField(clazz, field); GET_STATIC_FIELD_ID(field, clazz, "ERROR_RESOURCE_CONTENTION", "I"); gSessionExceptionErrorCodes.kResourceContention = env->GetStaticIntField(clazz, field); FIND_CLASS(clazz, "android/media/MediaDrm$KeyStatus"); gFields.keyStatus.classId = static_cast(env->NewGlobalRef(clazz)); GET_METHOD_ID(gFields.keyStatus.init, clazz, "", "([BI)V"); FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage"); gFields.logMessage.classId = static_cast(env->NewGlobalRef(clazz)); GET_METHOD_ID(gFields.logMessage.init, clazz, "", "(JILjava/lang/String;)V"); } static void android_media_MediaDrm_native_setup( JNIEnv *env, jobject thiz, jobject weak_this, jbyteArray uuidObj, jstring jappPackageName) { if (uuidObj == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "uuid is null"); return; } Vector uuid = JByteArrayToVector(env, uuidObj); if (uuid.size() != 16) { jniThrowException(env, "java/lang/IllegalArgumentException", "invalid UUID size, expected 16 bytes"); return; } String8 packageName; if (jappPackageName == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "application package name cannot be null"); return; } packageName = JStringToString8(env, jappPackageName); sp drm = new JDrm(env, thiz, uuid.array(), packageName); status_t err = drm->initCheck(); if (err != OK) { auto logs(DrmUtils::gLogBuf.getLogs()); auto msg(DrmUtils::GetExceptionMessage(err, "Failed to instantiate drm object", logs)); jniThrowException( env, "android/media/UnsupportedSchemeException", msg.c_str()); return; } sp listener = new JNIDrmListener(env, thiz, weak_this); drm->setListener(listener); setDrm(env, thiz, drm); } DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) { DrmPlugin::SecurityLevel level; if (jlevel == gSecurityLevels.kSecurityLevelMax) { level = DrmPlugin::kSecurityLevelMax; } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) { level = DrmPlugin::kSecurityLevelSwSecureCrypto; } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) { level = DrmPlugin::kSecurityLevelSwSecureDecode; } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) { level = DrmPlugin::kSecurityLevelHwSecureCrypto; } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) { level = DrmPlugin::kSecurityLevelHwSecureDecode; } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) { level = DrmPlugin::kSecurityLevelHwSecureAll; } else { level = DrmPlugin::kSecurityLevelUnknown; } return level; } static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) { sp drm = android::DrmUtils::MakeDrm(); if (drm == NULL) return env->NewByteArray(0); std::vector bv; drm->getSupportedSchemes(bv); jbyteArray jUuidBytes = env->NewByteArray(bv.size()); env->SetByteArrayRegion(jUuidBytes, 0, bv.size(), reinterpret_cast(bv.data())); return jUuidBytes; } static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType, jint jSecurityLevel) { if (uuidObj == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return false; } Vector uuid = JByteArrayToVector(env, uuidObj); if (uuid.size() != 16) { jniThrowException( env, "java/lang/IllegalArgumentException", "invalid UUID size, expected 16 bytes"); return false; } String8 mimeType; if (jmimeType != NULL) { mimeType = JStringToString8(env, jmimeType); } DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel); bool isSupported; status_t err = JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType, securityLevel, &isSupported); if (throwExceptionAsNecessary(env, NULL, err, "Failed to query crypto scheme support")) { return false; } return isSupported; } static jbyteArray android_media_MediaDrm_openSession( JNIEnv *env, jobject thiz, jint jlevel) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } Vector sessionId; DrmPlugin::SecurityLevel level = jintToSecurityLevel(jlevel); if (level == DrmPlugin::kSecurityLevelUnknown) { jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid security level"); return NULL; } status_t err = drm->openSession(level, sessionId); if (throwExceptionAsNecessary(env, drm, err, "Failed to open session")) { return NULL; } return VectorToJByteArray(env, sessionId); } static void android_media_MediaDrm_closeSession( JNIEnv *env, jobject thiz, jbyteArray jsessionId) { sp drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { return; } Vector sessionId(JByteArrayToVector(env, jsessionId)); status_t err = drm->closeSession(sessionId); throwExceptionAsNecessary(env, drm, err, "Failed to close session"); } static jobject android_media_MediaDrm_getKeyRequest( JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jinitData, jstring jmimeType, jint jkeyType, jobject joptParams) { sp drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { return NULL; } Vector sessionId(JByteArrayToVector(env, jsessionId)); Vector initData; if (jinitData != NULL) { initData = JByteArrayToVector(env, jinitData); } String8 mimeType; if (jmimeType != NULL) { mimeType = JStringToString8(env, jmimeType); } DrmPlugin::KeyType keyType; if (jkeyType == gKeyTypes.kKeyTypeStreaming) { keyType = DrmPlugin::kKeyType_Streaming; } else if (jkeyType == gKeyTypes.kKeyTypeOffline) { keyType = DrmPlugin::kKeyType_Offline; } else if (jkeyType == gKeyTypes.kKeyTypeRelease) { keyType = DrmPlugin::kKeyType_Release; } else { jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType"); return NULL; } KeyedVector optParams; if (joptParams != NULL) { bool isOK; optParams = HashMapToKeyedVector(env, joptParams, &isOK); if (!isOK) { return NULL; } } Vector request; String8 defaultUrl; DrmPlugin::KeyRequestType keyRequestType; status_t err = drm->getKeyRequest(sessionId, initData, mimeType, keyType, optParams, request, defaultUrl, &keyRequestType); if (throwExceptionAsNecessary(env, drm, err, "Failed to get key request")) { return NULL; } // Fill out return obj jclass clazz; FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest"); jobject keyObj = NULL; if (clazz) { keyObj = env->AllocObject(clazz); jbyteArray jrequest = VectorToJByteArray(env, request); env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest); jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string()); env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl); switch (keyRequestType) { case DrmPlugin::kKeyRequestType_Initial: env->SetIntField(keyObj, gFields.keyRequest.requestType, gKeyRequestTypes.kKeyRequestTypeInitial); break; case DrmPlugin::kKeyRequestType_Renewal: env->SetIntField(keyObj, gFields.keyRequest.requestType, gKeyRequestTypes.kKeyRequestTypeRenewal); break; case DrmPlugin::kKeyRequestType_Release: env->SetIntField(keyObj, gFields.keyRequest.requestType, gKeyRequestTypes.kKeyRequestTypeRelease); break; case DrmPlugin::kKeyRequestType_None: env->SetIntField(keyObj, gFields.keyRequest.requestType, gKeyRequestTypes.kKeyRequestTypeNone); break; case DrmPlugin::kKeyRequestType_Update: env->SetIntField(keyObj, gFields.keyRequest.requestType, gKeyRequestTypes.kKeyRequestTypeUpdate); break; default: throwStateException(env, "DRM plugin failure: unknown key request type", ERROR_DRM_UNKNOWN); break; } } return keyObj; } static jbyteArray android_media_MediaDrm_provideKeyResponse( JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jresponse) { sp drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { return NULL; } Vector sessionId(JByteArrayToVector(env, jsessionId)); if (jresponse == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "key response is null"); return NULL; } Vector response(JByteArrayToVector(env, jresponse)); Vector keySetId; status_t err = drm->provideKeyResponse(sessionId, response, keySetId); if (throwExceptionAsNecessary(env, drm, err, "Failed to handle key response")) { return NULL; } return VectorToJByteArray(env, keySetId); } static void android_media_MediaDrm_removeKeys( JNIEnv *env, jobject thiz, jbyteArray jkeysetId) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return; } if (jkeysetId == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "keySetId is null"); return; } Vector keySetId(JByteArrayToVector(env, jkeysetId)); status_t err = drm->removeKeys(keySetId); throwExceptionAsNecessary(env, drm, err, "Failed to remove keys"); } static void android_media_MediaDrm_restoreKeys( JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jkeysetId) { sp drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { return; } if (jkeysetId == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } Vector sessionId(JByteArrayToVector(env, jsessionId)); Vector keySetId(JByteArrayToVector(env, jkeysetId)); status_t err = drm->restoreKeys(sessionId, keySetId); throwExceptionAsNecessary(env, drm, err, "Failed to restore keys"); } static jobject android_media_MediaDrm_queryKeyStatus( JNIEnv *env, jobject thiz, jbyteArray jsessionId) { sp drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { return NULL; } Vector sessionId(JByteArrayToVector(env, jsessionId)); KeyedVector infoMap; status_t err = drm->queryKeyStatus(sessionId, infoMap); if (throwExceptionAsNecessary(env, drm, err, "Failed to query key status")) { return NULL; } return KeyedVectorToHashMap(env, infoMap); } static jobject android_media_MediaDrm_getProvisionRequestNative( JNIEnv *env, jobject thiz, jint jcertType, jstring jcertAuthority) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } Vector request; String8 defaultUrl; String8 certType; if (jcertType == gCertificateTypes.kCertificateTypeX509) { certType = "X.509"; } else if (jcertType == gCertificateTypes.kCertificateTypeNone) { certType = "none"; } else { certType = "invalid"; } String8 certAuthority = JStringToString8(env, jcertAuthority); status_t err = drm->getProvisionRequest(certType, certAuthority, request, defaultUrl); if (throwExceptionAsNecessary(env, drm, err, "Failed to get provision request")) { return NULL; } // Fill out return obj jclass clazz; FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest"); jobject provisionObj = NULL; if (clazz) { provisionObj = env->AllocObject(clazz); jbyteArray jrequest = VectorToJByteArray(env, request); env->SetObjectField(provisionObj, gFields.provisionRequest.data, jrequest); jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string()); env->SetObjectField(provisionObj, gFields.provisionRequest.defaultUrl, jdefaultUrl); } return provisionObj; } static jobject android_media_MediaDrm_provideProvisionResponseNative( JNIEnv *env, jobject thiz, jbyteArray jresponse) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } if (jresponse == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "provision response is null"); return NULL; } Vector response(JByteArrayToVector(env, jresponse)); Vector certificate, wrappedKey; status_t err = drm->provideProvisionResponse(response, certificate, wrappedKey); // Fill out return obj jclass clazz = gFields.certificateClassId; jobject certificateObj = NULL; if (clazz && certificate.size() && wrappedKey.size()) { certificateObj = env->AllocObject(clazz); jbyteArray jcertificate = VectorToJByteArray(env, certificate); env->SetObjectField(certificateObj, gFields.certificate.certificateData, jcertificate); jbyteArray jwrappedKey = VectorToJByteArray(env, wrappedKey); env->SetObjectField(certificateObj, gFields.certificate.wrappedPrivateKey, jwrappedKey); } throwExceptionAsNecessary(env, drm, err, "Failed to handle provision response"); return certificateObj; } static jobject android_media_MediaDrm_getSecureStops( JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } List> secureStops; status_t err = drm->getSecureStops(secureStops); if (throwExceptionAsNecessary(env, drm, err, "Failed to get secure stops")) { return NULL; } return ListOfVectorsToArrayListOfByteArray(env, secureStops); } static jobject android_media_MediaDrm_getSecureStopIds( JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } List> secureStopIds; status_t err = drm->getSecureStopIds(secureStopIds); if (throwExceptionAsNecessary(env, drm, err, "Failed to get secure stop Ids")) { return NULL; } return ListOfVectorsToArrayListOfByteArray(env, secureStopIds); } static jbyteArray android_media_MediaDrm_getSecureStop( JNIEnv *env, jobject thiz, jbyteArray ssid) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } Vector secureStop; status_t err = drm->getSecureStop(JByteArrayToVector(env, ssid), secureStop); if (throwExceptionAsNecessary(env, drm, err, "Failed to get secure stop")) { return NULL; } return VectorToJByteArray(env, secureStop); } static void android_media_MediaDrm_releaseSecureStops( JNIEnv *env, jobject thiz, jbyteArray jssRelease) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return; } Vector ssRelease(JByteArrayToVector(env, jssRelease)); status_t err = drm->releaseSecureStops(ssRelease); throwExceptionAsNecessary(env, drm, err, "Failed to release secure stops"); } static void android_media_MediaDrm_removeSecureStop( JNIEnv *env, jobject thiz, jbyteArray ssid) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return; } status_t err = drm->removeSecureStop(JByteArrayToVector(env, ssid)); throwExceptionAsNecessary(env, drm, err, "Failed to remove secure stop"); } static void android_media_MediaDrm_removeAllSecureStops( JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return; } status_t err = drm->removeAllSecureStops(); throwExceptionAsNecessary(env, drm, err, "Failed to remove all secure stops"); } static jint HdcpLevelTojint(DrmPlugin::HdcpLevel level) { switch(level) { case DrmPlugin::kHdcpLevelUnknown: return gHdcpLevels.kHdcpLevelUnknown; case DrmPlugin::kHdcpNone: return gHdcpLevels.kHdcpNone; case DrmPlugin::kHdcpV1: return gHdcpLevels.kHdcpV1; case DrmPlugin::kHdcpV2: return gHdcpLevels.kHdcpV2; case DrmPlugin::kHdcpV2_1: return gHdcpLevels.kHdcpV2_1; case DrmPlugin::kHdcpV2_2: return gHdcpLevels.kHdcpV2_2; case DrmPlugin::kHdcpV2_3: return gHdcpLevels.kHdcpV2_3; case DrmPlugin::kHdcpNoOutput: return gHdcpLevels.kHdcpNoOutput; } return gHdcpLevels.kHdcpNone; } static jint android_media_MediaDrm_getConnectedHdcpLevel(JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return gHdcpLevels.kHdcpNone; } DrmPlugin::HdcpLevel connected = DrmPlugin::kHdcpNone; DrmPlugin::HdcpLevel max = DrmPlugin::kHdcpNone; status_t err = drm->getHdcpLevels(&connected, &max); if (throwExceptionAsNecessary(env, drm, err, "Failed to get HDCP levels")) { return gHdcpLevels.kHdcpLevelUnknown; } return HdcpLevelTojint(connected); } static jint android_media_MediaDrm_getMaxHdcpLevel(JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return gHdcpLevels.kHdcpLevelUnknown; } DrmPlugin::HdcpLevel connected = DrmPlugin::kHdcpLevelUnknown; DrmPlugin::HdcpLevel max = DrmPlugin::kHdcpLevelUnknown; status_t err = drm->getHdcpLevels(&connected, &max); if (throwExceptionAsNecessary(env, drm, err, "Failed to get HDCP levels")) { return gHdcpLevels.kHdcpLevelUnknown; } return HdcpLevelTojint(max); } static jint android_media_MediaDrm_getOpenSessionCount(JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return 0; } uint32_t open = 0, max = 0; status_t err = drm->getNumberOfSessions(&open, &max); if (throwExceptionAsNecessary(env, drm, err, "Failed to get number of sessions")) { return 0; } return open; } static jint android_media_MediaDrm_getMaxSessionCount(JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return 0; } uint32_t open = 0, max = 0; status_t err = drm->getNumberOfSessions(&open, &max); if (throwExceptionAsNecessary(env, drm, err, "Failed to get number of sessions")) { return 0; } return max; } static jint android_media_MediaDrm_getSecurityLevel(JNIEnv *env, jobject thiz, jbyteArray jsessionId) { sp drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { return gSecurityLevels.kSecurityLevelUnknown; } Vector sessionId(JByteArrayToVector(env, jsessionId)); DrmPlugin::SecurityLevel level = DrmPlugin::kSecurityLevelUnknown; status_t err = drm->getSecurityLevel(sessionId, &level); if (throwExceptionAsNecessary(env, drm, err, "Failed to get security level")) { return gSecurityLevels.kSecurityLevelUnknown; } switch(level) { case DrmPlugin::kSecurityLevelSwSecureCrypto: return gSecurityLevels.kSecurityLevelSwSecureCrypto; case DrmPlugin::kSecurityLevelSwSecureDecode: return gSecurityLevels.kSecurityLevelSwSecureDecode; case DrmPlugin::kSecurityLevelHwSecureCrypto: return gSecurityLevels.kSecurityLevelHwSecureCrypto; case DrmPlugin::kSecurityLevelHwSecureDecode: return gSecurityLevels.kSecurityLevelHwSecureDecode; case DrmPlugin::kSecurityLevelHwSecureAll: return gSecurityLevels.kSecurityLevelHwSecureAll; default: return gSecurityLevels.kSecurityLevelUnknown; } } static jobject android_media_MediaDrm_getOfflineLicenseKeySetIds( JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } List > keySetIds; status_t err = drm->getOfflineLicenseKeySetIds(keySetIds); if (throwExceptionAsNecessary(env, drm, err, "Failed to get offline key set Ids")) { return NULL; } return ListOfVectorsToArrayListOfByteArray(env, keySetIds); } static void android_media_MediaDrm_removeOfflineLicense( JNIEnv *env, jobject thiz, jbyteArray keySetId) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return; } status_t err = drm->removeOfflineLicense(JByteArrayToVector(env, keySetId)); throwExceptionAsNecessary(env, drm, err, "Failed to remove offline license"); } static jint android_media_MediaDrm_getOfflineLicenseState(JNIEnv *env, jobject thiz, jbyteArray jkeySetId) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return gOfflineLicenseStates.kOfflineLicenseStateUnknown; } Vector keySetId(JByteArrayToVector(env, jkeySetId)); DrmPlugin::OfflineLicenseState state = DrmPlugin::kOfflineLicenseStateUnknown; status_t err = drm->getOfflineLicenseState(keySetId, &state); if (throwExceptionAsNecessary(env, drm, err, "Failed to get offline license state")) { return gOfflineLicenseStates.kOfflineLicenseStateUnknown; } switch(state) { case DrmPlugin::kOfflineLicenseStateUsable: return gOfflineLicenseStates.kOfflineLicenseStateUsable; case DrmPlugin::kOfflineLicenseStateReleased: return gOfflineLicenseStates.kOfflineLicenseStateReleased; default: return gOfflineLicenseStates.kOfflineLicenseStateUnknown; } } static jstring android_media_MediaDrm_getPropertyString( JNIEnv *env, jobject thiz, jstring jname) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } if (jname == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "property name String is null"); return NULL; } String8 name = JStringToString8(env, jname); String8 value; status_t err = drm->getPropertyString(name, value); if (throwExceptionAsNecessary(env, drm, err, "Failed to get property")) { return NULL; } return env->NewStringUTF(value.string()); } static jbyteArray android_media_MediaDrm_getPropertyByteArray( JNIEnv *env, jobject thiz, jstring jname) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } if (jname == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "property name String is null"); return NULL; } String8 name = JStringToString8(env, jname); Vector value; status_t err = drm->getPropertyByteArray(name, value); if (throwExceptionAsNecessary(env, drm, err, "Failed to get property")) { return NULL; } return VectorToJByteArray(env, value); } static void android_media_MediaDrm_setPropertyString( JNIEnv *env, jobject thiz, jstring jname, jstring jvalue) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return; } if (jname == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "property name String is null"); return; } if (jvalue == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "property value String is null"); return; } String8 name = JStringToString8(env, jname); String8 value = JStringToString8(env, jvalue); status_t err = drm->setPropertyString(name, value); throwExceptionAsNecessary(env, drm, err, "Failed to set property"); } static void android_media_MediaDrm_setPropertyByteArray( JNIEnv *env, jobject thiz, jstring jname, jbyteArray jvalue) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return; } if (jname == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "property name String is null"); return; } if (jvalue == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "property value byte array is null"); return; } String8 name = JStringToString8(env, jname); Vector value = JByteArrayToVector(env, jvalue); status_t err = drm->setPropertyByteArray(name, value); throwExceptionAsNecessary(env, drm, err, "Failed to set property"); } static void android_media_MediaDrm_setCipherAlgorithmNative( JNIEnv *env, jobject /* thiz */, jobject jdrm, jbyteArray jsessionId, jstring jalgorithm) { sp drm = GetDrm(env, jdrm); if (!CheckSession(env, drm, jsessionId)) { return; } if (jalgorithm == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "algorithm String is null"); return; } Vector sessionId(JByteArrayToVector(env, jsessionId)); String8 algorithm = JStringToString8(env, jalgorithm); status_t err = drm->setCipherAlgorithm(sessionId, algorithm); throwExceptionAsNecessary(env, drm, err, "Failed to set cipher algorithm"); } static void android_media_MediaDrm_setMacAlgorithmNative( JNIEnv *env, jobject /* thiz */, jobject jdrm, jbyteArray jsessionId, jstring jalgorithm) { sp drm = GetDrm(env, jdrm); if (!CheckSession(env, drm, jsessionId)) { return; } if (jalgorithm == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "algorithm String is null"); return; } Vector sessionId(JByteArrayToVector(env, jsessionId)); String8 algorithm = JStringToString8(env, jalgorithm); status_t err = drm->setMacAlgorithm(sessionId, algorithm); throwExceptionAsNecessary(env, drm, err, "Failed to set mac algorithm"); } static jbyteArray android_media_MediaDrm_encryptNative( JNIEnv *env, jobject /* thiz */, jobject jdrm, jbyteArray jsessionId, jbyteArray jkeyId, jbyteArray jinput, jbyteArray jiv) { sp drm = GetDrm(env, jdrm); if (!CheckSession(env, drm, jsessionId)) { return NULL; } if (jkeyId == NULL || jinput == NULL || jiv == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "required argument is null"); return NULL; } Vector sessionId(JByteArrayToVector(env, jsessionId)); Vector keyId(JByteArrayToVector(env, jkeyId)); Vector input(JByteArrayToVector(env, jinput)); Vector iv(JByteArrayToVector(env, jiv)); Vector output; status_t err = drm->encrypt(sessionId, keyId, input, iv, output); if (throwExceptionAsNecessary(env, drm, err, "Failed to encrypt")) { return NULL; } return VectorToJByteArray(env, output); } static jbyteArray android_media_MediaDrm_decryptNative( JNIEnv *env, jobject /* thiz */, jobject jdrm, jbyteArray jsessionId, jbyteArray jkeyId, jbyteArray jinput, jbyteArray jiv) { sp drm = GetDrm(env, jdrm); if (!CheckSession(env, drm, jsessionId)) { return NULL; } if (jkeyId == NULL || jinput == NULL || jiv == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "required argument is null"); return NULL; } Vector sessionId(JByteArrayToVector(env, jsessionId)); Vector keyId(JByteArrayToVector(env, jkeyId)); Vector input(JByteArrayToVector(env, jinput)); Vector iv(JByteArrayToVector(env, jiv)); Vector output; status_t err = drm->decrypt(sessionId, keyId, input, iv, output); if (throwExceptionAsNecessary(env, drm, err, "Failed to decrypt")) { return NULL; } return VectorToJByteArray(env, output); } static jbyteArray android_media_MediaDrm_signNative( JNIEnv *env, jobject /* thiz */, jobject jdrm, jbyteArray jsessionId, jbyteArray jkeyId, jbyteArray jmessage) { sp drm = GetDrm(env, jdrm); if (!CheckSession(env, drm, jsessionId)) { return NULL; } if (jkeyId == NULL || jmessage == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "required argument is null"); return NULL; } Vector sessionId(JByteArrayToVector(env, jsessionId)); Vector keyId(JByteArrayToVector(env, jkeyId)); Vector message(JByteArrayToVector(env, jmessage)); Vector signature; status_t err = drm->sign(sessionId, keyId, message, signature); if (throwExceptionAsNecessary(env, drm, err, "Failed to sign")) { return NULL; } return VectorToJByteArray(env, signature); } static jboolean android_media_MediaDrm_verifyNative( JNIEnv *env, jobject /* thiz */, jobject jdrm, jbyteArray jsessionId, jbyteArray jkeyId, jbyteArray jmessage, jbyteArray jsignature) { sp drm = GetDrm(env, jdrm); if (!CheckSession(env, drm, jsessionId)) { return false; } if (jkeyId == NULL || jmessage == NULL || jsignature == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "required argument is null"); return false; } Vector sessionId(JByteArrayToVector(env, jsessionId)); Vector keyId(JByteArrayToVector(env, jkeyId)); Vector message(JByteArrayToVector(env, jmessage)); Vector signature(JByteArrayToVector(env, jsignature)); bool match; status_t err = drm->verify(sessionId, keyId, message, signature, match); throwExceptionAsNecessary(env, drm, err, "Failed to verify"); return match; } static jobject android_media_MediaDrm_native_getMetrics(JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } // Retrieve current metrics snapshot from drm. PersistableBundle metrics; sp consumer(new DrmMetricsConsumer(&metrics)); status_t err = drm->getMetrics(consumer); if (err != OK) { ALOGE("getMetrics failed: %d", (int)err); return (jobject) NULL; } return MediaMetricsJNI::nativeToJavaPersistableBundle(env, &metrics); } static jbyteArray android_media_MediaDrm_signRSANative( JNIEnv *env, jobject /* thiz */, jobject jdrm, jbyteArray jsessionId, jstring jalgorithm, jbyteArray jwrappedKey, jbyteArray jmessage) { sp drm = GetDrm(env, jdrm); if (!CheckSession(env, drm, jsessionId)) { return NULL; } if (jalgorithm == NULL || jwrappedKey == NULL || jmessage == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", "required argument is null"); return NULL; } Vector sessionId(JByteArrayToVector(env, jsessionId)); String8 algorithm = JStringToString8(env, jalgorithm); Vector wrappedKey(JByteArrayToVector(env, jwrappedKey)); Vector message(JByteArrayToVector(env, jmessage)); Vector signature; status_t err = drm->signRSA(sessionId, algorithm, message, wrappedKey, signature); if (throwExceptionAsNecessary(env, drm, err, "Failed to sign")) { return NULL; } return VectorToJByteArray(env, signature); } static jboolean android_media_MediaDrm_requiresSecureDecoder( JNIEnv *env, jobject thiz, jstring jmimeType, jint jSecurityLevel) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return JNI_FALSE; } String8 mimeType; if (jmimeType != NULL) { mimeType = JStringToString8(env, jmimeType); } DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel); if (securityLevel == DrmPlugin::kSecurityLevelUnknown) { jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid security level"); return JNI_FALSE; } bool required = false; status_t err = OK; if (securityLevel == DrmPlugin::kSecurityLevelMax) { err = drm->requiresSecureDecoder(mimeType.c_str(), &required); } else { err = drm->requiresSecureDecoder(mimeType.c_str(), securityLevel, &required); } if (throwExceptionAsNecessary(env, drm, err, "Failed to query secure decoder requirement")) { return false; } return required; } static void android_media_MediaDrm_setPlaybackId( JNIEnv *env, jobject thiz, jbyteArray jsessionId, jstring jplaybackId) { sp drm = GetDrm(env, thiz); if (!CheckSession(env, drm, jsessionId)) { return; } Vector sessionId(JByteArrayToVector(env, jsessionId)); String8 playbackId; if (jplaybackId != NULL) { playbackId = JStringToString8(env, jplaybackId); } status_t err = drm->setPlaybackId(sessionId, playbackId.c_str()); throwExceptionAsNecessary(env, drm, err, "Failed to set playbackId"); } static jobject android_media_MediaDrm_getLogMessages( JNIEnv *env, jobject thiz) { sp drm = GetDrm(env, thiz); if (!CheckDrm(env, drm)) { return NULL; } Vector logs; status_t err = drm->getLogMessages(logs); ALOGI("drm->getLogMessages %zu logs", logs.size()); if (throwExceptionAsNecessary(env, drm, err, "Failed to get log messages")) { return NULL; } return hidlLogMessagesToJavaList(env, logs); } static const JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaDrm_native_release }, { "native_init", "()V", (void *)android_media_MediaDrm_native_init }, { "native_setup", "(Ljava/lang/Object;[BLjava/lang/String;)V", (void *)android_media_MediaDrm_native_setup }, { "getSupportedCryptoSchemesNative", "()[B", (void *)android_media_MediaDrm_getSupportedCryptoSchemesNative }, { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;I)Z", (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative }, { "openSessionNative", "(I)[B", (void *)android_media_MediaDrm_openSession }, { "closeSessionNative", "([B)V", (void *)android_media_MediaDrm_closeSession }, { "getKeyRequestNative", "([B[BLjava/lang/String;ILjava/util/HashMap;)" "Landroid/media/MediaDrm$KeyRequest;", (void *)android_media_MediaDrm_getKeyRequest }, { "provideKeyResponse", "([B[B)[B", (void *)android_media_MediaDrm_provideKeyResponse }, { "removeKeys", "([B)V", (void *)android_media_MediaDrm_removeKeys }, { "restoreKeys", "([B[B)V", (void *)android_media_MediaDrm_restoreKeys }, { "queryKeyStatus", "([B)Ljava/util/HashMap;", (void *)android_media_MediaDrm_queryKeyStatus }, { "getProvisionRequestNative", "(ILjava/lang/String;)Landroid/media/MediaDrm$ProvisionRequest;", (void *)android_media_MediaDrm_getProvisionRequestNative }, { "provideProvisionResponseNative", "([B)Landroid/media/MediaDrm$Certificate;", (void *)android_media_MediaDrm_provideProvisionResponseNative }, { "getSecureStops", "()Ljava/util/List;", (void *)android_media_MediaDrm_getSecureStops }, { "getSecureStopIds", "()Ljava/util/List;", (void *)android_media_MediaDrm_getSecureStopIds }, { "getSecureStop", "([B)[B", (void *)android_media_MediaDrm_getSecureStop }, { "releaseSecureStops", "([B)V", (void *)android_media_MediaDrm_releaseSecureStops }, { "removeSecureStop", "([B)V", (void *)android_media_MediaDrm_removeSecureStop }, { "removeAllSecureStops", "()V", (void *)android_media_MediaDrm_removeAllSecureStops }, { "getConnectedHdcpLevel", "()I", (void *)android_media_MediaDrm_getConnectedHdcpLevel }, { "getMaxHdcpLevel", "()I", (void *)android_media_MediaDrm_getMaxHdcpLevel }, { "getOpenSessionCount", "()I", (void *)android_media_MediaDrm_getOpenSessionCount }, { "getMaxSessionCount", "()I", (void *)android_media_MediaDrm_getMaxSessionCount }, { "getSecurityLevel", "([B)I", (void *)android_media_MediaDrm_getSecurityLevel }, { "removeOfflineLicense", "([B)V", (void *)android_media_MediaDrm_removeOfflineLicense }, { "getOfflineLicenseKeySetIds", "()Ljava/util/List;", (void *)android_media_MediaDrm_getOfflineLicenseKeySetIds }, { "getOfflineLicenseState", "([B)I", (void *)android_media_MediaDrm_getOfflineLicenseState }, { "getPropertyString", "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_MediaDrm_getPropertyString }, { "getPropertyByteArray", "(Ljava/lang/String;)[B", (void *)android_media_MediaDrm_getPropertyByteArray }, { "setPropertyString", "(Ljava/lang/String;Ljava/lang/String;)V", (void *)android_media_MediaDrm_setPropertyString }, { "setPropertyByteArray", "(Ljava/lang/String;[B)V", (void *)android_media_MediaDrm_setPropertyByteArray }, { "setCipherAlgorithmNative", "(Landroid/media/MediaDrm;[BLjava/lang/String;)V", (void *)android_media_MediaDrm_setCipherAlgorithmNative }, { "setMacAlgorithmNative", "(Landroid/media/MediaDrm;[BLjava/lang/String;)V", (void *)android_media_MediaDrm_setMacAlgorithmNative }, { "encryptNative", "(Landroid/media/MediaDrm;[B[B[B[B)[B", (void *)android_media_MediaDrm_encryptNative }, { "decryptNative", "(Landroid/media/MediaDrm;[B[B[B[B)[B", (void *)android_media_MediaDrm_decryptNative }, { "signNative", "(Landroid/media/MediaDrm;[B[B[B)[B", (void *)android_media_MediaDrm_signNative }, { "verifyNative", "(Landroid/media/MediaDrm;[B[B[B[B)Z", (void *)android_media_MediaDrm_verifyNative }, { "signRSANative", "(Landroid/media/MediaDrm;[BLjava/lang/String;[B[B)[B", (void *)android_media_MediaDrm_signRSANative }, { "getMetricsNative", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaDrm_native_getMetrics }, { "requiresSecureDecoder", "(Ljava/lang/String;I)Z", (void *)android_media_MediaDrm_requiresSecureDecoder }, { "setPlaybackId", "([BLjava/lang/String;)V", (void *)android_media_MediaDrm_setPlaybackId }, { "getLogMessages", "()Ljava/util/List;", (void *)android_media_MediaDrm_getLogMessages }, }; int register_android_media_Drm(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaDrm", gMethods, NELEM(gMethods)); }