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 17 package android.media.misc.cts; 18 19 import android.media.MediaCas; 20 import android.media.MediaCas.PluginDescriptor; 21 import android.media.MediaCas.Session; 22 import android.media.MediaCasException; 23 import android.media.MediaCasException.UnsupportedCasException; 24 import android.media.MediaCasStateException; 25 import android.media.MediaCodec; 26 import android.media.MediaDescrambler; 27 import android.os.Build; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.platform.test.annotations.AppModeFull; 31 import android.platform.test.annotations.Presubmit; 32 import android.platform.test.annotations.RequiresDevice; 33 import android.test.AndroidTestCase; 34 import android.util.Log; 35 36 import androidx.test.filters.SmallTest; 37 import androidx.test.InstrumentationRegistry; 38 39 import com.android.compatibility.common.util.ApiLevelUtil; 40 import com.android.compatibility.common.util.MediaUtils; 41 import com.android.compatibility.common.util.PropertyUtil; 42 43 import java.lang.ArrayIndexOutOfBoundsException; 44 import java.nio.ByteBuffer; 45 import java.util.Arrays; 46 import java.util.concurrent.CountDownLatch; 47 import java.util.concurrent.TimeUnit; 48 import java.util.regex.Matcher; 49 import java.util.regex.Pattern; 50 51 @Presubmit 52 @SmallTest 53 @RequiresDevice 54 @AppModeFull(reason = "TODO: evaluate and port to instant") 55 public class MediaCasTest extends AndroidTestCase { 56 private static final String TAG = "MediaCasTest"; 57 58 // CA System Ids used for testing 59 private static final int sInvalidSystemId = 0; 60 private static final int sClearKeySystemId = 0xF6D8; 61 private static final int API_LEVEL_BEFORE_CAS_SESSION = 28; 62 private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R); 63 private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S); 64 65 // ClearKey CAS/Descrambler test vectors 66 private static final String sProvisionStr = 67 "{ " + 68 " \"id\": 21140844, " + 69 " \"name\": \"Test Title\", " + 70 " \"lowercase_organization_name\": \"Android\", " + 71 " \"asset_key\": { " + 72 " \"encryption_key\": \"nezAr3CHFrmBR9R8Tedotw==\" " + 73 " }, " + 74 " \"cas_type\": 1, " + 75 " \"track_types\": [ ] " + 76 "} " ; 77 78 private static final String sEcmBufferStr = 79 "00 00 01 f0 00 50 00 01 00 00 00 01 00 46 00 00" + 80 "00 02 00 00 00 00 00 01 00 00 27 10 02 00 01 77" + 81 "01 42 95 6c 0e e3 91 bc fd 05 b1 60 4f 17 82 a4" + 82 "86 9b 23 56 00 01 00 00 00 01 00 00 27 10 02 00" + 83 "01 77 01 42 95 6c d7 43 62 f8 1c 62 19 05 c7 3a" + 84 "42 cd fd d9 13 48 " ; 85 86 private static final String sInputBufferStr = 87 "00 00 00 01 09 f0 00 00 00 01 67 42 c0 1e db 01" + 88 "40 16 ec 04 40 00 00 03 00 40 00 00 0f 03 c5 8b" + 89 "b8 00 00 00 01 68 ca 8c b2 00 00 01 06 05 ff ff" + 90 "70 dc 45 e9 bd e6 d9 48 b7 96 2c d8 20 d9 23 ee" + 91 "ef 78 32 36 34 20 2d 20 63 6f 72 65 20 31 34 32" + 92 "20 2d 20 48 2e 32 36 34 2f 4d 50 45 47 2d 34 20" + 93 "41 56 43 20 63 6f 64 65 63 20 2d 20 43 6f 70 79" + 94 "6c 65 66 74 20 32 30 30 33 2d 32 30 31 34 20 2d" + 95 "20 68 74 74 70 3a 2f 2f 77 77 77 2e 76 69 64 65" + 96 "6f 6c 61 6e 2e 6f 72 67 2f 78 32 36 34 2e 68 74" + 97 "6d 6c 6e 45 21 82 38 f0 9d 7d 96 e6 94 ae e2 87" + 98 "8f 04 49 e5 f6 8c 8b 9a 10 18 ba 94 e9 22 31 04" + 99 "7e 60 5b c4 24 00 90 62 0d dc 85 74 75 78 d0 14" + 100 "08 cb 02 1d 7d 9d 34 e8 81 b9 f7 09 28 79 29 8d" + 101 "e3 14 ed 5f ca af f4 1c 49 15 e1 80 29 61 76 80" + 102 "43 f8 58 53 40 d7 31 6d 61 81 41 e9 77 9f 9c e1" + 103 "6d f2 ee d9 c8 67 d2 5f 48 73 e3 5c cd a7 45 58" + 104 "bb dd 28 1d 68 fc b4 c6 f6 92 f6 30 03 aa e4 32" + 105 "f6 34 51 4b 0f 8c f9 ac 98 22 fb 49 c8 bf ca 8c" + 106 "80 86 5d d7 a4 52 b1 d9 a6 04 4e b3 2d 1f b8 35" + 107 "cc 45 6d 9c 20 a7 a4 34 59 72 e3 ae ba 49 de d1" + 108 "aa ee 3d 77 fc 5d c6 1f 9d ac c2 15 66 b8 e1 54" + 109 "4e 74 93 db 9a 24 15 6e 20 a3 67 3e 5a 24 41 5e" + 110 "b0 e6 35 87 1b c8 7a f9 77 65 e0 01 f2 4c e4 2b" + 111 "a9 64 96 96 0b 46 ca ea 79 0e 78 a3 5f 43 fc 47" + 112 "6a 12 fa c4 33 0e 88 1c 19 3a 00 c3 4e b5 d8 fa" + 113 "8e f1 bc 3d b2 7e 50 8d 67 c3 6b ed e2 ea a6 1f" + 114 "25 24 7c 94 74 50 49 e3 c6 58 2e fd 28 b4 c6 73" + 115 "b1 53 74 27 94 5c df 69 b7 a1 d7 f5 d3 8a 2c 2d" + 116 "b4 5e 8a 16 14 54 64 6e 00 6b 11 59 8a 63 38 80" + 117 "76 c3 d5 59 f7 3f d2 fa a5 ca 82 ff 4a 62 f0 e3" + 118 "42 f9 3b 38 27 8a 89 aa 50 55 4b 29 f1 46 7c 75" + 119 "ef 65 af 9b 0d 6d da 25 94 14 c1 1b f0 c5 4c 24" + 120 "0e 65 " ; 121 122 private static final String sExpectedOutputBufferStr = 123 "00 00 00 01 09 f0 00 00 00 01 67 42 c0 1e db 01" + 124 "40 16 ec 04 40 00 00 03 00 40 00 00 0f 03 c5 8b" + 125 "b8 00 00 00 01 68 ca 8c b2 00 00 01 06 05 ff ff" + 126 "70 dc 45 e9 bd e6 d9 48 b7 96 2c d8 20 d9 23 ee" + 127 "ef 78 32 36 34 20 2d 20 63 6f 72 65 20 31 34 32" + 128 "20 2d 20 48 2e 32 36 34 2f 4d 50 45 47 2d 34 20" + 129 "41 56 43 20 63 6f 64 65 63 20 2d 20 43 6f 70 79" + 130 "6c 65 66 74 20 32 30 30 33 2d 32 30 31 34 20 2d" + 131 "20 68 74 74 70 3a 2f 2f 77 77 77 2e 76 69 64 65" + 132 "6f 6c 61 6e 2e 6f 72 67 2f 78 32 36 34 2e 68 74" + 133 "6d 6c 20 2d 20 6f 70 74 69 6f 6e 73 3a 20 63 61" + 134 "62 61 63 3d 30 20 72 65 66 3d 32 20 64 65 62 6c" + 135 "6f 63 6b 3d 31 3a 30 3a 30 20 61 6e 61 6c 79 73" + 136 "65 3d 30 78 31 3a 30 78 31 31 31 20 6d 65 3d 68" + 137 "65 78 20 73 75 62 6d 65 3d 37 20 70 73 79 3d 31" + 138 "20 70 73 79 5f 72 64 3d 31 2e 30 30 3a 30 2e 30" + 139 "30 20 6d 69 78 65 64 5f 72 65 66 3d 31 20 6d 65" + 140 "5f 72 61 6e 67 65 3d 31 36 20 63 68 72 6f 6d 61" + 141 "5f 6d 65 3d 31 20 74 72 65 6c 6c 69 73 3d 31 20" + 142 "38 78 38 64 63 74 3d 30 20 63 71 6d 3d 30 20 64" + 143 "65 61 64 7a 6f 6e 65 3d 32 31 2c 31 31 20 66 61" + 144 "73 74 5f 70 73 6b 69 70 3d 31 20 63 68 72 6f 6d" + 145 "61 5f 71 70 5f 6f 66 66 73 65 74 3d 2d 32 20 74" + 146 "68 72 65 61 64 73 3d 36 30 20 6c 6f 6f 6b 61 68" + 147 "65 61 64 5f 74 68 72 65 61 64 73 3d 35 20 73 6c" + 148 "69 63 65 64 5f 74 68 72 65 61 64 73 3d 30 20 6e" + 149 "72 3d 30 20 64 65 63 69 6d 61 74 65 3d 31 20 69" + 150 "6e 74 65 72 6c 61 63 65 64 3d 30 20 62 6c 75 72" + 151 "61 79 5f 63 6f 6d 70 61 74 3d 30 20 63 6f 6e 73" + 152 "74 72 61 69 6e 65 64 5f 69 6e 74 72 61 3d 30 20" + 153 "62 66 72 61 6d 65 73 3d 30 20 77 65 69 67 68 74" + 154 "70 3d 30 20 6b 65 79 69 6e 74 3d 32 35 30 20 6b" + 155 "65 79 69 6e 74 5f 6d 69 6e 3d 32 35 20 73 63 65" + 156 "6e 65 " ; 157 158 @Override setUp()159 public void setUp() throws Exception { 160 super.setUp(); 161 // Need MANAGE_USERS or CREATE_USERS permission to access ActivityManager#getCurrentUser in 162 // MediaCas. It is used by all tests, then adopt it from shell in setup 163 InstrumentationRegistry 164 .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(); 165 } 166 167 @Override tearDown()168 protected void tearDown() throws Exception { 169 InstrumentationRegistry 170 .getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 171 super.tearDown(); 172 } 173 /** 174 * Test that all enumerated CA systems can be instantiated. 175 * 176 * Due to the vendor-proprietary nature of CAS, we cannot verify all operations 177 * of an arbitrary plugin. We can only verify that isSystemIdSupported() is 178 * consistent with the enumeration results, and all enumerated CA system ids can 179 * be instantiated. 180 */ testEnumeratePlugins()181 public void testEnumeratePlugins() throws Exception { 182 PluginDescriptor[] descriptors = MediaCas.enumeratePlugins(); 183 for (int i = 0; i < descriptors.length; i++) { 184 Log.d(TAG, "desciptor[" + i + "]: id=" + descriptors[i].getSystemId() 185 + ", name=" + descriptors[i].getName()); 186 MediaCas mediaCas = null; 187 MediaDescrambler descrambler = null; 188 byte[] sessionId = null, streamSessionId = null; 189 try { 190 final int CA_system_id = descriptors[i].getSystemId(); 191 if (!MediaCas.isSystemIdSupported(CA_system_id)) { 192 fail("Enumerated " + descriptors[i] + " but is not supported."); 193 } 194 try { 195 mediaCas = new MediaCas(CA_system_id); 196 } catch (UnsupportedCasException e) { 197 Log.d(TAG, "Enumerated " + descriptors[i] 198 + " but cannot instantiate MediaCas."); 199 throw new UnsupportedCasException( 200 descriptors[i] + " is enumerated, but cannot instantiate" ); 201 } 202 try { 203 descrambler = new MediaDescrambler(CA_system_id); 204 } catch (UnsupportedCasException e) { 205 // The descrambler can be supported through Tuner since R. 206 if (mIsAtLeastR) { 207 Log.d(TAG, "Enumerated " 208 + descriptors[i] + ", it doesn't support MediaDescrambler."); 209 } else { 210 fail("Enumerated " + descriptors[i] 211 + " but cannot instantiate MediaDescrambler."); 212 } 213 } 214 215 // Should always accept a listener (even if the plugin doesn't use it) 216 mediaCas.setEventListener(new MediaCas.EventListener() { 217 @Override 218 public void onEvent(MediaCas MediaCas, int event, int arg, byte[] data) { 219 Log.d(TAG, "Received MediaCas event: " 220 + "event=" + event + ", arg=" + arg 221 + ", data=" + Arrays.toString(data)); 222 } 223 @Override 224 public void onSessionEvent(MediaCas MediaCas, MediaCas.Session session, 225 int event, int arg, byte[] data) { 226 Log.d(TAG, "Received MediaCas Session event: " 227 + "event=" + event + ", arg=" + arg 228 + ", data=" + Arrays.toString(data)); 229 } 230 }, null); 231 } finally { 232 if (mediaCas != null) { 233 mediaCas.close(); 234 } 235 if (descrambler != null) { 236 descrambler.close(); 237 } 238 } 239 } 240 } 241 testInvalidSystemIdFails()242 public void testInvalidSystemIdFails() throws Exception { 243 assertFalse("Invalid id " + sInvalidSystemId + " should not be supported", 244 MediaCas.isSystemIdSupported(sInvalidSystemId)); 245 246 MediaCas unsupportedCAS = null; 247 MediaDescrambler unsupportedDescrambler = null; 248 249 try { 250 try { 251 unsupportedCAS = new MediaCas(sInvalidSystemId); 252 fail("Shouldn't be able to create MediaCas with invalid id " + sInvalidSystemId); 253 } catch (UnsupportedCasException e) { 254 // expected 255 } 256 257 try { 258 unsupportedDescrambler = new MediaDescrambler(sInvalidSystemId); 259 fail("Shouldn't be able to create MediaDescrambler with invalid id " + sInvalidSystemId); 260 } catch (UnsupportedCasException e) { 261 // expected 262 } 263 } finally { 264 if (unsupportedCAS != null) { 265 unsupportedCAS.close(); 266 } 267 if (unsupportedDescrambler != null) { 268 unsupportedDescrambler.close(); 269 } 270 } 271 } 272 testClearKeyPluginInstalled()273 public void testClearKeyPluginInstalled() throws Exception { 274 PluginDescriptor[] descriptors = MediaCas.enumeratePlugins(); 275 for (int i = 0; i < descriptors.length; i++) { 276 if (descriptors[i].getSystemId() == sClearKeySystemId) { 277 return; 278 } 279 } 280 fail("ClearKey plugin " + String.format("0x%d", sClearKeySystemId) + " is not found"); 281 } 282 283 /** 284 * Test that valid call sequences succeed. 285 */ testClearKeyApis()286 public void testClearKeyApis() throws Exception { 287 MediaCas mediaCas = null; 288 MediaDescrambler descrambler = null; 289 290 try { 291 if (mIsAtLeastR) { 292 mediaCas = new MediaCas(getContext(), sClearKeySystemId, null, 293 android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); 294 } else { 295 mediaCas = new MediaCas(sClearKeySystemId); 296 } 297 descrambler = new MediaDescrambler(sClearKeySystemId); 298 299 mediaCas.provision(sProvisionStr); 300 301 byte[] pvtData = new byte[256]; 302 mediaCas.setPrivateData(pvtData); 303 304 Session session = mediaCas.openSession(); 305 if (session == null) { 306 fail("Can't open session for program"); 307 } 308 309 if (mIsAtLeastR) { 310 Log.d(TAG, "Session Id = " + Arrays.toString(session.getSessionId())); 311 } 312 313 session.setPrivateData(pvtData); 314 315 Session streamSession = mediaCas.openSession(); 316 if (streamSession == null) { 317 fail("Can't open session for stream"); 318 } 319 streamSession.setPrivateData(pvtData); 320 321 descrambler.setMediaCasSession(session); 322 323 descrambler.setMediaCasSession(streamSession); 324 325 mediaCas.refreshEntitlements(3, null); 326 327 byte[] refreshBytes = new byte[4]; 328 refreshBytes[0] = 0; 329 refreshBytes[1] = 1; 330 refreshBytes[2] = 2; 331 refreshBytes[3] = 3; 332 333 mediaCas.refreshEntitlements(10, refreshBytes); 334 335 final HandlerThread thread = new HandlerThread("EventListenerHandlerThread"); 336 thread.start(); 337 Handler handler = new Handler(thread.getLooper()); 338 testEventEcho(mediaCas, 1, 2, null /* data */, handler); 339 testSessionEventEcho(mediaCas, session, 1, 2, null /* data */, handler); 340 if (mIsAtLeastR) { 341 testOpenSessionEcho(mediaCas, 0, 2, handler); 342 } 343 thread.interrupt(); 344 345 String eventDataString = "event data string"; 346 byte[] eventData = eventDataString.getBytes(); 347 testEventEcho(mediaCas, 3, 4, eventData, null /* handler */); 348 testSessionEventEcho(mediaCas, session, 3, 4, eventData, null /* handler */); 349 350 String emm = "clear key emm"; 351 byte[] emmData = emm.getBytes(); 352 mediaCas.processEmm(emmData); 353 354 byte[] ecmData = loadByteArrayFromString(sEcmBufferStr); 355 session.processEcm(ecmData); 356 streamSession.processEcm(ecmData); 357 358 ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler); 359 ByteBuffer expectedOutputBuf = ByteBuffer.wrap( 360 loadByteArrayFromString(sExpectedOutputBufferStr)); 361 assertTrue("Incorrect decryption result", 362 expectedOutputBuf.compareTo(outputBuf) == 0); 363 364 session.close(); 365 streamSession.close(); 366 } finally { 367 if (mediaCas != null) { 368 mediaCas.close(); 369 } 370 if (descrambler != null) { 371 descrambler.close(); 372 } 373 } 374 } 375 376 /** 377 * Test that all sessions are closed after a MediaCas object is released. 378 */ testClearKeySessionClosedAfterRelease()379 public void testClearKeySessionClosedAfterRelease() throws Exception { 380 MediaCas mediaCas = null; 381 MediaDescrambler descrambler = null; 382 383 try { 384 mediaCas = new MediaCas(sClearKeySystemId); 385 descrambler = new MediaDescrambler(sClearKeySystemId); 386 mediaCas.provision(sProvisionStr); 387 388 Session session = mediaCas.openSession(); 389 if (session == null) { 390 fail("Can't open session for program"); 391 } 392 393 Session streamSession = mediaCas.openSession(); 394 if (streamSession == null) { 395 fail("Can't open session for stream"); 396 } 397 398 mediaCas.close(); 399 mediaCas = null; 400 401 try { 402 descrambler.setMediaCasSession(session); 403 fail("Program session not closed after MediaCas is released"); 404 } catch (MediaCasStateException e) { 405 Log.d(TAG, "setMediaCasSession throws " 406 + e.getDiagnosticInfo() + " (as expected)"); 407 } 408 try { 409 descrambler.setMediaCasSession(streamSession); 410 fail("Stream session not closed after MediaCas is released"); 411 } catch (MediaCasStateException e) { 412 Log.d(TAG, "setMediaCasSession throws " 413 + e.getDiagnosticInfo() + " (as expected)"); 414 } 415 } finally { 416 if (mediaCas != null) { 417 mediaCas.close(); 418 } 419 if (descrambler != null) { 420 descrambler.close(); 421 } 422 } 423 } 424 425 /** 426 * Test that invalid call sequences fail with expected exceptions. 427 */ testClearKeyExceptions()428 public void testClearKeyExceptions() throws Exception { 429 MediaCas mediaCas = null; 430 MediaDescrambler descrambler = null; 431 432 try { 433 mediaCas = new MediaCas(sClearKeySystemId); 434 descrambler = new MediaDescrambler(sClearKeySystemId); 435 436 /* 437 * Test MediaCas exceptions 438 */ 439 440 // provision should fail with an invalid asset string 441 try { 442 mediaCas.provision("invalid asset string"); 443 fail("provision shouldn't succeed with invalid asset"); 444 } catch (MediaCasStateException e) { 445 Log.d(TAG, "provision throws " + e.getDiagnosticInfo() + " (as expected)"); 446 } 447 448 // processEmm should reject invalid offset and length 449 String emm = "clear key emm"; 450 byte[] emmData = emm.getBytes(); 451 try { 452 mediaCas.processEmm(emmData, 8, 40); 453 } catch (ArrayIndexOutOfBoundsException e) { 454 Log.d(TAG, "processEmm throws ArrayIndexOutOfBoundsException (as expected)"); 455 } 456 457 // open a session, then close it so that it should become invalid 458 Session invalidSession = mediaCas.openSession(); 459 if (invalidSession == null) { 460 fail("Can't open session for program"); 461 } 462 invalidSession.close(); 463 464 byte[] ecmData = loadByteArrayFromString(sEcmBufferStr); 465 466 // processEcm should fail with an invalid session id 467 try { 468 invalidSession.processEcm(ecmData); 469 fail("processEcm shouldn't succeed with invalid session id"); 470 } catch (MediaCasStateException e) { 471 Log.d(TAG, "processEcm throws " + e.getDiagnosticInfo() + " (as expected)"); 472 } 473 474 Session session = mediaCas.openSession(); 475 if (session == null) { 476 fail("Can't open session for program"); 477 } 478 479 // processEcm should fail without provisioning 480 try { 481 session.processEcm(ecmData); 482 fail("processEcm shouldn't succeed without provisioning"); 483 } catch (MediaCasException.NotProvisionedException e) { 484 Log.d(TAG, "processEcm throws NotProvisionedException (as expected)"); 485 } 486 487 // Now provision it, and expect failures other than NotProvisionedException 488 mediaCas.provision(sProvisionStr); 489 490 // processEcm should fail with ecm buffer that's too short 491 try { 492 session.processEcm(ecmData, 0, 8); 493 fail("processEcm shouldn't succeed with truncated ecm"); 494 } catch (IllegalArgumentException e) { 495 Log.d(TAG, "processEcm throws " + e.toString() + " (as expected)"); 496 } 497 498 // processEcm should fail with ecm with bad descriptor count 499 try { 500 ecmData[17] = 3; // change the descriptor count field to 3 (invalid) 501 session.processEcm(ecmData); 502 fail("processEcm shouldn't succeed with altered descriptor count"); 503 } catch (MediaCasStateException e) { 504 Log.d(TAG, "processEcm throws " + e.getDiagnosticInfo() + " (as expected)"); 505 } 506 507 /* 508 * Test MediaDescrambler exceptions 509 */ 510 511 // setMediaCasSession should fail with an invalid session id 512 try { 513 descrambler.setMediaCasSession(invalidSession); 514 fail("setMediaCasSession shouldn't succeed with invalid session id"); 515 } catch (MediaCasStateException e) { 516 Log.d(TAG, "setMediaCasSession throws " 517 + e.getDiagnosticInfo() + " (as expected)"); 518 } 519 520 // descramble should fail without a valid session 521 try { 522 ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler); 523 fail("descramble should fail without a valid session"); 524 } catch (MediaCasStateException e) { 525 Log.d(TAG, "descramble throws " + e.getDiagnosticInfo() + " (as expected)"); 526 } 527 528 // Now set a valid session, should still fail because no valid ecm is processed 529 descrambler.setMediaCasSession(session); 530 try { 531 ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler); 532 fail("descramble should fail without valid ecm"); 533 } catch (MediaCasStateException e) { 534 Log.d(TAG, "descramble throws " + e.getDiagnosticInfo() + " (as expected)"); 535 } 536 } finally { 537 if (mediaCas != null) { 538 mediaCas.close(); 539 } 540 if (descrambler != null) { 541 descrambler.close(); 542 } 543 } 544 } 545 546 /** 547 * Test Resource Lost Event. 548 */ testResourceLostEvent()549 public void testResourceLostEvent() throws Exception { 550 MediaCas mediaCas = null; 551 if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return; 552 553 try { 554 mediaCas = new MediaCas(getContext(), sClearKeySystemId, null, 555 android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); 556 557 mediaCas.provision(sProvisionStr); 558 559 byte[] pvtData = new byte[256]; 560 mediaCas.setPrivateData(pvtData); 561 562 Session session = mediaCas.openSession(); 563 if (session == null) { 564 fail("Can't open session for program"); 565 } 566 567 Session streamSession = mediaCas.openSession(); 568 if (streamSession == null) { 569 fail("Can't open session for stream"); 570 } 571 572 final HandlerThread thread = new HandlerThread("EventListenerHandlerThread"); 573 thread.start(); 574 Handler handler = new Handler(thread.getLooper()); 575 testForceResourceLost(mediaCas, handler); 576 thread.interrupt(); 577 578 } finally { 579 if (mediaCas != null) { 580 mediaCas.close(); 581 } 582 } 583 } 584 585 /** 586 * Test Set Event Listener in MediaCas Constructor. 587 */ testConstructWithEventListener()588 public void testConstructWithEventListener() throws Exception { 589 MediaCas mediaCas = null; 590 if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return; 591 592 try { 593 TestEventListener listener = new TestEventListener(); 594 HandlerThread thread = new HandlerThread("EventListenerHandlerThread"); 595 thread.start(); 596 Handler handler = new Handler(thread.getLooper()); 597 598 mediaCas = new MediaCas(getContext(), sClearKeySystemId, null, 599 android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, handler, 600 listener); 601 602 thread.interrupt(); 603 604 } finally { 605 if (mediaCas != null) { 606 mediaCas.close(); 607 } 608 } 609 } 610 611 private class TestEventListener implements MediaCas.EventListener { 612 private final CountDownLatch mLatch = new CountDownLatch(1); 613 private final MediaCas mMediaCas; 614 private final MediaCas.Session mSession; 615 private final int mEvent; 616 private final int mArg; 617 private final byte[] mData; 618 private boolean mIsIdential; 619 TestEventListener()620 TestEventListener() { 621 mMediaCas = null; 622 mEvent = 0; 623 mArg = 0; 624 mData = null; 625 mSession = null; 626 } 627 TestEventListener(MediaCas mediaCas, int event, int arg, byte[] data)628 TestEventListener(MediaCas mediaCas, int event, int arg, byte[] data) { 629 mMediaCas = mediaCas; 630 mEvent = event; 631 mArg = arg; 632 mData = data; 633 mSession = null; 634 } 635 TestEventListener(MediaCas mediaCas, MediaCas.Session session, int event, int arg, byte[] data)636 TestEventListener(MediaCas mediaCas, MediaCas.Session session, int event, 637 int arg, byte[] data) { 638 mMediaCas = mediaCas; 639 mSession = session; 640 mEvent = event; 641 mArg = arg; 642 mData = data; 643 } 644 TestEventListener(MediaCas mediaCas, int intent, int scramblingMode)645 TestEventListener(MediaCas mediaCas, int intent, int scramblingMode) { 646 mMediaCas = mediaCas; 647 mEvent = intent; 648 mArg = scramblingMode; 649 mData = null; 650 mSession = null; 651 } 652 TestEventListener(MediaCas mediaCas)653 TestEventListener(MediaCas mediaCas) { 654 mMediaCas = mediaCas; 655 mEvent = 0; 656 mArg = 0; 657 mData = null; 658 mSession = null; 659 } 660 waitForResult()661 boolean waitForResult() { 662 try { 663 if (!mLatch.await(1, TimeUnit.SECONDS)) { 664 return false; 665 } 666 return mIsIdential; 667 } catch (InterruptedException e) {} 668 return false; 669 } 670 671 @Override onEvent(MediaCas mediaCas, int event, int arg, byte[] data)672 public void onEvent(MediaCas mediaCas, int event, int arg, byte[] data) { 673 Log.d(TAG, "Received MediaCas event: event=" + event 674 + ", arg=" + arg + ", data=" + Arrays.toString(data)); 675 if (mediaCas == mMediaCas && event == mEvent 676 && arg == mArg && (Arrays.equals(data, mData) || 677 data == null && mData.length == 0 || 678 mData == null && data.length == 0)) { 679 mIsIdential = true; 680 } 681 mLatch.countDown(); 682 } 683 684 @Override onSessionEvent(MediaCas mediaCas, MediaCas.Session session, int event, int arg, byte[] data)685 public void onSessionEvent(MediaCas mediaCas, MediaCas.Session session, 686 int event, int arg, byte[] data) { 687 Log.d(TAG, "Received MediaCas session event: event=" + event 688 + ", arg=" + arg + ", data=" + Arrays.toString(data)); 689 if (mediaCas == mMediaCas && mSession.equals(session) && event == mEvent 690 && arg == mArg && (Arrays.equals(data, mData) || 691 data == null && mData.length == 0 || 692 mData == null && data.length == 0)) { 693 mIsIdential = true; 694 } 695 mLatch.countDown(); 696 } 697 698 @Override onPluginStatusUpdate(MediaCas mediaCas, int statusUpdated, int arg)699 public void onPluginStatusUpdate(MediaCas mediaCas, int statusUpdated, int arg) { 700 Log.d(TAG, "Received MediaCas Status Update event"); 701 if (mediaCas == mMediaCas && statusUpdated == mEvent && arg == mArg ) { 702 mIsIdential = true; 703 } 704 mLatch.countDown(); 705 } 706 707 @Override onResourceLost(MediaCas mediaCas)708 public void onResourceLost(MediaCas mediaCas) { 709 Log.d(TAG, "Received MediaCas Resource Lost event"); 710 if (mediaCas == mMediaCas) { 711 mIsIdential = true; 712 } 713 mLatch.countDown(); 714 } 715 } 716 717 // helper to send an event and wait for echo testEventEcho(MediaCas mediaCas, int event, int arg, byte[] data, Handler handler)718 private void testEventEcho(MediaCas mediaCas, int event, 719 int arg, byte[] data, Handler handler) throws Exception { 720 TestEventListener listener = new TestEventListener(mediaCas, event, arg, data); 721 mediaCas.setEventListener(listener, handler); 722 mediaCas.sendEvent(event, arg, data); 723 assertTrue("Didn't receive event callback for " + event, listener.waitForResult()); 724 } 725 726 // helper to send an event and wait for echo testSessionEventEcho(MediaCas mediaCas, MediaCas.Session session, int event, int arg, byte[] data, Handler handler)727 private void testSessionEventEcho(MediaCas mediaCas, MediaCas.Session session, int event, 728 int arg, byte[] data, Handler handler) throws Exception { 729 TestEventListener listener = new TestEventListener(mediaCas, session, event, arg, data); 730 mediaCas.setEventListener(listener, handler); 731 try { 732 session.sendSessionEvent(event, arg, data); 733 } catch (UnsupportedCasException e) { 734 if (!PropertyUtil.isVendorApiLevelNewerThan(API_LEVEL_BEFORE_CAS_SESSION)){ 735 Log.d(TAG, "Send Session Event isn't supported, Skipped this test case"); 736 return; 737 } 738 throw e; 739 } 740 assertTrue("Didn't receive session event callback for " + event, listener.waitForResult()); 741 } 742 743 // helper to open Session with scrambling mode and wait for echo for status change event testOpenSessionEcho(MediaCas mediaCas, int intent, int scramblingMode, Handler handler)744 private void testOpenSessionEcho(MediaCas mediaCas, int intent, int scramblingMode, 745 Handler handler) throws Exception { 746 TestEventListener listener = new TestEventListener(mediaCas, intent, scramblingMode); 747 mediaCas.setEventListener(listener, handler); 748 try { 749 mediaCas.openSession(intent, scramblingMode); 750 } catch (UnsupportedCasException e) { 751 if (!PropertyUtil.isVendorApiLevelNewerThan(API_LEVEL_BEFORE_CAS_SESSION + 1)) { 752 Log.d(TAG, 753 "Opens Session with scramblingMode isn't supported, Skipped this test case"); 754 return; 755 } 756 throw e; 757 } 758 assertTrue("Didn't receive Echo from openSession with scrambling mode: " + scramblingMode, 759 listener.waitForResult()); 760 } 761 762 // helper to force to lose resource and wait for Resource Lost event testForceResourceLost(MediaCas mediaCas, Handler handler)763 private void testForceResourceLost(MediaCas mediaCas, Handler handler) throws Exception { 764 TestEventListener listener = new TestEventListener(mediaCas); 765 mediaCas.setEventListener(listener, handler); 766 mediaCas.forceResourceLost(); 767 assertTrue("Didn't receive Resource Lost event ", listener.waitForResult()); 768 } 769 770 // helper to descramble from the sample input (sInputBufferStr) and get output buffer descrambleTestInputBuffer( MediaDescrambler descrambler)771 private ByteBuffer descrambleTestInputBuffer( 772 MediaDescrambler descrambler) throws Exception { 773 MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo(); 774 int[] numBytesOfClearData = new int[] { 162, 0, 0 }; 775 int[] numBytesOfEncryptedData = new int[] { 0, 184, 184 }; 776 byte[] key = new byte[16]; 777 key[0] = 2; // scrambling mode = even key 778 byte[] iv = new byte[16]; // not used 779 cryptoInfo.set(3, numBytesOfClearData, numBytesOfEncryptedData, 780 key, iv, MediaCodec.CRYPTO_MODE_AES_CBC); 781 ByteBuffer inputBuf = ByteBuffer.wrap( 782 loadByteArrayFromString(sInputBufferStr)); 783 ByteBuffer outputBuf = ByteBuffer.allocate(inputBuf.capacity()); 784 descrambler.descramble(inputBuf, outputBuf, cryptoInfo); 785 786 return outputBuf; 787 } 788 789 // helper to load byte[] from a String loadByteArrayFromString(final String str)790 private byte[] loadByteArrayFromString(final String str) { 791 Pattern pattern = Pattern.compile("[0-9a-fA-F]{2}"); 792 Matcher matcher = pattern.matcher(str); 793 // allocate a large enough byte array first 794 byte[] tempArray = new byte[str.length() / 2]; 795 int i = 0; 796 while (matcher.find()) { 797 tempArray[i++] = (byte)Integer.parseInt(matcher.group(), 16); 798 } 799 return Arrays.copyOfRange(tempArray, 0, i); 800 } 801 } 802