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