Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / media / base / android / java / src / org / chromium / media / MediaDrmBridge.java
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
115         private PendingCreateSessionData(int sessionId, byte[] initData, String mimeType) {
116             mSessionId = sessionId;
117             mInitData = initData;
118             mMimeType = mimeType;
119         }
120
121         private int sessionId() { return mSessionId; }
122         private byte[] initData() { return mInitData; }
123         private String mimeType() { return mMimeType; }
124     }
125
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      */
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
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      */
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                 Log.d(TAG, "MediaCrypto successfully created!");
209                 mSessionIds.put(mMediaCryptoSession, INVALID_SESSION_ID);
210                 // Notify the native code that MediaCrypto is ready.
211                 nativeOnMediaCryptoReady(mNativeMediaDrmBridge);
212                 return true;
213             } else {
214                 Log.e(TAG, "Cannot create MediaCrypto for unsupported scheme.");
215             }
216         } catch (android.media.MediaCryptoException e) {
217             Log.e(TAG, "Cannot create MediaCrypto", e);
218         }
219
220         release();
221         return false;
222     }
223
224     /**
225      * Open a new session..
226      *
227      * @return the session opened. Returns null if unexpected error happened.
228      */
229     private ByteBuffer openSession() throws android.media.NotProvisionedException {
230         assert mMediaDrm != null;
231         try {
232             byte[] session = mMediaDrm.openSession();
233             // ByteBuffer.wrap() is backed by the byte[]. Make a clone here in
234             // case the underlying byte[] is modified.
235             return ByteBuffer.wrap(session.clone());
236         } catch (java.lang.RuntimeException e) {  // TODO(xhwang): Drop this?
237             Log.e(TAG, "Cannot open a new session", e);
238             release();
239             return null;
240         } catch (android.media.NotProvisionedException e) {
241             // Throw NotProvisionedException so that we can startProvisioning().
242             throw e;
243         } catch (android.media.MediaDrmException e) {
244             // Other MediaDrmExceptions (e.g. ResourceBusyException) are not
245             // recoverable.
246             Log.e(TAG, "Cannot open a new session", e);
247             release();
248             return null;
249         }
250     }
251
252     /**
253      * Close a session.
254      *
255      * @param session to be closed.
256      */
257     private void closeSession(ByteBuffer session) {
258         assert mMediaDrm != null;
259         mMediaDrm.closeSession(session.array());
260     }
261
262     /**
263      * Check whether the crypto scheme is supported for the given container.
264      * If |containerMimeType| is an empty string, we just return whether
265      * the crypto scheme is supported.
266      *
267      * @return true if the container and the crypto scheme is supported, or
268      * false otherwise.
269      */
270     @CalledByNative
271     private static boolean isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType) {
272         UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
273
274         if (containerMimeType.isEmpty()) {
275             return MediaDrm.isCryptoSchemeSupported(cryptoScheme);
276         }
277
278         return MediaDrm.isCryptoSchemeSupported(cryptoScheme, containerMimeType);
279     }
280
281     /**
282      * Create a new MediaDrmBridge from the crypto scheme UUID.
283      *
284      * @param schemeUUID Crypto scheme UUID.
285      * @param securityLevel Security level to be used.
286      * @param nativeMediaDrmBridge Native object of this class.
287      */
288     @CalledByNative
289     private static MediaDrmBridge create(byte[] schemeUUID, long nativeMediaDrmBridge) {
290         UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
291         if (cryptoScheme == null || !MediaDrm.isCryptoSchemeSupported(cryptoScheme)) {
292             return null;
293         }
294
295         boolean singleSessionMode = false;
296         if (Build.VERSION.RELEASE.equals("4.4")) {
297             singleSessionMode = true;
298         }
299         Log.d(TAG, "MediaDrmBridge uses " +
300                 (singleSessionMode ? "single" : "multiple") + "-session mode.");
301
302         MediaDrmBridge mediaDrmBridge = null;
303         try {
304             mediaDrmBridge = new MediaDrmBridge(
305                 cryptoScheme, nativeMediaDrmBridge, singleSessionMode);
306             Log.d(TAG, "MediaDrmBridge successfully created.");
307         } catch (android.media.UnsupportedSchemeException e) {
308             Log.e(TAG, "Unsupported DRM scheme", e);
309         } catch (java.lang.IllegalArgumentException e) {
310             Log.e(TAG, "Failed to create MediaDrmBridge", e);
311         } catch (java.lang.IllegalStateException e) {
312             Log.e(TAG, "Failed to create MediaDrmBridge", e);
313         }
314
315         return mediaDrmBridge;
316     }
317
318     /**
319      * Set the security level that the MediaDrm object uses.
320      * This function should be called right after we construct MediaDrmBridge
321      * and before we make any other calls.
322      */
323     @CalledByNative
324     private boolean setSecurityLevel(String securityLevel) {
325         if (mMediaDrm == null || mMediaCrypto != null) {
326             return false;
327         }
328
329         String currentSecurityLevel = mMediaDrm.getPropertyString(SECURITY_LEVEL);
330         Log.e(TAG, "Security level: current " + currentSecurityLevel + ", new " + securityLevel);
331         if (securityLevel.equals(currentSecurityLevel)) {
332             // No need to set the same security level again. This is not just
333             // a shortcut! Setting the same security level actually causes an
334             // exception in MediaDrm!
335             return true;
336         }
337
338         try {
339             mMediaDrm.setPropertyString(SECURITY_LEVEL, securityLevel);
340             return true;
341         } catch (java.lang.IllegalArgumentException e) {
342             Log.e(TAG, "Failed to set security level " + securityLevel, e);
343         } catch (java.lang.IllegalStateException e) {
344             Log.e(TAG, "Failed to set security level " + securityLevel, e);
345         }
346
347         Log.e(TAG, "Security level " + securityLevel + " not supported!");
348         return false;
349     }
350
351     /**
352      * Return the MediaCrypto object if available.
353      */
354     @CalledByNative
355     private MediaCrypto getMediaCrypto() {
356         return mMediaCrypto;
357     }
358
359     /**
360      * Reset the device DRM credentials.
361      */
362     @CalledByNative
363     private void resetDeviceCredentials() {
364         mResetDeviceCredentialsPending = true;
365         MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
366         PostRequestTask postTask = new PostRequestTask(request.getData());
367         postTask.execute(request.getDefaultUrl());
368     }
369
370     /**
371      * Release the MediaDrmBridge object.
372      */
373     @CalledByNative
374     private void release() {
375         // Do not reset mHandler and mNativeMediaDrmBridge so that we can still
376         // post KeyError back to native code.
377
378         mPendingCreateSessionDataQueue.clear();
379         mPendingCreateSessionDataQueue = null;
380
381         for (ByteBuffer session : mSessionIds.keySet()) {
382             closeSession(session);
383         }
384         mSessionIds.clear();
385         mSessionIds = null;
386         mSessionMimeTypes.clear();
387         mSessionMimeTypes = null;
388
389         // This session was closed in the "for" loop above.
390         mMediaCryptoSession = null;
391
392         if (mMediaCrypto != null) {
393             mMediaCrypto.release();
394             mMediaCrypto = null;
395         }
396
397         if (mMediaDrm != null) {
398             mMediaDrm.release();
399             mMediaDrm = null;
400         }
401     }
402
403     /**
404      * Get a key request.
405      *
406      * @param session Session on which we need to get the key request.
407      * @param data Data needed to get the key request.
408      * @param mime Mime type to get the key request.
409      *
410      * @return the key request.
411      */
412     private MediaDrm.KeyRequest getKeyRequest(ByteBuffer session, byte[] data, String mime)
413             throws android.media.NotProvisionedException {
414         assert mMediaDrm != null;
415         assert mMediaCrypto != null;
416         assert !mProvisioningPending;
417
418         HashMap<String, String> optionalParameters = new HashMap<String, String>();
419         MediaDrm.KeyRequest request = mMediaDrm.getKeyRequest(
420                 session.array(), data, mime, MediaDrm.KEY_TYPE_STREAMING, optionalParameters);
421         String result = (request != null) ? "successed" : "failed";
422         Log.d(TAG, "getKeyRequest " + result + "!");
423         return request;
424     }
425
426     /**
427      * Save data to |mPendingCreateSessionDataQueue| so that we can resume the
428      * createSession() call later.
429      */
430     private void savePendingCreateSessionData(int sessionId, byte[] initData, String mime) {
431         Log.d(TAG, "savePendingCreateSessionData()");
432         mPendingCreateSessionDataQueue.offer(
433                 new PendingCreateSessionData(sessionId, initData, mime));
434     }
435
436     /**
437      * Process all pending createSession() calls synchronously.
438      */
439     private void processPendingCreateSessionData() {
440         Log.d(TAG, "processPendingCreateSessionData()");
441         assert mMediaDrm != null;
442
443         // Check mMediaDrm != null because error may happen in createSession().
444         // Check !mProvisioningPending because NotProvisionedException may be
445         // thrown in createSession().
446         while (mMediaDrm != null && !mProvisioningPending &&
447                 !mPendingCreateSessionDataQueue.isEmpty()) {
448             PendingCreateSessionData pendingData = mPendingCreateSessionDataQueue.poll();
449             int sessionId = pendingData.sessionId();
450             byte[] initData = pendingData.initData();
451             String mime = pendingData.mimeType();
452             createSession(sessionId, initData, mime);
453         }
454     }
455
456     /**
457      * Process pending operations asynchrnously.
458      */
459     private void resumePendingOperations() {
460         mHandler.post(new Runnable(){
461             @Override
462             public void run() {
463                 processPendingCreateSessionData();
464             }
465         });
466     }
467
468     /**
469      * Create a session with |sessionId|, |initData| and |mime|.
470      * In multiple session mode, a new session will be open. In single session
471      * mode, the mMediaCryptoSession will be used.
472      *
473      * @param sessionId ID for the session to be created.
474      * @param initData Data needed to generate the key request.
475      * @param mime Mime type.
476      */
477     @CalledByNative
478     private void createSession(int sessionId, byte[] initData, String mime) {
479         Log.d(TAG, "createSession()");
480         if (mMediaDrm == null) {
481             Log.e(TAG, "createSession() called when MediaDrm is null.");
482             return;
483         }
484
485         if (mProvisioningPending) {
486             assert mMediaCrypto == null;
487             savePendingCreateSessionData(sessionId, initData, mime);
488             return;
489         }
490
491         boolean newSessionOpened = false;
492         ByteBuffer session = null;
493         try {
494             // Create MediaCrypto if necessary.
495             if (mMediaCrypto == null && !createMediaCrypto()) {
496               onSessionError(sessionId);
497                 return;
498             }
499             assert mMediaCrypto != null;
500             assert mSessionIds.containsKey(mMediaCryptoSession);
501
502             if (mSingleSessionMode) {
503                 session = mMediaCryptoSession;
504                 if (mSessionMimeTypes.get(session) != null &&
505                         !mSessionMimeTypes.get(session).equals(mime)) {
506                     Log.e(TAG, "Only one mime type is supported in single session mode.");
507                     onSessionError(sessionId);
508                     return;
509                 }
510             } else {
511                 session = openSession();
512                 if (session == null) {
513                     Log.e(TAG, "Cannot open session in createSession().");
514                     onSessionError(sessionId);
515                     return;
516                 }
517                 newSessionOpened = true;
518                 assert !mSessionIds.containsKey(session);
519             }
520
521             MediaDrm.KeyRequest request = null;
522             request = getKeyRequest(session, initData, mime);
523             if (request == null) {
524                 if (newSessionOpened) {
525                     closeSession(session);
526                 }
527                 onSessionError(sessionId);
528                 return;
529             }
530
531             onSessionCreated(sessionId, getWebSessionId(session));
532             onSessionMessage(sessionId, request);
533             if (newSessionOpened) {
534                 Log.d(TAG, "createSession(): Session " + getWebSessionId(session) +
535                         " (" + sessionId + ") created.");
536             }
537
538             mSessionIds.put(session, sessionId);
539             mSessionMimeTypes.put(session, mime);
540         } catch (android.media.NotProvisionedException e) {
541             Log.e(TAG, "Device not provisioned", e);
542             if (newSessionOpened) {
543                 closeSession(session);
544             }
545             savePendingCreateSessionData(sessionId, initData, mime);
546             startProvisioning();
547         }
548     }
549
550     /**
551      * Returns whether |sessionId| is a valid key session, excluding the media
552      * crypto session in multi-session mode.
553      *
554      * @param sessionId Crypto session Id.
555      */
556     private boolean sessionExists(ByteBuffer session) {
557         if (mMediaCryptoSession == null) {
558             assert mSessionIds.isEmpty();
559             Log.e(TAG, "Session doesn't exist because media crypto session is not created.");
560             return false;
561         }
562         assert mSessionIds.containsKey(mMediaCryptoSession);
563
564         if (mSingleSessionMode) {
565             return mMediaCryptoSession.equals(session);
566         }
567
568         return !session.equals(mMediaCryptoSession) && mSessionIds.containsKey(session);
569     }
570
571     /**
572      * Cancel a key request for a session Id.
573      *
574      * @param sessionId Reference ID of session to be released.
575      */
576     @CalledByNative
577     private void releaseSession(int sessionId) {
578         Log.d(TAG, "releaseSession(): " + sessionId);
579         if (mMediaDrm == null) {
580             Log.e(TAG, "releaseSession() called when MediaDrm is null.");
581             return;
582         }
583
584         ByteBuffer session = getSession(sessionId);
585         if (session == null) {
586             Log.e(TAG, "Invalid sessionId in releaseSession.");
587             onSessionError(sessionId);
588             return;
589         }
590
591         mMediaDrm.removeKeys(session.array());
592
593         // We don't close the media crypto session in single session mode.
594         if (!mSingleSessionMode) {
595             Log.d(TAG, "Session " + sessionId + "closed.");
596             closeSession(session);
597             mSessionIds.remove(session);
598             onSessionClosed(sessionId);
599         }
600     }
601
602     /**
603      * Add a key for a session Id.
604      *
605      * @param sessionId Reference ID of session to be updated.
606      * @param key Response data from the server.
607      */
608     @CalledByNative
609     private void updateSession(int sessionId, byte[] key) {
610         Log.d(TAG, "updateSession(): " + sessionId);
611         if (mMediaDrm == null) {
612             Log.e(TAG, "updateSession() called when MediaDrm is null.");
613             return;
614         }
615
616         // TODO(xhwang): We should be able to DCHECK this when WD EME is implemented.
617         ByteBuffer session = getSession(sessionId);
618         if (!sessionExists(session)) {
619             Log.e(TAG, "Invalid session in updateSession.");
620             onSessionError(sessionId);
621             return;
622         }
623
624         try {
625             try {
626                 mMediaDrm.provideKeyResponse(session.array(), key);
627             } catch (java.lang.IllegalStateException e) {
628                 // This is not really an exception. Some error code are incorrectly
629                 // reported as an exception.
630                 // TODO(qinmin): remove this exception catch when b/10495563 is fixed.
631                 Log.e(TAG, "Exception intentionally caught when calling provideKeyResponse()", e);
632             }
633             onSessionReady(sessionId);
634             Log.d(TAG, "Key successfully added for session " + sessionId);
635             return;
636         } catch (android.media.NotProvisionedException e) {
637             // TODO(xhwang): Should we handle this?
638             Log.e(TAG, "failed to provide key response", e);
639         } catch (android.media.DeniedByServerException e) {
640             Log.e(TAG, "failed to provide key response", e);
641         }
642         onSessionError(sessionId);
643         release();
644     }
645
646     /**
647      * Return the security level of this DRM object.
648      */
649     @CalledByNative
650     private String getSecurityLevel() {
651         if (mMediaDrm == null) {
652             Log.e(TAG, "getSecurityLevel() called when MediaDrm is null.");
653             return null;
654         }
655         return mMediaDrm.getPropertyString("securityLevel");
656     }
657
658     private void startProvisioning() {
659         Log.d(TAG, "startProvisioning");
660         assert mMediaDrm != null;
661         assert !mProvisioningPending;
662         mProvisioningPending = true;
663         MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
664         PostRequestTask postTask = new PostRequestTask(request.getData());
665         postTask.execute(request.getDefaultUrl());
666     }
667
668     /**
669      * Called when the provision response is received.
670      *
671      * @param response Response data from the provision server.
672      */
673     private void onProvisionResponse(byte[] response) {
674         Log.d(TAG, "onProvisionResponse()");
675         assert mProvisioningPending;
676         mProvisioningPending = false;
677
678         // If |mMediaDrm| is released, there is no need to callback native.
679         if (mMediaDrm == null) {
680             return;
681         }
682
683         boolean success = provideProvisionResponse(response);
684
685         if (mResetDeviceCredentialsPending) {
686             nativeOnResetDeviceCredentialsCompleted(mNativeMediaDrmBridge, success);
687             mResetDeviceCredentialsPending = false;
688         }
689
690         if (success) {
691             resumePendingOperations();
692         }
693     }
694
695     /**
696      * Provide the provisioning response to MediaDrm.
697      * @returns false if the response is invalid or on error, true otherwise.
698      */
699     boolean provideProvisionResponse(byte[] response) {
700         if (response == null || response.length == 0) {
701             Log.e(TAG, "Invalid provision response.");
702             return false;
703         }
704
705         try {
706             mMediaDrm.provideProvisionResponse(response);
707             return true;
708         } catch (android.media.DeniedByServerException e) {
709             Log.e(TAG, "failed to provide provision response", e);
710         } catch (java.lang.IllegalStateException e) {
711             Log.e(TAG, "failed to provide provision response", e);
712         }
713         return false;
714     }
715
716     private void onSessionCreated(final int sessionId, final String webSessionId) {
717         mHandler.post(new Runnable(){
718             @Override
719             public void run() {
720                 nativeOnSessionCreated(mNativeMediaDrmBridge, sessionId, webSessionId);
721             }
722         });
723     }
724
725     private void onSessionMessage(final int sessionId, final MediaDrm.KeyRequest request) {
726         mHandler.post(new Runnable(){
727             @Override
728             public void run() {
729                 nativeOnSessionMessage(mNativeMediaDrmBridge, sessionId,
730                         request.getData(), request.getDefaultUrl());
731             }
732         });
733     }
734
735     private void onSessionReady(final int sessionId) {
736         mHandler.post(new Runnable() {
737             @Override
738             public void run() {
739                 nativeOnSessionReady(mNativeMediaDrmBridge, sessionId);
740             }
741         });
742     }
743
744     private void onSessionClosed(final int sessionId) {
745         mHandler.post(new Runnable() {
746             @Override
747             public void run() {
748                 nativeOnSessionClosed(mNativeMediaDrmBridge, sessionId);
749             }
750         });
751     }
752
753     private void onSessionError(final int sessionId) {
754         // TODO(qinmin): pass the error code to native.
755         mHandler.post(new Runnable() {
756             @Override
757             public void run() {
758                 nativeOnSessionError(mNativeMediaDrmBridge, sessionId);
759             }
760         });
761     }
762
763     private String getWebSessionId(ByteBuffer session) {
764         String webSessionId = null;
765         try {
766             webSessionId = new String(session.array(), "UTF-8");
767         } catch (java.io.UnsupportedEncodingException e) {
768             Log.e(TAG, "getWebSessionId failed", e);
769         } catch (java.lang.NullPointerException e) {
770             Log.e(TAG, "getWebSessionId failed", e);
771         }
772         return webSessionId;
773     }
774
775     private class MediaDrmListener implements MediaDrm.OnEventListener {
776         @Override
777         public void onEvent(
778                 MediaDrm mediaDrm, byte[] sessionArray, int event, int extra, byte[] data) {
779             if (sessionArray == null) {
780                 Log.e(TAG, "MediaDrmListener: Null session.");
781                 return;
782             }
783             ByteBuffer session = ByteBuffer.wrap(sessionArray);
784             if (!sessionExists(session)) {
785                 Log.e(TAG, "MediaDrmListener: Invalid session.");
786                 return;
787             }
788             Integer sessionId = mSessionIds.get(session);
789             if (sessionId == null || sessionId == INVALID_SESSION_ID) {
790                 Log.e(TAG, "MediaDrmListener: Invalid session ID.");
791                 return;
792             }
793             switch(event) {
794                 case MediaDrm.EVENT_PROVISION_REQUIRED:
795                     Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
796                     break;
797                 case MediaDrm.EVENT_KEY_REQUIRED:
798                     Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
799                     if (mProvisioningPending) {
800                         return;
801                     }
802                     String mime = mSessionMimeTypes.get(session);
803                     MediaDrm.KeyRequest request = null;
804                     try {
805                         request = getKeyRequest(session, data, mime);
806                     } catch (android.media.NotProvisionedException e) {
807                         Log.e(TAG, "Device not provisioned", e);
808                         startProvisioning();
809                         return;
810                     }
811                     if (request != null) {
812                         onSessionMessage(sessionId, request);
813                     } else {
814                         onSessionError(sessionId);
815                     }
816                     break;
817                 case MediaDrm.EVENT_KEY_EXPIRED:
818                     Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
819                     onSessionError(sessionId);
820                     break;
821                 case MediaDrm.EVENT_VENDOR_DEFINED:
822                     Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
823                     assert false;  // Should never happen.
824                     break;
825                 default:
826                     Log.e(TAG, "Invalid DRM event " + event);
827                     return;
828             }
829         }
830     }
831
832     private class PostRequestTask extends AsyncTask<String, Void, Void> {
833         private static final String TAG = "PostRequestTask";
834
835         private byte[] mDrmRequest;
836         private byte[] mResponseBody;
837
838         public PostRequestTask(byte[] drmRequest) {
839             mDrmRequest = drmRequest;
840         }
841
842         @Override
843         protected Void doInBackground(String... urls) {
844             mResponseBody = postRequest(urls[0], mDrmRequest);
845             if (mResponseBody != null) {
846                 Log.d(TAG, "response length=" + mResponseBody.length);
847             }
848             return null;
849         }
850
851         private byte[] postRequest(String url, byte[] drmRequest) {
852             HttpClient httpClient = new DefaultHttpClient();
853             HttpPost httpPost = new HttpPost(url + "&signedRequest=" + new String(drmRequest));
854
855             Log.d(TAG, "PostRequest:" + httpPost.getRequestLine());
856             try {
857                 // Add data
858                 httpPost.setHeader("Accept", "*/*");
859                 httpPost.setHeader("User-Agent", "Widevine CDM v1.0");
860                 httpPost.setHeader("Content-Type", "application/json");
861
862                 // Execute HTTP Post Request
863                 HttpResponse response = httpClient.execute(httpPost);
864
865                 byte[] responseBody;
866                 int responseCode = response.getStatusLine().getStatusCode();
867                 if (responseCode == 200) {
868                     responseBody = EntityUtils.toByteArray(response.getEntity());
869                 } else {
870                     Log.d(TAG, "Server returned HTTP error code " + responseCode);
871                     return null;
872                 }
873                 return responseBody;
874             } catch (ClientProtocolException e) {
875                 e.printStackTrace();
876             } catch (IOException e) {
877                 e.printStackTrace();
878             }
879             return null;
880         }
881
882         @Override
883         protected void onPostExecute(Void v) {
884             onProvisionResponse(mResponseBody);
885         }
886     }
887
888     public static void addKeySystemUuidMapping(String keySystem, UUID uuid) {
889         ByteBuffer uuidBuffer = ByteBuffer.allocateDirect(16);
890         // MSB (byte) should be positioned at the first element.
891         uuidBuffer.order(ByteOrder.BIG_ENDIAN);
892         uuidBuffer.putLong(uuid.getMostSignificantBits());
893         uuidBuffer.putLong(uuid.getLeastSignificantBits());
894         nativeAddKeySystemUuidMapping(keySystem, uuidBuffer);
895     }
896
897     private native void nativeOnMediaCryptoReady(long nativeMediaDrmBridge);
898
899     private native void nativeOnSessionCreated(long nativeMediaDrmBridge, int sessionId,
900                                                String webSessionId);
901
902     private native void nativeOnSessionMessage(long nativeMediaDrmBridge, int sessionId,
903                                                byte[] message, String destinationUrl);
904
905     private native void nativeOnSessionReady(long nativeMediaDrmBridge, int sessionId);
906
907     private native void nativeOnSessionClosed(long nativeMediaDrmBridge, int sessionId);
908
909     private native void nativeOnSessionError(long nativeMediaDrmBridge, int sessionId);
910
911     private native void nativeOnResetDeviceCredentialsCompleted(
912             long nativeMediaDrmBridge, boolean success);
913
914     private static native void nativeAddKeySystemUuidMapping(String keySystem, ByteBuffer uuid);
915 }