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