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