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