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