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