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