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.nio.ByteOrder;
26import java.util.ArrayDeque;
27import java.util.HashMap;
28import 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")
35public 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}
916