• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.media;
6 
7 import android.media.MediaCrypto;
8 import android.media.MediaDrm;
9 import android.os.AsyncTask;
10 import android.os.Build;
11 import android.os.Handler;
12 import android.util.Log;
13 
14 import org.apache.http.HttpResponse;
15 import org.apache.http.client.ClientProtocolException;
16 import org.apache.http.client.HttpClient;
17 import org.apache.http.client.methods.HttpPost;
18 import org.apache.http.impl.client.DefaultHttpClient;
19 import org.apache.http.util.EntityUtils;
20 import org.chromium.base.CalledByNative;
21 import org.chromium.base.JNINamespace;
22 
23 import java.io.IOException;
24 import java.nio.ByteBuffer;
25 import java.nio.ByteOrder;
26 import java.util.ArrayDeque;
27 import java.util.HashMap;
28 import java.util.UUID;
29 
30 /**
31  * A wrapper of the android MediaDrm class. Each MediaDrmBridge manages multiple
32  * sessions for a single MediaSourcePlayer.
33  */
34 @JNINamespace("media")
35 public class MediaDrmBridge {
36     // Implementation Notes:
37     // - A media crypto session (mMediaCryptoSession) is opened after MediaDrm
38     //   is created. This session will be added to mSessionIds.
39     //   a) In multiple session mode, this session will only be used to create
40     //      the MediaCrypto object. It's associated mime type is always null and
41     //      it's session ID is always INVALID_SESSION_ID.
42     //   b) In single session mode, this session will be used to create the
43     //      MediaCrypto object and will be used to call getKeyRequest() and
44     //      manage all keys.  The session ID will always be the lastest session
45     //      ID passed by the caller.
46     // - Each createSession() call creates a new session. All sessions are
47     //   managed in mSessionIds.
48     // - Whenever NotProvisionedException is thrown, we will clean up the
49     //   current state and start the provisioning process.
50     // - When provisioning is finished, we will try to resume suspended
51     //   operations:
52     //   a) Create the media crypto session if it's not created.
53     //   b) Finish createSession() if previous createSession() was interrupted
54     //      by a NotProvisionedException.
55     // - Whenever an unexpected error occurred, we'll call release() to release
56     //   all resources and clear all states. In that case all calls to this
57     //   object will be no-op. All public APIs and callbacks should check
58     //   mMediaBridge to make sure release() hasn't been called. Also, we call
59     //   release() immediately after the error happens (e.g. after mMediaDrm)
60     //   calls. Indirect calls should not call release() again to avoid
61     //   duplication (even though it doesn't hurt to call release() twice).
62 
63     private static final String TAG = "MediaDrmBridge";
64     private static final String SECURITY_LEVEL = "securityLevel";
65     private static final String PRIVACY_MODE = "privacyMode";
66     private static final String SESSION_SHARING = "sessionSharing";
67     private static final String ENABLE = "enable";
68     private static final int INVALID_SESSION_ID = 0;
69 
70     private MediaDrm mMediaDrm;
71     private long mNativeMediaDrmBridge;
72     private UUID mSchemeUUID;
73     private Handler mHandler;
74 
75     // In this mode, we only open one session, i.e. mMediaCryptoSession.
76     private boolean mSingleSessionMode;
77 
78     // A session only for the purpose of creating a MediaCrypto object.
79     // This session is opened when createSession() is called for the first
80     // time.
81     // - In multiple session mode, all following createSession() calls
82     // should create a new session and use it to call getKeyRequest(). No
83     // getKeyRequest() should ever be called on this media crypto session.
84     // - In single session mode, all createSession() calls use the same
85     // media crypto session. When createSession() is called with a new
86     // initData, previously added keys may not be available anymore.
87     private ByteBuffer mMediaCryptoSession;
88     private MediaCrypto mMediaCrypto;
89 
90     // The map of all opened sessions to their session reference IDs.
91     private HashMap<ByteBuffer, Integer> mSessionIds;
92     // The map of all opened sessions to their mime types.
93     private HashMap<ByteBuffer, String> mSessionMimeTypes;
94 
95     // The queue of all pending createSession() data.
96     private ArrayDeque<PendingCreateSessionData> mPendingCreateSessionDataQueue;
97 
98     private boolean mResetDeviceCredentialsPending;
99 
100     // MediaDrmBridge is waiting for provisioning response from the server.
101     //
102     // Notes about NotProvisionedException: This exception can be thrown in a
103     // lot of cases. To streamline implementation, we do not catch it in private
104     // non-native methods and only catch it in public APIs.
105     private boolean mProvisioningPending;
106 
107     /**
108      *  This class contains data needed to call createSession().
109      */
110     private static class PendingCreateSessionData {
111         private final int mSessionId;
112         private final byte[] mInitData;
113         private final String mMimeType;
114 
PendingCreateSessionData(int sessionId, byte[] initData, String mimeType)115         private PendingCreateSessionData(int sessionId, byte[] initData, String mimeType) {
116             mSessionId = sessionId;
117             mInitData = initData;
118             mMimeType = mimeType;
119         }
120 
sessionId()121         private int sessionId() { return mSessionId; }
initData()122         private byte[] initData() { return mInitData; }
mimeType()123         private String mimeType() { return mMimeType; }
124     }
125 
getUUIDFromBytes(byte[] data)126     private static UUID getUUIDFromBytes(byte[] data) {
127         if (data.length != 16) {
128             return null;
129         }
130         long mostSigBits = 0;
131         long leastSigBits = 0;
132         for (int i = 0; i < 8; i++) {
133             mostSigBits = (mostSigBits << 8) | (data[i] & 0xff);
134         }
135         for (int i = 8; i < 16; i++) {
136             leastSigBits = (leastSigBits << 8) | (data[i] & 0xff);
137         }
138         return new UUID(mostSigBits, leastSigBits);
139     }
140 
141     /**
142      *  Gets session associated with the sessionId.
143      *
144      *  @return session if sessionId maps a valid opened session. Returns null
145      *  otherwise.
146      */
getSession(int sessionId)147     private ByteBuffer getSession(int sessionId) {
148         for (ByteBuffer session : mSessionIds.keySet()) {
149             if (mSessionIds.get(session) == sessionId) {
150                 return session;
151             }
152         }
153         return null;
154     }
155 
MediaDrmBridge(UUID schemeUUID, long nativeMediaDrmBridge, boolean singleSessionMode)156     private MediaDrmBridge(UUID schemeUUID, long nativeMediaDrmBridge, boolean singleSessionMode)
157             throws android.media.UnsupportedSchemeException {
158         mSchemeUUID = schemeUUID;
159         mMediaDrm = new MediaDrm(schemeUUID);
160         mNativeMediaDrmBridge = nativeMediaDrmBridge;
161         mHandler = new Handler();
162         mSingleSessionMode = singleSessionMode;
163         mSessionIds = new HashMap<ByteBuffer, Integer>();
164         mSessionMimeTypes = new HashMap<ByteBuffer, String>();
165         mPendingCreateSessionDataQueue = new ArrayDeque<PendingCreateSessionData>();
166         mResetDeviceCredentialsPending = false;
167         mProvisioningPending = false;
168 
169         mMediaDrm.setOnEventListener(new MediaDrmListener());
170         mMediaDrm.setPropertyString(PRIVACY_MODE, ENABLE);
171         if (!mSingleSessionMode) {
172             mMediaDrm.setPropertyString(SESSION_SHARING, ENABLE);
173         }
174 
175         // We could open a MediaCrypto session here to support faster start of
176         // clear lead (no need to wait for createSession()). But on
177         // Android, memory and battery resources are precious and we should
178         // only create a session when we are sure we'll use it.
179         // TODO(xhwang): Investigate other options to support fast start.
180     }
181 
182     /**
183      * Create a MediaCrypto object.
184      *
185      * @return whether a MediaCrypto object is successfully created.
186      */
createMediaCrypto()187     private boolean createMediaCrypto() throws android.media.NotProvisionedException {
188         if (mMediaDrm == null) {
189             return false;
190         }
191         assert !mProvisioningPending;
192         assert mMediaCryptoSession == null;
193         assert mMediaCrypto == null;
194 
195         // Open media crypto session.
196         mMediaCryptoSession = openSession();
197         if (mMediaCryptoSession == null) {
198             Log.e(TAG, "Cannot create MediaCrypto Session.");
199             return false;
200         }
201         Log.d(TAG, "MediaCrypto Session created: " + mMediaCryptoSession);
202 
203         // Create MediaCrypto object.
204         try {
205             if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) {
206                 final byte[] mediaCryptoSession = mMediaCryptoSession.array();
207                 mMediaCrypto = new MediaCrypto(mSchemeUUID, mediaCryptoSession);
208                 assert mMediaCrypto != null;
209                 Log.d(TAG, "MediaCrypto successfully created!");
210                 mSessionIds.put(mMediaCryptoSession, INVALID_SESSION_ID);
211                 // Notify the native code that MediaCrypto is ready.
212                 nativeOnMediaCryptoReady(mNativeMediaDrmBridge);
213                 return true;
214             } else {
215                 Log.e(TAG, "Cannot create MediaCrypto for unsupported scheme.");
216             }
217         } catch (android.media.MediaCryptoException e) {
218             Log.e(TAG, "Cannot create MediaCrypto", e);
219         }
220 
221         release();
222         return false;
223     }
224 
225     /**
226      * Open a new session..
227      *
228      * @return the session opened. Returns null if unexpected error happened.
229      */
openSession()230     private ByteBuffer openSession() throws android.media.NotProvisionedException {
231         assert mMediaDrm != null;
232         try {
233             byte[] session = mMediaDrm.openSession();
234             // ByteBuffer.wrap() is backed by the byte[]. Make a clone here in
235             // case the underlying byte[] is modified.
236             return ByteBuffer.wrap(session.clone());
237         } catch (java.lang.RuntimeException e) {  // TODO(xhwang): Drop this?
238             Log.e(TAG, "Cannot open a new session", e);
239             release();
240             return null;
241         } catch (android.media.NotProvisionedException e) {
242             // Throw NotProvisionedException so that we can startProvisioning().
243             throw e;
244         } catch (android.media.MediaDrmException e) {
245             // Other MediaDrmExceptions (e.g. ResourceBusyException) are not
246             // recoverable.
247             Log.e(TAG, "Cannot open a new session", e);
248             release();
249             return null;
250         }
251     }
252 
253     /**
254      * Close a session.
255      *
256      * @param session to be closed.
257      */
closeSession(ByteBuffer session)258     private void closeSession(ByteBuffer session) {
259         assert mMediaDrm != null;
260         mMediaDrm.closeSession(session.array());
261     }
262 
263     /**
264      * Check whether the crypto scheme is supported for the given container.
265      * If |containerMimeType| is an empty string, we just return whether
266      * the crypto scheme is supported.
267      *
268      * @return true if the container and the crypto scheme is supported, or
269      * false otherwise.
270      */
271     @CalledByNative
isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType)272     private static boolean isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType) {
273         UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
274 
275         if (containerMimeType.isEmpty()) {
276             return MediaDrm.isCryptoSchemeSupported(cryptoScheme);
277         }
278 
279         return MediaDrm.isCryptoSchemeSupported(cryptoScheme, containerMimeType);
280     }
281 
282     /**
283      * Create a new MediaDrmBridge from the crypto scheme UUID.
284      *
285      * @param schemeUUID Crypto scheme UUID.
286      * @param securityLevel Security level to be used.
287      * @param nativeMediaDrmBridge Native object of this class.
288      */
289     @CalledByNative
create(byte[] schemeUUID, long nativeMediaDrmBridge)290     private static MediaDrmBridge create(byte[] schemeUUID, long nativeMediaDrmBridge) {
291         UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
292         if (cryptoScheme == null || !MediaDrm.isCryptoSchemeSupported(cryptoScheme)) {
293             return null;
294         }
295 
296         boolean singleSessionMode = false;
297         if (Build.VERSION.RELEASE.equals("4.4")) {
298             singleSessionMode = true;
299         }
300         Log.d(TAG, "MediaDrmBridge uses " +
301                 (singleSessionMode ? "single" : "multiple") + "-session mode.");
302 
303         MediaDrmBridge mediaDrmBridge = null;
304         try {
305             mediaDrmBridge = new MediaDrmBridge(
306                 cryptoScheme, nativeMediaDrmBridge, singleSessionMode);
307             Log.d(TAG, "MediaDrmBridge successfully created.");
308         } catch (android.media.UnsupportedSchemeException e) {
309             Log.e(TAG, "Unsupported DRM scheme", e);
310         } catch (java.lang.IllegalArgumentException e) {
311             Log.e(TAG, "Failed to create MediaDrmBridge", e);
312         } catch (java.lang.IllegalStateException e) {
313             Log.e(TAG, "Failed to create MediaDrmBridge", e);
314         }
315 
316         return mediaDrmBridge;
317     }
318 
319     /**
320      * Set the security level that the MediaDrm object uses.
321      * This function should be called right after we construct MediaDrmBridge
322      * and before we make any other calls.
323      */
324     @CalledByNative
setSecurityLevel(String securityLevel)325     private boolean setSecurityLevel(String securityLevel) {
326         if (mMediaDrm == null || mMediaCrypto != null) {
327             return false;
328         }
329 
330         String currentSecurityLevel = mMediaDrm.getPropertyString(SECURITY_LEVEL);
331         Log.e(TAG, "Security level: current " + currentSecurityLevel + ", new " + securityLevel);
332         if (securityLevel.equals(currentSecurityLevel)) {
333             // No need to set the same security level again. This is not just
334             // a shortcut! Setting the same security level actually causes an
335             // exception in MediaDrm!
336             return true;
337         }
338 
339         try {
340             mMediaDrm.setPropertyString(SECURITY_LEVEL, securityLevel);
341             return true;
342         } catch (java.lang.IllegalArgumentException e) {
343             Log.e(TAG, "Failed to set security level " + securityLevel, e);
344         } catch (java.lang.IllegalStateException e) {
345             Log.e(TAG, "Failed to set security level " + securityLevel, e);
346         }
347 
348         Log.e(TAG, "Security level " + securityLevel + " not supported!");
349         return false;
350     }
351 
352     /**
353      * Return the MediaCrypto object if available.
354      */
355     @CalledByNative
getMediaCrypto()356     private MediaCrypto getMediaCrypto() {
357         return mMediaCrypto;
358     }
359 
360     /**
361      * Reset the device DRM credentials.
362      */
363     @CalledByNative
resetDeviceCredentials()364     private void resetDeviceCredentials() {
365         mResetDeviceCredentialsPending = true;
366         MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
367         PostRequestTask postTask = new PostRequestTask(request.getData());
368         postTask.execute(request.getDefaultUrl());
369     }
370 
371     /**
372      * Release the MediaDrmBridge object.
373      */
374     @CalledByNative
release()375     private void release() {
376         // Do not reset mHandler and mNativeMediaDrmBridge so that we can still
377         // post KeyError back to native code.
378 
379         mPendingCreateSessionDataQueue.clear();
380         mPendingCreateSessionDataQueue = null;
381 
382         for (ByteBuffer session : mSessionIds.keySet()) {
383             closeSession(session);
384         }
385         mSessionIds.clear();
386         mSessionIds = null;
387         mSessionMimeTypes.clear();
388         mSessionMimeTypes = null;
389 
390         // This session was closed in the "for" loop above.
391         mMediaCryptoSession = null;
392 
393         if (mMediaCrypto != null) {
394             mMediaCrypto.release();
395             mMediaCrypto = null;
396         }
397 
398         if (mMediaDrm != null) {
399             mMediaDrm.release();
400             mMediaDrm = null;
401         }
402     }
403 
404     /**
405      * Get a key request.
406      *
407      * @param session Session on which we need to get the key request.
408      * @param data Data needed to get the key request.
409      * @param mime Mime type to get the key request.
410      *
411      * @return the key request.
412      */
getKeyRequest(ByteBuffer session, byte[] data, String mime)413     private MediaDrm.KeyRequest getKeyRequest(ByteBuffer session, byte[] data, String mime)
414             throws android.media.NotProvisionedException {
415         assert mMediaDrm != null;
416         assert mMediaCrypto != null;
417         assert !mProvisioningPending;
418 
419         HashMap<String, String> optionalParameters = new HashMap<String, String>();
420         MediaDrm.KeyRequest request = mMediaDrm.getKeyRequest(
421                 session.array(), data, mime, MediaDrm.KEY_TYPE_STREAMING, optionalParameters);
422         String result = (request != null) ? "successed" : "failed";
423         Log.d(TAG, "getKeyRequest " + result + "!");
424         return request;
425     }
426 
427     /**
428      * Save data to |mPendingCreateSessionDataQueue| so that we can resume the
429      * createSession() call later.
430      */
savePendingCreateSessionData(int sessionId, byte[] initData, String mime)431     private void savePendingCreateSessionData(int sessionId, byte[] initData, String mime) {
432         Log.d(TAG, "savePendingCreateSessionData()");
433         mPendingCreateSessionDataQueue.offer(
434                 new PendingCreateSessionData(sessionId, initData, mime));
435     }
436 
437     /**
438      * Process all pending createSession() calls synchronously.
439      */
processPendingCreateSessionData()440     private void processPendingCreateSessionData() {
441         Log.d(TAG, "processPendingCreateSessionData()");
442         assert mMediaDrm != null;
443 
444         // Check mMediaDrm != null because error may happen in createSession().
445         // Check !mProvisioningPending because NotProvisionedException may be
446         // thrown in createSession().
447         while (mMediaDrm != null && !mProvisioningPending &&
448                 !mPendingCreateSessionDataQueue.isEmpty()) {
449             PendingCreateSessionData pendingData = mPendingCreateSessionDataQueue.poll();
450             int sessionId = pendingData.sessionId();
451             byte[] initData = pendingData.initData();
452             String mime = pendingData.mimeType();
453             createSession(sessionId, initData, mime);
454         }
455     }
456 
457     /**
458      * Process pending operations asynchrnously.
459      */
resumePendingOperations()460     private void resumePendingOperations() {
461         mHandler.post(new Runnable(){
462             @Override
463             public void run() {
464                 processPendingCreateSessionData();
465             }
466         });
467     }
468 
469     /**
470      * Create a session with |sessionId|, |initData| and |mime|.
471      * In multiple session mode, a new session will be open. In single session
472      * mode, the mMediaCryptoSession will be used.
473      *
474      * @param sessionId ID for the session to be created.
475      * @param initData Data needed to generate the key request.
476      * @param mime Mime type.
477      */
478     @CalledByNative
createSession(int sessionId, byte[] initData, String mime)479     private void createSession(int sessionId, byte[] initData, String mime) {
480         Log.d(TAG, "createSession()");
481         if (mMediaDrm == null) {
482             Log.e(TAG, "createSession() called when MediaDrm is null.");
483             return;
484         }
485 
486         if (mProvisioningPending) {
487             assert mMediaCrypto == null;
488             savePendingCreateSessionData(sessionId, initData, mime);
489             return;
490         }
491 
492         boolean newSessionOpened = false;
493         ByteBuffer session = null;
494         try {
495             // Create MediaCrypto if necessary.
496             if (mMediaCrypto == null && !createMediaCrypto()) {
497               onSessionError(sessionId);
498                 return;
499             }
500             assert mMediaCrypto != null;
501             assert mSessionIds.containsKey(mMediaCryptoSession);
502 
503             if (mSingleSessionMode) {
504                 session = mMediaCryptoSession;
505                 if (mSessionMimeTypes.get(session) != null &&
506                         !mSessionMimeTypes.get(session).equals(mime)) {
507                     Log.e(TAG, "Only one mime type is supported in single session mode.");
508                     onSessionError(sessionId);
509                     return;
510                 }
511             } else {
512                 session = openSession();
513                 if (session == null) {
514                     Log.e(TAG, "Cannot open session in createSession().");
515                     onSessionError(sessionId);
516                     return;
517                 }
518                 newSessionOpened = true;
519                 assert !mSessionIds.containsKey(session);
520             }
521 
522             MediaDrm.KeyRequest request = null;
523             request = getKeyRequest(session, initData, mime);
524             if (request == null) {
525                 if (newSessionOpened) {
526                     closeSession(session);
527                 }
528                 onSessionError(sessionId);
529                 return;
530             }
531 
532             onSessionCreated(sessionId, getWebSessionId(session));
533             onSessionMessage(sessionId, request);
534             if (newSessionOpened) {
535                 Log.d(TAG, "createSession(): Session " + getWebSessionId(session) +
536                         " (" + sessionId + ") created.");
537             }
538 
539             mSessionIds.put(session, sessionId);
540             mSessionMimeTypes.put(session, mime);
541         } catch (android.media.NotProvisionedException e) {
542             Log.e(TAG, "Device not provisioned", e);
543             if (newSessionOpened) {
544                 closeSession(session);
545             }
546             savePendingCreateSessionData(sessionId, initData, mime);
547             startProvisioning();
548         }
549     }
550 
551     /**
552      * Returns whether |sessionId| is a valid key session, excluding the media
553      * crypto session in multi-session mode.
554      *
555      * @param sessionId Crypto session Id.
556      */
sessionExists(ByteBuffer session)557     private boolean sessionExists(ByteBuffer session) {
558         if (mMediaCryptoSession == null) {
559             assert mSessionIds.isEmpty();
560             Log.e(TAG, "Session doesn't exist because media crypto session is not created.");
561             return false;
562         }
563         assert mSessionIds.containsKey(mMediaCryptoSession);
564 
565         if (mSingleSessionMode) {
566             return mMediaCryptoSession.equals(session);
567         }
568 
569         return !session.equals(mMediaCryptoSession) && mSessionIds.containsKey(session);
570     }
571 
572     /**
573      * Cancel a key request for a session Id.
574      *
575      * @param sessionId Reference ID of session to be released.
576      */
577     @CalledByNative
releaseSession(int sessionId)578     private void releaseSession(int sessionId) {
579         Log.d(TAG, "releaseSession(): " + sessionId);
580         if (mMediaDrm == null) {
581             Log.e(TAG, "releaseSession() called when MediaDrm is null.");
582             return;
583         }
584 
585         ByteBuffer session = getSession(sessionId);
586         if (session == null) {
587             Log.e(TAG, "Invalid sessionId in releaseSession.");
588             onSessionError(sessionId);
589             return;
590         }
591 
592         mMediaDrm.removeKeys(session.array());
593 
594         // We don't close the media crypto session in single session mode.
595         if (!mSingleSessionMode) {
596             Log.d(TAG, "Session " + sessionId + "closed.");
597             closeSession(session);
598             mSessionIds.remove(session);
599             onSessionClosed(sessionId);
600         }
601     }
602 
603     /**
604      * Add a key for a session Id.
605      *
606      * @param sessionId Reference ID of session to be updated.
607      * @param key Response data from the server.
608      */
609     @CalledByNative
updateSession(int sessionId, byte[] key)610     private void updateSession(int sessionId, byte[] key) {
611         Log.d(TAG, "updateSession(): " + sessionId);
612         if (mMediaDrm == null) {
613             Log.e(TAG, "updateSession() called when MediaDrm is null.");
614             return;
615         }
616 
617         // TODO(xhwang): We should be able to DCHECK this when WD EME is implemented.
618         ByteBuffer session = getSession(sessionId);
619         if (!sessionExists(session)) {
620             Log.e(TAG, "Invalid session in updateSession.");
621             onSessionError(sessionId);
622             return;
623         }
624 
625         try {
626             try {
627                 mMediaDrm.provideKeyResponse(session.array(), key);
628             } catch (java.lang.IllegalStateException e) {
629                 // This is not really an exception. Some error code are incorrectly
630                 // reported as an exception.
631                 // TODO(qinmin): remove this exception catch when b/10495563 is fixed.
632                 Log.e(TAG, "Exception intentionally caught when calling provideKeyResponse()", e);
633             }
634             onSessionReady(sessionId);
635             Log.d(TAG, "Key successfully added for session " + sessionId);
636             return;
637         } catch (android.media.NotProvisionedException e) {
638             // TODO(xhwang): Should we handle this?
639             Log.e(TAG, "failed to provide key response", e);
640         } catch (android.media.DeniedByServerException e) {
641             Log.e(TAG, "failed to provide key response", e);
642         }
643         onSessionError(sessionId);
644         release();
645     }
646 
647     /**
648      * Return the security level of this DRM object.
649      */
650     @CalledByNative
getSecurityLevel()651     private String getSecurityLevel() {
652         if (mMediaDrm == null) {
653             Log.e(TAG, "getSecurityLevel() called when MediaDrm is null.");
654             return null;
655         }
656         return mMediaDrm.getPropertyString("securityLevel");
657     }
658 
startProvisioning()659     private void startProvisioning() {
660         Log.d(TAG, "startProvisioning");
661         assert mMediaDrm != null;
662         assert !mProvisioningPending;
663         mProvisioningPending = true;
664         MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
665         PostRequestTask postTask = new PostRequestTask(request.getData());
666         postTask.execute(request.getDefaultUrl());
667     }
668 
669     /**
670      * Called when the provision response is received.
671      *
672      * @param response Response data from the provision server.
673      */
onProvisionResponse(byte[] response)674     private void onProvisionResponse(byte[] response) {
675         Log.d(TAG, "onProvisionResponse()");
676         assert mProvisioningPending;
677         mProvisioningPending = false;
678 
679         // If |mMediaDrm| is released, there is no need to callback native.
680         if (mMediaDrm == null) {
681             return;
682         }
683 
684         boolean success = provideProvisionResponse(response);
685 
686         if (mResetDeviceCredentialsPending) {
687             nativeOnResetDeviceCredentialsCompleted(mNativeMediaDrmBridge, success);
688             mResetDeviceCredentialsPending = false;
689         }
690 
691         if (success) {
692             resumePendingOperations();
693         }
694     }
695 
696     /**
697      * Provide the provisioning response to MediaDrm.
698      * @returns false if the response is invalid or on error, true otherwise.
699      */
provideProvisionResponse(byte[] response)700     boolean provideProvisionResponse(byte[] response) {
701         if (response == null || response.length == 0) {
702             Log.e(TAG, "Invalid provision response.");
703             return false;
704         }
705 
706         try {
707             mMediaDrm.provideProvisionResponse(response);
708             return true;
709         } catch (android.media.DeniedByServerException e) {
710             Log.e(TAG, "failed to provide provision response", e);
711         } catch (java.lang.IllegalStateException e) {
712             Log.e(TAG, "failed to provide provision response", e);
713         }
714         return false;
715     }
716 
onSessionCreated(final int sessionId, final String webSessionId)717     private void onSessionCreated(final int sessionId, final String webSessionId) {
718         mHandler.post(new Runnable(){
719             @Override
720             public void run() {
721                 nativeOnSessionCreated(mNativeMediaDrmBridge, sessionId, webSessionId);
722             }
723         });
724     }
725 
onSessionMessage(final int sessionId, final MediaDrm.KeyRequest request)726     private void onSessionMessage(final int sessionId, final MediaDrm.KeyRequest request) {
727         mHandler.post(new Runnable(){
728             @Override
729             public void run() {
730                 nativeOnSessionMessage(mNativeMediaDrmBridge, sessionId,
731                         request.getData(), request.getDefaultUrl());
732             }
733         });
734     }
735 
onSessionReady(final int sessionId)736     private void onSessionReady(final int sessionId) {
737         mHandler.post(new Runnable() {
738             @Override
739             public void run() {
740                 nativeOnSessionReady(mNativeMediaDrmBridge, sessionId);
741             }
742         });
743     }
744 
onSessionClosed(final int sessionId)745     private void onSessionClosed(final int sessionId) {
746         mHandler.post(new Runnable() {
747             @Override
748             public void run() {
749                 nativeOnSessionClosed(mNativeMediaDrmBridge, sessionId);
750             }
751         });
752     }
753 
onSessionError(final int sessionId)754     private void onSessionError(final int sessionId) {
755         // TODO(qinmin): pass the error code to native.
756         mHandler.post(new Runnable() {
757             @Override
758             public void run() {
759                 nativeOnSessionError(mNativeMediaDrmBridge, sessionId);
760             }
761         });
762     }
763 
getWebSessionId(ByteBuffer session)764     private String getWebSessionId(ByteBuffer session) {
765         String webSessionId = null;
766         try {
767             webSessionId = new String(session.array(), "UTF-8");
768         } catch (java.io.UnsupportedEncodingException e) {
769             Log.e(TAG, "getWebSessionId failed", e);
770         } catch (java.lang.NullPointerException e) {
771             Log.e(TAG, "getWebSessionId failed", e);
772         }
773         return webSessionId;
774     }
775 
776     private class MediaDrmListener implements MediaDrm.OnEventListener {
777         @Override
onEvent( MediaDrm mediaDrm, byte[] session_array, int event, int extra, byte[] data)778         public void onEvent(
779                 MediaDrm mediaDrm, byte[] session_array, int event, int extra, byte[] data) {
780             if (session_array == null) {
781                 Log.e(TAG, "MediaDrmListener: Null session.");
782                 return;
783             }
784             ByteBuffer session = ByteBuffer.wrap(session_array);
785             if (!sessionExists(session)) {
786                 Log.e(TAG, "MediaDrmListener: Invalid session.");
787                 return;
788             }
789             Integer sessionId = mSessionIds.get(session);
790             if (sessionId == null || sessionId == INVALID_SESSION_ID) {
791                 Log.e(TAG, "MediaDrmListener: Invalid session ID.");
792                 return;
793             }
794             switch(event) {
795                 case MediaDrm.EVENT_PROVISION_REQUIRED:
796                     Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
797                     break;
798                 case MediaDrm.EVENT_KEY_REQUIRED:
799                     Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
800                     if (mProvisioningPending) {
801                         return;
802                     }
803                     String mime = mSessionMimeTypes.get(session);
804                     MediaDrm.KeyRequest request = null;
805                     try {
806                         request = getKeyRequest(session, data, mime);
807                     } catch (android.media.NotProvisionedException e) {
808                         Log.e(TAG, "Device not provisioned", e);
809                         startProvisioning();
810                         return;
811                     }
812                     if (request != null) {
813                         onSessionMessage(sessionId, request);
814                     } else {
815                         onSessionError(sessionId);
816                     }
817                     break;
818                 case MediaDrm.EVENT_KEY_EXPIRED:
819                     Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
820                     onSessionError(sessionId);
821                     break;
822                 case MediaDrm.EVENT_VENDOR_DEFINED:
823                     Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
824                     assert false;  // Should never happen.
825                     break;
826                 default:
827                     Log.e(TAG, "Invalid DRM event " + event);
828                     return;
829             }
830         }
831     }
832 
833     private class PostRequestTask extends AsyncTask<String, Void, Void> {
834         private static final String TAG = "PostRequestTask";
835 
836         private byte[] mDrmRequest;
837         private byte[] mResponseBody;
838 
PostRequestTask(byte[] drmRequest)839         public PostRequestTask(byte[] drmRequest) {
840             mDrmRequest = drmRequest;
841         }
842 
843         @Override
doInBackground(String... urls)844         protected Void doInBackground(String... urls) {
845             mResponseBody = postRequest(urls[0], mDrmRequest);
846             if (mResponseBody != null) {
847                 Log.d(TAG, "response length=" + mResponseBody.length);
848             }
849             return null;
850         }
851 
postRequest(String url, byte[] drmRequest)852         private byte[] postRequest(String url, byte[] drmRequest) {
853             HttpClient httpClient = new DefaultHttpClient();
854             HttpPost httpPost = new HttpPost(url + "&signedRequest=" + new String(drmRequest));
855 
856             Log.d(TAG, "PostRequest:" + httpPost.getRequestLine());
857             try {
858                 // Add data
859                 httpPost.setHeader("Accept", "*/*");
860                 httpPost.setHeader("User-Agent", "Widevine CDM v1.0");
861                 httpPost.setHeader("Content-Type", "application/json");
862 
863                 // Execute HTTP Post Request
864                 HttpResponse response = httpClient.execute(httpPost);
865 
866                 byte[] responseBody;
867                 int responseCode = response.getStatusLine().getStatusCode();
868                 if (responseCode == 200) {
869                     responseBody = EntityUtils.toByteArray(response.getEntity());
870                 } else {
871                     Log.d(TAG, "Server returned HTTP error code " + responseCode);
872                     return null;
873                 }
874                 return responseBody;
875             } catch (ClientProtocolException e) {
876                 e.printStackTrace();
877             } catch (IOException e) {
878                 e.printStackTrace();
879             }
880             return null;
881         }
882 
883         @Override
onPostExecute(Void v)884         protected void onPostExecute(Void v) {
885             onProvisionResponse(mResponseBody);
886         }
887     }
888 
addKeySystemUuidMapping(String keySystem, UUID uuid)889     public static void addKeySystemUuidMapping(String keySystem, UUID uuid) {
890         ByteBuffer uuidBuffer = ByteBuffer.allocateDirect(16);
891         // MSB (byte) should be positioned at the first element.
892         uuidBuffer.order(ByteOrder.BIG_ENDIAN);
893         uuidBuffer.putLong(uuid.getMostSignificantBits());
894         uuidBuffer.putLong(uuid.getLeastSignificantBits());
895         nativeAddKeySystemUuidMapping(keySystem, uuidBuffer);
896     }
897 
nativeOnMediaCryptoReady(long nativeMediaDrmBridge)898     private native void nativeOnMediaCryptoReady(long nativeMediaDrmBridge);
899 
nativeOnSessionCreated(long nativeMediaDrmBridge, int sessionId, String webSessionId)900     private native void nativeOnSessionCreated(long nativeMediaDrmBridge, int sessionId,
901                                                String webSessionId);
902 
nativeOnSessionMessage(long nativeMediaDrmBridge, int sessionId, byte[] message, String destinationUrl)903     private native void nativeOnSessionMessage(long nativeMediaDrmBridge, int sessionId,
904                                                byte[] message, String destinationUrl);
905 
nativeOnSessionReady(long nativeMediaDrmBridge, int sessionId)906     private native void nativeOnSessionReady(long nativeMediaDrmBridge, int sessionId);
907 
nativeOnSessionClosed(long nativeMediaDrmBridge, int sessionId)908     private native void nativeOnSessionClosed(long nativeMediaDrmBridge, int sessionId);
909 
nativeOnSessionError(long nativeMediaDrmBridge, int sessionId)910     private native void nativeOnSessionError(long nativeMediaDrmBridge, int sessionId);
911 
nativeOnResetDeviceCredentialsCompleted( long nativeMediaDrmBridge, boolean success)912     private native void nativeOnResetDeviceCredentialsCompleted(
913             long nativeMediaDrmBridge, boolean success);
914 
nativeAddKeySystemUuidMapping(String keySystem, ByteBuffer uuid)915     private static native void nativeAddKeySystemUuidMapping(String keySystem, ByteBuffer uuid);
916 }
917