MediaDrm.java revision 16b8cffb2893c10c35788191847500004da466d1
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import android.media.MediaDrmException;
20import java.lang.ref.WeakReference;
21import java.util.UUID;
22import java.util.HashMap;
23import java.util.List;
24import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
27import android.os.Bundle;
28import android.util.Log;
29
30/**
31 * MediaDrm can be used in conjunction with {@link android.media.MediaCrypto}
32 * to obtain keys for decrypting protected media data.
33 *
34 * Crypto schemes are assigned 16 byte UUIDs,
35 * the method {@link #isCryptoSchemeSupported} can be used to query if a given
36 * scheme is supported on the device.
37 *
38 * <a name="Callbacks"></a>
39 * <h3>Callbacks</h3>
40 * <p>Applications may want to register for informational events in order
41 * to be informed of some internal state update during playback or streaming.
42 * Registration for these events is done via a call to
43 * {@link #setOnEventListener(OnInfoListener)}setOnInfoListener,
44 * In order to receive the respective callback
45 * associated with this listener, applications are required to create
46 * MediaDrm objects on a thread with its own Looper running (main UI
47 * thread by default has a Looper running).
48 *
49 * @hide -- don't expose yet
50 */
51public final class MediaDrm {
52
53    private final static String TAG = "MediaDrm";
54
55    private EventHandler mEventHandler;
56    private OnEventListener mOnEventListener;
57
58    private int mNativeContext;
59
60    /**
61     * Query if the given scheme identified by its UUID is supported on
62     * this device.
63     * @param uuid The UUID of the crypto scheme.
64     */
65    public static final boolean isCryptoSchemeSupported(UUID uuid) {
66        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid));
67    }
68
69    private static final byte[] getByteArrayFromUUID(UUID uuid) {
70        long msb = uuid.getMostSignificantBits();
71        long lsb = uuid.getLeastSignificantBits();
72
73        byte[] uuidBytes = new byte[16];
74        for (int i = 0; i < 8; ++i) {
75            uuidBytes[i] = (byte)(msb >>> (8 * (7 - i)));
76            uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i)));
77        }
78
79        return uuidBytes;
80    }
81
82    private static final native boolean isCryptoSchemeSupportedNative(byte[] uuid);
83
84    /**
85     * Instantiate a MediaDrm object using opaque, crypto scheme specific
86     * data.
87     * @param uuid The UUID of the crypto scheme.
88     */
89    public MediaDrm(UUID uuid) throws MediaDrmException {
90        Looper looper;
91        if ((looper = Looper.myLooper()) != null) {
92            mEventHandler = new EventHandler(this, looper);
93        } else if ((looper = Looper.getMainLooper()) != null) {
94            mEventHandler = new EventHandler(this, looper);
95        } else {
96            mEventHandler = null;
97        }
98
99        /* Native setup requires a weak reference to our object.
100         * It's easier to create it here than in C++.
101         */
102        native_setup(new WeakReference<MediaDrm>(this),
103                     getByteArrayFromUUID(uuid));
104    }
105
106    /**
107     * Register a callback to be invoked when an event occurs
108     *
109     * @param listener the callback that will be run
110     */
111    public void setOnEventListener(OnEventListener listener)
112    {
113        mOnEventListener = listener;
114    }
115
116    /**
117     * Interface definition for a callback to be invoked when a drm event
118     * occurs.
119     */
120    public interface OnEventListener
121    {
122        /**
123         * Called when an event occurs that requires the app to be notified
124         *
125         * @param md the MediaDrm object on which the event occurred
126         * @param sessionId the DRM session ID on which the event occurred
127         * @param event indicates the event type
128         * @param extra an secondary error code
129         * @param data optional byte array of data that may be associated with the event
130         */
131        void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data);
132    }
133
134    public static final int MEDIA_DRM_EVENT_PROVISION_REQUIRED = 1;
135    public static final int MEDIA_DRM_EVENT_KEY_REQUIRED = 2;
136    public static final int MEDIA_DRM_EVENT_KEY_EXPIRED = 3;
137    public static final int MEDIA_DRM_EVENT_VENDOR_DEFINED = 4;
138
139    /* Do not change these values without updating their counterparts
140     * in include/media/mediadrm.h!
141     */
142    private static final int DRM_EVENT = 200;
143    private class EventHandler extends Handler
144    {
145        private MediaDrm mMediaDrm;
146
147        public EventHandler(MediaDrm md, Looper looper) {
148            super(looper);
149            mMediaDrm = md;
150        }
151
152        @Override
153        public void handleMessage(Message msg) {
154            if (mMediaDrm.mNativeContext == 0) {
155                Log.w(TAG, "MediaDrm went away with unhandled events");
156                return;
157            }
158            switch(msg.what) {
159
160            case DRM_EVENT:
161                Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
162
163                if (mOnEventListener != null) {
164                    Bundle bundle = msg.getData();
165                    byte[] sessionId = bundle.getByteArray("sessionId");
166                    byte[] data = bundle.getByteArray("data");
167                    mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
168                }
169                return;
170
171            default:
172                Log.e(TAG, "Unknown message type " + msg.what);
173                return;
174            }
175        }
176    }
177
178    /*
179     * Called from native code when an interesting event happens.  This method
180     * just uses the EventHandler system to post the event back to the main app thread.
181     * We use a weak reference to the original MediaPlayer object so that the native
182     * code is safe from the object disappearing from underneath it.  (This is
183     * the cookie passed to native_setup().)
184     */
185    private static void postEventFromNative(Object mediadrm_ref,
186                                            int what, int arg1, int arg2, Object obj)
187    {
188        MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get();
189        if (md == null) {
190            return;
191        }
192        if (md.mEventHandler != null) {
193            Message m = md.mEventHandler.obtainMessage(what, arg1, arg2, obj);
194            md.mEventHandler.sendMessage(m);
195        }
196    }
197
198    /**
199     *  Open a new session with the MediaDrm object.  A session ID is returned.
200     */
201    public native byte[] openSession() throws MediaDrmException;
202
203    /**
204     *  Close a session on the MediaDrm object that was previously opened
205     *  with {@link #openSession}.
206     */
207    public native void closeSession(byte[] sessionId) throws MediaDrmException;
208
209    public static final int MEDIA_DRM_KEY_TYPE_STREAMING = 1;
210    public static final int MEDIA_DRM_KEY_TYPE_OFFLINE = 2;
211
212    public final class KeyRequest {
213        public KeyRequest() {}
214        public byte[] data;
215        public String defaultUrl;
216    };
217
218    /**
219     * A key request/response exchange occurs between the app and a license
220     * server to obtain the keys to decrypt encrypted content.  getKeyRequest()
221     * is used to obtain an opaque key request byte array that is delivered to the
222     * license server.  The opaque key request byte array is returned in
223     * KeyRequest.data.  The recommended URL to deliver the key request to is
224     * returned in KeyRequest.defaultUrl.
225     *
226     * After the app has received the key request response from the server,
227     * it should deliver to the response to the DRM engine plugin using the method
228     * {@link #provideKeyResponse}.
229     *
230     * @param sessonId the session ID for the drm session
231     * @param init container-specific data, its meaning is interpreted based on the
232     * mime type provided in the mimeType parameter.  It could contain, for example,
233     * the content ID, key ID or other data obtained from the content metadata that is
234     * required in generating the key request.
235     * @param mimeType identifies the mime type of the content
236     * @param keyType specifes if the request is for streaming or offline content
237     * @param optionalParameters are included in the key request message to
238     * allow a client application to provide additional message parameters to the server.
239     */
240    public native KeyRequest getKeyRequest(byte[] sessionId, byte[] init,
241                                           String mimeType, int keyType,
242                                           HashMap<String, String> optionalParameters)
243        throws MediaDrmException;
244
245    /**
246     * A key response is received from the license server by the app, then it is
247     * provided to the DRM engine plugin using provideKeyResponse. The byte array
248     * returned is a keySetId that can be used to later restore the keys to a new
249     * session with the method {@link restoreKeys}, enabling offline key use.
250     *
251     * @param sessionId the session ID for the DRM session
252     * @param response the byte array response from the server
253     */
254    public native byte[] provideKeyResponse(byte[] sessionId, byte[] response)
255        throws MediaDrmException;
256
257    /**
258     * Restore persisted offline keys into a new session.  keySetId identifies the
259     * keys to load, obtained from a prior call to {@link provideKeyResponse}.
260     *
261     * @param sessionId the session ID for the DRM session
262     * @param keySetId identifies the saved key set to restore
263     */
264    public native void restoreKeys(byte[] sessionId, byte[] keySetId)
265        throws MediaDrmException;
266
267    /**
268     * Remove the persisted keys associated with an offline license.  Keys are persisted
269     * when {@link provideKeyResponse} is called with keys obtained from the method
270     * {@link getKeyRequest} using keyType = MEDIA_DRM_KEY_TYPE_OFFLINE.
271     *
272     * @param keySetId identifies the saved key set to remove
273     */
274    public native void removeKeys(byte[] keySetId) throws MediaDrmException;
275
276    /**
277     * Request an informative description of the key status for the session.  The status is
278     * in the form of {name, value} pairs.  Since DRM license policies vary by vendor,
279     * the specific status field names are determined by each DRM vendor.  Refer to your
280     * DRM provider documentation for definitions of the field names for a particular
281     * DRM engine plugin.
282     *
283     * @param sessionId the session ID for the DRM session
284     */
285    public native HashMap<String, String> queryKeyStatus(byte[] sessionId)
286        throws MediaDrmException;
287
288    public final class ProvisionRequest {
289        public ProvisionRequest() {}
290        public byte[] data;
291        public String defaultUrl;
292    }
293
294    /**
295     * A provision request/response exchange occurs between the app and a provisioning
296     * server to retrieve a device certificate.  If provisionining is required, the
297     * MEDIA_DRM_EVENT_PROVISION_REQUIRED event will be sent to the event handler.
298     * getProvisionRequest is used to obtain the opaque provision request byte array that
299     * should be delivered to the provisioning server. The provision request byte array
300     * is returned in ProvisionRequest.data. The recommended URL to deliver the provision
301     * request to is returned in ProvisionRequest.defaultUrl.
302     */
303    public native ProvisionRequest getProvisionRequest() throws MediaDrmException;
304
305    /**
306     * After a provision response is received by the app, it is provided to the DRM
307     * engine plugin using this method.
308     *
309     * @param response the opaque provisioning response byte array to provide to the
310     * DRM engine plugin.
311     */
312    public native void provideProvisionResponse(byte[] response)
313        throws MediaDrmException;
314
315    /**
316     * A means of enforcing the contractual requirement for a concurrent stream limit
317     * per subscriber across devices is provided via SecureStop.  SecureStop is a means
318     * of securely monitoring the lifetime of sessions. Since playback on a device can
319     * be interrupted due to reboot, power failure, etc. a means of persisting the
320     * lifetime information on the device is needed.
321     *
322     * A signed version of the sessionID is written to persistent storage on the device
323     * when each MediaCrypto object is created. The sessionID is signed by the device
324     * private key to prevent tampering.
325     *
326     * In the normal case, playback will be completed, the session destroyed and the
327     * Secure Stops will be queried. The App queries secure stops and forwards the
328     * secure stop message to the server which verifies the signature and notifies the
329     * server side database that the session destruction has been confirmed. The persisted
330     * record on the client is only removed after positive confirmation that the server
331     * received the message using releaseSecureStops().
332     */
333    public native List<byte[]> getSecureStops() throws MediaDrmException;
334
335
336    /**
337     * Process the SecureStop server response message ssRelease.  After authenticating
338     * the message, remove the SecureStops identiied in the response.
339     *
340     * @param ssRelease the server response indicating which secure stops to release
341     */
342    public native void releaseSecureStops(byte[] ssRelease)
343        throws MediaDrmException;
344
345
346    /**
347     * Read a DRM engine plugin property value, given the property name string.  There are
348     * several forms of property access functions, depending on the data type returned.
349     *
350     * Standard fields names are:
351     *   vendor         String - identifies the maker of the DRM engine plugin
352     *   version        String - identifies the version of the DRM engine plugin
353     *   description    String - describes the DRM engine plugin
354     *   deviceUniqueId byte[] - The device unique identifier is established during device
355     *                           provisioning and provides a means of uniquely identifying
356     *                           each device
357     *   algorithms     String - a comma-separate list of cipher and mac algorithms supported
358     *                           by CryptoSession.  The list may be empty if the DRM engine
359     *                           plugin does not support CryptoSession operations.
360     */
361    public native String getPropertyString(String propertyName)
362        throws MediaDrmException;
363
364    public native byte[] getPropertyByteArray(String propertyName)
365        throws MediaDrmException;
366
367    /**
368     * Write a DRM engine plugin property value.  There are several forms of
369     * property setting functions, depending on the data type being set.
370     */
371    public native void setPropertyString(String propertyName, String value)
372        throws MediaDrmException;
373
374    public native void setPropertyByteArray(String propertyName, byte[] value)
375        throws MediaDrmException;
376
377    /**
378     * In addition to supporting decryption of DASH Common Encrypted Media, the
379     * MediaDrm APIs provide the ability to securely deliver session keys from
380     * an operator's session key server to a client device, based on the factory-installed
381     * root of trust, and provide the ability to do encrypt, decrypt, sign and verify
382     * with the session key on arbitrary user data.
383     *
384     * The CryptoSession class implements generic encrypt/decrypt/sign/verify methods
385     * based on the established session keys.  These keys are exchanged using the
386     * getKeyRequest/provideKeyResponse methods.
387     *
388     * Applications of this capability could include securing various types of
389     * purchased or private content, such as applications, books and other media,
390     * photos or media delivery protocols.
391     *
392     * Operators can create session key servers that are functionally similar to a
393     * license key server, except that instead of receiving license key requests and
394     * providing encrypted content keys which are used specifically to decrypt A/V media
395     * content, the session key server receives session key requests and provides
396     * encrypted session keys which can be used for general purpose crypto operations.
397     */
398
399    private static final native void setCipherAlgorithmNative(MediaDrm drm, byte[] sessionId,
400                                                              String algorithm);
401
402    private static final native void setMacAlgorithmNative(MediaDrm drm, byte[] sessionId,
403                                                           String algorithm);
404
405    private static final native byte[] encryptNative(MediaDrm drm, byte[] sessionId,
406                                                     byte[] keyId, byte[] input, byte[] iv);
407
408    private static final native byte[] decryptNative(MediaDrm drm, byte[] sessionId,
409                                                     byte[] keyId, byte[] input, byte[] iv);
410
411    private static final native byte[] signNative(MediaDrm drm, byte[] sessionId,
412                                                  byte[] keyId, byte[] message);
413
414    private static final native boolean verifyNative(MediaDrm drm, byte[] sessionId,
415                                                     byte[] keyId, byte[] message,
416                                                     byte[] signature);
417
418    public final class CryptoSession {
419        private MediaDrm mDrm;
420        private byte[] mSessionId;
421
422        /**
423         * Construct a CryptoSession which can be used to encrypt, decrypt,
424         * sign and verify messages or data using the session keys established
425         * for the session using methods {@link getKeyRequest} and
426         * {@link provideKeyResponse} using a session key server.
427         *
428         * @param sessionId the session ID for the session containing keys
429         * to be used for encrypt, decrypt, sign and/or verify
430         *
431         * @param cipherAlgorithm the algorithm to use for encryption and
432         * decryption ciphers. The algorithm string conforms to JCA Standard
433         * Names for Cipher Transforms and is case insensitive.  For example
434         * "AES/CBC/PKCS5Padding".
435         *
436         * @param macAlgorithm the algorithm to use for sign and verify
437         * The algorithm string conforms to JCA Standard Names for Mac
438         * Algorithms and is case insensitive.  For example "HmacSHA256".
439         *
440         * The list of supported algorithms for a DRM engine plugin can be obtained
441         * using the method {@link getPropertyString("algorithms")}
442         */
443
444        public CryptoSession(MediaDrm drm, byte[] sessionId,
445                             String cipherAlgorithm, String macAlgorithm)
446            throws MediaDrmException {
447            mSessionId = sessionId;
448            mDrm = drm;
449            setCipherAlgorithmNative(drm, sessionId, cipherAlgorithm);
450            setMacAlgorithmNative(drm, sessionId, macAlgorithm);
451        }
452
453        public byte[] encrypt(byte[] keyid, byte[] input, byte[] iv) {
454            return encryptNative(mDrm, mSessionId, keyid, input, iv);
455        }
456
457        public byte[] decrypt(byte[] keyid, byte[] input, byte[] iv) {
458            return decryptNative(mDrm, mSessionId, keyid, input, iv);
459        }
460
461        public byte[] sign(byte[] keyid, byte[] message) {
462            return signNative(mDrm, mSessionId, keyid, message);
463        }
464        public boolean verify(byte[] keyid, byte[] message, byte[] signature) {
465            return verifyNative(mDrm, mSessionId, keyid, message, signature);
466        }
467    };
468
469    public CryptoSession getCryptoSession(byte[] sessionId,
470                                          String cipherAlgorithm,
471                                          String macAlgorithm)
472        throws MediaDrmException {
473        return new CryptoSession(this, sessionId, cipherAlgorithm, macAlgorithm);
474    }
475
476    @Override
477    protected void finalize() {
478        native_finalize();
479    }
480
481    public native final void release();
482    private static native final void native_init();
483
484    private native final void native_setup(Object mediadrm_this, byte[] uuid)
485        throws MediaDrmException;
486
487    private native final void native_finalize();
488
489    static {
490        System.loadLibrary("media_jni");
491        native_init();
492    }
493}
494