1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.media.drmframework.cts;
17 
18 import android.app.DownloadManager;
19 import android.app.DownloadManager.Request;
20 
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.PackageManager;
26 import android.content.res.AssetFileDescriptor;
27 import android.content.res.Resources;
28 import android.media.MediaDrm;
29 import android.media.MediaPlayer;
30 import android.media.MediaPlayer.DrmInfo;
31 import android.media.ResourceBusyException;
32 import android.media.UnsupportedSchemeException;
33 import android.media.cts.MediaStubActivity;
34 import android.media.cts.TestUtils.Monitor;
35 import android.net.Uri;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.PersistableBundle;
41 import android.os.SystemClock;
42 import android.test.ActivityInstrumentationTestCase2;
43 import android.util.Base64;
44 import android.util.Log;
45 
46 import com.android.compatibility.common.util.MediaUtils;
47 
48 import java.io.ByteArrayOutputStream;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52 import java.net.HttpCookie;
53 import java.net.HttpURLConnection;
54 import java.net.URL;
55 import java.nio.charset.Charset;
56 import java.nio.ByteBuffer;
57 import java.util.Arrays;
58 import java.util.HashMap;
59 import java.util.HashSet;
60 import java.util.List;
61 import java.util.Map;
62 import java.util.Set;
63 import java.util.UUID;
64 import java.util.Vector;
65 import java.util.logging.Logger;
66 import java.util.concurrent.atomic.AtomicBoolean;
67 
68 import org.json.JSONArray;
69 import org.json.JSONException;
70 import org.json.JSONObject;
71 
72 
73 /**
74  * Base class for tests which use MediaPlayer to play audio or video.
75  */
76 public class MediaPlayerDrmTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> {
77     protected static final int STREAM_RETRIES = 3;
78 
79     protected Monitor mOnVideoSizeChangedCalled = new Monitor();
80     protected Monitor mOnErrorCalled = new Monitor();
81 
82     protected Context mContext;
83     protected Resources mResources;
84 
85     protected MediaPlayer mMediaPlayer = null;
86     protected MediaStubActivity mActivity;
87 
MediaPlayerDrmTestBase()88     public MediaPlayerDrmTestBase() {
89         super(MediaStubActivity.class);
90     }
91 
92     @Override
setUp()93     protected void setUp() throws Exception {
94         super.setUp();
95         mActivity = getActivity();
96         getInstrumentation().waitForIdleSync();
97         try {
98             runTestOnUiThread(new Runnable() {
99                 public void run() {
100                     mMediaPlayer = new MediaPlayer();
101                 }
102             });
103         } catch (Throwable e) {
104             e.printStackTrace();
105             fail();
106         }
107         mContext = getInstrumentation().getTargetContext();
108         mResources = mContext.getResources();
109     }
110 
111     @Override
tearDown()112     protected void tearDown() throws Exception {
113         if (mMediaPlayer != null) {
114             mMediaPlayer.release();
115             mMediaPlayer = null;
116         }
117         mActivity = null;
118         super.tearDown();
119     }
120 
setOnErrorListener()121     protected void setOnErrorListener() {
122         mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
123             @Override
124             public boolean onError(MediaPlayer mp, int what, int extra) {
125                 mOnErrorCalled.signal();
126                 return false;
127             }
128         });
129     }
130 
131     private static class PrepareFailedException extends Exception {}
132 
133     //////////////////////////////////////////////////////////////////////////////////////////
134     // Modular DRM
135 
136     private static final String TAG = "MediaPlayerDrmTestBase";
137 
138     // PLAY_TIME_MS dictates the total run time of the playback
139     // tests. To meet the under 30 seconds CTS quality bar,
140     // the first ten seconds plays the clear lead,
141     // the next ten seconds verifies encrypted playback.
142     // This applies to both streaming and offline tests.
143     protected static final int PLAY_TIME_MS = 20 * 1000;
144     protected byte[] mKeySetId;
145     protected boolean mAudioOnly;
146 
147     private static final byte[] CLEAR_KEY_CENC = {
148             (byte)0x1a, (byte)0x8a, (byte)0x20, (byte)0x95,
149             (byte)0xe4, (byte)0xde, (byte)0xb2, (byte)0xd2,
150             (byte)0x9e, (byte)0xc8, (byte)0x16, (byte)0xac,
151             (byte)0x7b, (byte)0xae, (byte)0x20, (byte)0x82
152             };
153 
154     private static final UUID CLEARKEY_SCHEME_UUID =
155             new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
156 
157     final byte[] CLEARKEY_PSSH = hexStringToByteArray(
158             "0000003470737368" +  // BMFF box header (4 bytes size + 'pssh')
159             "01000000" +          // Full box header (version = 1 flags = 0)
160             "1077efecc0b24d02" +  // SystemID
161             "ace33c1e52e2fb4b" +
162             "00000001" +          // Number of key ids
163             "60061e017e477e87" +  // Key id
164             "7e57d00d1ed00d1e" +
165             "00000000"            // Size of Data, must be zero
166             );
167 
168 
169     protected enum ModularDrmTestType {
170         V0_SYNC_TEST,
171         V1_ASYNC_TEST,
172         V2_SYNC_CONFIG_TEST,
173         V3_ASYNC_DRMPREPARED_TEST,
174         V4_SYNC_OFFLINE_KEY,
175         V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER,
176     }
177 
178     // TODO: After living on these tests for a while, we can consider grouping them based on
179     // the asset such that each asset is downloaded once and played back with multiple tests.
playModularDrmVideoDownload(Uri uri, Uri path, int width, int height, ModularDrmTestType testType)180     protected void playModularDrmVideoDownload(Uri uri, Uri path, int width, int height,
181             ModularDrmTestType testType) throws Exception {
182         Uri file = uri;
183         long id = -1;
184         MediaDownloadManager mediaDownloadManager = new MediaDownloadManager(mContext);
185         if (uri.getScheme().startsWith("file")) {
186             Log.i(TAG, "Playing existing file:" + uri);
187             // file = uri;
188         } else {
189             final long DOWNLOAD_TIMEOUT_SECONDS = 600;
190             Log.i(TAG, "Downloading file:" + path);
191             id = mediaDownloadManager.downloadFileWithRetries(
192                     uri, path, DOWNLOAD_TIMEOUT_SECONDS, STREAM_RETRIES);
193             assertFalse("Download " + uri + " failed.", id == -1);
194             file = mediaDownloadManager.getUriForDownloadedFile(id);
195             Log.i(TAG, "Downloaded file:" + path + " id:" + id + " uri:" + file);
196         }
197 
198         try {
199             playModularDrmVideo(file, width, height, testType);
200         } finally {
201             if (id != -1) {
202                 mediaDownloadManager.removeFile(id);
203             }
204         }
205     }
206 
playModularDrmVideo(Uri uri, int width, int height, ModularDrmTestType testType)207     protected void playModularDrmVideo(Uri uri, int width, int height,
208             ModularDrmTestType testType) throws Exception {
209         // Force gc for a clean start
210         System.gc();
211 
212         playModularDrmVideoWithRetries(uri, width, height, PLAY_TIME_MS, testType);
213     }
214 
playModularDrmVideoWithRetries(Uri file, Integer width, Integer height, int playTime, ModularDrmTestType testType)215     protected void playModularDrmVideoWithRetries(Uri file, Integer width, Integer height,
216             int playTime, ModularDrmTestType testType) throws Exception {
217 
218         // first the synchronous variation
219         boolean playedSuccessfully = false;
220         for (int i = 0; i < STREAM_RETRIES; i++) {
221             try {
222                 Log.v(TAG, "playVideoWithRetries(" + testType + ") try " + i);
223                 playLoadedModularDrmVideo(file, width, height, playTime, testType);
224 
225                 playedSuccessfully = true;
226                 break;
227             } catch (PrepareFailedException e) {
228                 // we can fail because of network issues, so try again
229                 Log.w(TAG, "playVideoWithRetries(" + testType + ") failed on try " + i +
230                         ", trying playback again");
231                 mMediaPlayer.stop();
232                 mMediaPlayer.reset();
233             }
234         }
235         assertTrue("Stream did not play successfully after all attempts (syncDrmSetup)",
236                 playedSuccessfully);
237     }
238 
239     /**
240      * Play a video which has already been loaded with setDataSource().
241      * The DRM setup is performed synchronously.
242      *
243      * @param file data source
244      * @param width width of the video to verify, or null to skip verification
245      * @param height height of the video to verify, or null to skip verification
246      * @param playTime length of time to play video, or 0 to play entire video
247      * @param testType test type
248      */
playLoadedModularDrmVideo(final Uri file, final Integer width, final Integer height, int playTime, ModularDrmTestType testType)249     private void playLoadedModularDrmVideo(final Uri file, final Integer width,
250             final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
251 
252         switch (testType) {
253             case V0_SYNC_TEST:
254             case V1_ASYNC_TEST:
255             case V2_SYNC_CONFIG_TEST:
256             case V3_ASYNC_DRMPREPARED_TEST:
257             case V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER:
258                 playLoadedModularDrmVideo_Generic(file, width, height, playTime, testType);
259                 break;
260 
261             case V4_SYNC_OFFLINE_KEY:
262                 playLoadedModularDrmVideo_V4_offlineKey(file, width, height, playTime);
263                 break;
264         }
265     }
266 
playLoadedModularDrmVideo_Generic(final Uri file, final Integer width, final Integer height, int playTime, ModularDrmTestType testType)267     private void playLoadedModularDrmVideo_Generic(final Uri file, final Integer width,
268             final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
269 
270         final float leftVolume = 0.5f;
271         final float rightVolume = 0.5f;
272 
273         mAudioOnly = (width == 0);
274 
275         try {
276             Log.v(TAG, "playLoadedVideo: setDataSource()");
277             mMediaPlayer.setDataSource(mContext, file);
278         } catch (IOException e) {
279             e.printStackTrace();
280             throw new PrepareFailedException();
281         }
282 
283         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
284         mMediaPlayer.setScreenOnWhilePlaying(true);
285         mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
286             @Override
287             public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
288                 Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
289                 mOnVideoSizeChangedCalled.signal();
290             }
291         });
292         mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
293             @Override
294             public boolean onError(MediaPlayer mp, int what, int extra) {
295                 fail("Media player had error " + what + " playing video");
296                 return true;
297             }
298         });
299 
300         try {
301             switch (testType) {
302             case V0_SYNC_TEST:
303                 preparePlayerAndDrm_V0_syncDrmSetup();
304                 break;
305 
306             case V1_ASYNC_TEST:
307                 preparePlayerAndDrm_V1_asyncDrmSetup();
308                 break;
309 
310             case V2_SYNC_CONFIG_TEST:
311                 preparePlayerAndDrm_V2_syncDrmSetupPlusConfig();
312                 break;
313 
314             case V3_ASYNC_DRMPREPARED_TEST:
315                 preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener();
316                 break;
317 
318             case V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER:
319                 preparePlayerAndDrm_V5_asyncDrmSetupWithHandler();
320                 break;
321             }
322 
323         } catch (IOException e) {
324             e.printStackTrace();
325             throw new PrepareFailedException();
326         }
327 
328 
329         final Monitor playbackCompleted = new Monitor();
330         mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
331             @Override
332             public void onCompletion(MediaPlayer mp) {
333                 Log.v(TAG, "playLoadedVideo: onCompletion");
334                 playbackCompleted.signal();
335             }
336         });
337 
338         Log.v(TAG, "playLoadedVideo: start()");
339         mMediaPlayer.start();
340         if (!mAudioOnly) {
341             mOnVideoSizeChangedCalled.waitForSignal();
342         }
343         mMediaPlayer.setVolume(leftVolume, rightVolume);
344 
345         // waiting to complete
346         if (playTime == 0) {
347             Log.v(TAG, "playLoadedVideo: waiting for playback completion");
348             playbackCompleted.waitForSignal();
349         } else {
350             Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
351             playbackCompleted.waitForSignal(playTime);
352         }
353 
354         Log.v(TAG, "playLoadedVideo: stopping");
355         mMediaPlayer.stop();
356         Log.v(TAG, "playLoadedVideo: stopped");
357 
358         try {
359             Log.v(TAG, "playLoadedVideo: releaseDrm");
360             mMediaPlayer.releaseDrm();
361         } catch (Exception e) {
362             e.printStackTrace();
363             throw new PrepareFailedException();
364         }
365     }
366 
preparePlayerAndDrm_V0_syncDrmSetup()367     private void preparePlayerAndDrm_V0_syncDrmSetup() throws Exception {
368         Log.v(TAG, "preparePlayerAndDrm_V0: calling prepare()");
369         mMediaPlayer.prepare();
370 
371         DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
372         if (drmInfo != null) {
373             setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
374                     MediaDrm.KEY_TYPE_STREAMING);
375             Log.v(TAG, "preparePlayerAndDrm_V0: setupDrm done!");
376         }
377     }
378 
preparePlayerAndDrm_V1_asyncDrmSetup()379     private void preparePlayerAndDrm_V1_asyncDrmSetup() throws InterruptedException {
380         Monitor onPreparedCalled = new Monitor();
381         final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
382 
383         mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
384             @Override
385             public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
386                 Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo" + drmInfo);
387 
388                 // in the callback (async mode) so handling exceptions here
389                 try {
390                     setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
391                             MediaDrm.KEY_TYPE_STREAMING);
392                 } catch (Exception e) {
393                     Log.v(TAG, "preparePlayerAndDrm_V1: setupDrm EXCEPTION " + e);
394                     asyncSetupDrmError.set(true);
395                 }
396 
397                 Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo done!");
398             }
399         });
400 
401         mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
402             @Override
403             public void onPrepared(MediaPlayer mp) {
404                 Log.v(TAG, "preparePlayerAndDrm_V1: onPrepared");
405 
406                 onPreparedCalled.signal();
407             }
408         });
409 
410         Log.v(TAG, "preparePlayerAndDrm_V1: calling prepareAsync()");
411         mMediaPlayer.prepareAsync();
412 
413         // Waiting till the player is prepared
414         onPreparedCalled.waitForSignal();
415 
416         // to handle setupDrm error (async) in the main thread rather than the callback
417         if (asyncSetupDrmError.get()) {
418             fail("preparePlayerAndDrm_V1: setupDrm");
419         }
420     }
421 
preparePlayerAndDrm_V2_syncDrmSetupPlusConfig()422     private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
423         mMediaPlayer.setOnDrmConfigHelper(new MediaPlayer.OnDrmConfigHelper() {
424             @Override
425             public void onDrmConfig(MediaPlayer mp) {
426                 String WIDEVINE_SECURITY_LEVEL_3 = "L3";
427                 String SECURITY_LEVEL_PROPERTY = "securityLevel";
428 
429                 try {
430                     String level = mp.getDrmPropertyString(SECURITY_LEVEL_PROPERTY);
431                     Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: " +
432                             SECURITY_LEVEL_PROPERTY + " -> " + level);
433                     mp.setDrmPropertyString(SECURITY_LEVEL_PROPERTY, WIDEVINE_SECURITY_LEVEL_3);
434                     level = mp.getDrmPropertyString(SECURITY_LEVEL_PROPERTY);
435                     Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: " +
436                             SECURITY_LEVEL_PROPERTY + " -> " + level);
437                 } catch (MediaPlayer.NoDrmSchemeException e) {
438                     Log.v(TAG, "preparePlayerAndDrm_V2: NoDrmSchemeException");
439                 } catch (Exception e) {
440                     Log.v(TAG, "preparePlayerAndDrm_V2: onDrmConfig EXCEPTION " + e);
441                 }
442             }
443         });
444 
445         Log.v(TAG, "preparePlayerAndDrm_V2: calling prepare()");
446         mMediaPlayer.prepare();
447 
448         DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
449         if (drmInfo != null) {
450             setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
451                     MediaDrm.KEY_TYPE_STREAMING);
452             Log.v(TAG, "preparePlayerAndDrm_V2: setupDrm done!");
453         }
454     }
455 
preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener()456     private void preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener()
457             throws InterruptedException {
458         Monitor onPreparedCalled = new Monitor();
459         final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
460 
461         mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
462             @Override
463             public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
464                 Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo" + drmInfo);
465 
466                 // DRM preperation
467                 UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
468                 if (supportedSchemes.length == 0) {
469                     Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: No supportedSchemes");
470                     asyncSetupDrmError.set(true);
471                     return;
472                 }
473 
474                 // setting up with the first supported UUID
475                 // instead of supportedSchemes[0] in GTS
476                 UUID drmScheme = CLEARKEY_SCHEME_UUID;
477                 Log.d(TAG, "preparePlayerAndDrm_V3: onDrmInfo: selected " + drmScheme);
478 
479                 try {
480                     Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
481                     mp.prepareDrm(drmScheme);
482                     Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: called prepareDrm");
483                 } catch (Exception e) {
484                     e.printStackTrace();
485                     Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: prepareDrm exception " + e);
486                     asyncSetupDrmError.set(true);
487                     return;
488                 }
489 
490                 Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
491             }
492         });
493 
494         mMediaPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() {
495             @Override
496             public void onDrmPrepared(MediaPlayer mp, int status) {
497                 Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared status: " + status);
498 
499                 assertTrue("preparePlayerAndDrm_V3: onDrmPrepared did not succeed",
500                            status == MediaPlayer.PREPARE_DRM_STATUS_SUCCESS);
501 
502                 DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
503 
504                 // in the callback (async mode) so handling exceptions here
505                 try {
506                     setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
507                             MediaDrm.KEY_TYPE_STREAMING);
508                 } catch (Exception e) {
509                     Log.v(TAG, "preparePlayerAndDrm_V3: setupDrm EXCEPTION " + e);
510                     asyncSetupDrmError.set(true);
511                 }
512 
513                 Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared done!");
514             }
515         });
516 
517         mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
518             @Override
519             public void onPrepared(MediaPlayer mp) {
520                 Log.v(TAG, "preparePlayerAndDrm_V3: onPrepared");
521 
522                 onPreparedCalled.signal();
523                 Log.v(TAG, "preparePlayerAndDrm_V3: onPrepared done!");
524             }
525         });
526 
527         Log.v(TAG, "preparePlayerAndDrm_V3: calling prepareAsync()");
528         mMediaPlayer.prepareAsync();
529 
530         // Waiting till the player is prepared
531         onPreparedCalled.waitForSignal();
532 
533         // to handle setupDrm error (async) in the main thread rather than the callback
534         if (asyncSetupDrmError.get()) {
535             fail("preparePlayerAndDrm_V3: setupDrm");
536         }
537     }
538 
playLoadedModularDrmVideo_V4_offlineKey(final Uri file, final Integer width, final Integer height, int playTime)539     private void playLoadedModularDrmVideo_V4_offlineKey(final Uri file, final Integer width,
540             final Integer height, int playTime) throws Exception {
541         final float leftVolume = 0.5f;
542         final float rightVolume = 0.5f;
543 
544         mAudioOnly = (width == 0);
545 
546         Log.v(TAG, "playLoadedModularDrmVideo_V4_offlineKey: setDisplay " +
547                 mActivity.getSurfaceHolder());
548         mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
549         mMediaPlayer.setScreenOnWhilePlaying(true);
550         mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
551             @Override
552             public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
553                 Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
554                 mOnVideoSizeChangedCalled.signal();
555             }
556         });
557         mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
558             @Override
559             public boolean onError(MediaPlayer mp, int what, int extra) {
560                 fail("Media player had error " + what + " playing video");
561                 return true;
562             }
563         });
564 
565         final Monitor playbackCompleted = new Monitor();
566         mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
567             @Override
568             public void onCompletion(MediaPlayer mp) {
569                 Log.v(TAG, "playLoadedVideo: onCompletion");
570                 playbackCompleted.signal();
571             }
572         });
573 
574         DrmInfo drmInfo = null;
575 
576         for (int round = 0; round < 2 ; round++) {
577             boolean keyRequestRound = (round == 0);
578             boolean restoreRound = (round == 1);
579             Log.v(TAG, "playLoadedVideo: round " + round);
580 
581             try {
582                 Log.v(TAG, "playLoadedVideo: setDataSource()");
583                 mMediaPlayer.setDataSource(mContext, file);
584 
585                 Log.v(TAG, "playLoadedVideo: prepare()");
586                 mMediaPlayer.prepare();
587 
588                 // but preparing the DRM every time with proper key request type
589                 drmInfo = mMediaPlayer.getDrmInfo();
590                 if (drmInfo != null) {
591                     if (keyRequestRound) {
592                         // asking for offline keys
593                         setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
594                                  MediaDrm.KEY_TYPE_OFFLINE);
595                     } else if (restoreRound) {
596                         setupDrmRestore(drmInfo, true /* prepareDrm */);
597                     } else {
598                         fail("preparePlayer: unexpected round " + round);
599                     }
600                     Log.v(TAG, "preparePlayer: setupDrm done!");
601                 }
602 
603             } catch (IOException e) {
604                 e.printStackTrace();
605                 throw new PrepareFailedException();
606             }
607 
608             Log.v(TAG, "playLoadedVideo: start()");
609             mMediaPlayer.start();
610             if (!mAudioOnly) {
611                 mOnVideoSizeChangedCalled.waitForSignal();
612             }
613             mMediaPlayer.setVolume(leftVolume, rightVolume);
614 
615             // waiting to complete
616             if (playTime == 0) {
617                 Log.v(TAG, "playLoadedVideo: waiting for playback completion");
618                 playbackCompleted.waitForSignal();
619             } else {
620                 Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
621                 playbackCompleted.waitForSignal(playTime);
622             }
623 
624             Log.v(TAG, "playLoadedVideo: stopping");
625             mMediaPlayer.stop();
626             Log.v(TAG, "playLoadedVideo: stopped");
627 
628             try {
629                 if (drmInfo != null) {
630                     if (restoreRound) {
631                         // releasing the offline key
632                         setupDrm(null /* drmInfo */, false /* prepareDrm */,
633                                  true /* synchronousNetworking */, MediaDrm.KEY_TYPE_RELEASE);
634                         Log.v(TAG, "playLoadedVideo: released offline keys");
635                     }
636 
637                     Log.v(TAG, "playLoadedVideo: releaseDrm");
638                     mMediaPlayer.releaseDrm();
639                 }
640             } catch (Exception e) {
641                 e.printStackTrace();
642                 throw new PrepareFailedException();
643             }
644 
645             if (keyRequestRound) {
646                 playbackCompleted.reset();
647                 final int SLEEP_BETWEEN_ROUNDS = 1000;
648                 Thread.sleep(SLEEP_BETWEEN_ROUNDS);
649 
650                 Log.v(TAG, "playLoadedVideo: reset");
651                 mMediaPlayer.reset();
652             }
653         } // for
654     }
655 
preparePlayerAndDrm_V5_asyncDrmSetupWithHandler()656     private void preparePlayerAndDrm_V5_asyncDrmSetupWithHandler()
657             throws InterruptedException {
658         Monitor onPreparedCalled = new Monitor();
659         Monitor onDrmPreparedCalled = new Monitor();
660         final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
661 
662         Log.v(TAG, "preparePlayerAndDrm_V5: started " +  Thread.currentThread());
663         final HandlerThread handlerThread = new HandlerThread("ModDrmHandlerThread");
664         handlerThread.start();
665         Handler handler = new Handler(handlerThread.getLooper());
666 
667         mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
668             @Override
669             public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
670                 Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo " + drmInfo +
671                         " " + Thread.currentThread());
672 
673                 // DRM preperation
674                 UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
675                 if (supportedSchemes.length == 0) {
676                     Log.e(TAG, "preparePlayerAndDrm_V5: onDrmInfo: No supportedSchemes");
677                     asyncSetupDrmError.set(true);
678                     // we won't call prepareDrm anymore but need to get passed the wait
679                     onDrmPreparedCalled.signal();
680                     return;
681                 }
682 
683                 // instead of supportedSchemes[0] in GTS
684                 UUID drmScheme = CLEARKEY_SCHEME_UUID;
685                 Log.d(TAG, "preparePlayerAndDrm_V5: onDrmInfo: selected " + drmScheme);
686 
687                 try {
688                     Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo: calling prepareDrm");
689                     mp.prepareDrm(drmScheme);
690                     Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo: called prepareDrm");
691                 } catch (Exception e) {
692                     e.printStackTrace();
693                     Log.e(TAG, "preparePlayerAndDrm_V5: onDrmInfo: prepareDrm exception " + e);
694                     asyncSetupDrmError.set(true);
695                     // need to get passed the wait
696                     onDrmPreparedCalled.signal();
697                     return;
698                 }
699 
700                 Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo done!");
701             }
702         }, handler);
703 
704         mMediaPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() {
705             @Override
706             public void onDrmPrepared(MediaPlayer mp, int status) {
707                 Log.v(TAG, "preparePlayerAndDrm_V5: onDrmPrepared status: " + status +
708                         " " + Thread.currentThread());
709 
710                 assertTrue("preparePlayerAndDrm_V5: onDrmPrepared did not succeed",
711                         status == MediaPlayer.PREPARE_DRM_STATUS_SUCCESS);
712 
713                 DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
714 
715                 // in the callback (async mode) so handling exceptions here
716                 try {
717                     setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
718                             MediaDrm.KEY_TYPE_STREAMING);
719                 } catch (Exception e) {
720                     Log.v(TAG, "preparePlayerAndDrm_V5: setupDrm EXCEPTION " + e);
721                     asyncSetupDrmError.set(true);
722                 }
723 
724                 onDrmPreparedCalled.signal();
725                 Log.v(TAG, "preparePlayerAndDrm_V5: onDrmPrepared done!");
726             }
727         }, handler);
728 
729         mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
730             @Override
731             public void onPrepared(MediaPlayer mp) {
732                 Log.v(TAG, "preparePlayerAndDrm_V5: onPrepared " + Thread.currentThread());
733 
734                 onPreparedCalled.signal();
735                 Log.v(TAG, "preparePlayerAndDrm_V5: onPrepared done!");
736             }
737         });
738 
739         Log.v(TAG, "preparePlayerAndDrm_V5: calling prepareAsync()");
740         mMediaPlayer.prepareAsync();
741 
742         // Waiting till the player is prepared
743         onPreparedCalled.waitForSignal();
744         // Unlike v3, onDrmPrepared is not synced to onPrepared b/c of its own thread handler
745         onDrmPreparedCalled.waitForSignal();
746 
747         // to handle setupDrm error (async) in the main thread rather than the callback
748         if (asyncSetupDrmError.get()) {
749             fail("preparePlayerAndDrm_V5: setupDrm");
750         }
751 
752         // stop the handler thread; callbacks are processed by now.
753         handlerThread.quit();
754     }
755 
756     // Converts a BMFF PSSH initData to a raw cenc initData
makeCencPSSH(UUID uuid, byte[] bmffPsshData)757     protected byte[] makeCencPSSH(UUID uuid, byte[] bmffPsshData) {
758         byte[] pssh_header = new byte[] { (byte)'p', (byte)'s', (byte)'s', (byte)'h' };
759         byte[] pssh_version = new byte[] { 1, 0, 0, 0 };
760         int boxSizeByteCount = 4;
761         int uuidByteCount = 16;
762         int dataSizeByteCount = 4;
763         // Per "W3C cenc Initialization Data Format" document:
764         // box size + 'pssh' + version + uuid + payload + size of data
765         int boxSize = boxSizeByteCount + pssh_header.length + pssh_version.length +
766             uuidByteCount + bmffPsshData.length + dataSizeByteCount;
767         int dataSize = 0;
768 
769         // the default write is big-endian, i.e., network byte order
770         ByteBuffer rawPssh = ByteBuffer.allocate(boxSize);
771         rawPssh.putInt(boxSize);
772         rawPssh.put(pssh_header);
773         rawPssh.put(pssh_version);
774         rawPssh.putLong(uuid.getMostSignificantBits());
775         rawPssh.putLong(uuid.getLeastSignificantBits());
776         rawPssh.put(bmffPsshData);
777         rawPssh.putInt(dataSize);
778 
779         return rawPssh.array();
780     }
781 
782     /*
783      * Sets up the DRM for the first DRM scheme from the supported list.
784      *
785      * @param drmInfo DRM info of the source
786      * @param prepareDrm whether prepareDrm should be called
787      * @param synchronousNetworking whether the network operation of key request/response will
788      *        be performed synchronously
789      */
setupDrm(DrmInfo drmInfo, boolean prepareDrm, boolean synchronousNetworking, int keyType)790     private void setupDrm(DrmInfo drmInfo, boolean prepareDrm, boolean synchronousNetworking,
791             int keyType) throws Exception {
792         Log.d(TAG, "setupDrm: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm +
793                 " synchronousNetworking: " + synchronousNetworking);
794         try {
795             byte[] initData = null;
796             String mime = null;
797             String keyTypeStr = "Unexpected";
798 
799             switch (keyType) {
800             case MediaDrm.KEY_TYPE_STREAMING:
801             case MediaDrm.KEY_TYPE_OFFLINE:
802                 // DRM preparation
803                 UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
804                 if (supportedSchemes.length == 0) {
805                     fail("setupDrm: No supportedSchemes");
806                 }
807 
808                 // instead of supportedSchemes[0] in GTS
809                 UUID drmScheme = CLEARKEY_SCHEME_UUID;
810                 Log.d(TAG, "setupDrm: selected " + drmScheme);
811 
812                 if (prepareDrm) {
813                     mMediaPlayer.prepareDrm(drmScheme);
814                 }
815 
816                 byte[] psshData = drmInfo.getPssh().get(drmScheme);
817                 // diverging from GTS
818                 if (psshData == null) {
819                     initData = CLEARKEY_PSSH;
820                     Log.d(TAG, "setupDrm: CLEARKEY scheme not found in PSSH. Using default data.");
821                 } else {
822                     // Can skip conversion if ClearKey adds support for BMFF initData (b/64863112)
823                     initData = makeCencPSSH(CLEARKEY_SCHEME_UUID, psshData);
824                 }
825                 Log.d(TAG, "setupDrm: initData[" + drmScheme + "]: " + Arrays.toString(initData));
826 
827                 // diverging from GTS
828                 mime = "cenc";
829 
830                 keyTypeStr = (keyType == MediaDrm.KEY_TYPE_STREAMING) ?
831                         "KEY_TYPE_STREAMING" : "KEY_TYPE_OFFLINE";
832                 break;
833 
834             case MediaDrm.KEY_TYPE_RELEASE:
835                 if (mKeySetId == null) {
836                     fail("setupDrm: KEY_TYPE_RELEASE requires a valid keySetId.");
837                 }
838                 keyTypeStr = "KEY_TYPE_RELEASE";
839                 break;
840 
841             default:
842                 fail("setupDrm: Unexpected keyType " + keyType);
843             }
844 
845             final MediaDrm.KeyRequest request = mMediaPlayer.getKeyRequest(
846                     (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
847                     initData,
848                     mime,
849                     keyType,
850                     null /* optionalKeyRequestParameters */
851                     );
852 
853             Log.d(TAG, "setupDrm: mMediaPlayer.getKeyRequest(" + keyTypeStr +
854                     ") request -> " + request);
855 
856             // diverging from GTS
857             byte[][] clearKeys = new byte[][] { CLEAR_KEY_CENC };
858             byte[] response = createKeysResponse(request, clearKeys);
859 
860             // null is returned when the response is for a streaming or release request.
861             byte[] keySetId = mMediaPlayer.provideKeyResponse(
862                     (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
863                     response);
864             Log.d(TAG, "setupDrm: provideKeyResponse -> " + Arrays.toString(keySetId));
865             // storing offline key for a later restore
866             mKeySetId = (keyType == MediaDrm.KEY_TYPE_OFFLINE) ? keySetId : null;
867 
868         } catch (MediaPlayer.NoDrmSchemeException e) {
869             Log.d(TAG, "setupDrm: NoDrmSchemeException");
870             e.printStackTrace();
871             throw e;
872         } catch (MediaPlayer.ProvisioningNetworkErrorException e) {
873             Log.d(TAG, "setupDrm: ProvisioningNetworkErrorException");
874             e.printStackTrace();
875             throw e;
876         } catch (MediaPlayer.ProvisioningServerErrorException e) {
877             Log.d(TAG, "setupDrm: ProvisioningServerErrorException");
878             e.printStackTrace();
879             throw e;
880         } catch (UnsupportedSchemeException e) {
881             Log.d(TAG, "setupDrm: UnsupportedSchemeException");
882             e.printStackTrace();
883             throw e;
884         } catch (ResourceBusyException e) {
885             Log.d(TAG, "setupDrm: ResourceBusyException");
886             e.printStackTrace();
887             throw e;
888         } catch (Exception e) {
889             Log.d(TAG, "setupDrm: Exception " + e);
890             e.printStackTrace();
891             throw e;
892         }
893     } // setupDrm
894 
setupDrmRestore(DrmInfo drmInfo, boolean prepareDrm)895     private void setupDrmRestore(DrmInfo drmInfo, boolean prepareDrm) throws Exception {
896         Log.d(TAG, "setupDrmRestore: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm);
897         try {
898             if (prepareDrm) {
899                 // DRM preparation
900                 UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
901                 if (supportedSchemes.length == 0) {
902                     fail("setupDrmRestore: No supportedSchemes");
903                 }
904 
905                 // instead of supportedSchemes[0] in GTS
906                 UUID drmScheme = CLEARKEY_SCHEME_UUID;
907                 Log.d(TAG, "setupDrmRestore: selected " + drmScheme);
908 
909                 mMediaPlayer.prepareDrm(drmScheme);
910             }
911 
912             if (mKeySetId == null) {
913                 fail("setupDrmRestore: Offline key has not been setup.");
914             }
915 
916             mMediaPlayer.restoreKeys(mKeySetId);
917 
918         } catch (MediaPlayer.NoDrmSchemeException e) {
919             Log.v(TAG, "setupDrmRestore: NoDrmSchemeException");
920             e.printStackTrace();
921             throw e;
922         } catch (Exception e) {
923             Log.v(TAG, "setupDrmRestore: Exception " + e);
924             e.printStackTrace();
925             throw e;
926         }
927     } // setupDrmRestore
928 
929     //////////////////////////////////////////////////////////////////////////////////////////////
930     // Diverging from GTS
931 
932     // Clearkey helpers
933 
934     /**
935      * Convert a hex string into byte array.
936      */
hexStringToByteArray(String s)937     private static byte[] hexStringToByteArray(String s) {
938         int len = s.length();
939         byte[] data = new byte[len / 2];
940         for (int i = 0; i < len; i += 2) {
941             data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) +
942                                    Character.digit(s.charAt(i + 1), 16));
943         }
944         return data;
945     }
946 
947     /**
948      * Extracts key ids from the pssh blob returned by getKeyRequest() and
949      * places it in keyIds.
950      * keyRequestBlob format (section 5.1.3.1):
951      * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
952      *
953      * @return size of keyIds vector that contains the key ids, 0 for error
954      */
getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds)955     private int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
956         if (0 == keyRequestBlob.length || keyIds == null) {
957             Log.e(TAG, "getKeyIds: Empty keyRequestBlob or null keyIds.");
958             return 0;
959         }
960 
961         String jsonLicenseRequest = new String(keyRequestBlob);
962         keyIds.clear();
963 
964         try {
965             JSONObject license = new JSONObject(jsonLicenseRequest);
966             Log.v(TAG, "getKeyIds: license: " + license);
967             final JSONArray ids = license.getJSONArray("kids");
968             Log.v(TAG, "getKeyIds: ids: " + ids);
969             for (int i = 0; i < ids.length(); ++i) {
970                 keyIds.add(ids.getString(i));
971             }
972         } catch (JSONException e) {
973             Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
974             return 0;
975         }
976         return keyIds.size();
977     }
978 
979     /**
980      * Creates the JSON Web Key string.
981      *
982      * @return JSON Web Key string.
983      */
createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys)984     private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
985         String jwkSet = "{\"keys\":[";
986         for (int i = 0; i < keyIds.size(); ++i) {
987             String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
988             String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
989 
990             jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
991                     "\",\"k\":\"" + key + "\"}";
992         }
993         jwkSet += "]}";
994         return jwkSet;
995     }
996 
997     /**
998      * Retrieves clear key ids from KeyRequest and creates the response in place.
999      */
createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys)1000     private byte[] createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys) {
1001 
1002         Vector<String> keyIds = new Vector<String>();
1003         if (0 == getKeyIds(keyRequest.getData(), keyIds)) {
1004             Log.e(TAG, "No key ids found in initData");
1005             return null;
1006         }
1007 
1008         if (clearKeys.length != keyIds.size()) {
1009             Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
1010                     keyIds.size() + ", keys=" + clearKeys.length);
1011             return null;
1012         }
1013 
1014         // Base64 encodes clearkeys. Keys are known to the application.
1015         Vector<String> keys = new Vector<String>();
1016         for (int i = 0; i < clearKeys.length; ++i) {
1017             String clearKey = Base64.encodeToString(clearKeys[i],
1018                     Base64.NO_PADDING | Base64.NO_WRAP);
1019             keys.add(clearKey);
1020         }
1021 
1022         String jwkSet = createJsonWebKeySet(keyIds, keys);
1023         byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
1024 
1025         return jsonResponse;
1026     }
1027 
1028     //////////////////////////////////////////////////////////////////////////////////////////////
1029     // Playback/download helpers
1030 
1031     private static class MediaDownloadManager {
1032         private static final String TAG = "MediaDownloadManager";
1033 
1034         private final Context mContext;
1035         private final DownloadManager mDownloadManager;
1036 
MediaDownloadManager(Context context)1037         public MediaDownloadManager(Context context) {
1038             mContext = context;
1039             mDownloadManager =
1040                     (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
1041         }
1042 
downloadFileWithRetries(Uri uri, Uri file, long timeout, int retries)1043         public long downloadFileWithRetries(Uri uri, Uri file, long timeout, int retries)
1044             throws Exception {
1045             long id = -1;
1046             for (int i = 0; i < retries; i++) {
1047                 try {
1048                     id = downloadFile(uri, file, timeout);
1049                     if (id != -1) {
1050                         break;
1051                     }
1052                 } catch (Exception e) {
1053                     removeFile(id);
1054                     Log.w(TAG, "Download failed " + i + " times ");
1055                 }
1056             }
1057             return id;
1058         }
1059 
downloadFile(Uri uri, Uri file, long timeout)1060         public long downloadFile(Uri uri, Uri file, long timeout) throws Exception {
1061             Log.i(TAG, "uri:" + uri + " file:" + file + " wait:" + timeout + " Secs");
1062             final DownloadReceiver receiver = new DownloadReceiver();
1063             long id = -1;
1064             try {
1065                 IntentFilter intentFilter =
1066                         new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
1067                 mContext.registerReceiver(receiver, intentFilter,
1068                         Context.RECEIVER_EXPORTED_UNAUDITED);
1069 
1070                 Request request = new Request(uri);
1071                 request.setDestinationUri(file);
1072                 id = mDownloadManager.enqueue(request);
1073                 Log.i(TAG, "enqueue:" + id);
1074 
1075                 receiver.waitForDownloadComplete(timeout, id);
1076             } finally {
1077                 mContext.unregisterReceiver(receiver);
1078             }
1079             return id;
1080         }
1081 
removeFile(long id)1082         public void removeFile(long id) {
1083             Log.i(TAG, "removeFile:" + id);
1084             mDownloadManager.remove(id);
1085         }
1086 
getUriForDownloadedFile(long id)1087         public Uri getUriForDownloadedFile(long id) {
1088             return mDownloadManager.getUriForDownloadedFile(id);
1089         }
1090 
1091         private final class DownloadReceiver extends BroadcastReceiver {
1092             private HashSet<Long> mCompleteIds = new HashSet<>();
1093 
DownloadReceiver()1094             public DownloadReceiver() {
1095             }
1096 
1097             @Override
onReceive(Context context, Intent intent)1098             public void onReceive(Context context, Intent intent) {
1099                 synchronized (mCompleteIds) {
1100                     if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
1101                         mCompleteIds.add(
1102                                 intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
1103                         mCompleteIds.notifyAll();
1104                     }
1105                 }
1106             }
1107 
isCompleteLocked(long... ids)1108             private boolean isCompleteLocked(long... ids) {
1109                 for (long id : ids) {
1110                     if (!mCompleteIds.contains(id)) {
1111                         return false;
1112                     }
1113                 }
1114                 return true;
1115             }
1116 
waitForDownloadComplete(long timeoutSecs, long... waitForIds)1117             public void waitForDownloadComplete(long timeoutSecs, long... waitForIds)
1118                 throws InterruptedException {
1119                 if (waitForIds.length == 0) {
1120                     throw new IllegalArgumentException("Missing IDs to wait for");
1121                 }
1122 
1123                 final long startTime = SystemClock.elapsedRealtime();
1124                 do {
1125                     synchronized (mCompleteIds) {
1126                         mCompleteIds.wait(1000);
1127                         if (isCompleteLocked(waitForIds)) {
1128                             return;
1129                         }
1130                     }
1131                 } while ((SystemClock.elapsedRealtime() - startTime) < timeoutSecs * 1000);
1132 
1133                 throw new InterruptedException("Timeout waiting for IDs " +
1134                         Arrays.toString(waitForIds) + "; received " + mCompleteIds.toString()
1135                         + ".  Make sure you have WiFi or some other connectivity for this test.");
1136             }
1137         }
1138 
1139     }  // MediaDownloadManager
1140 
1141 }
1142