diff options
| author | 2013-02-08 10:20:44 -0800 | |
|---|---|---|
| committer | 2013-03-13 13:40:56 -0700 | |
| commit | 8a0c80fdcc46faa8cb8c9f4dda06f4b63ec2f906 (patch) | |
| tree | 3d46fd9b56e0eb9ada7b89db2f89266763d05ae0 | |
| parent | 86277467eec5e269328364e91abe164991653635 (diff) | |
Implementing MediaDrm APIs
Change-Id: Ib6eeb9c04c5c5cf1d485f9004cd3e6a1047a1d19
| -rw-r--r-- | media/java/android/media/MediaDrm.java | 366 | ||||
| -rw-r--r-- | media/java/android/media/MediaDrmException.java | 29 | ||||
| -rw-r--r-- | media/jni/Android.mk | 1 | ||||
| -rw-r--r-- | media/jni/android_media_MediaDrm.cpp | 817 | ||||
| -rw-r--r-- | media/jni/android_media_MediaDrm.h | 54 | ||||
| -rw-r--r-- | media/jni/android_media_MediaPlayer.cpp | 6 |
6 files changed, 1273 insertions, 0 deletions
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java new file mode 100644 index 000000000000..4561d3fb6367 --- /dev/null +++ b/media/java/android/media/MediaDrm.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 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. + */ + +package android.media; + +import android.media.MediaDrmException; +import java.lang.ref.WeakReference; +import java.util.UUID; +import java.util.HashMap; +import java.util.List; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Bundle; +import android.util.Log; + +/** + * MediaDrm class can be used in conjunction with {@link android.media.MediaCrypto} + * to obtain licenses for decoding encrypted media data. + * + * Crypto schemes are assigned 16 byte UUIDs, + * the method {@link #isCryptoSchemeSupported} can be used to query if a given + * scheme is supported on the device. + * + * <a name="Callbacks"></a> + * <h3>Callbacks</h3> + * <p>Applications may want to register for informational events in order + * to be informed of some internal state update during playback or streaming. + * Registration for these events is done via a call to + * {@link #setOnEventListener(OnInfoListener)}setOnInfoListener, + * In order to receive the respective callback + * associated with this listener, applications are required to create + * MediaDrm objects on a thread with its own Looper running (main UI + * thread by default has a Looper running). + * + * @hide -- don't expose yet + */ +public final class MediaDrm { + + private final static String TAG = "MediaDrm"; + + private EventHandler mEventHandler; + private OnEventListener mOnEventListener; + + private int mNativeContext; + + /** + * Query if the given scheme identified by its UUID is supported on + * this device. + * @param uuid The UUID of the crypto scheme. + */ + public static final boolean isCryptoSchemeSupported(UUID uuid) { + return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid)); + } + + private static final byte[] getByteArrayFromUUID(UUID uuid) { + long msb = uuid.getMostSignificantBits(); + long lsb = uuid.getLeastSignificantBits(); + + byte[] uuidBytes = new byte[16]; + for (int i = 0; i < 8; ++i) { + uuidBytes[i] = (byte)(msb >>> (8 * (7 - i))); + uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i))); + } + + return uuidBytes; + } + + private static final native boolean isCryptoSchemeSupportedNative(byte[] uuid); + + /** + * Instantiate a MediaDrm object using opaque, crypto scheme specific + * data. + * @param uuid The UUID of the crypto scheme. + */ + public MediaDrm(UUID uuid) throws MediaDrmException { + Looper looper; + if ((looper = Looper.myLooper()) != null) { + mEventHandler = new EventHandler(this, looper); + } else if ((looper = Looper.getMainLooper()) != null) { + mEventHandler = new EventHandler(this, looper); + } else { + mEventHandler = null; + } + + /* Native setup requires a weak reference to our object. + * It's easier to create it here than in C++. + */ + native_setup(new WeakReference<MediaDrm>(this), + getByteArrayFromUUID(uuid)); + } + + /** + * Register a callback to be invoked when an event occurs + * + * @param listener the callback that will be run + */ + public void setOnEventListener(OnEventListener listener) + { + mOnEventListener = listener; + } + + /** + * Interface definition for a callback to be invoked when a drm event + * occurs. + */ + public interface OnEventListener + { + /** + * Called when an event occurs that requires the app to be notified + * + * @param md the MediaDrm object on which the event occurred + * @param sessionId the DRM session ID on which the event occurred + * @param event indicates the event type + * @param extra an secondary error code + * @param data optional byte array of data that may be associated with the event + */ + void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data); + } + + /* Do not change these values without updating their counterparts + * in include/media/mediadrm.h! + */ + private static final int DRM_EVENT = 200; + + private class EventHandler extends Handler + { + private MediaDrm mMediaDrm; + + public EventHandler(MediaDrm md, Looper looper) { + super(looper); + mMediaDrm = md; + } + + @Override + public void handleMessage(Message msg) { + if (mMediaDrm.mNativeContext == 0) { + Log.w(TAG, "MediaDrm went away with unhandled events"); + return; + } + switch(msg.what) { + + case DRM_EVENT: + Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")"); + + if (mOnEventListener != null) { + Bundle bundle = msg.getData(); + byte[] sessionId = bundle.getByteArray("sessionId"); + byte[] data = bundle.getByteArray("data"); + mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data); + } + return; + + default: + Log.e(TAG, "Unknown message type " + msg.what); + return; + } + } + } + + /* + * Called from native code when an interesting event happens. This method + * just uses the EventHandler system to post the event back to the main app thread. + * We use a weak reference to the original MediaPlayer object so that the native + * code is safe from the object disappearing from underneath it. (This is + * the cookie passed to native_setup().) + */ + private static void postEventFromNative(Object mediadrm_ref, + int what, int arg1, int arg2, Object obj) + { + MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get(); + if (md == null) { + return; + } + if (md.mEventHandler != null) { + Message m = md.mEventHandler.obtainMessage(what, arg1, arg2, obj); + md.mEventHandler.sendMessage(m); + } + } + + /** + * Open a new session with the MediaDrm object. A session ID is returned. + */ + public native byte[] openSession() throws MediaDrmException; + + /** + * Close a session on the MediaDrm object. + */ + public native void closeSession(byte[] sessionId) throws MediaDrmException; + + public static final int MEDIA_DRM_LICENSE_TYPE_STREAMING = 1; + public static final int MEDIA_DRM_LICENSE_TYPE_OFFLINE = 2; + + public final class LicenseRequest { + public LicenseRequest() {} + public byte[] data; + public String defaultUrl; + }; + + /** + * A license request/response exchange occurs between the app and a License + * Server to obtain the keys required to decrypt the content. getLicenseRequest() + * is used to obtain an opaque license request byte array that is delivered to the + * license server. The opaque license request byte array is returned in + * LicenseReqeust.data. The recommended URL to deliver the license request to is + * returned in LicenseRequest.defaultUrl + * + * @param sessonId the session ID for the drm session + * @param init container-specific data, its meaning is interpreted based on the + * mime type provided in the mimeType parameter. It could contain, for example, + * the content ID, key ID or other data obtained from the content metadata that is + * required in generating the license request. + * @param mimeType identifies the mime type of the content + * @param licenseType specifes if the license is for streaming or offline content + * @param optionalParameters are included in the license server request message to + * allow a client application to provide additional message parameters to the server. + */ + public native LicenseRequest getLicenseRequest( byte[] sessionId, byte[] init, + String mimeType, int licenseType, + HashMap<String, String> optionalParameters ) + throws MediaDrmException; + + /** + * After a license response is received by the app, it is provided to the DRM plugin + * using provideLicenseResponse. + * + * @param sessionId the session ID for the DRM session + * @param response the byte array response from the server + */ + public native void provideLicenseResponse( byte[] sessionId, byte[] response ) + throws MediaDrmException; + + /** + * Remove the keys associated with a license for a session + * @param sessionId the session ID for the DRM session + */ + public native void removeLicense( byte[] sessionId ) throws MediaDrmException; + + /** + * Request an informative description of the license for the session. The status is + * in the form of {name, value} pairs. Since DRM license policies vary by vendor, + * the specific status field names are determined by each DRM vendor. Refer to your + * DRM provider documentation for definitions of the field names for a particular + * DrmEngine. + * + * @param sessionId the session ID for the DRM session + */ + public native HashMap<String, String> queryLicenseStatus( byte[] sessionId ) + throws MediaDrmException; + + public final class ProvisionRequest { + public ProvisionRequest() {} + public byte[] data; + public String defaultUrl; + } + + /** + * A provision request/response exchange occurs between the app and a provisioning + * server to retrieve a device certificate. getProvisionRequest is used to obtain + * an opaque license request byte array that is delivered to the provisioning server. + * The opaque provision request byte array is returned in ProvisionRequest.data + * The recommended URL to deliver the license request to is returned in + * ProvisionRequest.defaultUrl. + */ + public native ProvisionRequest getProvisionRequest() throws MediaDrmException; + + /** + * After a provision response is received by the app, it is provided to the DRM + * plugin using this method. + * + * @param response the opaque provisioning response byte array to provide to the + * DrmEngine. + */ + public native void provideProvisionResponse( byte[] response ) + throws MediaDrmException; + + /** + * A means of enforcing the contractual requirement for a concurrent stream limit + * per subscriber across devices is provided via SecureStop. SecureStop is a means + * of securely monitoring the lifetime of sessions. Since playback on a device can + * be interrupted due to reboot, power failure, etc. a means of persisting the + * lifetime information on the device is needed. + * + * A signed version of the sessionID is written to persistent storage on the device + * when each MediaCrypto object is created. The sessionID is signed by the device + * private key to prevent tampering. + * + * In the normal case, playback will be completed, the session destroyed and the + * Secure Stops will be queried. The App queries secure stops and forwards the + * secure stop message to the server which verifies the signature and notifies the + * server side database that the session destruction has been confirmed. The persisted + * record on the client is only removed after positive confirmation that the server + * received the message using releaseSecureStops(). + */ + public native List<byte[]> getSecureStops() throws MediaDrmException; + + + /** + * Process the SecureStop server response message ssRelease. After authenticating + * the message, remove the SecureStops identiied in the response. + * + * @param ssRelease the server response indicating which secure stops to release + */ + public native void releaseSecureStops( byte[] ssRelease ) + throws MediaDrmException; + + + /** + * Read a Drm plugin property value, given the property name string. There are several + * forms of property access functions, depending on the data type returned. + * + * Standard fields names are: + * vendor String - identifies the maker of the plugin + * version String - identifies the version of the plugin + * description String - describes the plugin + * deviceUniqueId byte[] - The device unique identifier is established during device + * provisioning and provides a means of uniquely identifying + * each device + */ + public native String getPropertyString( String propertyName ) + throws MediaDrmException; + + public native byte[] getPropertyByteArray( String propertyName ) + throws MediaDrmException; + + /** + * Write a Drm plugin property value. There are several forms of property setting + * functions, depending on the data type being set. + */ + public native void setPropertyString( String propertyName, String value ) + throws MediaDrmException; + + public native void setPropertyByteArray( String propertyName, byte[] value ) + throws MediaDrmException; + + @Override + protected void finalize() { + native_finalize(); + } + + public native final void release(); + private static native final void native_init(); + + private native final void native_setup(Object mediadrm_this, byte[] uuid) + throws MediaDrmException; + + private native final void native_finalize(); + + static { + System.loadLibrary("media_jni"); + native_init(); + } +} diff --git a/media/java/android/media/MediaDrmException.java b/media/java/android/media/MediaDrmException.java new file mode 100644 index 000000000000..6f81f9024537 --- /dev/null +++ b/media/java/android/media/MediaDrmException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 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. + */ + +package android.media; + +/** + * Exception thrown if MediaDrm object could not be instantiated for + * whatever reason. + * + * @hide -- don't expose yet + */ +public final class MediaDrmException extends Exception { + public MediaDrmException(String detailMessage) { + super(detailMessage); + } +} diff --git a/media/jni/Android.mk b/media/jni/Android.mk index ac8fb743ca76..687306053a68 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -5,6 +5,7 @@ LOCAL_SRC_FILES:= \ android_media_MediaCrypto.cpp \ android_media_MediaCodec.cpp \ android_media_MediaCodecList.cpp \ + android_media_MediaDrm.cpp \ android_media_MediaExtractor.cpp \ android_media_MediaMuxer.cpp \ android_media_MediaPlayer.cpp \ diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp new file mode 100644 index 000000000000..9938f7633b14 --- /dev/null +++ b/media/jni/android_media_MediaDrm.cpp @@ -0,0 +1,817 @@ +/* + * 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 <utils/Log.h> + +#include "android_media_MediaDrm.h" + +#include "android_runtime/AndroidRuntime.h" +#include "jni.h" +#include "JNIHelp.h" + +#include <binder/IServiceManager.h> +#include <media/IDrm.h> +#include <media/IMediaPlayerService.h> +#include <media/stagefright/foundation/ADebug.h> + +namespace android { + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method " fieldName); + +struct RequestFields { + jfieldID data; + jfieldID defaultUrl; +}; + +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 fields_t { + jfieldID context; + RequestFields licenseRequest; + RequestFields provisionRequest; + ArrayListFields arraylist; + HashmapFields hashmap; + SetFields set; + IteratorFields iterator; + EntryFields entry; +}; + +static fields_t gFields; + +static bool throwExceptionAsNecessary( + JNIEnv *env, status_t err, const char *msg = NULL) { + + if (err == BAD_VALUE) { + jniThrowException(env, "java/lang/IllegalArgumentException", msg); + return true; + } else if (err != OK) { + jniThrowException(env, "java/lang/IllegalStateException", msg); + return true; + } + return false; +} + +static sp<IDrm> GetDrm(JNIEnv *env, jobject thiz) { + JDrm *jdrm = (JDrm *)env->GetIntField(thiz, gFields.context); + return jdrm ? jdrm->getDrm() : NULL; +} + +JDrm::JDrm( + JNIEnv *env, jobject thiz, const uint8_t uuid[16]) { + mObject = env->NewWeakGlobalRef(thiz); + mDrm = MakeDrm(uuid); +} + +JDrm::~JDrm() { + mDrm.clear(); + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; +} + +// static +sp<IDrm> JDrm::MakeDrm() { + sp<IServiceManager> sm = defaultServiceManager(); + + sp<IBinder> binder = + sm->getService(String16("media.player")); + + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + + if (service == NULL) { + return NULL; + } + + sp<IDrm> drm = service->makeDrm(); + + if (drm == NULL || (drm->initCheck() != OK && drm->initCheck() != NO_INIT)) { + return NULL; + } + + return drm; +} + +// static +sp<IDrm> JDrm::MakeDrm(const uint8_t uuid[16]) { + sp<IDrm> drm = MakeDrm(); + + if (drm == NULL) { + return NULL; + } + + status_t err = drm->createPlugin(uuid); + + if (err != OK) { + return NULL; + } + + return drm; +} + +// static +bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16]) { + sp<IDrm> drm = MakeDrm(); + + if (drm == NULL) { + return false; + } + + return drm->isCryptoSchemeSupported(uuid); +} + +status_t JDrm::initCheck() const { + return mDrm == NULL ? NO_INIT : OK; +} + +// JNI conversion utilities +static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray) { + Vector<uint8_t> 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<uint8_t> 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) { + jboolean isCopy; + String8 result; + + const char *s = env->GetStringUTFChars(jstr, &isCopy); + 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<k, v> hm; + Set<Entry<k, v> > s = hm.entrySet(); + Iterator i = s.iterator(); + Entry e = s.next(); +*/ + +static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &hashMap) { + jclass clazz; + FIND_CLASS(clazz, "java/lang/String"); + KeyedVector<String8, String8> keyedVector; + + 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 (!env->IsInstanceOf(obj, clazz)) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + } + jstring jkey = static_cast<jstring>(obj); + + obj = env->CallObjectMethod(entry, gFields.entry.getValue); + if (!env->IsInstanceOf(obj, clazz)) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + } + jstring jvalue = static_cast<jstring>(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<String8, String8> const &map) { + jclass clazz; + FIND_CLASS(clazz, "java/util/HashMap"); + 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<Vector<uint8_t> > list) { + jclass clazz; + FIND_CLASS(clazz, "java/util/ArrayList"); + jobject arrayList = env->NewObject(clazz, gFields.arraylist.init); + List<Vector<uint8_t> >::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<JDrm> setDrm( + JNIEnv *env, jobject thiz, const sp<JDrm> &drm) { + sp<JDrm> old = (JDrm *)env->GetIntField(thiz, gFields.context); + if (drm != NULL) { + drm->incStrong(thiz); + } + if (old != NULL) { + old->decStrong(thiz); + } + env->SetIntField(thiz, gFields.context, (int)drm.get()); + + return old; +} + +static bool CheckSession(JNIEnv *env, const sp<IDrm> &drm, jbyteArray const &jsessionId) +{ + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return false; + } + + if (jsessionId == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + return true; +} + +static void android_media_MediaDrm_release(JNIEnv *env, jobject thiz) { + setDrm(env, thiz, NULL); +} + +static void android_media_MediaDrm_native_init(JNIEnv *env) { + jclass clazz; + FIND_CLASS(clazz, "android/media/MediaDrm"); + GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "I"); + + FIND_CLASS(clazz, "android/media/MediaDrm$LicenseRequest"); + GET_FIELD_ID(gFields.licenseRequest.data, clazz, "data", "[B"); + GET_FIELD_ID(gFields.licenseRequest.defaultUrl, clazz, "defaultUrl", "Ljava/lang/String;"); + + FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest"); + GET_FIELD_ID(gFields.provisionRequest.data, clazz, "data", "[B"); + GET_FIELD_ID(gFields.provisionRequest.defaultUrl, clazz, "defaultUrl", "Ljava/lang/String;"); + + FIND_CLASS(clazz, "java/util/ArrayList"); + GET_METHOD_ID(gFields.arraylist.init, clazz, "<init>", "()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, "<init>", "()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;"); +} + +static void android_media_MediaDrm_native_setup( + JNIEnv *env, jobject thiz, + jobject weak_this, jbyteArray uuidObj) { + + if (uuidObj == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj); + + if (uuid.size() != 16) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + sp<JDrm> drm = new JDrm(env, thiz, uuid.array()); + + status_t err = drm->initCheck(); + + if (err != OK) { + jniThrowException( + env, + "android/media/MediaDrmException", + "Failed to instantiate drm object."); + return; + } + + setDrm(env, thiz, drm); +} + +static void android_media_MediaDrm_native_finalize( + JNIEnv *env, jobject thiz) { + android_media_MediaDrm_release(env, thiz); +} + +static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( + JNIEnv *env, jobject thiz, jbyteArray uuidObj) { + + if (uuidObj == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj); + + if (uuid.size() != 16) { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + NULL); + return false; + } + + return JDrm::IsCryptoSchemeSupported(uuid.array()); +} + +static jbyteArray android_media_MediaDrm_openSession( + JNIEnv *env, jobject thiz) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + Vector<uint8_t> sessionId; + status_t err = drm->openSession(sessionId); + + if (throwExceptionAsNecessary(env, err, "Failed to open session")) { + return NULL; + } + + return VectorToJByteArray(env, sessionId); +} + +static void android_media_MediaDrm_closeSession( + JNIEnv *env, jobject thiz, jbyteArray jsessionId) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (!CheckSession(env, drm, jsessionId)) { + return; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + + status_t err = drm->closeSession(sessionId); + + throwExceptionAsNecessary(env, err, "Failed to close session"); +} + +static jobject android_media_MediaDrm_getLicenseRequest( + JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jinitData, + jstring jmimeType, jint jlicenseType, jobject joptParams) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (!CheckSession(env, drm, jsessionId)) { + return NULL; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + + Vector<uint8_t> initData; + if (jinitData != NULL) { + initData = JByteArrayToVector(env, jinitData); + } + + String8 mimeType; + if (jmimeType != NULL) { + mimeType = JStringToString8(env, jmimeType); + } + + DrmPlugin::LicenseType licenseType = (DrmPlugin::LicenseType)jlicenseType; + + KeyedVector<String8, String8> optParams; + if (joptParams != NULL) { + optParams = HashMapToKeyedVector(env, joptParams); + } + + Vector<uint8_t> request; + String8 defaultUrl; + + status_t err = drm->getLicenseRequest(sessionId, initData, mimeType, + licenseType, optParams, request, defaultUrl); + + if (throwExceptionAsNecessary(env, err, "Failed to get license request")) { + return NULL; + } + + // Fill out return obj + jclass clazz; + FIND_CLASS(clazz, "android/media/MediaDrm$LicenseRequest"); + + jobject licenseObj = NULL; + + if (clazz) { + licenseObj = env->AllocObject(clazz); + jbyteArray jrequest = VectorToJByteArray(env, request); + env->SetObjectField(licenseObj, gFields.licenseRequest.data, jrequest); + + jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string()); + env->SetObjectField(licenseObj, gFields.licenseRequest.defaultUrl, jdefaultUrl); + } + + return licenseObj; +} + +static void android_media_MediaDrm_provideLicenseResponse( + JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jresponse) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (!CheckSession(env, drm, jsessionId)) { + return; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + + if (jresponse == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + Vector<uint8_t> response(JByteArrayToVector(env, jresponse)); + + status_t err = drm->provideLicenseResponse(sessionId, response); + + throwExceptionAsNecessary(env, err, "Failed to handle license response"); +} + +static void android_media_MediaDrm_removeLicense( + JNIEnv *env, jobject thiz, jbyteArray jsessionId) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (!CheckSession(env, drm, jsessionId)) { + return; + } + + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + + status_t err = drm->removeLicense(sessionId); + + throwExceptionAsNecessary(env, err, "Failed to remove license"); +} + +static jobject android_media_MediaDrm_queryLicenseStatus( + JNIEnv *env, jobject thiz, jbyteArray jsessionId) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (!CheckSession(env, drm, jsessionId)) { + return NULL; + } + Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId)); + + KeyedVector<String8, String8> infoMap; + + status_t err = drm->queryLicenseStatus(sessionId, infoMap); + + if (throwExceptionAsNecessary(env, err, "Failed to query license")) { + return NULL; + } + + return KeyedVectorToHashMap(env, infoMap); +} + +static jobject android_media_MediaDrm_getProvisionRequest( + JNIEnv *env, jobject thiz) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + Vector<uint8_t> request; + String8 defaultUrl; + + status_t err = drm->getProvisionRequest(request, defaultUrl); + + if (throwExceptionAsNecessary(env, 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 void android_media_MediaDrm_provideProvisionResponse( + JNIEnv *env, jobject thiz, jbyteArray jresponse) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (jresponse == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + Vector<uint8_t> response(JByteArrayToVector(env, jresponse)); + + status_t err = drm->provideProvisionResponse(response); + + throwExceptionAsNecessary(env, err, "Failed to handle provision response"); +} + +static jobject android_media_MediaDrm_getSecureStops( + JNIEnv *env, jobject thiz) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + List<Vector<uint8_t> > secureStops; + + status_t err = drm->getSecureStops(secureStops); + + if (throwExceptionAsNecessary(env, err, "Failed to get secure stops")) { + return NULL; + } + + return ListOfVectorsToArrayListOfByteArray(env, secureStops); +} + +static void android_media_MediaDrm_releaseSecureStops( + JNIEnv *env, jobject thiz, jbyteArray jssRelease) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + Vector<uint8_t> ssRelease(JByteArrayToVector(env, jssRelease)); + + status_t err = drm->releaseSecureStops(ssRelease); + + throwExceptionAsNecessary(env, err, "Failed to release secure stops"); +} + +static jstring android_media_MediaDrm_getPropertyString( + JNIEnv *env, jobject thiz, jstring jname) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + if (jname == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + String8 name = JStringToString8(env, jname); + String8 value; + + status_t err = drm->getPropertyString(name, value); + + if (throwExceptionAsNecessary(env, 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<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + if (jname == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + String8 name = JStringToString8(env, jname); + Vector<uint8_t> value; + + status_t err = drm->getPropertyByteArray(name, value); + + if (throwExceptionAsNecessary(env, 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<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (jname == NULL || jvalue == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + String8 name = JStringToString8(env, jname); + String8 value = JStringToString8(env, jvalue); + + status_t err = drm->setPropertyString(name, value); + + throwExceptionAsNecessary(env, err, "Failed to set property"); +} + +static void android_media_MediaDrm_setPropertyByteArray( + JNIEnv *env, jobject thiz, jstring jname, jbyteArray jvalue) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (jname == NULL || jvalue == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + String8 name = JStringToString8(env, jname); + Vector<uint8_t> value = JByteArrayToVector(env, jvalue); + + status_t err = drm->setPropertyByteArray(name, value); + + throwExceptionAsNecessary(env, err, "Failed to set property"); +} + + +static JNINativeMethod gMethods[] = { + { "release", "()V", (void *)android_media_MediaDrm_release }, + { "native_init", "()V", (void *)android_media_MediaDrm_native_init }, + + { "native_setup", "(Ljava/lang/Object;[B)V", + (void *)android_media_MediaDrm_native_setup }, + + { "native_finalize", "()V", + (void *)android_media_MediaDrm_native_finalize }, + + { "isCryptoSchemeSupportedNative", "([B)Z", + (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative }, + + { "openSession", "()[B", + (void *)android_media_MediaDrm_openSession }, + + { "closeSession", "([B)V", + (void *)android_media_MediaDrm_closeSession }, + + { "getLicenseRequest", "([B[BLjava/lang/String;ILjava/util/HashMap;)" + "Landroid/media/MediaDrm$LicenseRequest;", + (void *)android_media_MediaDrm_getLicenseRequest }, + + { "provideLicenseResponse", "([B[B)V", + (void *)android_media_MediaDrm_provideLicenseResponse }, + + { "removeLicense", "([B)V", + (void *)android_media_MediaDrm_removeLicense }, + + { "queryLicenseStatus", "([B)Ljava/util/HashMap;", + (void *)android_media_MediaDrm_queryLicenseStatus }, + + { "getProvisionRequest", "()Landroid/media/MediaDrm$ProvisionRequest;", + (void *)android_media_MediaDrm_getProvisionRequest }, + + { "provideProvisionResponse", "([B)V", + (void *)android_media_MediaDrm_provideProvisionResponse }, + + { "getSecureStops", "()Ljava/util/List;", + (void *)android_media_MediaDrm_getSecureStops }, + + { "releaseSecureStops", "([B)V", + (void *)android_media_MediaDrm_releaseSecureStops }, + + { "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 }, +}; + +int register_android_media_Drm(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaDrm", gMethods, NELEM(gMethods)); +} + diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h new file mode 100644 index 000000000000..01067c4fef09 --- /dev/null +++ b/media/jni/android_media_MediaDrm.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#ifndef _ANDROID_MEDIA_DRM_H_ +#define _ANDROID_MEDIA_DRM_H_ + +#include "jni.h" + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +namespace android { + +struct IDrm; + +struct JDrm : public RefBase { + static bool IsCryptoSchemeSupported(const uint8_t uuid[16]); + + JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16]); + + status_t initCheck() const; + + sp<IDrm> getDrm() { return mDrm; } + +protected: + virtual ~JDrm(); + +private: + jweak mObject; + sp<IDrm> mDrm; + + static sp<IDrm> MakeDrm(); + static sp<IDrm> MakeDrm(const uint8_t uuid[16]); + + DISALLOW_EVIL_CONSTRUCTORS(JDrm); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_DRM_H_ diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index e9f6a1ee8859..c5098ce1c7f2 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -879,6 +879,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) } extern int register_android_media_Crypto(JNIEnv *env); +extern int register_android_media_Drm(JNIEnv *env); extern int register_android_media_MediaCodec(JNIEnv *env); extern int register_android_media_MediaExtractor(JNIEnv *env); extern int register_android_media_MediaCodecList(JNIEnv *env); @@ -979,6 +980,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_media_Drm(env) < 0) { + ALOGE("ERROR: MediaDrm native registration failed"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; |