/*
* 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.
*
*
*
Callbacks
* 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(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 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 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 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();
}
}