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