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