• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 package android.media.drmframework.cts;
17 
18 import android.media.MediaCodecInfo.CodecCapabilities;
19 import android.media.MediaDrm;
20 import android.media.MediaDrm.KeyStatus;
21 import android.media.MediaDrm.MediaDrmStateException;
22 import android.media.MediaDrmException;
23 import android.media.MediaDrmThrowable;
24 import android.media.MediaFormat;
25 import android.media.NotProvisionedException;
26 import android.media.ResourceBusyException;
27 import android.media.UnsupportedSchemeException;
28 import android.media.cts.MediaCodecClearKeyPlayer;
29 import android.media.cts.MediaCodecPlayerTestBase;
30 import android.media.cts.MediaStubActivity;
31 import android.media.cts.TestUtils.Monitor;
32 import android.net.Uri;
33 import android.os.Build;
34 import android.os.Looper;
35 import android.os.SystemProperties;
36 import android.platform.test.annotations.AppModeFull;
37 import android.platform.test.annotations.Presubmit;
38 import android.util.Base64;
39 import android.util.Log;
40 import android.view.Surface;
41 
42 import androidx.annotation.NonNull;
43 import androidx.test.filters.SdkSuppress;
44 
45 import com.android.compatibility.common.util.ApiLevelUtil;
46 import com.android.compatibility.common.util.ApiTest;
47 import com.android.compatibility.common.util.FrameworkSpecificTest;
48 import com.android.compatibility.common.util.ModuleSpecificTest;
49 
50 import org.json.JSONArray;
51 import org.json.JSONException;
52 import org.json.JSONObject;
53 import org.junit.Assert;
54 
55 import java.io.File;
56 import java.nio.charset.Charset;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.HashMap;
60 import java.util.List;
61 import java.util.Set;
62 import java.util.UUID;
63 import java.util.Vector;
64 
65 /**
66  * Tests of MediaPlayer streaming capabilities.
67  */
68 @AppModeFull(reason = "Instant apps cannot access the SD card")
69 public class MediaDrmClearkeyTest extends MediaCodecPlayerTestBase<MediaStubActivity> {
70 
71     private static final String TAG = MediaDrmClearkeyTest.class.getSimpleName();
72 
73     // Add additional keys here if the content has more keys.
74     private static final byte[] CLEAR_KEY_CENC = {
75             (byte)0x3f, (byte)0x0a, (byte)0x33, (byte)0xf3, (byte)0x40, (byte)0x98, (byte)0xb9, (byte)0xe2,
76             (byte)0x2b, (byte)0xc0, (byte)0x78, (byte)0xe0, (byte)0xa1, (byte)0xb5, (byte)0xe8, (byte)0x54 };
77 
78     private static final byte[] CLEAR_KEY_WEBM = "_CLEAR_KEY_WEBM_".getBytes();
79 
80     private static final int NUMBER_OF_SECURE_STOPS = 10;
81     private static final int VIDEO_WIDTH_CENC = 1280;
82     private static final int VIDEO_HEIGHT_CENC = 720;
83     private static final int VIDEO_WIDTH_WEBM = 352;
84     private static final int VIDEO_HEIGHT_WEBM = 288;
85     private static final int VIDEO_WIDTH_MPEG2TS = 320;
86     private static final int VIDEO_HEIGHT_MPEG2TS = 240;
87     private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
88     private static final String MIME_VIDEO_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
89 
90     // Property Keys
91     private static final String ALGORITHMS_PROPERTY_KEY = MediaDrm.PROPERTY_ALGORITHMS;
92     private static final String DESCRIPTION_PROPERTY_KEY = MediaDrm.PROPERTY_DESCRIPTION;
93     private static final String DEVICEID_PROPERTY_KEY = "deviceId";
94     private static final String INVALID_PROPERTY_KEY = "invalid property key";
95     private static final String LISTENER_TEST_SUPPORT_PROPERTY_KEY = "listenerTestSupport";
96     private static final String VENDOR_PROPERTY_KEY = MediaDrm.PROPERTY_VENDOR;
97     private static final String VERSION_PROPERTY_KEY = MediaDrm.PROPERTY_VERSION;
98 
99     // Error message
100     private static final String ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED = "Crypto scheme is not supported";
101     private static final String MEDIA_DIR = WorkDir.getMediaDirString();
102     private static final Uri CENC_AUDIO_URL =
103             Uri.fromFile(new File(MEDIA_DIR + "llama_aac_audio.mp4"));
104     private static final Uri CENC_VIDEO_URL =
105             Uri.fromFile(new File(MEDIA_DIR + "llama_h264_main_720p_8000.mp4"));
106     private static final Uri WEBM_URL =
107             Uri.fromFile(new File(MEDIA_DIR
108                     + "video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt"
109                     + ".webm"));
110     private static final Uri MPEG2TS_SCRAMBLED_URL =
111             Uri.fromFile(new File(MEDIA_DIR + "segment000001_scrambled.ts"));
112     private static final Uri MPEG2TS_CLEAR_URL =
113             Uri.fromFile(new File(MEDIA_DIR + "segment000001.ts"));
114 
115     private static final UUID COMMON_PSSH_SCHEME_UUID =
116             new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
117     private static final UUID CLEARKEY_SCHEME_UUID =
118             new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
119 
120     private byte[] mDrmInitData;
121     private byte[] mKeySetId;
122     private byte[] mSessionId;
123     private Monitor mSessionMonitor = new Monitor();
124     private Looper mLooper;
125     private MediaDrm mDrm = null;
126     private final Object mLock = new Object();
127     private boolean mEventListenerCalled;
128     private boolean mExpirationUpdateReceived;
129     private boolean mLostStateReceived;
130 
131     private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
132     private static final boolean VNDK_IS_AT_LEAST_U =
133             SystemProperties.getInt("ro.vndk.version", Build.VERSION_CODES.CUR_DEVELOPMENT)
134                     >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
135 
136     private static final boolean FIRST_RELEASE_IS_AT_LEAST_U =
137             SystemProperties.getInt("ro.product.first_api_level", Build.VERSION_CODES.CUR_DEVELOPMENT)
138                 >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
139 
MediaDrmClearkeyTest()140     public MediaDrmClearkeyTest() {
141         super(MediaStubActivity.class);
142     }
143 
144     @Override
setUp()145     protected void setUp() throws Exception {
146         super.setUp();
147         if (false == deviceHasMediaDrm()) {
148             tearDown();
149         }
150     }
151 
152     @Override
tearDown()153     protected void tearDown() throws Exception {
154         super.tearDown();
155     }
156 
deviceHasMediaDrm()157     private boolean deviceHasMediaDrm() {
158         // ClearKey is introduced after KitKat.
159         if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
160             return false;
161         }
162         return true;
163     }
164 
165     /**
166      * Extracts key ids from the pssh blob returned by getKeyRequest() and
167      * places it in keyIds.
168      * keyRequestBlob format (section 5.1.3.1):
169      * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#clear-key
170      *
171      * @return size of keyIds vector that contains the key ids, 0 for error
172      */
getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds)173     private static int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
174         if (0 == keyRequestBlob.length || keyIds == null)
175             return 0;
176 
177         String jsonLicenseRequest = new String(keyRequestBlob);
178         keyIds.clear();
179 
180         try {
181             JSONObject license = new JSONObject(jsonLicenseRequest);
182             final JSONArray ids = license.getJSONArray("kids");
183             for (int i = 0; i < ids.length(); ++i) {
184                 keyIds.add(ids.getString(i));
185             }
186         } catch (JSONException e) {
187             Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
188             return 0;
189         }
190         return keyIds.size();
191     }
192 
193     /**
194      * Creates the JSON Web Key string.
195      *
196      * @return JSON Web Key string.
197      */
createJsonWebKeySet( Vector<String> keyIds, Vector<String> keys, int keyType)198     private static String createJsonWebKeySet(
199             Vector<String> keyIds, Vector<String> keys, int keyType) {
200         String jwkSet = "{\"keys\":[";
201         for (int i = 0; i < keyIds.size(); ++i) {
202             String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
203             String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
204 
205             jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
206                     "\",\"k\":\"" + key + "\"}";
207         }
208         jwkSet += "], \"type\":";
209         if (keyType == MediaDrm.KEY_TYPE_OFFLINE || keyType == MediaDrm.KEY_TYPE_RELEASE) {
210             jwkSet += "\"persistent-license\" }";
211         } else {
212             jwkSet += "\"temporary\" }";
213         }
214         return jwkSet;
215     }
216 
217     /**
218      * Retrieves clear key ids from getKeyRequest(), create JSON Web Key
219      * set and send it to the CDM via provideKeyResponse().
220      *
221      * @return key set ID
222      */
retrieveKeys(MediaDrm drm, String initDataType, byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds)223     public static byte[] retrieveKeys(MediaDrm drm, String initDataType,
224             byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds) {
225         MediaDrm.KeyRequest drmRequest = null;
226         try {
227             drmRequest = drm.getKeyRequest(sessionId, drmInitData, initDataType,
228                     keyType, null);
229         } catch (Exception e) {
230             e.printStackTrace();
231             Log.i(TAG, "Failed to get key request: " + e.toString());
232         }
233         if (drmRequest == null) {
234             Log.e(TAG, "Failed getKeyRequest");
235             return null;
236         }
237 
238         Vector<String> keyIds = new Vector<String>();
239         if (0 == getKeyIds(drmRequest.getData(), keyIds)) {
240             Log.e(TAG, "No key ids found in initData");
241             return null;
242         }
243 
244         if (clearKeyIds.length != keyIds.size()) {
245             Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
246                     keyIds.size() + ", keys=" + clearKeyIds.length);
247             return null;
248         }
249 
250         // Base64 encodes clearkeys. Keys are known to the application.
251         Vector<String> keys = new Vector<String>();
252         for (int i = 0; i < clearKeyIds.length; ++i) {
253             String clearKey = Base64.encodeToString(clearKeyIds[i],
254                     Base64.NO_PADDING | Base64.NO_WRAP);
255             keys.add(clearKey);
256         }
257 
258         String jwkSet = createJsonWebKeySet(keyIds, keys, keyType);
259         byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
260 
261         try {
262             try {
263                 return drm.provideKeyResponse(sessionId, jsonResponse);
264             } catch (IllegalStateException e) {
265                 Log.e(TAG, "Failed to provide key response: " + e.toString());
266             }
267         } catch (Exception e) {
268             e.printStackTrace();
269             Log.e(TAG, "Failed to provide key response: " + e.toString());
270         }
271         return null;
272     }
273 
274     /**
275      * Retrieves clear key ids from getKeyRequest(), create JSON Web Key
276      * set and send it to the CDM via provideKeyResponse().
277      */
getKeys(MediaDrm drm, String initDataType, byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds)278     private void getKeys(MediaDrm drm, String initDataType,
279             byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds) {
280         mKeySetId = retrieveKeys(drm, initDataType, sessionId, drmInitData, keyType, clearKeyIds);
281     }
282 
startDrm(final byte[][] clearKeyIds, final String initDataType, final UUID drmSchemeUuid, int keyType)283     private @NonNull MediaDrm startDrm(final byte[][] clearKeyIds, final String initDataType,
284                                        final UUID drmSchemeUuid, int keyType) {
285         if (!MediaDrm.isCryptoSchemeSupported(drmSchemeUuid)) {
286             throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
287         }
288 
289         new Thread() {
290             @Override
291             public void run() {
292                 if (mDrm != null) {
293                     Log.e(TAG, "Failed to startDrm: already started");
294                     return;
295                 }
296                 // Set up a looper to handle events
297                 Looper.prepare();
298 
299                 // Save the looper so that we can terminate this thread
300                 // after we are done with it.
301                 mLooper = Looper.myLooper();
302 
303                 try {
304                     mDrm = new MediaDrm(drmSchemeUuid);
305                 } catch (MediaDrmException e) {
306                     Log.e(TAG, "Failed to create MediaDrm: " + e.getMessage());
307                     return;
308                 }
309 
310                 synchronized(mLock) {
311                     mDrm.setOnEventListener(new MediaDrm.OnEventListener() {
312                             @Override
313                             public void onEvent(MediaDrm md, byte[] sid, int event,
314                                     int extra, byte[] data) {
315                                 if (md != mDrm) {
316                                     Log.e(TAG, "onEvent callback: drm object mismatch");
317                                     return;
318                                 } else if (!Arrays.equals(mSessionId, sid)) {
319                                     Log.e(TAG, "onEvent callback: sessionId mismatch: |" +
320                                             Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
321                                     return;
322                                 }
323 
324                                 mEventListenerCalled = true;
325                                 if (event == MediaDrm.EVENT_PROVISION_REQUIRED) {
326                                     Log.i(TAG, "MediaDrm event: Provision required");
327                                 } else if (event == MediaDrm.EVENT_KEY_REQUIRED) {
328                                     Log.i(TAG, "MediaDrm event: Key required");
329                                     getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
330                                             keyType, clearKeyIds);
331                                 } else if (event == MediaDrm.EVENT_KEY_EXPIRED) {
332                                     Log.i(TAG, "MediaDrm event: Key expired");
333                                     getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
334                                             keyType, clearKeyIds);
335                                 } else if (event == MediaDrm.EVENT_VENDOR_DEFINED) {
336                                     Log.i(TAG, "MediaDrm event: Vendor defined");
337                                 } else if (event == MediaDrm.EVENT_SESSION_RECLAIMED) {
338                                     Log.i(TAG, "MediaDrm event: Session reclaimed");
339                                 } else {
340                                     Log.e(TAG, "MediaDrm event not supported: " + event);
341                                 }
342                             }
343                         });
344                     mDrm.setOnExpirationUpdateListener(new MediaDrm.OnExpirationUpdateListener() {
345                             @Override
346                             public void onExpirationUpdate(MediaDrm md, byte[] sid, long expirationTime) {
347                                 if (md != mDrm) {
348                                     Log.e(TAG, "onExpirationUpdate callback: drm object mismatch");
349                                 } else if (!Arrays.equals(mSessionId, sid)) {
350                                     Log.e(TAG, "onExpirationUpdate callback: sessionId mismatch: |" +
351                                             Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
352                                 } else {
353                                     mExpirationUpdateReceived = true;
354                                 }
355                             }
356                         }, null);
357                     mDrm.setOnSessionLostStateListener(new MediaDrm.OnSessionLostStateListener() {
358                             @Override
359                             public void onSessionLostState(MediaDrm md, byte[] sid) {
360                                 if (md != mDrm) {
361                                     Log.e(TAG, "onSessionLostState callback: drm object mismatch");
362                                 } else if (!Arrays.equals(mSessionId, sid)) {
363                                     Log.e(TAG, "onSessionLostState callback: sessionId mismatch: |" +
364                                             Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
365                                 } else {
366                                     mLostStateReceived = true;
367                                 }
368                             }
369                         }, null);
370                     mDrm.setOnKeyStatusChangeListener(new MediaDrm.OnKeyStatusChangeListener() {
371                             @Override
372                             public void onKeyStatusChange(MediaDrm md, byte[] sessionId,
373                                     List<KeyStatus> keyInformation, boolean hasNewUsableKey) {
374                                 Log.d(TAG, "onKeyStatusChange");
375                                 assertTrue(md == mDrm);
376                                 assertTrue(Arrays.equals(sessionId, mSessionId));
377                                 mSessionMonitor.signal();
378                                 assertTrue(hasNewUsableKey);
379 
380                                 assertEquals(3, keyInformation.size());
381                                 KeyStatus keyStatus = keyInformation.get(0);
382                                 assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0xa, 0xb, 0xc}));
383                                 assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_USABLE);
384                                 keyStatus = keyInformation.get(1);
385                                 assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0xd, 0xe, 0xf}));
386                                 assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_EXPIRED);
387                                 keyStatus = keyInformation.get(2);
388                                 assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0x0, 0x1, 0x2}));
389                                 assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_USABLE_IN_FUTURE);
390                             }
391                         }, null);
392 
393                     mLock.notify();
394                 }
395                 Looper.loop();  // Blocks forever until Looper.quit() is called.
396             }
397         }.start();
398 
399         // wait for mDrm to be created
400         synchronized(mLock) {
401             try {
402                 mLock.wait(1000);
403             } catch (Exception e) {
404             }
405         }
406         return mDrm;
407     }
408 
stopDrm(MediaDrm drm)409     private void stopDrm(MediaDrm drm) {
410         if (drm != mDrm) {
411             Log.e(TAG, "invalid drm specified in stopDrm");
412         }
413         mLooper.quit();
414         mDrm.close();
415         mDrm = null;
416     }
417 
openSession(MediaDrm drm)418     private @NonNull byte[] openSession(MediaDrm drm) {
419         byte[] mSessionId = null;
420         boolean mRetryOpen;
421         do {
422             try {
423                 mRetryOpen = false;
424                 mSessionId = drm.openSession();
425             } catch (Exception e) {
426                 mRetryOpen = true;
427             }
428         } while (mRetryOpen);
429         return mSessionId;
430     }
431 
closeSession(MediaDrm drm, byte[] sessionId)432     private void closeSession(MediaDrm drm, byte[] sessionId) {
433         drm.closeSession(sessionId);
434     }
435 
436     /**
437      * Tests clear key system playback.
438      */
testClearKeyPlayback( UUID drmSchemeUuid, String videoMime, String[] videoFeatures, String initDataType, byte[][] clearKeyIds, Uri audioUrl, boolean audioEncrypted, Uri videoUrl, boolean videoEncrypted, int videoWidth, int videoHeight, boolean scrambled, int keyType)439     private void testClearKeyPlayback(
440             UUID drmSchemeUuid,
441             String videoMime, String[] videoFeatures,
442             String initDataType, byte[][] clearKeyIds,
443             Uri audioUrl, boolean audioEncrypted,
444             Uri videoUrl, boolean videoEncrypted,
445             int videoWidth, int videoHeight, boolean scrambled, int keyType) throws Exception {
446 
447         if (isWatchDevice()) {
448             return;
449         }
450 
451         MediaDrm drm = null;
452         mSessionId = null;
453         final boolean hasDrm = !scrambled && drmSchemeUuid != null;
454         if (hasDrm) {
455             drm = startDrm(clearKeyIds, initDataType, drmSchemeUuid, keyType);
456             mSessionId = openSession(drm);
457         }
458 
459         if (!preparePlayback(videoMime, videoFeatures, audioUrl, audioEncrypted, videoUrl,
460                 videoEncrypted, videoWidth, videoHeight, scrambled, mSessionId, getSurfaces())) {
461             // Allow device to skip test to keep existing behavior.
462             // We should throw an exception for new tests.
463             return;
464         }
465 
466         if (hasDrm) {
467             mDrmInitData = mMediaCodecPlayer.getDrmInitData();
468             getKeys(mDrm, initDataType, mSessionId, mDrmInitData, keyType, clearKeyIds);
469         }
470 
471         if (hasDrm && keyType == MediaDrm.KEY_TYPE_OFFLINE) {
472             closeSession(drm, mSessionId);
473             mSessionMonitor.waitForSignal();
474             mSessionId = openSession(drm);
475             if (mKeySetId.length > 0) {
476                 drm.restoreKeys(mSessionId, mKeySetId);
477             } else {
478                 closeSession(drm, mSessionId);
479                 stopDrm(drm);
480                 throw new Error("Invalid keySetId size for offline license");
481             }
482         }
483 
484         // starts video playback
485         playUntilEnd();
486         if (hasDrm) {
487             closeSession(drm, mSessionId);
488             stopDrm(drm);
489         }
490     }
491 
492     /**
493      * Tests KEY_TYPE_RELEASE for offline license.
494      */
495     @Presubmit
496     @FrameworkSpecificTest
testReleaseOfflineLicense()497     public void testReleaseOfflineLicense() throws Exception {
498         if (isWatchDevice()) {
499             return;
500         }
501 
502         byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
503         mSessionId = null;
504         String initDataType = "cenc";
505 
506         MediaDrm drm = startDrm(clearKeyIds, initDataType,
507                 CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_OFFLINE);
508         mSessionId = openSession(drm);
509 
510         if (false == playbackPreCheck(MIME_VIDEO_AVC,
511                 new String[] { CodecCapabilities.FEATURE_SecurePlayback }, CENC_VIDEO_URL,
512                 VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
513             // retry with unsecure codec
514             if (false == playbackPreCheck(MIME_VIDEO_AVC,
515                     new String[0], CENC_VIDEO_URL,
516                     VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
517                 Log.e(TAG, "Failed playback precheck");
518                 return;
519             }
520         }
521 
522         mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
523                 getSurfaces(),
524                 mSessionId, false /*scrambled */,
525                 mContext);
526 
527         mMediaCodecPlayer.setAudioDataSource(CENC_AUDIO_URL, null, false);
528         mMediaCodecPlayer.setVideoDataSource(CENC_VIDEO_URL, null, true);
529         mMediaCodecPlayer.start();
530         if (!mMediaCodecPlayer.prepare()) {
531             Log.i(TAG, "Media Player could not be prepared.");
532             return;
533         }
534         mDrmInitData = mMediaCodecPlayer.getDrmInitData();
535 
536         // Create and store the offline license
537         getKeys(mDrm, initDataType, mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_OFFLINE,
538                 clearKeyIds);
539 
540         // Verify the offline license is valid
541         closeSession(drm, mSessionId);
542         mSessionMonitor.waitForSignal();
543         mDrm.clearOnKeyStatusChangeListener();
544         mSessionId = openSession(drm);
545         drm.restoreKeys(mSessionId, mKeySetId);
546         closeSession(drm, mSessionId);
547 
548         // Release the offline license
549         getKeys(mDrm, initDataType, mKeySetId, mDrmInitData, MediaDrm.KEY_TYPE_RELEASE,
550                 clearKeyIds);
551 
552         // Verify restoreKeys will throw an exception if the offline license
553         // has already been released
554         mSessionId = openSession(drm);
555         try {
556             drm.restoreKeys(mSessionId, mKeySetId);
557         } catch (MediaDrmStateException e) {
558             // Expected exception caught, all is good
559             return;
560         } finally {
561             closeSession(drm, mSessionId);
562             stopDrm(drm);
563         }
564 
565         // Did not receive expected exception, throw an Error
566         throw new Error("Did not receive expected exception from restoreKeys");
567     }
568 
queryKeyStatus(@onNull final MediaDrm drm, @NonNull final byte[] sessionId)569     private boolean queryKeyStatus(@NonNull final MediaDrm drm, @NonNull final byte[] sessionId) {
570         final HashMap<String, String> keyStatus = drm.queryKeyStatus(sessionId);
571         if (keyStatus.isEmpty()) {
572             Log.e(TAG, "queryKeyStatus: empty key status");
573             return false;
574         }
575 
576         final Set<String> keySet = keyStatus.keySet();
577         final int numKeys = keySet.size();
578         final String[] keys = keySet.toArray(new String[numKeys]);
579         for (int i = 0; i < numKeys; ++i) {
580             final String key = keys[i];
581             Log.i(TAG, "queryKeyStatus: key=" + key + ", value=" + keyStatus.get(key));
582         }
583 
584         return true;
585     }
586 
587     @Presubmit
588     @FrameworkSpecificTest
testQueryKeyStatus()589     public void testQueryKeyStatus() throws Exception {
590         if (isWatchDevice()) {
591             // skip this test on watch because it calls
592             // addTrack that requires codec
593             return;
594         }
595 
596         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
597                 CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
598 
599         mSessionId = openSession(drm);
600 
601         // Test default key status, should not be defined
602         final HashMap<String, String> keyStatus = drm.queryKeyStatus(mSessionId);
603         if (!keyStatus.isEmpty()) {
604             closeSession(drm, mSessionId);
605             stopDrm(drm);
606             throw new Error("query default key status failed");
607         }
608 
609         // Test valid key status
610         mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
611                 getSurfaces(),
612                 mSessionId, false,
613                 mContext);
614         mMediaCodecPlayer.setAudioDataSource(CENC_AUDIO_URL, null, false);
615         mMediaCodecPlayer.setVideoDataSource(CENC_VIDEO_URL, null, true);
616         mMediaCodecPlayer.start();
617         if (!mMediaCodecPlayer.prepare()) {
618             Log.i(TAG, "Media Player could not be prepared.");
619             return;
620         }
621 
622         mDrmInitData = mMediaCodecPlayer.getDrmInitData();
623         getKeys(drm, "cenc", mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_STREAMING,
624                 new byte[][] { CLEAR_KEY_CENC });
625         boolean success = true;
626         if (!queryKeyStatus(drm, mSessionId)) {
627             success = false;
628         }
629 
630         mMediaCodecPlayer.reset();
631         closeSession(drm, mSessionId);
632         stopDrm(drm);
633         if (!success) {
634             throw new Error("query key status failed");
635         }
636     }
637 
638     @Presubmit
639     @FrameworkSpecificTest
testOfflineKeyManagement()640     public void testOfflineKeyManagement() throws Exception {
641         if (isWatchDevice()) {
642             // skip this test on watch because it calls
643             // addTrack that requires codec
644             return;
645         }
646 
647         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
648                 CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_OFFLINE);
649 
650         if (getClearkeyVersion(drm).matches("1.[01]")) {
651             Log.i(TAG, "Skipping testsOfflineKeyManagement: clearkey 1.2 required");
652             return;
653         }
654 
655         mSessionId = openSession(drm);
656 
657         // Test get offline keys
658         mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
659                 getSurfaces(),
660                 mSessionId, false,
661                 mContext);
662         mMediaCodecPlayer.setAudioDataSource(CENC_AUDIO_URL, null, false);
663         mMediaCodecPlayer.setVideoDataSource(CENC_VIDEO_URL, null, true);
664         mMediaCodecPlayer.start();
665         if (!mMediaCodecPlayer.prepare()) {
666             Log.i(TAG, "Media Player could not be prepared.");
667             return;
668         }
669 
670         try {
671             mDrmInitData = mMediaCodecPlayer.getDrmInitData();
672 
673             List<byte[]> keySetIds = drm.getOfflineLicenseKeySetIds();
674             int preCount = keySetIds.size();
675 
676             getKeys(drm, "cenc", mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_OFFLINE,
677                     new byte[][] { CLEAR_KEY_CENC });
678 
679             if (drm.getOfflineLicenseState(mKeySetId) != MediaDrm.OFFLINE_LICENSE_STATE_USABLE) {
680                 throw new Error("Offline license state is not usable");
681             }
682 
683             keySetIds = drm.getOfflineLicenseKeySetIds();
684 
685             if (keySetIds.size() != preCount + 1) {
686                 throw new Error("KeySetIds size did not increment");
687             }
688 
689             boolean found = false;
690             for (int i = 0; i < keySetIds.size(); i++) {
691                 if (Arrays.equals(keySetIds.get(i), mKeySetId)) {
692                     found = true;
693                     break;
694                 }
695             }
696             if (!found) {
697                 throw new Error("New KeySetId is missing from KeySetIds");
698             }
699 
700             drm.removeOfflineLicense(mKeySetId);
701 
702             keySetIds = drm.getOfflineLicenseKeySetIds();
703             if (keySetIds.size() != preCount) {
704                 throw new Error("KeySetIds size is incorrect");
705             }
706 
707             found = false;
708             for (int i = 0; i < keySetIds.size(); i++) {
709                 if (Arrays.equals(keySetIds.get(i), mKeySetId)) {
710                     found = true;
711                     break;
712                 }
713             }
714 
715             if (found) {
716                 throw new Error("New KeySetId is still in from KeySetIds after removal");
717             }
718 
719             // TODO: after RELEASE is implemented: add offline key, release it
720             // get offline key status, check state is inactive
721         } finally {
722             mMediaCodecPlayer.reset();
723             closeSession(drm, mSessionId);
724             stopDrm(drm);
725         }
726     }
727 
728     // returns FEATURE_SecurePlayback if device supports secure codec,
729     // else returns an empty string for the codec feature
determineCodecFeatures(String mime, int videoWidth, int videoHeight)730     private String[] determineCodecFeatures(String mime,
731             int videoWidth, int videoHeight) {
732         String[] codecFeatures = { CodecCapabilities.FEATURE_SecurePlayback };
733         if (!isResolutionSupported(MIME_VIDEO_AVC, codecFeatures,
734             VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
735             // for device that does not support secure codec
736             codecFeatures = new String[0];
737         }
738         return codecFeatures;
739     }
740 
741     @FrameworkSpecificTest
testClearKeyPlaybackCenc()742     public void testClearKeyPlaybackCenc() throws Exception {
743         String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
744             VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
745         testClearKeyPlayback(
746                 COMMON_PSSH_SCHEME_UUID,
747                 // using secure codec even though it is clear key DRM
748                 MIME_VIDEO_AVC, codecFeatures,
749                 "cenc", new byte[][]{CLEAR_KEY_CENC},
750                 CENC_AUDIO_URL, false  /* audioEncrypted */,
751                 CENC_VIDEO_URL, true /* videoEncrypted */,
752                 VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
753                 MediaDrm.KEY_TYPE_STREAMING);
754     }
755 
756     @Presubmit
757     @FrameworkSpecificTest
testClearKeyPlaybackCenc2()758     public void testClearKeyPlaybackCenc2() throws Exception {
759         String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
760             VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
761         testClearKeyPlayback(
762                 CLEARKEY_SCHEME_UUID,
763                 // using secure codec even though it is clear key DRM
764                 MIME_VIDEO_AVC, codecFeatures,
765                 "cenc", new byte[][]{CLEAR_KEY_CENC},
766                 CENC_AUDIO_URL, false /* audioEncrypted */,
767                 CENC_VIDEO_URL, true /* videoEncrypted */,
768                 VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
769                 MediaDrm.KEY_TYPE_STREAMING);
770     }
771 
772     @Presubmit
773     @FrameworkSpecificTest
testClearKeyPlaybackOfflineCenc()774     public void testClearKeyPlaybackOfflineCenc() throws Exception {
775         String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
776             VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
777         testClearKeyPlayback(
778                 CLEARKEY_SCHEME_UUID,
779                 // using secure codec even though it is clear key DRM
780                 MIME_VIDEO_AVC, codecFeatures,
781                 "cenc", new byte[][]{CLEAR_KEY_CENC},
782                 CENC_AUDIO_URL, false /* audioEncrypted */,
783                 CENC_VIDEO_URL, true /* videoEncrypted */,
784                 VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
785                 MediaDrm.KEY_TYPE_OFFLINE);
786     }
787 
788     @FrameworkSpecificTest
testClearKeyPlaybackWebm()789     public void testClearKeyPlaybackWebm() throws Exception {
790         testClearKeyPlayback(
791                 CLEARKEY_SCHEME_UUID,
792                 MIME_VIDEO_VP8, new String[0],
793                 "webm", new byte[][]{CLEAR_KEY_WEBM},
794                 WEBM_URL, true /* audioEncrypted */,
795                 WEBM_URL, true /* videoEncrypted */,
796                 VIDEO_WIDTH_WEBM, VIDEO_HEIGHT_WEBM, false /* scrambled */,
797                 MediaDrm.KEY_TYPE_STREAMING);
798     }
799 
800     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
801     @ModuleSpecificTest
testClearKeyPlaybackMpeg2ts()802     public void testClearKeyPlaybackMpeg2ts() throws Exception {
803         testClearKeyPlayback(
804             CLEARKEY_SCHEME_UUID,
805             MIME_VIDEO_AVC, new String[0],
806             "mpeg2ts", null,
807             MPEG2TS_SCRAMBLED_URL, false /* audioEncrypted */,
808             MPEG2TS_SCRAMBLED_URL, false /* videoEncrypted */,
809             VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, true /* scrambled */,
810             MediaDrm.KEY_TYPE_STREAMING);
811     }
812 
813     @ModuleSpecificTest
testPlaybackMpeg2ts()814     public void testPlaybackMpeg2ts() throws Exception {
815         testClearKeyPlayback(
816                 CLEARKEY_SCHEME_UUID,
817                 MIME_VIDEO_AVC, new String[0],
818                 "mpeg2ts", null,
819                 MPEG2TS_CLEAR_URL, false /* audioEncrypted */,
820                 MPEG2TS_CLEAR_URL, false /* videoEncrypted */,
821                 VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false /* scrambled */,
822                 MediaDrm.KEY_TYPE_STREAMING);
823     }
824 
getStringProperty(final MediaDrm drm, final String key)825     private String getStringProperty(final MediaDrm drm,  final String key) {
826         String value = "";
827         try {
828             value = drm.getPropertyString(key);
829         } catch (IllegalArgumentException e) {
830             // Expected exception for invalid key
831             Log.d(TAG, "Expected result: " + e.getMessage());
832         } catch (Exception e) {
833             throw new Error(e.getMessage() + "-" + key);
834         }
835         return value;
836     }
837 
getByteArrayProperty(final MediaDrm drm, final String key)838     private byte[] getByteArrayProperty(final MediaDrm drm,  final String key) {
839         byte[] bytes = new byte[0];
840         try {
841             bytes = drm.getPropertyByteArray(key);
842         } catch (IllegalArgumentException e) {
843             // Expected exception for invalid key
844             Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
845         } catch (Exception e) {
846             throw new Error(e.getMessage() + "-" + key);
847         }
848         return bytes;
849     }
850 
setStringProperty(final MediaDrm drm, final String key, final String value)851     private void setStringProperty(final MediaDrm drm, final String key, final String value) {
852         try {
853             drm.setPropertyString(key, value);
854         } catch (IllegalArgumentException e) {
855             // Expected exception for invalid key
856             Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
857         } catch (Exception e) {
858             throw new Error(e.getMessage() + "-" + key);
859         }
860     }
861 
setByteArrayProperty(final MediaDrm drm, final String key, final byte[] bytes)862     private void setByteArrayProperty(final MediaDrm drm, final String key, final byte[] bytes) {
863         try {
864             drm.setPropertyByteArray(key, bytes);
865         } catch (IllegalArgumentException e) {
866             // Expected exception for invalid key
867             Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
868         } catch (Exception e) {
869             throw new Error(e.getMessage() + "-" + key);
870         }
871     }
872 
873     @Presubmit
874     @FrameworkSpecificTest
testGetProperties()875     public void testGetProperties() throws Exception {
876         if (watchHasNoClearkeySupport()) {
877             return;
878         }
879 
880         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
881                 "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
882 
883         try {
884             // The following tests will not verify the value we are getting
885             // back since it could change in the future.
886             final String[] sKeys = {
887                     DESCRIPTION_PROPERTY_KEY, LISTENER_TEST_SUPPORT_PROPERTY_KEY,
888                     VENDOR_PROPERTY_KEY, VERSION_PROPERTY_KEY};
889             String value;
890             for (String key : sKeys) {
891                 value = getStringProperty(drm, key);
892                 Log.d(TAG, "getPropertyString returns: " + key + ", " + value);
893                 if (value.isEmpty()) {
894                     throw new Error("Failed to get property for: " + key);
895                 }
896             }
897 
898             if (cannotHandleGetPropertyByteArray(drm)) {
899                 Log.i(TAG, "Skipping testGetProperties: byte array properties not implemented "
900                         + "on devices launched before P");
901                 return;
902             }
903 
904             byte[] bytes = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
905             if (0 == bytes.length) {
906                 throw new Error("Failed to get property for: " + DEVICEID_PROPERTY_KEY);
907             }
908 
909             // Test with an invalid property key.
910             value = getStringProperty(drm, INVALID_PROPERTY_KEY);
911             bytes = getByteArrayProperty(drm, INVALID_PROPERTY_KEY);
912             if (!value.isEmpty() || 0 != bytes.length) {
913                 throw new Error("get property failed using an invalid property key");
914             }
915         } finally {
916             stopDrm(drm);
917         }
918     }
919 
920     @Presubmit
921     @FrameworkSpecificTest
testSetProperties()922     public void testSetProperties() throws Exception {
923         if (watchHasNoClearkeySupport()) {
924             return;
925         }
926 
927         MediaDrm drm = startDrm(new byte[][]{CLEAR_KEY_CENC},
928                 "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
929 
930         try {
931             if (cannotHandleSetPropertyString(drm)) {
932                 Log.i(TAG, "Skipping testSetProperties: set property string not implemented "
933                         + "on devices launched before P");
934                 return;
935             }
936 
937             // Test setting predefined string property
938             // - Save the value to be restored later
939             // - Set the property value
940             // - Check the value that was set
941             // - Restore previous value
942             String listenerTestSupport = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
943 
944             setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, "testing");
945 
946             String value = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
947             if (!value.equals("testing")) {
948                 throw new Error("Failed to set property: " + LISTENER_TEST_SUPPORT_PROPERTY_KEY);
949             }
950 
951             setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, listenerTestSupport);
952 
953             // Test setting immutable properties
954             HashMap<String, String> defaultImmutableProperties = new HashMap<String, String>();
955             defaultImmutableProperties.put(ALGORITHMS_PROPERTY_KEY,
956                     getStringProperty(drm, ALGORITHMS_PROPERTY_KEY));
957             defaultImmutableProperties.put(DESCRIPTION_PROPERTY_KEY,
958                     getStringProperty(drm, DESCRIPTION_PROPERTY_KEY));
959             defaultImmutableProperties.put(VENDOR_PROPERTY_KEY,
960                     getStringProperty(drm, VENDOR_PROPERTY_KEY));
961             defaultImmutableProperties.put(VERSION_PROPERTY_KEY,
962                     getStringProperty(drm, VERSION_PROPERTY_KEY));
963 
964             HashMap<String, String> immutableProperties = new HashMap<String, String>();
965             immutableProperties.put(ALGORITHMS_PROPERTY_KEY, "brute force");
966             immutableProperties.put(DESCRIPTION_PROPERTY_KEY, "testing only");
967             immutableProperties.put(VENDOR_PROPERTY_KEY, "my Google");
968             immutableProperties.put(VERSION_PROPERTY_KEY, "undefined");
969 
970             for (String key : immutableProperties.keySet()) {
971                 setStringProperty(drm, key, immutableProperties.get(key));
972             }
973 
974             // Verify the immutable properties have not been set
975             for (String key : immutableProperties.keySet()) {
976                 value = getStringProperty(drm, key);
977                 if (!defaultImmutableProperties.get(key).equals(getStringProperty(drm, key))) {
978                     throw new Error("Immutable property has changed, key=" + key);
979                 }
980             }
981 
982             // Test setPropertyByteArray for immutable property
983             final byte[] bytes = new byte[] {
984                     0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
985                     0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};
986 
987             final byte[] deviceId = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
988 
989             setByteArrayProperty(drm, DEVICEID_PROPERTY_KEY, bytes);
990 
991             // Verify deviceId has not changed
992             if (!Arrays.equals(deviceId, getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY))) {
993                 throw new Error("Failed to set byte array for key=" + DEVICEID_PROPERTY_KEY);
994             }
995 
996             for (String k: new String[] {"oemError", "errorContext"}) {
997                 testIntegerProperties(drm, k);
998             }
999         } finally {
1000             stopDrm(drm);
1001         }
1002     }
1003 
1004     private final static int CLEARKEY_MAX_SESSIONS = 10;
1005 
1006     @Presubmit
1007     @FrameworkSpecificTest
testGetNumberOfSessions()1008     public void testGetNumberOfSessions() {
1009         if (watchHasNoClearkeySupport()) {
1010             return;
1011         }
1012 
1013         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
1014                 "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
1015 
1016         try {
1017             if (getClearkeyVersion(drm).equals("1.0")) {
1018                 Log.i(TAG, "Skipping testGetNumberOfSessions: not supported by clearkey 1.0");
1019                 return;
1020             }
1021 
1022             int maxSessionCount = drm.getMaxSessionCount();
1023             if (maxSessionCount != CLEARKEY_MAX_SESSIONS) {
1024                 throw new Error("expected max session count to be " +
1025                         CLEARKEY_MAX_SESSIONS);
1026             }
1027             int initialOpenSessionCount = drm.getOpenSessionCount();
1028             if (initialOpenSessionCount == maxSessionCount) {
1029                 throw new Error("all sessions open, can't do increment test");
1030             }
1031             mSessionId = openSession(drm);
1032             try {
1033                 if (drm.getOpenSessionCount() != initialOpenSessionCount + 1) {
1034                     throw new Error("openSessionCount didn't increment");
1035                 }
1036             } finally {
1037                 closeSession(drm, mSessionId);
1038             }
1039         } finally {
1040             stopDrm(drm);
1041         }
1042     }
1043 
1044     @Presubmit
1045     @FrameworkSpecificTest
testHdcpLevels()1046     public void testHdcpLevels() {
1047         if (watchHasNoClearkeySupport()) {
1048             return;
1049         }
1050 
1051         MediaDrm drm = null;
1052         try {
1053             drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
1054 
1055             if (getClearkeyVersion(drm).equals("1.0")) {
1056                 Log.i(TAG, "Skipping testHdcpLevels: not supported by clearkey 1.0");
1057                 return;
1058             }
1059 
1060             if (drm.getConnectedHdcpLevel() != MediaDrm.HDCP_NONE) {
1061                 throw new Error("expected connected hdcp level to be HDCP_NONE");
1062             }
1063 
1064             if (drm.getMaxHdcpLevel() != MediaDrm.HDCP_NO_DIGITAL_OUTPUT) {
1065                 throw new Error("expected max hdcp level to be HDCP_NO_DIGITAL_OUTPUT");
1066             }
1067         } catch(Exception e) {
1068             throw new Error("Unexpected exception ", e);
1069         } finally {
1070             if (drm != null) {
1071                 drm.close();
1072             }
1073         }
1074     }
1075 
1076     @Presubmit
1077     @FrameworkSpecificTest
testSecurityLevels()1078     public void testSecurityLevels() {
1079         if (watchHasNoClearkeySupport()) {
1080             return;
1081         }
1082 
1083         MediaDrm drm = null;
1084         byte[] sessionId = null;
1085         try {
1086             drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
1087 
1088             if (getClearkeyVersion(drm).equals("1.0")) {
1089                 Log.i(TAG, "Skipping testSecurityLevels: not supported by clearkey 1.0");
1090                 return;
1091             }
1092 
1093             sessionId = drm.openSession(MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO);
1094             if (drm.getSecurityLevel(sessionId) != MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO) {
1095                 throw new Error("expected security level to be SECURITY_LEVEL_SW_SECURE_CRYPTO");
1096             }
1097             drm.closeSession(sessionId);
1098             sessionId = null;
1099 
1100             sessionId = drm.openSession();
1101             if (drm.getSecurityLevel(sessionId) != MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO) {
1102                 throw new Error("expected security level to be SECURITY_LEVEL_SW_SECURE_CRYPTO");
1103             }
1104             drm.closeSession(sessionId);
1105             sessionId = null;
1106 
1107             try {
1108                 sessionId = drm.openSession(MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE);
1109             } catch (IllegalArgumentException e) {
1110                 /* caught expected exception */
1111             } catch (Exception e) {
1112                 throw new Exception ("did't get expected IllegalArgumentException" +
1113                         " while opening a session with disallowed security level");
1114             } finally  {
1115                 if (sessionId != null) {
1116                     drm.closeSession(sessionId);
1117                     sessionId = null;
1118                 }
1119             }
1120         } catch(Exception e) {
1121             throw new Error("Unexpected exception ", e);
1122         } finally  {
1123             if (sessionId != null) {
1124                 drm.closeSession(sessionId);
1125             }
1126             if (drm != null) {
1127                 drm.close();
1128             }
1129         }
1130      }
1131 
1132     @Presubmit
1133     @FrameworkSpecificTest
testSecureStop()1134     public void testSecureStop() {
1135         if (watchHasNoClearkeySupport()) {
1136             return;
1137         }
1138 
1139         MediaDrm drm = startDrm(new byte[][] {CLEAR_KEY_CENC}, "cenc",
1140                 CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
1141 
1142         byte[] sessionId = null;
1143         try {
1144             if (getClearkeyVersion(drm).equals("1.0")) {
1145                 Log.i(TAG, "Skipping testSecureStop: not supported in ClearKey v1.0");
1146                 return;
1147             }
1148 
1149             drm.removeAllSecureStops();
1150             Log.d(TAG, "Test getSecureStops from an empty list.");
1151             List<byte[]> secureStops = drm.getSecureStops();
1152             assertTrue(secureStops.isEmpty());
1153 
1154             Log.d(TAG, "Test getSecureStopIds from an empty list.");
1155             List<byte[]> secureStopIds = drm.getSecureStopIds();
1156             assertTrue(secureStopIds.isEmpty());
1157 
1158             mSessionId = openSession(drm);
1159 
1160             mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
1161                     getSurfaces(), mSessionId, false, mContext);
1162             mMediaCodecPlayer.setAudioDataSource(CENC_AUDIO_URL, null, false);
1163             mMediaCodecPlayer.setVideoDataSource(CENC_VIDEO_URL, null, true);
1164             mMediaCodecPlayer.start();
1165             if (!mMediaCodecPlayer.prepare()) {
1166                 Log.i(TAG, "Media Player could not be prepared.");
1167                 return;
1168             }
1169             mDrmInitData = mMediaCodecPlayer.getDrmInitData();
1170 
1171             for (int i = 0; i < NUMBER_OF_SECURE_STOPS; ++i) {
1172                 getKeys(drm, "cenc", mSessionId, mDrmInitData,
1173                         MediaDrm.KEY_TYPE_STREAMING, new byte[][] {CLEAR_KEY_CENC});
1174             }
1175             Log.d(TAG, "Test getSecureStops.");
1176             secureStops = drm.getSecureStops();
1177             assertEquals(NUMBER_OF_SECURE_STOPS, secureStops.size());
1178 
1179             Log.d(TAG, "Test getSecureStopIds.");
1180             secureStopIds = drm.getSecureStopIds();
1181             assertEquals(NUMBER_OF_SECURE_STOPS, secureStopIds.size());
1182 
1183             Log.d(TAG, "Test getSecureStop using secure stop Ids.");
1184             for (int i = 0; i < secureStops.size(); ++i) {
1185                 byte[] secureStop = drm.getSecureStop(secureStopIds.get(i));
1186                 assertTrue(Arrays.equals(secureStops.get(i), secureStop));
1187             }
1188 
1189             Log.d(TAG, "Test removeSecureStop given a secure stop Id.");
1190             drm.removeSecureStop(secureStopIds.get(NUMBER_OF_SECURE_STOPS - 1));
1191             secureStops = drm.getSecureStops();
1192             secureStopIds = drm.getSecureStopIds();
1193             assertEquals(NUMBER_OF_SECURE_STOPS - 1, secureStops.size());
1194             assertEquals(NUMBER_OF_SECURE_STOPS - 1, secureStopIds.size());
1195 
1196             Log.d(TAG, "Test releaseSecureStops given a release message.");
1197             // Simulate server response message by removing
1198             // every other secure stops to make it interesting.
1199             List<byte[]> releaseList = new ArrayList<byte[]>();
1200             int releaseListSize = 0;
1201             for (int i = 0; i < secureStops.size(); i += 2) {
1202                 byte[] secureStop = secureStops.get(i);
1203                 releaseList.add(secureStop);
1204                 releaseListSize += secureStop.length;
1205             }
1206 
1207             // ClearKey's release message format (this is a format shared between
1208             // the server and the drm service).
1209             // The clearkey implementation expects the message to contain
1210             // a 4 byte count of the number of fixed length secure stops
1211             // to follow.
1212             String count = String.format("%04d", releaseList.size());
1213             byte[] releaseMessage = new byte[count.length() + releaseListSize];
1214 
1215             byte[] buffer = count.getBytes();
1216             System.arraycopy(buffer, 0, releaseMessage, 0, count.length());
1217 
1218             int destPosition = count.length();
1219             for (int i = 0; i < releaseList.size(); ++i) {
1220                 byte[] secureStop = releaseList.get(i);
1221                 int secureStopSize = secureStop.length;
1222                 System.arraycopy(secureStop, 0, releaseMessage, destPosition, secureStopSize);
1223                 destPosition += secureStopSize;
1224             }
1225 
1226             drm.releaseSecureStops(releaseMessage);
1227             secureStops = drm.getSecureStops();
1228             secureStopIds = drm.getSecureStopIds();
1229             // All odd numbered secure stops are removed in the test,
1230             // leaving 2nd, 4th, 6th and the 8th element.
1231             assertEquals((NUMBER_OF_SECURE_STOPS - 1) / 2, secureStops.size());
1232             assertEquals((NUMBER_OF_SECURE_STOPS - 1 ) / 2, secureStopIds.size());
1233 
1234             Log.d(TAG, "Test removeAllSecureStops.");
1235             drm.removeAllSecureStops();
1236             secureStops = drm.getSecureStops();
1237             assertTrue(secureStops.isEmpty());
1238             secureStopIds = drm.getSecureStopIds();
1239             assertTrue(secureStopIds.isEmpty());
1240 
1241             mMediaCodecPlayer.reset();
1242             closeSession(drm, mSessionId);
1243         } catch (Exception e) {
1244             throw new Error("Unexpected exception", e);
1245         } finally {
1246             if (sessionId != null) {
1247                 drm.closeSession(sessionId);
1248             }
1249             stopDrm(drm);
1250         }
1251     }
1252 
1253     /**
1254      * Test that the framework handles a device returning
1255      * ::android::hardware::drm@1.2::Status::ERROR_DRM_RESOURCE_CONTENTION.
1256      * Expected behavior: throws MediaDrm.SessionException with
1257      * errorCode ERROR_RESOURCE_CONTENTION
1258      */
1259     @Presubmit
1260     @FrameworkSpecificTest
testResourceContentionError()1261     public void testResourceContentionError() {
1262 
1263         if (watchHasNoClearkeySupport()) {
1264             return;
1265         }
1266 
1267         MediaDrm drm = null;
1268         boolean gotException = false;
1269         final int OEM_ERROR = 123;
1270         final int ERROR_CONTEXT = 456;
1271 
1272         try {
1273             drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
1274             drm.setPropertyString("drmErrorTest", "resourceContention");
1275             if (getClearkeyVersionInt(drm) >= 14) {
1276                 drm.setPropertyString("oemError", Integer.toString(OEM_ERROR));
1277                 drm.setPropertyString("errorContext", Integer.toString(ERROR_CONTEXT));
1278             }
1279             byte[] sessionId = drm.openSession();
1280 
1281             try {
1282                 byte[] ignoredInitData = new byte[] { 1 };
1283                 drm.getKeyRequest(sessionId, ignoredInitData, "cenc", MediaDrm.KEY_TYPE_STREAMING, null);
1284             } catch (MediaDrm.SessionException e) {
1285                 if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION) {
1286                     throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
1287                 }
1288                 if(sIsAtLeastS && !e.isTransient()) {
1289                         throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
1290                 }
1291                 if (getClearkeyVersionInt(drm) >= 14) {
1292                     final MediaDrmThrowable mdt = e;
1293                     final int RESOURCE_CONTENTION_AIDL = 16;
1294                     assertEquals("Vendor Error mismatch", mdt.getVendorError(), RESOURCE_CONTENTION_AIDL);
1295                     assertEquals("OEM Error mismatch", mdt.getOemError(), OEM_ERROR);
1296                     assertEquals("Error context mismatch", mdt.getErrorContext(), ERROR_CONTEXT);
1297                 }
1298                 gotException = true;
1299             }
1300         } catch(Exception e) {
1301             throw new Error("Unexpected exception ", e);
1302         } finally {
1303             if (drm != null) {
1304                 drm.close();
1305             }
1306         }
1307         if (!gotException) {
1308             throw new Error("Didn't receive expected MediaDrm.SessionException");
1309         }
1310     }
1311 
1312     /**
1313      * Test sendExpirationUpdate and onExpirationUpdateListener
1314      *
1315      * Expected behavior: the EXPIRATION_UPDATE event arrives
1316      * at the onExpirationUpdateListener with the expiry time
1317      */
1318     @Presubmit
1319     @FrameworkSpecificTest
testOnExpirationUpdateListener()1320     public void testOnExpirationUpdateListener() {
1321 
1322         if (watchHasNoClearkeySupport()) {
1323             return;
1324         }
1325 
1326         MediaDrm drm = null;
1327         mSessionId = null;
1328         mExpirationUpdateReceived = false;
1329 
1330         // provideKeyResponse calls sendExpirationUpdate method
1331         // for testing purpose, we therefore start a license request
1332         // which calls provideKeyResonpse
1333         byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
1334         int keyType = MediaDrm.KEY_TYPE_STREAMING;
1335         String initDataType = new String("cenc");
1336 
1337         drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
1338         mSessionId = openSession(drm);
1339         try {
1340             if (!preparePlayback(
1341                     MIME_VIDEO_AVC,
1342                     new String[0],
1343                     CENC_AUDIO_URL, false /* audioEncrypted */ ,
1344                     CENC_VIDEO_URL, true /* videoEncrypted */,
1345                     VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
1346                     mSessionId, getSurfaces())) {
1347                 closeSession(drm, mSessionId);
1348                 stopDrm(drm);
1349                 return;
1350             }
1351         } catch (Exception e) {
1352             throw new Error("Unexpected exception ", e);
1353         }
1354 
1355         mDrmInitData = mMediaCodecPlayer.getDrmInitData();
1356         getKeys(drm, initDataType, mSessionId, mDrmInitData,
1357                     keyType, clearKeyIds);
1358 
1359         // wait for the event to arrive
1360         try {
1361             closeSession(drm, mSessionId);
1362             // wait up to 2 seconds for event
1363             for (int i = 0; i < 20 && !mExpirationUpdateReceived; i++) {
1364                 try {
1365                     Thread.sleep(100);
1366                 } catch (InterruptedException e) {
1367                 }
1368             }
1369             if (!mExpirationUpdateReceived) {
1370                 throw new Error("EXPIRATION_UPDATE event was not received by the listener");
1371             }
1372           } catch (MediaDrmStateException e) {
1373                 throw new Error("Unexpected exception from closing session: ", e);
1374         } finally {
1375             stopDrm(drm);
1376         }
1377     }
1378 
1379     /**
1380      * Test that the onExpirationUpdateListener
1381      * listener is not called after
1382      * clearOnExpirationUpdateListener is called.
1383      */
1384     @Presubmit
1385     @FrameworkSpecificTest
testClearOnExpirationUpdateListener()1386     public void testClearOnExpirationUpdateListener() {
1387 
1388         if (watchHasNoClearkeySupport()) {
1389             return;
1390         }
1391 
1392         MediaDrm drm = null;
1393         mSessionId = null;
1394         mExpirationUpdateReceived = false;
1395 
1396         // provideKeyResponse calls sendExpirationUpdate method
1397         // for testing purpose, we therefore start a license request
1398         // which calls provideKeyResonpse
1399         byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
1400         int keyType = MediaDrm.KEY_TYPE_STREAMING;
1401         String initDataType = new String("cenc");
1402 
1403         drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
1404         mSessionId = openSession(drm);
1405         try {
1406             if (!preparePlayback(
1407                     MIME_VIDEO_AVC,
1408                     new String[0],
1409                     CENC_AUDIO_URL, false /* audioEncrypted */ ,
1410                     CENC_VIDEO_URL, true /* videoEncrypted */,
1411                     VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
1412                     mSessionId, getSurfaces())) {
1413                 closeSession(drm, mSessionId);
1414                 stopDrm(drm);
1415                 return;
1416             }
1417         } catch (Exception e) {
1418             throw new Error("Unexpected exception ", e);
1419         }
1420 
1421         // clear the expiration update listener
1422         drm.clearOnExpirationUpdateListener();
1423         mDrmInitData = mMediaCodecPlayer.getDrmInitData();
1424         getKeys(drm, initDataType, mSessionId, mDrmInitData,
1425                     keyType, clearKeyIds);
1426 
1427         // wait for the event, it should not arrive
1428         // because the expiration update listener has been cleared
1429         try {
1430             closeSession(drm, mSessionId);
1431             // wait up to 2 seconds for event
1432             for (int i = 0; i < 20 && !mExpirationUpdateReceived; i++) {
1433                 try {
1434                     Thread.sleep(100);
1435                 } catch (InterruptedException e) {
1436                 }
1437             }
1438             if (mExpirationUpdateReceived) {
1439                 throw new Error("onExpirationUpdateListener should not be called");
1440             }
1441         } catch (MediaDrmStateException e) {
1442               throw new Error("Unexpected exception from closing session: ", e);
1443         } finally {
1444             stopDrm(drm);
1445         }
1446     }
1447 
1448     /**
1449      * Test that after onClearEventListener is called,
1450      * MediaDrm's event listener is not called.
1451      *
1452      * Clearkey plugin's provideKeyResponse method sends a
1453      * vendor defined event to the media drm event listener
1454      * for testing purpose. Check that after onClearEventListener
1455      * is called, the event listener is not called.
1456      */
1457     @Presubmit
1458     @FrameworkSpecificTest
testClearOnEventListener()1459     public void testClearOnEventListener() {
1460 
1461         if (watchHasNoClearkeySupport()) {
1462             return;
1463         }
1464 
1465         MediaDrm drm = null;
1466         mSessionId = null;
1467         mEventListenerCalled = false;
1468 
1469         // provideKeyResponse in clearkey plugin sends a
1470         // vendor defined event to test the event listener;
1471         // we therefore start a license request which will
1472         // call provideKeyResonpse
1473         byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
1474         int keyType = MediaDrm.KEY_TYPE_STREAMING;
1475         String initDataType = new String("cenc");
1476 
1477         drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
1478         mSessionId = openSession(drm);
1479         try {
1480             if (!preparePlayback(
1481                     MIME_VIDEO_AVC,
1482                     new String[0],
1483                     CENC_AUDIO_URL, false /* audioEncrypted */ ,
1484                     CENC_VIDEO_URL, true /* videoEncrypted */,
1485                     VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
1486                     mSessionId, getSurfaces())) {
1487                 closeSession(drm, mSessionId);
1488                 stopDrm(drm);
1489                 return;
1490             }
1491         } catch (Exception e) {
1492             throw new Error("Unexpected exception ", e);
1493         }
1494 
1495         // test that the onEvent listener is called
1496         mDrmInitData = mMediaCodecPlayer.getDrmInitData();
1497         getKeys(drm, initDataType, mSessionId, mDrmInitData,
1498                     keyType, clearKeyIds);
1499 
1500         // wait for the vendor defined event, it should not arrive
1501         // because the event listener is cleared
1502         try {
1503             // wait up to 2 seconds for event
1504             for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
1505                 try {
1506                     Thread.sleep(100);
1507                 } catch (InterruptedException e) {
1508                 }
1509             }
1510             if (!mEventListenerCalled) {
1511                 closeSession(drm, mSessionId);
1512                 stopDrm(drm);
1513                 throw new Error("onEventListener should be called");
1514             }
1515         } catch (MediaDrmStateException e) {
1516               closeSession(drm, mSessionId);
1517               stopDrm(drm);
1518               throw new Error("Unexpected exception from closing session: ", e);
1519         }
1520 
1521         // clear the drm event listener
1522         // and test that the onEvent listener is not called
1523         mEventListenerCalled = false;
1524         drm.clearOnEventListener();
1525         getKeys(drm, initDataType, mSessionId, mDrmInitData,
1526                     keyType, clearKeyIds);
1527 
1528         // wait for the vendor defined event, it should not arrive
1529         // because the event listener is cleared
1530         try {
1531             closeSession(drm, mSessionId);
1532             // wait up to 2 seconds for event
1533             for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
1534                 try {
1535                     Thread.sleep(100);
1536                 } catch (InterruptedException e) {
1537                 }
1538             }
1539             if (mEventListenerCalled) {
1540                 throw new Error("onEventListener should not be called");
1541             }
1542         } catch (MediaDrmStateException e) {
1543               throw new Error("Unexpected exception from closing session: ", e);
1544         } finally {
1545             stopDrm(drm);
1546         }
1547     }
1548 
1549     /**
1550      * Test that the framework handles a device returning invoking
1551      * the ::android::hardware::drm@1.2::sendSessionLostState callback
1552      * Expected behavior: OnSessionLostState is called with
1553      * the sessionId
1554      */
1555     @Presubmit
1556     @FrameworkSpecificTest
testSessionLostStateError()1557     public void testSessionLostStateError() {
1558 
1559         if (watchHasNoClearkeySupport()) {
1560             return;
1561         }
1562 
1563         boolean gotException = false;
1564         mLostStateReceived = false;
1565 
1566         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
1567                 CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
1568 
1569         mDrm.setPropertyString("drmErrorTest", "lostState");
1570         mSessionId = openSession(drm);
1571 
1572         // simulates session lost state here, detected by closeSession
1573 
1574         try {
1575             try {
1576                 closeSession(drm, mSessionId);
1577             } catch (MediaDrmStateException e) {
1578                 gotException = true; // expected for lost state
1579             }
1580             // wait up to 2 seconds for event
1581             for (int i = 0; i < 20 && !mLostStateReceived; i++) {
1582                 try {
1583                     Thread.sleep(100);
1584                 } catch (InterruptedException e) {
1585                 }
1586             }
1587             if (!mLostStateReceived) {
1588                 throw new Error("Callback for OnSessionLostStateListener not received");
1589             }
1590         } catch(Exception e) {
1591             throw new Error("Unexpected exception ", e);
1592         } finally {
1593             stopDrm(drm);
1594         }
1595         if (!gotException) {
1596             throw new Error("Didn't receive expected MediaDrmStateException");
1597         }
1598     }
1599 
1600     /**
1601      * Test that the framework handles a device ignoring
1602      * events for the onSessionLostStateListener after
1603      * clearOnSessionLostStateListener is called.
1604      *
1605      * Expected behavior: OnSessionLostState is not called with
1606      * the sessionId
1607      */
1608     @Presubmit
1609     @FrameworkSpecificTest
testClearOnSessionLostStateListener()1610     public void testClearOnSessionLostStateListener() {
1611 
1612         if (watchHasNoClearkeySupport()) {
1613             return;
1614         }
1615 
1616         boolean gotException = false;
1617         mLostStateReceived = false;
1618 
1619         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
1620                 CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
1621 
1622         mDrm.setPropertyString("drmErrorTest", "lostState");
1623         mSessionId = openSession(drm);
1624 
1625         // Simulates session lost state here, event is sent from closeSession.
1626         // The session lost state should not arrive in the listener
1627         // after clearOnSessionLostStateListener() is called.
1628         try {
1629             try {
1630                 mDrm.clearOnSessionLostStateListener();
1631                 Thread.sleep(2000);
1632                 closeSession(drm, mSessionId);
1633             } catch (MediaDrmStateException e) {
1634                 gotException = true; // expected for lost state
1635             }
1636             // wait up to 2 seconds for event
1637             for (int i = 0; i < 20 && !mLostStateReceived; i++) {
1638                 try {
1639                     Thread.sleep(100);
1640                 } catch (InterruptedException e) {
1641                 }
1642             }
1643             if (mLostStateReceived) {
1644                 throw new Error("Should not receive callback for OnSessionLostStateListener");
1645             }
1646         } catch(Exception e) {
1647             throw new Error("Unexpected exception ", e);
1648         } finally {
1649             stopDrm(drm);
1650         }
1651         if (!gotException) {
1652             throw new Error("Didn't receive expected MediaDrmStateException");
1653         }
1654     }
1655 
1656     @Presubmit
1657     @FrameworkSpecificTest
testIsCryptoSchemeSupportedWithSecurityLevel()1658     public void testIsCryptoSchemeSupportedWithSecurityLevel() {
1659         if (watchHasNoClearkeySupport()) {
1660             return;
1661         }
1662 
1663         if (MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID, "cenc",
1664                                              MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL)) {
1665             throw new Error("Clearkey claims to support SECURITY_LEVEL_HW_SECURE_ALL");
1666         }
1667         if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID, "cenc",
1668                                               MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO)) {
1669             throw new Error("Clearkey claims not to support SECURITY_LEVEL_SW_SECURE_CRYPTO");
1670         }
1671     }
1672 
1673     @Presubmit
1674     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
1675     @FrameworkSpecificTest
testMediaDrmStateExceptionErrorCode()1676     public void testMediaDrmStateExceptionErrorCode()
1677             throws ResourceBusyException, UnsupportedSchemeException, NotProvisionedException {
1678         if (watchHasNoClearkeySupport()) {
1679             return;
1680         }
1681 
1682         MediaDrm drm = null;
1683         try {
1684             drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
1685             byte[] sessionId = drm.openSession();
1686             drm.closeSession(sessionId);
1687 
1688             byte[] ignoredInitData = new byte[]{1};
1689             drm.getKeyRequest(sessionId, ignoredInitData, "cenc",
1690                     MediaDrm.KEY_TYPE_STREAMING,
1691                     null);
1692         } catch(MediaDrmStateException e) {
1693             Log.i(TAG, "Verifying exception error code", e);
1694             assertFalse("ERROR_SESSION_NOT_OPENED requires new session", e.isTransient());
1695             assertEquals("Expected ERROR_SESSION_NOT_OPENED",
1696                     MediaDrm.ErrorCodes.ERROR_SESSION_NOT_OPENED, e.getErrorCode());
1697             assertTrue("Expected ERROR_SESSION_NOT_OPENED value in info",
1698                     e.getDiagnosticInfo().contains(
1699                             String.valueOf(MediaDrm.ErrorCodes.ERROR_SESSION_NOT_OPENED)));
1700         }  finally {
1701             if (drm != null) {
1702                 drm.close();
1703             }
1704         }
1705     }
1706 
1707     /**
1708      * The test tries to enforce the behavior described
1709      * in {@link android.media.MediaDrm.KeyRequest#getDefaultUrl()}.
1710      * It should return an empty string if the default URL is not known.
1711      */
1712     @Presubmit
1713     @ApiTest(apis = {"android.media.MediaDrm.KeyRequest#getDefaultUrl"})
1714     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1715     @FrameworkSpecificTest
testGetKeyRequestDefaultUrl()1716     public void testGetKeyRequestDefaultUrl()
1717             throws UnsupportedSchemeException, NotProvisionedException {
1718         if (watchHasNoClearkeySupport() || !FIRST_RELEASE_IS_AT_LEAST_U || !VNDK_IS_AT_LEAST_U) {
1719             return;
1720         }
1721 
1722         MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
1723         byte[] sessionId = openSession(drm);
1724 
1725         try {
1726             if (!preparePlayback(
1727                     MIME_VIDEO_AVC,
1728                     new String[0],
1729                     CENC_AUDIO_URL, false /* audioEncrypted */ ,
1730                     CENC_VIDEO_URL, true /* videoEncrypted */,
1731                     VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
1732                     sessionId, getSurfaces())) {
1733                 closeSession(drm, sessionId);
1734                 stopDrm(drm);
1735                 return;
1736             }
1737         } catch (Exception e) {
1738             throw new Error("Unexpected exception ", e);
1739         }
1740 
1741         MediaDrm.KeyRequest drmRequest = drm.getKeyRequest(sessionId, mMediaCodecPlayer.getDrmInitData(), "cenc",
1742                 MediaDrm.KEY_TYPE_STREAMING,
1743                 null);
1744         String defaultUrl = drmRequest.getDefaultUrl();
1745         Log.i(TAG, "Default url is [" + defaultUrl + "].");
1746         assertEquals("Default url of key request should be empty", "", defaultUrl);
1747         drm.close();
1748     }
1749 
testIntegerProperties(MediaDrm drm, String testKey)1750     private void testIntegerProperties(MediaDrm drm, String testKey)
1751             throws ResourceBusyException, UnsupportedSchemeException, NotProvisionedException {
1752         if (getClearkeyVersionInt(drm) < 14) {
1753             return;
1754         }
1755         String testValue = "123456";
1756         assertEquals("Default value not 0", drm.getPropertyString(testKey), "0");
1757         Assert.assertThrows("Non-numeric must throw", Exception.class, () -> {
1758             drm.setPropertyString(testKey, "xyz"); });
1759         Assert.assertThrows("Non-integral must throw", Exception.class, () -> {
1760             drm.setPropertyString(testKey, "3.141"); });
1761         Assert.assertThrows("Out-of-range (MAX) must throw", Exception.class, () -> {
1762             drm.setPropertyString(testKey, Long.toString(Long.MAX_VALUE)); });
1763         Assert.assertThrows("Out-of-range (MIN) must throw", Exception.class, () -> {
1764             drm.setPropertyString(testKey, Long.toString(Long.MIN_VALUE)); });
1765         drm.setPropertyString(testKey, testValue);
1766         assertEquals("Property didn't match", drm.getPropertyString(testKey), testValue);
1767     }
1768 
getClearkeyVersion(MediaDrm drm)1769     private String getClearkeyVersion(MediaDrm drm) {
1770         try {
1771             return drm.getPropertyString("version");
1772         } catch (Exception e) {
1773             return "unavailable";
1774         }
1775     }
1776 
getClearkeyVersionInt(MediaDrm drm)1777     private int getClearkeyVersionInt(MediaDrm drm) {
1778         try {
1779             return Integer.parseInt(drm.getPropertyString("version"));
1780         } catch (Exception e) {
1781             return Integer.MIN_VALUE;
1782         }
1783     }
1784 
cannotHandleGetPropertyByteArray(MediaDrm drm)1785     private boolean cannotHandleGetPropertyByteArray(MediaDrm drm) {
1786         boolean apiNotSupported = false;
1787         byte[] bytes = new byte[0];
1788         try {
1789             bytes = drm.getPropertyByteArray(DEVICEID_PROPERTY_KEY);
1790         } catch (IllegalArgumentException e) {
1791             // Expected exception for invalid key or api not implemented
1792             apiNotSupported = true;
1793         }
1794         return apiNotSupported;
1795     }
1796 
cannotHandleSetPropertyString(MediaDrm drm)1797     private boolean cannotHandleSetPropertyString(MediaDrm drm) {
1798         boolean apiNotSupported = false;
1799         final byte[] bytes = new byte[0];
1800         try {
1801             drm.setPropertyString(LISTENER_TEST_SUPPORT_PROPERTY_KEY, "testing");
1802         } catch (IllegalArgumentException e) {
1803             // Expected exception for invalid key or api not implemented
1804             apiNotSupported = true;
1805         }
1806         return apiNotSupported;
1807     }
1808 
watchHasNoClearkeySupport()1809     private boolean watchHasNoClearkeySupport() {
1810         if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID)) {
1811             if (isWatchDevice()) {
1812                 return true;
1813             } else {
1814                 throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
1815             }
1816         }
1817         return false;
1818     }
1819 
getSurfaces()1820     private List<Surface> getSurfaces() {
1821         return Arrays.asList(getActivity().getSurfaceHolder().getSurface());
1822     }
1823 }
1824