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