1 /*
2  * Copyright (C) 2016 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 static org.junit.Assert.assertThat;
19 import static org.junit.matchers.JUnitMatchers.containsString;
20 
21 import android.content.pm.PackageManager;
22 import android.media.MediaDrm;
23 import android.net.Uri;
24 import android.os.Build;
25 import android.platform.test.annotations.AppModeFull;
26 import android.platform.test.annotations.Presubmit;
27 import android.util.Log;
28 import android.view.Surface;
29 
30 import androidx.test.filters.FlakyTest;
31 import androidx.test.filters.SdkSuppress;
32 
33 import com.android.compatibility.common.util.ApiLevelUtil;
34 import com.android.compatibility.common.util.MediaUtils;
35 
36 import java.io.File;
37 import java.nio.ByteBuffer;
38 import java.util.UUID;
39 
40 /**
41  * Tests MediaDrm NDK APIs. ClearKey system uses a subset of NDK APIs,
42  * this test only tests the APIs that are supported by ClearKey system.
43  */
44 @AppModeFull(reason = "TODO: evaluate and port to instant")
45 public class NativeMediaDrmClearkeyTest extends MediaPlayerDrmTestBase {
46     private static final String TAG = NativeMediaDrmClearkeyTest.class.getSimpleName();
47 
48     private static final int VIDEO_WIDTH_CENC = 1280;
49     private static final int VIDEO_HEIGHT_CENC = 720;
50     private static final String ISO_BMFF_VIDEO_MIME_TYPE = "video/avc";
51     private static final String MEDIA_DIR = WorkDir.getMediaDirString();
52     private static final Uri CENC_AUDIO_URL =
53             Uri.fromFile(new File(MEDIA_DIR + "llama_aac_audio.mp4"));
54     private static final Uri CENC_VIDEO_URL =
55             Uri.fromFile(new File(MEDIA_DIR + "llama_h264_main_720p_8000.mp4"));
56 
57     private static final int UUID_BYTE_SIZE = 16;
58     private static final UUID COMMON_PSSH_SCHEME_UUID =
59             new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
60     private static final UUID CLEARKEY_SCHEME_UUID =
61             new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
62     private static final UUID BAD_SCHEME_UUID =
63             new UUID(0xffffffffffffffffL, 0xffffffffffffffffL);
64 
65     static {
66         try {
67             System.loadLibrary("mediadrm_jni");
68         } catch (UnsatisfiedLinkError e) {
69             Log.e(TAG, "NativeMediaDrmClearkeyTest: Error loading JNI library");
70             e.printStackTrace();
71         }
72         try {
73             System.loadLibrary("mediandk");
74         } catch (UnsatisfiedLinkError e) {
75             Log.e(TAG, "NativeMediaDrmClearkeyTest: Error loading JNI library");
76             e.printStackTrace();
77         }
78     }
79 
80     public static class PlaybackParams {
81         public Surface surface;
82         public String mimeType;
83         public String audioUrl;
84         public String videoUrl;
85     }
86 
setUp()87     protected void setUp() throws Exception {
88         super.setUp();
89         if (false == deviceHasMediaDrm()) {
90             tearDown();
91         }
92     }
93 
tearDown()94     protected void tearDown() throws Exception {
95         super.tearDown();
96     }
97 
watchHasNoClearkeySupport()98     private boolean watchHasNoClearkeySupport() {
99         if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID)) {
100             if (isWatchDevice()) {
101                 return true;
102             } else {
103                 throw new Error("Crypto scheme is not supported");
104             }
105         }
106         return false;
107     }
108 
isWatchDevice()109     private boolean isWatchDevice() {
110         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
111     }
112 
deviceHasMediaDrm()113     private boolean deviceHasMediaDrm() {
114         // ClearKey is introduced after KitKat.
115         if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
116             return false;
117         }
118         return true;
119     }
120 
uuidByteArray(UUID uuid)121     private static final byte[] uuidByteArray(UUID uuid) {
122         ByteBuffer buffer = ByteBuffer.wrap(new byte[UUID_BYTE_SIZE]);
123         buffer.putLong(uuid.getMostSignificantBits());
124         buffer.putLong(uuid.getLeastSignificantBits());
125         return buffer.array();
126     }
127 
128     @Presubmit
testIsCryptoSchemeSupported()129     public void testIsCryptoSchemeSupported() throws Exception {
130         if (watchHasNoClearkeySupport()) {
131             return;
132         }
133 
134         assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID)));
135         assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
136     }
137 
138     @Presubmit
testIsCryptoSchemeNotSupported()139     public void testIsCryptoSchemeNotSupported() throws Exception {
140         assertFalse(isCryptoSchemeSupportedNative(uuidByteArray(BAD_SCHEME_UUID)));
141     }
142 
143     @Presubmit
testPssh()144     public void testPssh() throws Exception {
145         // The test uses a canned PSSH that contains the common box UUID.
146         assertTrue(testPsshNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
147                 CENC_VIDEO_URL.toString()));
148     }
149 
150     @Presubmit
testQueryKeyStatus()151     public void testQueryKeyStatus() throws Exception {
152         if (watchHasNoClearkeySupport()) {
153             return;
154         }
155 
156         assertTrue(testQueryKeyStatusNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
157     }
158 
159     @Presubmit
testFindSessionId()160     public void testFindSessionId() throws Exception {
161         if (watchHasNoClearkeySupport()) {
162             return;
163         }
164 
165         assertTrue(testFindSessionIdNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
166     }
167 
168     @Presubmit
testGetPropertyString()169     public void testGetPropertyString() throws Exception {
170         if (watchHasNoClearkeySupport()) {
171             return;
172         }
173 
174         StringBuffer value = new StringBuffer();
175         testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID), "description", value);
176         assertEquals("ClearKey CDM", value.toString());
177 
178         value.delete(0, value.length());
179         testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID), "description", value);
180         assertEquals("ClearKey CDM", value.toString());
181     }
182 
183     @Presubmit
testPropertyByteArray()184     public void testPropertyByteArray() throws Exception {
185         if (watchHasNoClearkeySupport()) {
186             return;
187         }
188 
189         assertTrue(testPropertyByteArrayNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
190     }
191 
192     @Presubmit
testUnknownPropertyString()193     public void testUnknownPropertyString() throws Exception {
194         StringBuffer value = new StringBuffer();
195 
196         try {
197             testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
198                     "unknown-property", value);
199             fail("Should have thrown an exception");
200         } catch (RuntimeException e) {
201             Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
202             assertThat(e.getMessage(), containsString("get property string returns"));
203         }
204 
205         value.delete(0, value.length());
206         try {
207             testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
208                     "unknown-property", value);
209             fail("Should have thrown an exception");
210         } catch (RuntimeException e) {
211             Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
212             assertThat(e.getMessage(), containsString("get property string returns"));
213         }
214     }
215 
216     /**
217      * Tests native clear key system playback.
218      */
testClearKeyPlayback( UUID drmSchemeUuid, String mimeType, Uri audioUrl, Uri videoUrl, int videoWidth, int videoHeight)219     private void testClearKeyPlayback(
220             UUID drmSchemeUuid, String mimeType, /*String initDataType,*/ Uri audioUrl, Uri videoUrl,
221             int videoWidth, int videoHeight) throws Exception {
222 
223         if (isWatchDevice()) {
224             return;
225         }
226 
227         if (!isCryptoSchemeSupportedNative(uuidByteArray(drmSchemeUuid))) {
228             throw new Error("Crypto scheme is not supported.");
229         }
230 
231         if (!MediaUtils.checkCodecsForPath(mContext, videoUrl.toString())) {
232             Log.i(TAG, "Device does not support " +
233                   videoWidth + "x" + videoHeight + " resolution for " + mimeType);
234             return;  // skip
235         }
236 
237         PlaybackParams params = new PlaybackParams();
238         params.surface = mActivity.getSurfaceHolder().getSurface();
239         params.mimeType = mimeType;
240         params.audioUrl = audioUrl.toString();
241         params.videoUrl = videoUrl.toString();
242 
243         if (!testClearKeyPlaybackNative(
244             uuidByteArray(drmSchemeUuid), params)) {
245             Log.e(TAG, "Fails play back using native media drm APIs.");
246         }
247         params.surface.release();
248     }
249 
isCryptoSchemeSupportedNative(final byte[] uuid)250     private static native boolean isCryptoSchemeSupportedNative(final byte[] uuid);
251 
testClearKeyPlaybackNative(final byte[] uuid, PlaybackParams params)252     private static native boolean testClearKeyPlaybackNative(final byte[] uuid,
253             PlaybackParams params);
254 
testFindSessionIdNative(final byte[] uuid)255     private static native boolean testFindSessionIdNative(final byte[] uuid);
256 
testGetPropertyStringNative(final byte[] uuid, final String name, StringBuffer value)257     private static native boolean testGetPropertyStringNative(final byte[] uuid,
258             final String name, StringBuffer value);
259 
testPropertyByteArrayNative(final byte[] uuid)260     private static native boolean testPropertyByteArrayNative(final byte[] uuid);
261 
testPsshNative(final byte[] uuid, final String videoUrl)262     private static native boolean testPsshNative(final byte[] uuid, final String videoUrl);
263 
testQueryKeyStatusNative(final byte[] uuid)264     private static native boolean testQueryKeyStatusNative(final byte[] uuid);
265 
testGetKeyRequestNative(final byte[] uuid, PlaybackParams params)266     private static native boolean testGetKeyRequestNative(final byte[] uuid,
267             PlaybackParams params);
268 
testClearKeyPlaybackCenc()269     public void testClearKeyPlaybackCenc() throws Exception {
270         testClearKeyPlayback(
271                 COMMON_PSSH_SCHEME_UUID,
272                 ISO_BMFF_VIDEO_MIME_TYPE,
273                 CENC_AUDIO_URL,
274                 CENC_VIDEO_URL,
275                 VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
276     }
277 
278     @FlakyTest(bugId = 173646795)
279     @Presubmit
testClearKeyPlaybackCenc2()280     public void testClearKeyPlaybackCenc2() throws Exception {
281         testClearKeyPlayback(
282                 CLEARKEY_SCHEME_UUID,
283                 ISO_BMFF_VIDEO_MIME_TYPE,
284                 CENC_AUDIO_URL,
285                 CENC_VIDEO_URL,
286                 VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
287     }
288 
289     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
testClearKeyGetKeyRequest()290     public void testClearKeyGetKeyRequest() throws Exception {
291         PlaybackParams params = new PlaybackParams();
292         params.surface = mActivity.getSurfaceHolder().getSurface();
293         params.mimeType = ISO_BMFF_VIDEO_MIME_TYPE;
294         params.audioUrl = CENC_AUDIO_URL.toString();
295         params.videoUrl = CENC_VIDEO_URL.toString();
296         boolean status = testGetKeyRequestNative(
297                 uuidByteArray(CLEARKEY_SCHEME_UUID),
298                 params);
299         assertTrue(status);
300         params.surface.release();
301     }
302 }
303 
304