• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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